diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0816a09 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = tab +insert_final_newline = true +max_line_length = 100 +trim_trailing_whitespace = true + +[*.json] +indent_style = space + +[*.{yaml,yml}] +indent_style = space diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..5fae357 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +@nodejs/userland-migrations diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a706d7..4d42fd3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,22 +10,28 @@ on: jobs: lint-and-types: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest strategy: matrix: - node-version: [23.x] + node: + - version: 23.x steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - run: npm ci - - run: npm run lint - - run: npm run format - - run: npm run test:types + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + - name: Set up Node.js ${{ matrix.node.version }} + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # 4.1.0 + with: + node-version: ${{ matrix.node.version }} + cache: 'npm' + - run: npm ci + - run: node --run lint + - run: node --run type-check tests: + if: github.event.pull_request.draft == false + strategy: fail-fast: false matrix: @@ -41,12 +47,16 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 - - name: Set up Node.js ${{ matrix.node.version }} - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # 4.1.0 - with: - node-version: ${{ matrix.node.version }} - cache: 'npm' - - run: npm ci - - run: npm run test:unit - - run: npm run test:e2e + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # 4.2.2 + - name: Set up Node.js ${{ matrix.node.version }} + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # 4.1.0 + with: + node-version: ${{ matrix.node.version }} + cache: 'npm' + - run: npm ci + - run: | + node \ + --run test \ + --test-coverage-lines=0.8 \ + --test-reporter-destination=./coverage.lcov \ + --test-reporter=lcov diff --git a/.vscode/settings.json b/.vscode/settings.json index 2d8ad4d..d3d7b2b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,11 @@ { "editor.formatOnSave": true, - "editor.defaultFormatter": "biomejs.biome", "javascript.updateImportsOnFileMove.enabled": "always", - "typescript.updateImportsOnFileMove.enabled": "always" -} \ No newline at end of file + "typescript.updateImportsOnFileMove.enabled": "always", + "editor.formatOnPaste": true, + "editor.wordWrap": "wordWrapColumn", + "editor.wordWrapColumn": 100, + "[markdown]": { + "editor.wordWrap": "off" + } +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c47a179..efaf605 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,18 +5,18 @@ 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`) + * unit tests + * end-to-end test(s) for accepted use-cases + * a `test` command in `package.json`; there may be sub-commands like `test:unit` & `test:e2e`, but there must be a parent that combines them. + * Include `--import='../../build/snapshots.mts` to standardise the filename (`${original_base_name}.snap.cjs`) across recipes. + * Ensure `--test-coverage-include` and `--test-coverage-exclude` are set correctly for the recipe's workspace. The root repo handles setting coverage rules like minimum line coverage. * 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. -> [!NOTE] -> 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`. +New recipes are added under `./recipes` in their own folder, succinctly named for what they do. 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`. ## Before pushing a commit -A convenient superset of checks is available via `node --run pre-commit`, which automatically fixes formatting and linting issues, checks types, and runs unit and end-to-end tests. Changes resulting from this should be committed. +A convenient superset of checks is available via `node --run pre-commit`, which automatically fixes formatting and linting issues (that are safe to fix), checks types, and runs tests. Changes resulting from this should be committed. diff --git a/biome.jsonc b/biome.jsonc index 5ebe181..504d158 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -1,18 +1,26 @@ { "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", - "formatter": { - "indentStyle": "tab", - "lineWidth": 100 + "files": { + "ignore": [ + "**/*.snap.cjs" + ] + }, + "organizeImports": { + "enabled": false }, // Rules for the linter "linter": { "rules": { "style": { + "noNonNullAssertion": "off", + "noParameterAssign": "off", + "noYodaExpression": "error", "useImportType": "error", "useNodeAssertStrict": "error", "useNodejsImportProtocol": "error" }, "suspicious": { + "noAssignInExpressions": "off", "noExplicitAny": "error", "noEmptyBlock": "error", "noDuplicateAtImportRules": "error", @@ -25,12 +33,16 @@ }, "nursery": { "noEnum": "error" + }, + "performance": { + "recommended": true } } }, // Language specific settings "javascript": { "formatter": { + "arrowParentheses": "always", "semicolons": "always", "quoteStyle": "single", "trailingCommas": "all" @@ -41,7 +53,8 @@ }, "json": { "formatter": { - "enabled": false + "enabled": true, + "indentStyle": "space" } }, // VSC specific settings diff --git a/package.json b/package.json index e3a9658..34197d4 100644 --- a/package.json +++ b/package.json @@ -3,14 +3,11 @@ "version": "1.0.0", "description": "A collection of migration recipes for userland code.", "scripts": { - "format:fix": "biome format --fix ./", - "format": "biome format ./", "lint:fix": "biome lint --fix ./", "lint": "biome lint ./", - "pre-commit": "node --run lint:fix; node --run format:fix; node --run test:types; node --run test:unit; node --run test:e2e", - "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.{mjs,mts}' './packages/*/*.e2e.{mjs,mts}'", - "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.{mjs,mts}' --test-coverage-lines=0.8 './packages/*/*.spec.{mjs,mts}'" + "pre-commit": "node --run lint:fix; node --run type-check; node --run test", + "test": "npm run test --workspaces -- --no-warnings --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout", + "type-check": "tsc" }, "repository": { "type": "git", diff --git a/tsconfig.json b/tsconfig.json index 3292d56..2a227e5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "rootDir": "./", "target": "ESNext", - // "moduleDetection": "force", + "lib": ["ESNext"], /* Modules */ "module": "NodeNext", @@ -24,7 +24,7 @@ /* Interop Constraints */ "verbatimModuleSyntax": true, - "allowSyntheticDefaultImports": false, + "allowSyntheticDefaultImports": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, @@ -40,5 +40,28 @@ "include": [ "./build/", "./recipes/" - ] + ], + "exclude": [ + "node_modules", + "**/build/**", + "**/fixtures/**", + "**/test/**", + "**/*.fixture.mjs", + "**/*.fixture.mts", + "**/*.fixture.js", + "**/*.fixture.ts", + "**/*.mock.mjs", + "**/*.e2e.mjs", + "**/*.e2e.mts", + "**/*.e2e.js", + "**/*.e2e.ts", + "**/*.spec.mjs", + "**/*.spec.mts", + "**/*.spec.js", + "**/*.spec.ts", + "**/*.test.mjs", + "**/*.test.mts", + "**/*.test.js", + "**/*.test.ts", + ], }