diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5cb1789..1d05e2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ permissions: jobs: audit: - name: Dependency audit (Node 20.19.0) + name: Dependency audit (Node LTS) runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -24,7 +24,7 @@ jobs: - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: 20.19.0 + node-version: "lts/*" cache: npm - name: Install dependencies run: npm ci @@ -39,7 +39,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node: [20.19.0, "lts/*"] + node: ["lts/*"] steps: - name: Checkout repository @@ -81,7 +81,7 @@ jobs: npm run pack:check packed-templates: - name: Packed template integration (Node 20.19.0) + name: Packed template integration (Node LTS) runs-on: ubuntu-latest timeout-minutes: 30 steps: @@ -90,7 +90,7 @@ jobs: - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: 20.19.0 + node-version: "lts/*" cache: npm - name: Install dependencies run: npm ci @@ -98,7 +98,7 @@ jobs: run: npm run test:templates peer-floor: - name: Packed Askr peer floor (0.0.53) + name: Packed Askr peer floor (0.0.85) runs-on: ubuntu-latest timeout-minutes: 15 steps: @@ -107,7 +107,7 @@ jobs: - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: 20.19.0 + node-version: "lts/*" cache: npm - name: Install dependencies run: npm ci @@ -115,7 +115,7 @@ jobs: run: npm run test:peer-floor benchmark: - name: Performance gate (Ubuntu, Node 20.19.0) + name: Performance gate (Ubuntu, Node LTS) runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -124,7 +124,7 @@ jobs: - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: 20.19.0 + node-version: "lts/*" cache: npm - name: Install dependencies run: npm ci diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4091229 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,36 @@ +# Repository Guidelines + +## Project Structure + +- `src/` contains the TypeScript CLI and library implementation. Commands live in `src/bin/`; analyzer, updater, guardrail, generator, and SSG modules are grouped by directory. +- `tests/` contains Vitest unit and contract tests. Packaging checks requiring nested installs live in `tests/integration/` and use `vitest.integration.config.ts`. +- `skills/` contains shipped agent skills; `docs/` contains command and design documentation; `benchmarks/` contains performance suites. +- Build artifacts are emitted to `dist/` and are not source files. + +## Build, Test, and Development Commands + +Use Node LTS and npm from the repository root. Install dependencies with `npm install` (CI uses `npm ci`). + +- `npm run fmt` formats the repository; add `-- --check` for CI-style verification. +- `npm run lint` runs Vite+ linting over source, tests, benchmarks, and config files. +- `npm run typecheck` runs the TypeScript compiler without emitting files. +- `npm test` runs the standard Vitest suite; `npm run test:coverage` adds coverage reporting. +- `npm run test:peer-floor` and `npm run test:templates` run packaging integration tests. +- `npm run build` produces the distributable CLI in `dist/`; `npm run dev` rebuilds in watch mode. +- `npm run check` runs the main release checks; `npm run bench` runs performance gates. + +## Coding Style and Naming + +Use TypeScript with two-space indentation, semicolons, and double-quoted strings. Let Vite+ format and lint enforce style. Use kebab-case for filenames, descriptive `*.test.ts` names, and keep tests near their domain (integration tests under `tests/integration/`). Prefer small, typed helpers and explicit error messages. + +## Testing Guidelines + +Add regression tests with every behavior change. Use Vitest’s `test`/`describe` APIs and deterministic fixtures; avoid sleeps and network-dependent assumptions outside packaging integration tests. Run focused tests first, then `npm test`, `npm run typecheck`, and relevant integration or benchmark commands. + +## Commits and Pull Requests + +Use imperative, conventional commit subjects such as `fix: ...`, `test: ...`, `ci: ...`, or `chore: ...`. Keep commits focused. Pull requests should explain the behavior change, identify validation commands and results, link the relevant issue, and call out workflow, package, or release implications. Do not publish packages or create tags without explicit authorization. + +## Configuration and Security + +Do not commit credentials, generated `dist/` output, temporary tarballs, or local `node_modules/`. Preserve `package-lock.json` whenever dependency manifests change. Prefer `npm ci` for reproducible CI installs and review npm script-install warnings before approving new dependencies. diff --git a/package-lock.json b/package-lock.json index c6f9876..74d9d16 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,38 +10,38 @@ "license": "Apache-2.0", "dependencies": { "@npmcli/config": "^11.0.1", - "js-yaml": "^5.2.2", + "js-yaml": "^5.2.3", "minimatch": "^10.2.6", "npm-registry-fetch": "^20.0.1", "parse5": "^8.0.1", "semver": "^7.8.5", - "tsx": "^4.23.1", + "tsx": "^4.23.5", "typescript": "^6.0.3" }, "bin": { "askr": "dist/cli.js" }, "devDependencies": { - "@askrjs/askr": ">=0.0.53 <0.1.0", - "@askrjs/charts": ">=0.1.0 <0.2.0", - "@askrjs/logos": ">=0.0.3 <0.1.0", - "@askrjs/lucide": ">=0.0.3 <0.1.0", - "@askrjs/themes": ">=0.0.12 <0.1.0", - "@askrjs/ui": ">=0.0.13 <0.1.0", - "@askrjs/vite": ">=0.0.6 <0.1.0", + "@askrjs/askr": ">=0.0.85 <0.1.0", + "@askrjs/charts": ">=0.1.4 <0.2.0", + "@askrjs/logos": ">=0.0.5 <0.1.0", + "@askrjs/lucide": ">=0.0.6 <0.1.0", + "@askrjs/themes": ">=0.0.21 <0.1.0", + "@askrjs/ui": ">=0.0.24 <0.1.0", + "@askrjs/vite": ">=0.0.11 <0.1.0", "@types/js-yaml": "^4.0.9", - "@types/node": "^26.1.1", - "@types/semver": "^7.7.1", + "@types/node": "^26.1.2", + "@types/semver": "^7.8.0", "@vitest/coverage-v8": "^4.1.10", - "publint": "^0.3.21", - "vite-plus": "0.2.6", + "publint": "^0.3.22", + "vite-plus": "0.2.7", "vitest": "^4.1.10" }, "engines": { "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "@askrjs/askr": ">=0.0.53 <0.1.0" + "@askrjs/askr": ">=0.0.85 <0.1.0" }, "peerDependenciesMeta": { "@askrjs/askr": { @@ -50,9 +50,9 @@ } }, "node_modules/@askrjs/askr": { - "version": "0.0.84", - "resolved": "https://registry.npmjs.org/@askrjs/askr/-/askr-0.0.84.tgz", - "integrity": "sha512-n7iV5767hZVts4yn7aQq5BceLerLBtLj7YC6jxsjFnOe0EwvuW6C0RyDUV3m1thSTXmsK/v/FWmGm3oyHPA2qg==", + "version": "0.0.85", + "resolved": "https://registry.npmjs.org/@askrjs/askr/-/askr-0.0.85.tgz", + "integrity": "sha512-bhz7ErGAArsNKvY8aw2x97rzybYHOO/VOpgv0zU/dmqFiN+kdQbELh0b4grm17H4LeVgbSfL1C4abxrQg2FXsw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -165,36 +165,36 @@ } }, "node_modules/@askrjs/themes": { - "version": "0.0.19", - "resolved": "https://registry.npmjs.org/@askrjs/themes/-/themes-0.0.19.tgz", - "integrity": "sha512-4THajTUAvDtn44yEwiOPOJc6ChdMHf+12ZreEzFB+FyflrmDbfRSYpRnRYJqTQpCi9dIBSCLYeS0GlMUK5H9Pg==", + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@askrjs/themes/-/themes-0.0.21.tgz", + "integrity": "sha512-y80AfHkWzJs6/FLlG2ym8p7123GGYGcnG4h1YMFHqi7tFL7hRJhY7m4zTGdwv41YsB0C98yJeC0s6xiAjpaC0g==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": ">=24.15.0" }, "peerDependencies": { - "@askrjs/askr": ">=0.0.64 <0.1.0", - "@askrjs/ui": ">=0.0.13 <0.1.0" + "@askrjs/askr": ">=0.0.85 <0.1.0", + "@askrjs/ui": ">=0.0.24 <0.1.0" } }, "node_modules/@askrjs/ui": { - "version": "0.0.23", - "resolved": "https://registry.npmjs.org/@askrjs/ui/-/ui-0.0.23.tgz", - "integrity": "sha512-AwxksLE6mX3NEilZ0zvvttb/HNyrQhlnQvzb33CTbgxbzsgzvshVulgokcwDaSzfk4AFdXq0mGglTd/LBONL6A==", + "version": "0.0.24", + "resolved": "https://registry.npmjs.org/@askrjs/ui/-/ui-0.0.24.tgz", + "integrity": "sha512-szET9P5q6KiR2LxgkQlCPjScVBNPt8u2YixrHAle9dOLDs3cb0Tmke2JHYoJVrmJVTajl8Ikt0FFTvSL+4xJkw==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": ">=24.15.0" }, "peerDependencies": { - "@askrjs/askr": ">=0.0.75 <0.1.0" + "@askrjs/askr": ">=0.0.85 <0.1.0" } }, "node_modules/@askrjs/vite": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/@askrjs/vite/-/vite-0.0.10.tgz", - "integrity": "sha512-vi6auCB7dZxtKl8uvD6wqus+cwWMMDiTAjyP9/p2ZahPMUkzbL6awzX7YwbopuNr9HNiA+Hz4cWww7afcFhtQw==", + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@askrjs/vite/-/vite-0.0.11.tgz", + "integrity": "sha512-2vQ+VICvf6H0wqG4J4BvKHXaqEEY2UzOfbAAJQj+Ino3FTr4ugWa2OWAp2OHa0+jJ7zdObTBqTZusZs9ny07qg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -206,10 +206,14 @@ }, "peerDependencies": { "@askrjs/askr": ">=0.0.52 <0.1.0", + "sharp": "^0.35.3", "vite": "^8.0.1", "vite-plus": "^0.2.4" }, "peerDependenciesMeta": { + "sharp": { + "optional": true + }, "vite": { "optional": true }, @@ -2196,9 +2200,9 @@ } }, "node_modules/@types/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-1mAINjtQCXXeLkJ9ehXkwOcBpqtLxiVtKhpUf83DdRNdQKV0iXZpaHYqRr7nj+wvxuJzoAmAwXI+sCNMv1CzLQ==", "dev": true, "license": "MIT" }, @@ -2388,9 +2392,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-core": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-core/-/vite-plus-core-0.2.6.tgz", - "integrity": "sha512-vqDuuLJWS2JHWkFO7r8Z6AehtKnurpnYtw2XEQtjIfwE2GgSAO3zJdPIeaEZiovS6CqfQa+iOMn3kzVzeTSpTw==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-core/-/vite-plus-core-0.2.7.tgz", + "integrity": "sha512-zqaBkM1XZ4IHvFJlG2uv7kQF+dqtUcxXSPGyn15BXRfQb101HPpXXe6PHi7YOmZmbQd+hnwNr5+aklXSriZhVQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2481,9 +2485,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-darwin-arm64": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-darwin-arm64/-/vite-plus-darwin-arm64-0.2.6.tgz", - "integrity": "sha512-QSZWgfqPx5lrIKZ0vwgVRujRqo7gORcc9Oq4fI1UDjKZFgYz9gXVMibSjEqYU3R1webN6FKTOCnzFmrfXchWYw==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-darwin-arm64/-/vite-plus-darwin-arm64-0.2.7.tgz", + "integrity": "sha512-1DsMuysinq4aDiM4212sp5vdDeBPZPdiWwOOZcAl2AVq25hhqDEHdken/PXKHmiXg8M2FGdydpcUtxR5kwdetg==", "cpu": [ "arm64" ], @@ -2498,9 +2502,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-darwin-x64": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-darwin-x64/-/vite-plus-darwin-x64-0.2.6.tgz", - "integrity": "sha512-pMQZGCQO0s+akuxbjgh31hOUgrdDndCmfbP7BciJuts436qrBb8KaVzBhNjJQtxD6H8yY21CIOjD8nlrp4eDIw==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-darwin-x64/-/vite-plus-darwin-x64-0.2.7.tgz", + "integrity": "sha512-ZYSau3poUyX1qPmxYzFuHtGIE7T0zI8KDY/oAXoDBb5M2HSd+uu0ov6jNETkBphMlcSmQ/lT+FFlAkCBZklpqg==", "cpu": [ "x64" ], @@ -2515,9 +2519,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-linux-arm64-gnu": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-arm64-gnu/-/vite-plus-linux-arm64-gnu-0.2.6.tgz", - "integrity": "sha512-0e6nrJgUDN5WwoDRjyh7KV/jbL7WDUhTEWslI64KGpuvD7M6OoPBXYo8Et176O2ENwf52CKVDjXwuvMMNIAzAA==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-arm64-gnu/-/vite-plus-linux-arm64-gnu-0.2.7.tgz", + "integrity": "sha512-7cpZOsHb6nuX6kRCXwWtzBxxJmCVrPcBe/H4q6n1yhWDtqZp/YY05CdFh458V7qUmIKIl70iA5vQ4M9qLPgYcA==", "cpu": [ "arm64" ], @@ -2535,9 +2539,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-linux-arm64-musl": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-arm64-musl/-/vite-plus-linux-arm64-musl-0.2.6.tgz", - "integrity": "sha512-j2yvyv3r2ZEuJG73rWlXFrYrxA7u5+bFSlWTqhsfHZhdb1FKMkklfYRuLtBBdBRLXJ+YMNt9GZT6VTVPJDZnnQ==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-arm64-musl/-/vite-plus-linux-arm64-musl-0.2.7.tgz", + "integrity": "sha512-Aaqm3rlIr6qV2CdQjkyBceQqUlBpvvhGjKfpdAWhPaur4nN44gmjky88vMqL/1R6pieCdfAtUw4a6IVQ/Dq5VQ==", "cpu": [ "arm64" ], @@ -2555,9 +2559,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-linux-x64-gnu": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-x64-gnu/-/vite-plus-linux-x64-gnu-0.2.6.tgz", - "integrity": "sha512-5vqldBBnRjIAUmQ88oKli+wPd5vSQaA2AZ2xs+omRlZfeJhC8chlud6DyJV6fVZCC9GI4otK2zSGpfQZx8Kc3A==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-x64-gnu/-/vite-plus-linux-x64-gnu-0.2.7.tgz", + "integrity": "sha512-LSdD1ID4Mdufckk/Oc+IzhyXCFFUVGR+teiToQNzhD3aoEhrJQMKarx/Ea+dInRdBTNmioAL2xT1R2HP9a+HOA==", "cpu": [ "x64" ], @@ -2575,9 +2579,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-linux-x64-musl": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-x64-musl/-/vite-plus-linux-x64-musl-0.2.6.tgz", - "integrity": "sha512-29ned+euDKAl+BmCigzGrPykQ/kxTjtsPGV6G3hkImwakHNUnmLRayqmsWKHSwXmBk5xo5m+wqjMbo3G0npJQg==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-linux-x64-musl/-/vite-plus-linux-x64-musl-0.2.7.tgz", + "integrity": "sha512-0SwXa8XyagGcyCfjYr6vrOCcEJ37VCvO8MV1y0HVjcO+eqH399NgWNvHRUHiA2baiWQeBBBB+B9WyCpdpKSiag==", "cpu": [ "x64" ], @@ -2595,9 +2599,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-win32-arm64-msvc": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-win32-arm64-msvc/-/vite-plus-win32-arm64-msvc-0.2.6.tgz", - "integrity": "sha512-4b5ASpQlkxSXDYzuM6MYsOc1GnYkqG9CbSA1QzobXm87V/Q+MGPn57eILyacgfbmNNLrO2HQ5hasoLvd2BXaSg==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-win32-arm64-msvc/-/vite-plus-win32-arm64-msvc-0.2.7.tgz", + "integrity": "sha512-99spn9NEABFyn2lCrZL1nfpX06HhDNyWfZvX+e8zvSqV4LP2td2O4jI6St6KjuwVrK0VveNI/vwzJAe3CSGbzQ==", "cpu": [ "arm64" ], @@ -2612,9 +2616,9 @@ } }, "node_modules/@voidzero-dev/vite-plus-win32-x64-msvc": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-win32-x64-msvc/-/vite-plus-win32-x64-msvc-0.2.6.tgz", - "integrity": "sha512-rvGkm4WmVkCKPhvUbLkRtlVhi7EYb8GU8KaqIGJ0dVfNQHDEqnFboaiUHuPN1npFNwB+80Ulzz9AkQ/Q8MswQA==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@voidzero-dev/vite-plus-win32-x64-msvc/-/vite-plus-win32-x64-msvc-0.2.7.tgz", + "integrity": "sha512-Qe5XZLntwivz2fc1POrJnN6vIYGPcNks5q+GHPaCmYUtIBmAx53ME9OC/tLPpfno2jlZAHLy1vLAirkMVzvrwA==", "cpu": [ "x64" ], @@ -4728,9 +4732,9 @@ "optional": true }, "node_modules/tsx": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", - "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", + "version": "4.23.5", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.5.tgz", + "integrity": "sha512-rw55FUaqOoI7RvlQwLbhO4nSDApnQ4/CykPuiQ/EPvtrX3WA9Ig55jIt9VvbBJbzJuj12ueRu4PMZ2SxPVbihg==", "license": "MIT", "dependencies": { "esbuild": "~0.28.0" @@ -4853,9 +4857,9 @@ } }, "node_modules/vite-plus": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/vite-plus/-/vite-plus-0.2.6.tgz", - "integrity": "sha512-fFX8GLENhtzvnE4NmTPC8INRxjD2kZKcqUW0p7jOdawmHTNZqM5FiieyWOG6WH1a/axu9FCsbUNb918grfzDTw==", + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/vite-plus/-/vite-plus-0.2.7.tgz", + "integrity": "sha512-4xFo1nDtH/isWBdJaCgmctMiNohbuxa0uiKA/cbpL8jT18Ax73Lt6rK20/LkqPG5a/HpvPNhDCS1QK58pH4txA==", "dev": true, "license": "MIT", "dependencies": { @@ -4870,7 +4874,7 @@ "@vitest/snapshot": "4.1.10", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", - "@voidzero-dev/vite-plus-core": "0.2.6", + "@voidzero-dev/vite-plus-core": "0.2.7", "oxfmt": "=0.60.0", "oxlint": "=1.75.0", "oxlint-tsgolint": "=7.0.2001", @@ -4886,14 +4890,14 @@ "node": "^20.19.0 || ^22.18.0 || >=24.11.0" }, "optionalDependencies": { - "@voidzero-dev/vite-plus-darwin-arm64": "0.2.6", - "@voidzero-dev/vite-plus-darwin-x64": "0.2.6", - "@voidzero-dev/vite-plus-linux-arm64-gnu": "0.2.6", - "@voidzero-dev/vite-plus-linux-arm64-musl": "0.2.6", - "@voidzero-dev/vite-plus-linux-x64-gnu": "0.2.6", - "@voidzero-dev/vite-plus-linux-x64-musl": "0.2.6", - "@voidzero-dev/vite-plus-win32-arm64-msvc": "0.2.6", - "@voidzero-dev/vite-plus-win32-x64-msvc": "0.2.6" + "@voidzero-dev/vite-plus-darwin-arm64": "0.2.7", + "@voidzero-dev/vite-plus-darwin-x64": "0.2.7", + "@voidzero-dev/vite-plus-linux-arm64-gnu": "0.2.7", + "@voidzero-dev/vite-plus-linux-arm64-musl": "0.2.7", + "@voidzero-dev/vite-plus-linux-x64-gnu": "0.2.7", + "@voidzero-dev/vite-plus-linux-x64-musl": "0.2.7", + "@voidzero-dev/vite-plus-win32-arm64-msvc": "0.2.7", + "@voidzero-dev/vite-plus-win32-x64-msvc": "0.2.7" }, "peerDependencies": { "@vitest/browser-playwright": "4.1.10", diff --git a/package.json b/package.json index 703e3ee..9010c3d 100644 --- a/package.json +++ b/package.json @@ -41,11 +41,11 @@ "fmt": "vp fmt .", "lint": "vp lint src tests benchmarks vite.config.ts vitest.config.ts vitest.bench.config.ts", "typecheck": "tsc -p tsconfig.json --noEmit", - "test:changelog": "node scripts/verify-changelog.mjs", - "test:peer-floor": "node scripts/verify-peer-floor.mjs", + "test:changelog": "vp test run -c vitest.config.ts tests/changelog.test.ts", + "test:peer-floor": "vp test run -c vitest.integration.config.ts tests/integration/peer-floor.test.ts", "test:publint": "publint", "pack:check": "npm pack --ignore-scripts --dry-run --json", - "test:templates": "node scripts/verify-packed-templates.mjs", + "test:templates": "vp test run -c vitest.integration.config.ts tests/integration/packed-templates.test.ts", "bench": "npm run build --silent && npm run bench:analyze && node --import tsx benchmarks/cli.mjs --gate", "bench:analyze": "vp test bench --run -c vitest.bench.config.ts", "bench:json": "npm run build --silent && node --import tsx benchmarks/cli.mjs --gate --json", @@ -55,32 +55,32 @@ }, "dependencies": { "@npmcli/config": "^11.0.1", - "js-yaml": "^5.2.2", + "js-yaml": "^5.2.3", "minimatch": "^10.2.6", "npm-registry-fetch": "^20.0.1", "parse5": "^8.0.1", "semver": "^7.8.5", - "tsx": "^4.23.1", + "tsx": "^4.23.5", "typescript": "^6.0.3" }, "devDependencies": { - "@askrjs/askr": ">=0.0.53 <0.1.0", - "@askrjs/charts": ">=0.1.0 <0.2.0", - "@askrjs/logos": ">=0.0.3 <0.1.0", - "@askrjs/lucide": ">=0.0.3 <0.1.0", - "@askrjs/themes": ">=0.0.12 <0.1.0", - "@askrjs/ui": ">=0.0.13 <0.1.0", - "@askrjs/vite": ">=0.0.6 <0.1.0", + "@askrjs/askr": ">=0.0.85 <0.1.0", + "@askrjs/charts": ">=0.1.4 <0.2.0", + "@askrjs/logos": ">=0.0.5 <0.1.0", + "@askrjs/lucide": ">=0.0.6 <0.1.0", + "@askrjs/themes": ">=0.0.21 <0.1.0", + "@askrjs/ui": ">=0.0.24 <0.1.0", + "@askrjs/vite": ">=0.0.11 <0.1.0", "@types/js-yaml": "^4.0.9", - "@types/node": "^26.1.1", - "@types/semver": "^7.7.1", + "@types/node": "^26.1.2", + "@types/semver": "^7.8.0", "@vitest/coverage-v8": "^4.1.10", - "publint": "^0.3.21", - "vite-plus": "0.2.6", + "publint": "^0.3.22", + "vite-plus": "0.2.7", "vitest": "^4.1.10" }, "peerDependencies": { - "@askrjs/askr": ">=0.0.53 <0.1.0" + "@askrjs/askr": ">=0.0.85 <0.1.0" }, "peerDependenciesMeta": { "@askrjs/askr": { diff --git a/scripts/verify-changelog.mjs b/scripts/verify-changelog.mjs deleted file mode 100644 index 23ea2a6..0000000 --- a/scripts/verify-changelog.mjs +++ /dev/null @@ -1,20 +0,0 @@ -import { readFile } from "node:fs/promises"; - -const [manifestSource, changelog] = await Promise.all([ - readFile(new URL("../package.json", import.meta.url), "utf8"), - readFile(new URL("../CHANGELOG.md", import.meta.url), "utf8"), -]); -const { version } = JSON.parse(manifestSource); - -if (!/^## \[Unreleased\]$/m.test(changelog)) { - throw new Error("CHANGELOG.md must contain an [Unreleased] section."); -} -if ( - !new RegExp(`^## \\[${version.replaceAll(".", "\\.")}\\] - \\d{4}-\\d{2}-\\d{2}$`, "m").test( - changelog, - ) -) { - throw new Error( - `CHANGELOG.md must contain a dated [${version}] section. Release PRs must move Unreleased entries into the new version.`, - ); -} diff --git a/scripts/verify-packed-templates.mjs b/scripts/verify-packed-templates.mjs deleted file mode 100644 index cdace29..0000000 --- a/scripts/verify-packed-templates.mjs +++ /dev/null @@ -1,70 +0,0 @@ -import { execFile } from "node:child_process"; -import fs from "node:fs/promises"; -import os from "node:os"; -import path from "node:path"; -import { promisify } from "node:util"; - -const run = promisify(execFile); -const repository = path.resolve(import.meta.dirname, ".."); -const allTemplates = ["full-stack", "spa", "ssr", "ssg", "startkit"]; -const templates = process.env.ASKR_TEMPLATE - ? allTemplates.filter((template) => template === process.env.ASKR_TEMPLATE) - : allTemplates; -if (templates.length === 0) throw new Error(`Unknown ASKR_TEMPLATE: ${process.env.ASKR_TEMPLATE}`); -const root = await fs.mkdtemp(path.join(os.tmpdir(), "askr-packed-templates-")); - -async function command(executable, args, cwd) { - const { stdout, stderr } = await run(executable, args, { - cwd, - env: { ...process.env, NO_COLOR: "1" }, - maxBuffer: 20 * 1024 * 1024, - }); - if (stdout) process.stdout.write(stdout); - if (stderr) process.stderr.write(stderr); -} - -try { - await command("npm", ["run", "build"], repository); - const { stdout } = await run("npm", ["pack", "--ignore-scripts", "--pack-destination", root], { - cwd: repository, - }); - const archive = path.join(root, stdout.trim().split(/\r?\n/).at(-1)); - - for (const template of templates) { - const fixtureRoot = path.join(root, template); - const project = path.join(fixtureRoot, `fixture-${template}`); - await fs.mkdir(fixtureRoot, { recursive: true }); - await command( - process.execPath, - [ - path.join(repository, "dist", "cli.js"), - "create", - template, - `fixture-${template}`, - "--dir", - project, - "--no-install", - "--no-skills", - ], - fixtureRoot, - ); - - const manifestPath = path.join(project, "package.json"); - const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8")); - manifest.devDependencies = { - ...manifest.devDependencies, - "@askrjs/cli": `file:${archive}`, - }; - await fs.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`); - await command("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund"], project); - await command("npm", ["run", "check"], project); - - if (template === "ssg") { - for (const relative of ["sitemap.xml", "robots.txt", ".askr/sitemap-manifest.json"]) { - await fs.access(path.join(project, "dist", relative)); - } - } - } -} finally { - await fs.rm(root, { recursive: true, force: true }); -} diff --git a/scripts/verify-peer-floor.mjs b/scripts/verify-peer-floor.mjs deleted file mode 100644 index 5d8fd98..0000000 --- a/scripts/verify-peer-floor.mjs +++ /dev/null @@ -1,85 +0,0 @@ -import { execFile } from "node:child_process"; -import fs from "node:fs/promises"; -import os from "node:os"; -import path from "node:path"; -import { promisify } from "node:util"; - -const run = promisify(execFile); -const repository = path.resolve(import.meta.dirname, ".."); -const root = await fs.mkdtemp(path.join(os.tmpdir(), "askr-cli-peer-floor-")); - -async function command(executable, args, cwd) { - const { stdout, stderr } = await run(executable, args, { - cwd, - env: { ...process.env, NO_COLOR: "1" }, - maxBuffer: 20 * 1024 * 1024, - }); - if (stdout) process.stdout.write(stdout); - if (stderr) process.stderr.write(stderr); - return stdout; -} - -try { - await command("npm", ["run", "build"], repository); - const packed = await command( - "npm", - ["pack", "--ignore-scripts", "--pack-destination", root], - repository, - ); - const archive = path.join(root, packed.trim().split(/\r?\n/).at(-1)); - - await fs.writeFile( - path.join(root, "package.json"), - `${JSON.stringify( - { - name: "askr-cli-peer-floor-consumer", - private: true, - type: "module", - dependencies: { - "@askrjs/askr": "0.0.53", - "@askrjs/cli": `file:${archive}`, - }, - }, - null, - 2, - )}\n`, - ); - await fs.writeFile( - path.join(root, "ssg.config.ts"), - `import { createRouteRegistry, route } from "@askrjs/askr/router"; - -const registry = createRouteRegistry(() => { - route("/", () => ({ type: "main", children: ["Askr peer floor"] })); -}); - -export const staticConfig = { - registry, - siteUrl: "https://example.com", - document: ({ appHtml }) => - '
' + - appHtml + - "", -}; -`, - ); - - await command("npm", ["install", "--ignore-scripts", "--no-audit", "--no-fund"], root); - await command( - path.join(root, "node_modules", ".bin", "askr"), - ["ssg", "--config", "./ssg.config.ts", "--output", "./dist"], - root, - ); - - const [html, sitemap, report] = await Promise.all([ - fs.readFile(path.join(root, "dist", "index.html"), "utf8"), - fs.readFile(path.join(root, "dist", "sitemap.xml"), "utf8"), - fs.readFile(path.join(root, "dist", ".askr", "ssg-output.json"), "utf8"), - ]); - if (!html.includes("Askr peer floor")) throw new Error("peer-floor route did not render"); - if (!sitemap.includes("https://example.com/")) throw new Error("peer-floor sitemap is invalid"); - if (JSON.parse(report).routes?.[0]?.route !== "/") { - throw new Error("peer-floor output report is invalid"); - } -} finally { - await fs.rm(root, { recursive: true, force: true }); -} diff --git a/tests/changelog.test.ts b/tests/changelog.test.ts new file mode 100644 index 0000000..9425d59 --- /dev/null +++ b/tests/changelog.test.ts @@ -0,0 +1,15 @@ +import { readFile } from "node:fs/promises"; +import { expect, test } from "vitest"; + +test("changelog has unreleased and current-version sections", async () => { + const [manifestSource, changelog] = await Promise.all([ + readFile(new URL("../package.json", import.meta.url), "utf8"), + readFile(new URL("../CHANGELOG.md", import.meta.url), "utf8"), + ]); + const { version } = JSON.parse(manifestSource) as { version: string }; + + expect(changelog).toMatch(/^## \[Unreleased\]$/m); + expect(changelog).toMatch( + new RegExp(`^## \\[${version.replaceAll(".", "\\.")}\\] - \\d{4}-\\d{2}-\\d{2}$`, "m"), + ); +}); diff --git a/tests/integration/packed-templates.test.ts b/tests/integration/packed-templates.test.ts new file mode 100644 index 0000000..096d608 --- /dev/null +++ b/tests/integration/packed-templates.test.ts @@ -0,0 +1,78 @@ +import { execFile } from "node:child_process"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; +import { expect, test } from "vitest"; + +const execFileAsync = promisify(execFile); +const repository = fileURLToPath(new URL("../../", import.meta.url)); +const allTemplates = ["full-stack", "spa", "ssr", "ssg", "startkit"]; + +async function run(command: string, args: string[], cwd: string): Promise