diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac82a7e..25782e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -286,7 +286,7 @@ jobs: env: MARKMV_TEST_FILESYSTEM_CASE_SENSITIVE: ${{ env.FILESYSTEM_IS_CASE_SENSITIVE }} MARKMV_TEST_SUPPORTS_SYMLINKS: ${{ env.FILESYSTEM_SUPPORTS_SYMLINKS }} - run: pnpm run test:run + run: pnpm run test - name: Report filesystem test results run: | diff --git a/.github/workflows/cross-platform-tests.yml b/.github/workflows/cross-platform-tests.yml index 77595cc..cce642e 100644 --- a/.github/workflows/cross-platform-tests.yml +++ b/.github/workflows/cross-platform-tests.yml @@ -167,7 +167,7 @@ jobs: echo "Symlinks: ${{ steps.filesystem.outputs.symlinks }}" # Run tests with cross-platform environment - pnpm run test:run + pnpm run test env: MARKMV_TEST_CROSS_PLATFORM: "true" diff --git a/.husky/pre-push b/.husky/pre-push index 6f2deaa..cf53f02 100755 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,6 +1,6 @@ # Typecheck and unit tests before push, to catch breakage before it reaches CI. pnpm run typecheck -pnpm run test:run +pnpm run test # actionlint gates CI workflows; warn-and-continue when the binary is missing locally (CI always runs it), so contributors without it are not blocked. if command -v actionlint >/dev/null 2>&1; then diff --git a/docs-src/guides/cross-platform-testing.md b/docs-src/guides/cross-platform-testing.md index ce7da77..533697b 100644 --- a/docs-src/guides/cross-platform-testing.md +++ b/docs-src/guides/cross-platform-testing.md @@ -137,7 +137,7 @@ npm run test:cross-platform:data npm run test:cross-platform:cli # Run with specific environment -MARKMV_TEST_CASE_SENSITIVE=false npm run test:run +MARKMV_TEST_CASE_SENSITIVE=false npm test ``` ### Manual Testing @@ -210,10 +210,10 @@ Enable debug output in tests: ```bash # Enable verbose test output -npm run test:run -- --reporter=verbose +npx vitest run --reporter=verbose # Run specific test file -npm run test:run -- src/utils/test-helpers.test.ts +npx vitest run src/utils/test-helpers.test.ts # Check filesystem capabilities node scripts/test-cross-platform.js --test-data-only diff --git a/eslint.config.ts b/eslint.config.ts index cdb833b..255c244 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -22,7 +22,29 @@ const publicModules = [ ]; export default defineConfig( - ...exadevConfig(), + ...exadevConfig({ + turbo: {}, + // The `root` section is switched off on purpose: its knip check expects a script that runs knip itself, which the turbo convention (a public script delegating to `_knip`) rules out, so both cannot hold in a turbo repository (ExaDev/eslint-config#110). + toolingWiring: { + root: false, + hooks: true, + publish: { tools: ["publint", "attw"] }, + }, + importPolicies: [ + { + files: ["src/**"], + ignores: ["**/*.test.ts", "src/test-support/**"], + deny: [ + { + specifiers: ["src/test-support", "src/utils/test-helpers"], + message: + "test doubles and helpers stay out of shipped code; import them from tests only", + }, + ], + computedSpecifiers: "report", + }, + ], + }), { // Tracked but still not meant to be linted or reformatted: .d.ts is generated at build time, both lockfiles are machine-written, and CHANGELOG.md is entirely semantic-release output rewritten wholesale on every release -- all three are tracked, so .gitignore doesn't exclude them, and reformatting CHANGELOG.md by hand here would just be undone (noisily) by the next release anyway. ignores: [ diff --git a/package.json b/package.json index c4ab203..b3eb80d 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "zod": "4.6.5" }, "devDependencies": { + "@arethetypeswrong/cli": "0.18.5", "@commitlint/cli": "^21.2.3", "@commitlint/config-conventional": "^21.2.3", "@commitlint/types": "^21.2.3", @@ -59,6 +60,7 @@ "knip": "^6.38.0", "lint-staged": "^17.6.0", "prettier": "^3.9.9", + "publint": "0.3.24", "semantic-release": "^25.0.9", "semver": "7.8.5", "tsx": "4.23.15", @@ -112,7 +114,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/ExaDev/markmv.git" + "url": "git+https://github.com/ExaDev/markmv.git" }, "scripts": { "_build": "tsc -p tsconfig.build.json", @@ -136,18 +138,18 @@ "lint:fix": "eslint . --fix", "mcp-server": "node dist/cli.js mcp", "prepare": "husky", - "prepublishOnly": "pnpm run build && pnpm run test:run", + "prepublishOnly": "pnpm run build && pnpm run test && publint && attw --pack --profile esm-only", "publish:dry": "npm pack --dry-run", "publish:npm": "npm publish --access public", "release": "semantic-release", "release:dry": "semantic-release --dry-run", "start": "node dist/cli.js", - "test": "vitest", + "test": "turbo run _test", "test:coverage": "vitest run --coverage", "test:cross-platform": "node scripts/test-cross-platform.js", "test:cross-platform:cli": "node scripts/test-cross-platform.js --cli-only", "test:cross-platform:data": "node scripts/test-cross-platform.js --test-data-only", - "test:run": "turbo run _test", + "test:watch": "vitest", "typecheck": "turbo run _typecheck" }, "type": "module", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fe0f29..5f202e0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -63,6 +63,9 @@ importers: specifier: 4.6.5 version: 4.6.5 devDependencies: + "@arethetypeswrong/cli": + specifier: 0.18.5 + version: 0.18.5 "@commitlint/cli": specifier: ^21.2.3 version: 21.2.3(@types/node@26.6.3)(@typescript/typescript6@6.0.2)(conventional-commits-parser@7.1.2) @@ -147,6 +150,9 @@ importers: prettier: specifier: ^3.9.9 version: 3.9.9 + publint: + specifier: 0.3.24 + version: 0.3.24 semantic-release: specifier: ^25.0.9 version: 25.0.9(@typescript/typescript6@6.0.2)(supports-color@7.2.0) @@ -203,6 +209,27 @@ packages: integrity: sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==, } + "@andrewbranch/untar.js@1.0.4": + resolution: + { + integrity: sha512-pVXSwPsLuw8IGLo2Di0EaOfsk+ntVvpkk942J/sHYIkwvtKUakEcPh7HBgZ6tuimgzKSEHgCvO4XgQ05DEbwDw==, + } + + "@arethetypeswrong/cli@0.18.5": + resolution: + { + integrity: sha512-gM+8vRsQOD/Uc7EnBedUhkG5OCsDWE4uoak5QvomGpMpaky0Eh41p04nIMgrWb8EOmqZUJGc6zz9hsP6E56R7g==, + } + engines: { node: ">=20" } + hasBin: true + + "@arethetypeswrong/core@0.18.5": + resolution: + { + integrity: sha512-9ytjzGwxjm9Uz7I9avfbt5vlQt6uk9uRRESzJjqrznl6WKvI6dwYTo+vJ3U02Wrq/mR3iql/PzhvHhKdJIAjDQ==, + } + engines: { node: ">=20" } + "@asamuzakjp/css-color@7.1.0": resolution: { @@ -260,6 +287,12 @@ packages: } engines: { node: ">=18" } + "@braidai/lang@1.1.2": + resolution: + { + integrity: sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==, + } + "@bramus/specificity@2.4.2": resolution: { @@ -976,6 +1009,12 @@ packages: integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==, } + "@loaderkit/resolve@1.0.6": + resolution: + { + integrity: sha512-G8FdIoF5CypfwmD9rl8BXod5HDn8JqB0CCNBXDTaRZ+yRYhARrrSToX1zg1zy9jX3zLqigsELwhT4gNtkdQAUg==, + } + "@microsoft/tsdoc-config@0.18.2": resolution: { @@ -1500,6 +1539,13 @@ packages: } engines: { node: ">=12" } + "@publint/pack@0.1.7": + resolution: + { + integrity: sha512-4EDEmvxWtgsCnnVeBvtFIFZtUhPPt1+bA9JrSwU4Sa//6oKtzCSlGGXYJr44OD9aGISymbieJ4mCKHUygUDU+g==, + } + engines: { node: ">=18" } + "@rollup/rollup-android-arm-eabi@4.62.4": resolution: { @@ -2804,6 +2850,12 @@ packages: integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==, } + cjs-module-lexer@1.4.3: + resolution: + { + integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==, + } + clean-stack@5.3.0: resolution: { @@ -2892,6 +2944,13 @@ packages: integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, } + commander@10.0.1: + resolution: + { + integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==, + } + engines: { node: ">=14" } + commander@15.0.0: resolution: { @@ -3684,6 +3743,12 @@ packages: picomatch: optional: true + fflate@0.8.3: + resolution: + { + integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==, + } + figures@2.0.0: resolution: { @@ -4714,6 +4779,14 @@ packages: engines: { node: ">= 18" } hasBin: true + marked@9.1.6: + resolution: + { + integrity: sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==, + } + engines: { node: ">= 16" } + hasBin: true + math-intrinsics@1.1.0: resolution: { @@ -5099,6 +5172,13 @@ packages: integrity: sha512-j5sNQm3VCpQQ7nTqGeOZtoJtV3uKERgCBm9QRhmGRiXiqkf7iRFOkfxdJRZWLkqYY8PNf4cDQF/WfXUYLENrRA==, } + mri@1.2.0: + resolution: + { + integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==, + } + engines: { node: ">=4" } + ms@2.1.3: resolution: { @@ -5696,6 +5776,14 @@ packages: kerberos: optional: true + publint@0.3.24: + resolution: + { + integrity: sha512-9zS56KrKBoqi5Qt8h92uMP8TTM9AYZSgnmCo4u2priMqkOZvQnTsziZ2p5LJ2ywbYkAjoCDp2jda9u4cgFefIw==, + } + engines: { node: ">=18" } + hasBin: true + punycode.js@2.3.1: resolution: { @@ -5896,6 +5984,13 @@ packages: integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==, } + sade@1.8.1: + resolution: + { + integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==, + } + engines: { node: ">=6" } + safe-buffer@5.1.2: resolution: { @@ -6515,6 +6610,14 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: ">=4.8.4 <6.1.0" + typescript@5.6.1-rc: + resolution: + { + integrity: sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==, + } + engines: { node: ">=14.17" } + hasBin: true + typescript@6.0.3: resolution: { @@ -6687,6 +6790,13 @@ packages: integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==, } + validate-npm-package-name@5.0.1: + resolution: + { + integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==, + } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + vary@1.1.2: resolution: { @@ -7039,6 +7149,29 @@ snapshots: "@actions/io@3.0.2": {} + "@andrewbranch/untar.js@1.0.4": {} + + "@arethetypeswrong/cli@0.18.5": + dependencies: + "@arethetypeswrong/core": 0.18.5 + chalk: 4.1.2 + cli-table3: 0.6.5 + commander: 10.0.1 + marked: 9.1.6 + marked-terminal: 7.3.0(marked@9.1.6) + semver: 7.8.5 + + "@arethetypeswrong/core@0.18.5": + dependencies: + "@andrewbranch/untar.js": 1.0.4 + "@loaderkit/resolve": 1.0.6 + cjs-module-lexer: 1.4.3 + fflate: 0.8.3 + lru-cache: 11.5.3 + semver: 7.8.5 + typescript: 5.6.1-rc + validate-npm-package-name: 5.0.1 + "@asamuzakjp/css-color@7.1.0": dependencies: "@csstools/css-calc": 3.4.1(@csstools/css-parser-algorithms@4.0.1(@csstools/css-tokenizer@4.0.2))(@csstools/css-tokenizer@4.0.2) @@ -7075,6 +7208,8 @@ snapshots: "@bcoe/v8-coverage@1.0.2": {} + "@braidai/lang@1.1.2": {} + "@bramus/specificity@2.4.2": dependencies: css-tree: 3.2.1 @@ -7475,6 +7610,10 @@ snapshots: "@keyv/serialize@1.1.1": {} + "@loaderkit/resolve@1.0.6": + dependencies: + "@braidai/lang": 1.1.2 + "@microsoft/tsdoc-config@0.18.2": dependencies: "@microsoft/tsdoc": 0.17.0 @@ -7720,6 +7859,10 @@ snapshots: "@pnpm/network.ca-file": 1.0.2 config-chain: 1.1.13 + "@publint/pack@0.1.7": + dependencies: + tinyexec: 1.3.1 + "@rollup/rollup-android-arm-eabi@4.62.4": optional: true @@ -8477,6 +8620,8 @@ snapshots: chardet@2.2.0: {} + cjs-module-lexer@1.4.3: {} + clean-stack@5.3.0: dependencies: escape-string-regexp: 5.0.0 @@ -8530,6 +8675,8 @@ snapshots: color-name@1.1.4: {} + commander@10.0.1: {} + commander@15.0.0: {} commander@8.3.0: {} @@ -9082,6 +9229,8 @@ snapshots: optionalDependencies: picomatch: 4.0.7 + fflate@0.8.3: {} + figures@2.0.0: dependencies: escape-string-regexp: 1.0.5 @@ -9671,8 +9820,21 @@ snapshots: node-emoji: 2.2.0 supports-hyperlinks: 3.2.0 + marked-terminal@7.3.0(marked@9.1.6): + dependencies: + ansi-escapes: 7.3.0 + ansi-regex: 6.3.0 + chalk: 5.6.2 + cli-highlight: 2.1.11 + cli-table3: 0.6.5 + marked: 9.1.6 + node-emoji: 2.2.0 + supports-hyperlinks: 3.2.0 + marked@15.0.12: {} + marked@9.1.6: {} + math-intrinsics@1.1.0: {} mdast-util-find-and-replace@3.0.2: @@ -10053,6 +10215,8 @@ snapshots: module-replacements@2.11.0: {} + mri@1.2.0: {} + ms@2.1.3: {} mute-stream@0.0.8: {} @@ -10354,6 +10518,13 @@ snapshots: proxy-agent-negotiate@1.1.0: {} + publint@0.3.24: + dependencies: + "@publint/pack": 0.1.7 + package-manager-detector: 1.8.0 + picocolors: 1.1.1 + sade: 1.8.1 + punycode.js@2.3.1: {} punycode@2.3.1: {} @@ -10523,6 +10694,10 @@ snapshots: dependencies: tslib: 2.8.1 + sade@1.8.1: + dependencies: + mri: 1.2.0 + safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} @@ -10900,6 +11075,8 @@ snapshots: transitivePeerDependencies: - supports-color + typescript@5.6.1-rc: {} + typescript@6.0.3: {} typescript@7.0.2: @@ -11003,6 +11180,8 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 + validate-npm-package-name@5.0.1: {} + vary@1.1.2: {} vfile-message@4.0.3: diff --git a/scripts/test-cross-platform.js b/scripts/test-cross-platform.js index 94950d6..d496384 100644 --- a/scripts/test-cross-platform.js +++ b/scripts/test-cross-platform.js @@ -106,7 +106,7 @@ function runTests() { try { // Run the test suite with our environment - execSync("npm run test:run", { + execSync("npm test", { env: testEnv, stdio: "inherit", }); diff --git a/turbo.json b/turbo.json index 659b944..211209c 100644 --- a/turbo.json +++ b/turbo.json @@ -2,7 +2,7 @@ "$schema": "https://turborepo.dev/schema.json", "tasks": { "_build": { - "inputs": ["src/**/*.ts", "tsconfig.json", "tsconfig.build.json"], + "inputs": ["src/**/*.ts", "tsconfig*.json"], "outputs": ["dist/**", "tsconfig.tsbuildinfo"] }, "_check": { @@ -23,7 +23,7 @@ "outputs": ["coverage/**"] }, "_typecheck": { - "inputs": ["src/**/*.ts", "tsconfig.json"], + "inputs": ["src/**/*.ts", "tsconfig*.json"], "outputs": [] } } diff --git a/vitest.config.ts b/vitest.config.ts index cc05625..86366df 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -64,13 +64,12 @@ export default defineConfig({ "release.config.*", ], include: ["src/**/*.ts"], + // Floors set at the coverage measured when they were introduced, rounded down, so coverage can only be held or raised. A `global` key here is not one Vitest reads, which is how the earlier thresholds came to enforce nothing. thresholds: { - global: { - branches: 40, - functions: 40, - lines: 40, - statements: 40, - }, + branches: 69, + functions: 86, + lines: 80, + statements: 80, }, }, include: [ @@ -79,6 +78,6 @@ export default defineConfig({ ".github/scripts/**/*.test.ts", "scripts/**/*.test.ts", ], - exclude: ["node_modules/", "dist/", "coverage/"], + exclude: ["**/node_modules/**", "**/dist/**", "**/coverage/**"], }, });