diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..9a3e346 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json", + "changelog": ["@changesets/changelog-github", { "repo": "expilu/smart-decisions" }], + "commit": false, + "access": "public", + "baseBranch": "main" +} diff --git a/.changeset/remove-openai-dependency.md b/.changeset/remove-openai-dependency.md new file mode 100644 index 0000000..55b7e2c --- /dev/null +++ b/.changeset/remove-openai-dependency.md @@ -0,0 +1,6 @@ +--- +'smart-decisions': minor +--- + +Removed the `openai` SDK dependency: the library now talks to the OpenAI-compatible +endpoint with an in-library HTTP transport. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a270b22 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,54 @@ +name: CI + +# Pull-request pipeline plus the always-on main baseline. Gate list mirrors +# `prepublishOnly` exactly: CI green here means a release would pass too. +# Failed tests land as inline annotations pinned to the changed lines; the +# coverage bot posts a per-PR comment comparing coverage against main. +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + code-quality: write # lets github-code-quality[bot] post coverage on the PR + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + node: [22, 24] # engines: >=22 — test the floor and the current LTS + + steps: + - uses: actions/checkout@v4 + # Pin the PR head SHA so coverage line numbers map onto the diff view. + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + cache: npm + + - run: npm ci + - run: npm run lint + - run: npm run format:check + - run: npm run typecheck + - run: npm run build + - run: npm run test:coverage + + - name: Upload coverage for PR comment + # Sugar, not a gate: coverage thresholds already fail the job via + # test:coverage. Upload hiccups (preview feature) must not block merges. + if: >- + matrix.node == 22 && + github.event_name == 'pull_request' && + !github.event.pull_request.head.repo.fork + uses: actions/upload-code-coverage@v1 + with: + file: coverage/cobertura-coverage.xml + language: JavaScript + label: code-coverage/vitest + fail-on-error: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d96d5bb --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,51 @@ +name: Release + +# Two modes, decided by changesets/action: +# - pending changesets on main -> opens/updates the "Version Packages" PR +# (bumps package.json, rewrites CHANGELOG.md, syncs src/version.ts) +# - no pending changesets (a Version Packages PR was just merged) +# -> builds, publishes to npm with provenance (OIDC trusted publishing, +# no stored token), tags vX.Y.Z, creates the GitHub release from the +# CHANGELOG.md section. +on: + push: + branches: [main] + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: write # tag + GitHub release + Version Packages PR + pull-requests: write # Version Packages PR + id-token: write # npm trusted publishing (OIDC) + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + # npm >= 11.5 is required for OIDC trusted publishing. + - run: npm i -g npm@11 + + - run: npm ci + + - uses: changesets/action@v1 + id: changesets + with: + version: npx changeset version && node ./scripts/sync-version.mjs + publish: node ./scripts/release.mjs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Tag and create GitHub release + if: steps.changesets.outputs.published == 'true' + run: node ./scripts/github-release.mjs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/package-lock.json b/package-lock.json index 7d7a1bb..7245d0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,8 @@ "version": "0.0.2", "license": "MIT", "devDependencies": { + "@changesets/changelog-github": "^1.0.1", + "@changesets/cli": "^3.0.3", "@types/node": "^22", "@vitest/coverage-v8": "^5.0.2", "eslint": "^10.11.0", @@ -107,6 +109,306 @@ "keyv": "^5.6.0" } }, + "node_modules/@changesets/apply-release-plan": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-8.1.1.tgz", + "integrity": "sha512-nk2Hbq3M9NeHMdaIRBDvawKrQJzXB9sGyBUE5yWnIKtvdcNCPj75zBekJYr0gTT2ecJH3eFeTrc8eisPImcv0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/config": "^4.0.1", + "@changesets/format": "^0.1.2", + "@changesets/git": "^4.0.1", + "@changesets/should-skip-package": "^1.0.0", + "@changesets/types": "^7.0.0", + "import-meta-resolve": "^4.2.0", + "jsonc-parser": "^3.3.1", + "semver": "^7.8.1" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/assemble-release-plan": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@changesets/assemble-release-plan/-/assemble-release-plan-7.0.0.tgz", + "integrity": "sha512-oEW8BxdA604kGGtDSCiHr5w9Tv4UWe9I2k61IBNZzCOE1kbYaJj4v+lFQNgcEZFkUc2pV/+hASErGDvpJOZCTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^1.0.0", + "@changesets/get-dependents-graph": "^3.0.0", + "@changesets/should-skip-package": "^1.0.0", + "@changesets/types": "^7.0.0", + "semver": "^7.8.1" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/changelog-git": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/changelog-git/-/changelog-git-1.0.0.tgz", + "integrity": "sha512-3Dst2Ime2Op5nd4XmWJLPIgp11ZFqJqSkVug9izK6TDcIV4YlhPS4ECbEVR+eGI0bk0r1ItogD4j2Oli87bJrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^7.0.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/changelog-github": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@changesets/changelog-github/-/changelog-github-1.0.1.tgz", + "integrity": "sha512-QUcrV7878yHlWPXXPA6gqSxNKwtVHCd1K22L7ZAoJw2yGy8K4QvjwOStD75+1Q9KD9ZSUgGS94HuYdd7HOVtpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/get-github-info": "^1.0.1", + "@changesets/types": "^7.0.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/cli": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@changesets/cli/-/cli-3.0.3.tgz", + "integrity": "sha512-L1i67bMfkK6iC/kDejuNO4pFgTRcWIObsD6Qof2woP1I4M8tj17DGlcpRaTzTv1DYSP5EMwWdEtR8X0Pj5Jg3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/apply-release-plan": "^8.1.1", + "@changesets/assemble-release-plan": "^7.0.0", + "@changesets/changelog-git": "^1.0.0", + "@changesets/config": "^4.0.1", + "@changesets/errors": "^1.0.0", + "@changesets/get-dependents-graph": "^3.0.0", + "@changesets/git": "^4.0.1", + "@changesets/pre": "^3.0.0", + "@changesets/read": "^1.0.1", + "@changesets/should-skip-package": "^1.0.0", + "@changesets/types": "^7.0.0", + "@changesets/write": "^1.0.1", + "@clack/prompts": "^1.7.0", + "@manypkg/get-packages": "^3.1.0", + "@pnpm/deps.graph-sequencer": "^1100.0.1", + "cac": "^7.0.0", + "import-meta-resolve": "^4.2.0", + "launch-editor": "^2.14.1", + "package-manager-detector": "^1.6.0", + "semver": "^7.8.1", + "tinyexec": "^1.3.1" + }, + "bin": { + "changeset": "bin.js" + }, + "engines": { + "node": "^22.11 || ^24 || >=26", + "npm": ">=10.9.0", + "pnpm": ">=10.0.0", + "yarn": ">=4.5.2" + } + }, + "node_modules/@changesets/config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@changesets/config/-/config-4.0.1.tgz", + "integrity": "sha512-PxpxSQLQSLDjceAgRq+4FWlU+HflDIw03mmPVipxoQx9xrXFUjmqTy3SE+n/2t+DqPdpPiLWVr5NRiLZvjyLwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/get-dependents-graph": "^3.0.0", + "@changesets/should-skip-package": "^1.0.0", + "@changesets/types": "^7.0.0", + "@manypkg/get-packages": "^3.1.0", + "picomatch": "^4.0.7" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/errors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/errors/-/errors-1.0.0.tgz", + "integrity": "sha512-ElN/mEzn6zmETgjwf5MclCMa9ef59sAR0lfO8VSYIsiRvbC2FbLB/92EoYw10Sl0kGixxHFiJZUSv7dA+YpR8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/format": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@changesets/format/-/format-0.1.2.tgz", + "integrity": "sha512-Caez5XtNXCFS/G5bwyav3wuXL0tMxVd2ZGbaumWbzN08tyzO21asCw7JZhNtVsAZDCvDRUzZN+Iit9SyRITSYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.8.0", + "tinyexec": "^1.3.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/get-dependents-graph": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@changesets/get-dependents-graph/-/get-dependents-graph-3.0.0.tgz", + "integrity": "sha512-ji/t5wFA1zREKXRUePE6Qi+Qu2UgxCeSSGQrphezwvQZrp49B7sJ+8+wvM0tA7zPeSxYKCojDy3WWgrl+s+awg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^7.0.0", + "semver": "^7.8.1" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/get-github-info": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@changesets/get-github-info/-/get-github-info-1.0.1.tgz", + "integrity": "sha512-ifLQCky/ZtDgZ4xCiUMjnLZSJ8xk52iIzMJbBBPlZWjr2CiTNTaTDddIVWKicrUAdWuLWL7PUw2fNa1yPwLpIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dataloader": "^2.2.3" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/git": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@changesets/git/-/git-4.0.1.tgz", + "integrity": "sha512-6vWpIAC4LkpmlqaIu37ViT5enyd9+uBwAiGqCGhASvkSHBgx4PrtTGXWTMFYpDmZbjgyonyVgMciPrW4MqtJsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^1.0.0", + "@changesets/types": "^7.0.0", + "@manypkg/get-packages": "^3.1.0", + "picomatch": "^4.0.4", + "tinyexec": "^1.3.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/parse/-/parse-1.0.0.tgz", + "integrity": "sha512-P0iaMb9p9CRYZiTgAllEIF9AUMQHIy1G72tKlcIqJp61icZSsKQNiOPdxAMZG8m/DvZwt/oz5xEbTpike//dWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^7.0.0", + "yaml": "^2.9.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/pre": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@changesets/pre/-/pre-3.0.0.tgz", + "integrity": "sha512-Zm/6YliV/a2oeWTqHJf6KxLrQwgcK1i/BRDl2m0EKZvbnxV5fG9QRhwJJGshjsZTUTS6dkfURQ2K6aAvgNw/3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/errors": "^1.0.0", + "@changesets/types": "^7.0.0", + "@manypkg/get-packages": "^3.1.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/read": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@changesets/read/-/read-1.0.1.tgz", + "integrity": "sha512-nzvSC6RcTiWf/dsuwZFXpLzGGsRHYCL68U3uHV7srsulU93aVWY7u2GEJiDZ+4GIIElfaW5EqtKC0UHroY+LTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/git": "^4.0.1", + "@changesets/parse": "^1.0.0", + "@changesets/types": "^7.0.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/should-skip-package": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@changesets/should-skip-package/-/should-skip-package-1.0.0.tgz", + "integrity": "sha512-pwqoJmbONn1XgXmZXPEExgAaT+HdZLjALFTDgIm+PnS5KeO2nLtzA2/Q+4aMFY14kFMuXKG30MObhvDzWgzDgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/types": "^7.0.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/types": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@changesets/types/-/types-7.0.0.tgz", + "integrity": "sha512-c5GoiQyt3pxiXjrWSNoP8/GRf4kG+VnKzovx1OQM8dYYALlSwgedmkPmJ+ZqGxqwg9D3Bkj85Uo4KLd5BN3A0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@changesets/write": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@changesets/write/-/write-1.0.1.tgz", + "integrity": "sha512-q/ThtP9gcnEP6xlv7LrY26C2mHqdGBti/MmOVngt/M/oIGYkssmQGxPK9WzBNt2juVcH/vml2WQ+ra8LXYOTaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@changesets/format": "^0.1.1", + "@changesets/types": "^7.0.0", + "human-id": "^4.2.0" + }, + "engines": { + "node": "^22.11 || ^24 || >=26" + } + }, + "node_modules/@clack/core": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.5.1.tgz", + "integrity": "sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@clack/prompts": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.8.1.tgz", + "integrity": "sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/core": "1.5.1", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", @@ -774,6 +1076,48 @@ "dev": true, "license": "MIT" }, + "node_modules/@manypkg/find-root": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-3.1.0.tgz", + "integrity": "sha512-BcSqCyKhBVZ5YkSzOiheMCV41kqAFptW6xGqYSTjkVTl9XQpr+pqHhwgGCOHQtjDCv7Is6EFyA14Sm5GVbVABA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@manypkg/tools": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@manypkg/get-packages": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@manypkg/get-packages/-/get-packages-3.1.0.tgz", + "integrity": "sha512-0TbBVyvPrP7xGYBI/cP8UP+yl/z+HtbTttAD7FMAJgn/kXOTwh5/60TsqP9ZYY710forNfyV0N8P/IE/ujGZJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@manypkg/find-root": "^3.1.0", + "@manypkg/tools": "^2.1.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@manypkg/tools": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@manypkg/tools/-/tools-2.1.2.tgz", + "integrity": "sha512-6QEf6yqFbETdwGITKq57aYoPfX/3K8XFNwsAlx0C1M7o8cb79sv1M3w+tWuWvIcSbNqrLF7OD7YpZMVVz335hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "jju": "^1.4.0", + "tinyglobby": "^0.2.13", + "yaml": "^2.9.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@oxc-project/types": { "version": "0.151.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.151.0.tgz", @@ -785,6 +1129,19 @@ "url": "https://github.com/sponsors/oxc-project" } }, + "node_modules/@pnpm/deps.graph-sequencer": { + "version": "1100.0.1", + "resolved": "https://registry.npmjs.org/@pnpm/deps.graph-sequencer/-/deps.graph-sequencer-1100.0.1.tgz", + "integrity": "sha512-pOr5+q1fLYKwFN3LAJuGZEnfXDcQ73zqgDHMtGy+K+uIoUqyY+6MeDCWFwfu+4EFuq76I5EPFofoNAI+Bmmq4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.13" + }, + "funding": { + "url": "https://opencollective.com/pnpm" + } + }, "node_modules/@rolldown/binding-android-arm-eabi": { "version": "1.2.11", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.11.tgz", @@ -1517,6 +1874,16 @@ "node": "20 || >=22" } }, + "node_modules/cac": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", + "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/cacheable": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", @@ -1556,6 +1923,13 @@ "node": ">= 8" } }, + "node_modules/dataloader": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz", + "integrity": "sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==", + "dev": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1850,6 +2224,33 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -1962,6 +2363,16 @@ "dev": true, "license": "MIT" }, + "node_modules/human-id": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/human-id/-/human-id-4.2.1.tgz", + "integrity": "sha512-zPGsiS+dWoTZtZ4AtpA9Y+BdSFSNWvnouNlWNoUFyAM6xHOHmdCvqO3k8AIbdamCOv4gUFUVNPf6rJFfc4UiJw==", + "dev": true, + "license": "MIT", + "bin": { + "human-id": "dist/cli.js" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -1972,6 +2383,17 @@ "node": ">= 4" } }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -2022,6 +2444,13 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jju": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", + "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", + "dev": true, + "license": "MIT" + }, "node_modules/js-tokens": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", @@ -2043,6 +2472,13 @@ "dev": true, "license": "MIT" }, + "node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, "node_modules/keyv": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", @@ -2053,6 +2489,17 @@ "@keyv/serialize": "^1.1.1" } }, + "node_modules/launch-editor": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.14.1.tgz", + "integrity": "sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.4" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2492,6 +2939,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-manager-detector": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", + "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2517,8 +2971,7 @@ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.7", @@ -2690,6 +3143,26 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.10.0.tgz", + "integrity": "sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2718,9 +3191,9 @@ } }, "node_modules/tinyexec": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", - "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "dev": true, "license": "MIT", "engines": { @@ -3055,6 +3528,22 @@ "node": ">=0.10.0" } }, + "node_modules/yaml": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz", + "integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index dfaa202..28e122d 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "lint": "eslint .", "format": "prettier --write .", "format:check": "prettier --check .", + "changeset": "changeset", "build": "tsc -p tsconfig.build.json", "typecheck": "tsc", "test": "vitest run", @@ -45,6 +46,8 @@ "dist" ], "devDependencies": { + "@changesets/changelog-github": "^1.0.1", + "@changesets/cli": "^3.0.3", "@types/node": "^22", "@vitest/coverage-v8": "^5.0.2", "eslint": "^10.11.0", diff --git a/scripts/github-release.mjs b/scripts/github-release.mjs new file mode 100644 index 0000000..c43f61d --- /dev/null +++ b/scripts/github-release.mjs @@ -0,0 +1,37 @@ +import { execSync } from 'node:child_process'; +import { mkdtempSync, readFileSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; + +/** + * Release-announcement step of the Release workflow. Runs after a successful + * npm publish and: + * + * 1. tags the release commit as vX.Y.Z (repo convention matches the existing + * v0.0.2 tag) and pushes the tag; + * 2. extracts this version's section from CHANGELOG.md and creates the GitHub + * release with it as the body — release notes and changelog share one + * source of truth. + */ +const { version } = JSON.parse(readFileSync('package.json', 'utf8')); +const tag = `v${version}`; + +execSync(`git tag ${tag}`, { stdio: 'inherit' }); +execSync(`git push origin ${tag}`, { stdio: 'inherit' }); + +const changelog = readFileSync('CHANGELOG.md', 'utf8'); +const section = changelog + .split(/^## /m) + .find((s) => s.startsWith(`${version} `) || s.startsWith(`v${version} `)); + +if (!section) { + throw new Error(`No CHANGELOG.md section found for ${version}`); +} + +const notesFile = join(mkdtempSync(join(tmpdir(), 'release-notes-')), 'notes.md'); +writeFileSync(notesFile, `${section.trimEnd()}\n`); + +execSync(`gh release create ${tag} --title "${tag}" --notes-file "${notesFile}"`, { + stdio: 'inherit', +}); +console.log(`Created GitHub release ${tag}`); diff --git a/scripts/release.mjs b/scripts/release.mjs new file mode 100644 index 0000000..4951ece --- /dev/null +++ b/scripts/release.mjs @@ -0,0 +1,35 @@ +import { execSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; + +/** + * Publish step of the Release workflow (changesets action's `publish` input). + * + * - Builds dist/ fresh before publishing. + * - Unsets GITHUB_TOKEN from the environment: with it present, npm refuses to + * fall through to OIDC trusted publishing. + * - Publishes with --provenance (requires npm >= 11.5 and the Trusted Publisher + * registered on npmjs.com for this repo + workflow). + * - Idempotent: skips if the exact version is already on the registry (safe on + * workflow retries). + */ +const { name, version } = JSON.parse(readFileSync('package.json', 'utf8')); + +const env = { ...process.env }; +delete env.GITHUB_TOKEN; + +execSync('npm run build', { stdio: 'inherit' }); + +let exists = false; +try { + execSync(`npm view ${name}@${version} version`, { env, stdio: 'ignore' }); + exists = true; +} catch { + exists = false; +} + +if (exists) { + console.log(`${name}@${version} is already published — nothing to do.`); +} else { + execSync(`npm publish --provenance --access public`, { env, stdio: 'inherit' }); + console.log(`Published ${name}@${version}`); +} diff --git a/scripts/sync-version.mjs b/scripts/sync-version.mjs new file mode 100644 index 0000000..117e364 --- /dev/null +++ b/scripts/sync-version.mjs @@ -0,0 +1,25 @@ +import { readFileSync, writeFileSync } from 'node:fs'; + +/** + * Keeps src/version.ts in sync with package.json. Runs inside the Version + * Packages PR (wired into changesets' `version` step), so the VERSION constant + * — shown in the User-Agent of every request — bumps together with the package. + * + * test/version.test.ts stays the tripwire: if this script is ever skipped, + * the version test fails in that same PR. + */ +const pkg = JSON.parse(readFileSync('package.json', 'utf8')); +const path = 'src/version.ts'; +const code = readFileSync(path, 'utf8'); +const updated = code.replace( + /export const VERSION = '[^']*';/, + `export const VERSION = '${pkg.version}';`, +); + +if (updated === code) { + console.error('sync-version: no version literal replaced in src/version.ts.'); + process.exitCode = 1; +} else { + writeFileSync(path, updated); + console.log(`sync-version: src/version.ts -> ${pkg.version}`); +} diff --git a/vitest.config.ts b/vitest.config.ts index 37efada..5f2f3f9 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -7,7 +7,14 @@ export default defineConfig({ // index.ts is a barrel of re-exports; types/ holds only interface/type // declarations, i.e. no executable code to cover. exclude: ['src/index.ts', 'src/types/**'], - reporter: ['text', 'html'], + reporter: [ + 'text', + 'html', + // Machine-readable output consumed by CI: json-summary for the coverage + // trend comment, cobertura for actions/upload-code-coverage. + 'json-summary', + 'cobertura', + ], thresholds: { lines: 100, functions: 100, branches: 100, statements: 100 }, }, },