diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c23badc --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs + +name: CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + Lint: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [23.x] + steps: + - uses: actions/checkout@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npm run test:lint + - run: npm run test:types + + Test: + runs-on: + macos-latest + ubuntu-latest + windows-latest + + strategy: + matrix: + node-version: [23.x, 22.x, 20.x] + fail-fast: false + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npm run test:unit + - run: npm run test:e2e diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a004a59 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,18 @@ +# Contributing + +A recipe generally has a few things: + +* A `README.md` explaining its purpose and use (including any options, and required and optional +files). +* Tests via node's test runner (min coverage: 80%) + * unit tests (file extension: `.spec.mjs` or `.spec.mts`) + * end-to-end test(s) for accepted use-cases (file extension: `.e2e.mjs` or `.e2e.mts`) +* Code comments (js docs, etc) +* Types (either via typescript or jsdoc) + +CI will run lint & type checking and all included test files against all PRs. + +> [!INFO] +> snapshots will be generated with the file extension `.snap.cjs`. + +New recipes are added under `./recipes` in their own folder, succinctly named for what it does. General-purpose recipes have simple names like `correct-ts-specifiers`. A suite of migrations has a name like `migrate from 18 to 20`, and more specific migrations are named like `migrate fs.readFile from 18 to 20`. diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb08a3e --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# Node.js userland migrations + +This repository contains codemodes (automated migrations) for "userland" code. These are intended to facilitate adopting new features and upgrading source-code affected by breaking changes. + +## Usage + +> [!CAUTION] +> These scripts change source code. Commit any unsaved changes before running them. Failing to do so may ruin your day. + +To run the transform scripts use [`codemod`](https://go.codemod.com/github) command below: + +```console +$ npx codemod --target [...options] +``` + +* `transform` - name of transform. see available transforms below. +* `path` - directory to transform. defaults to the current directory. + +See the [codemod CLI doc](https://go.codemod.com/cli-docs) for a full list of available commands. + +## Available codemods + +All Node.js codemods are also available in the [Codemod Registry](https://codemod.com/registry?framework=node.js). diff --git a/biome.jsonc b/biome.jsonc new file mode 100644 index 0000000..679df3f --- /dev/null +++ b/biome.jsonc @@ -0,0 +1,28 @@ +{ + "formatter": { + "indentStyle": "tab", + "lineWidth": 100 + }, + "javascript": { + "formatter": { + "semicolons": "always", + "quoteStyle": "single", + "trailingCommas": "all" + }, + "linter": { + "style": { + "useImportType": true + } + } + }, + "json": { + "formatter": { + "enabled": false + } + }, + "markdown": { + "formatter": { + "enabled": false + } + } +} diff --git a/build/snapshots.ts b/build/snapshots.ts new file mode 100644 index 0000000..3a00602 --- /dev/null +++ b/build/snapshots.ts @@ -0,0 +1,18 @@ +import { basename, dirname, extname, join } from 'node:path'; +import { snapshot } from 'node:test'; + + +snapshot.setResolveSnapshotPath(generateSnapshotPath); +/** + * @param testFilePath '/tmp/foo.test.js' + * @returns '/tmp/foo.test.snap.cjs' + */ +function generateSnapshotPath(testFilePath?: string) { + if (!testFilePath) return ''; + + const ext = extname(testFilePath); + const filename = basename(testFilePath, ext); + const base = dirname(testFilePath); + + return join(base, `${filename}.snap.cjs`); +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6dda7bb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,216 @@ +{ + "name": "userland-migrations", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "userland-migrations", + "version": "1.0.0", + "license": "MIT", + "workspaces": [ + "./recipes/*" + ], + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@types/node": "^22.9.1", + "typescript": "^5.6.3" + } + }, + "node_modules/@biomejs/biome": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", + "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", + "dev": true, + "hasInstallScript": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "1.9.4", + "@biomejs/cli-darwin-x64": "1.9.4", + "@biomejs/cli-linux-arm64": "1.9.4", + "@biomejs/cli-linux-arm64-musl": "1.9.4", + "@biomejs/cli-linux-x64": "1.9.4", + "@biomejs/cli-linux-x64-musl": "1.9.4", + "@biomejs/cli-win32-arm64": "1.9.4", + "@biomejs/cli-win32-x64": "1.9.4" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", + "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", + "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", + "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", + "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", + "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", + "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", + "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", + "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@types/node": { + "version": "22.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.1.tgz", + "integrity": "sha512-p8Yy/8sw1caA8CdRIQBG5tiLHmxtQKObCijiAa9Ez+d4+PRffM4054xbju0msf+cvhJpnFEeNjxmVT/0ipktrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.8" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..22610c8 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "userland-migrations", + "version": "1.0.0", + "description": "A collection of migration recipes for userland code.", + "main": "index.js", + "scripts": { + "test:e2e": "node --no-warnings --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=./coverage.lcov --test-reporter=spec --test-reporter-destination=stdout --import './build/snapshots.ts' -test --test-coverage-include='recipes/**/*' --test-coverage-exclude='**/*.e2e.m(j|t)s' './packages/*/*.e2e.m(j|t)s'", + "test:lint": "biome lint ./recipes", + "test:types": "tsc", + "test:unit": "node --no-warnings --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=./coverage.lcov --test-reporter=spec --test-reporter-destination=stdout --experimental-test-module-mocks --import './build/snapshots.ts' -test --test-coverage-include='recipes/**/*' --test-coverage-exclude='**/*.spec.m(j|t)s' --test-coverage-lines=0.8 './packages/*/*.spec.m(j|t)s'" + }, + "repository": { + "type": "git", + "url": "https://github.com/nodejs/userland-migrations" + }, + "keywords": [ + "automation", + "codemod", + "migrations", + "node.js" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/nodejs/userland-migrations/issues" + }, + "homepage": "https://nodejs.org/learn/userland-migrations", + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@types/node": "^22.9.1", + "typescript": "^5.6.3" + }, + "workspaces": [ + "./recipes/*" + ] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..59abe38 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "target": "ESNext", + // "moduleDetection": "force", + + /* Modules */ + "module": "NodeNext", + "rootDir": "./recipes/", + "moduleResolution": "nodenext", + "baseUrl": "./recipes/", + "allowImportingTsExtensions": true, + "resolvePackageJsonExports": true, + "resolvePackageJsonImports": true, + "resolveJsonModule": true, + "allowArbitraryExtensions": true, + + /* JavaScript Support */ + "allowJs": true, + "checkJs": true, + + /* Emit */ + "noEmit": true, + + /* Interop Constraints */ + "verbatimModuleSyntax": true, + "allowSyntheticDefaultImports": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + + /* Type Checking */ + "strict": true, + "noImplicitAny": true, + "strictNullChecks": false, + "strictFunctionTypes": true, + + /* Completeness */ + "skipLibCheck": true + } +}