diff --git a/.github/workflows/__greetings.yml b/.github/workflows/__greetings.yml index 1f577444..5e9d4c27 100644 --- a/.github/workflows/__greetings.yml +++ b/.github/workflows/__greetings.yml @@ -10,7 +10,7 @@ permissions: {} jobs: greetings: - uses: hoverkraft-tech/ci-github-common/.github/workflows/greetings.yml@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/.github/workflows/greetings.yml@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 permissions: contents: read issues: write diff --git a/.github/workflows/__main-ci.yml b/.github/workflows/__main-ci.yml index dbb89ecd..47eced87 100644 --- a/.github/workflows/__main-ci.yml +++ b/.github/workflows/__main-ci.yml @@ -35,7 +35,7 @@ jobs: release: needs: ci if: github.event_name != 'schedule' - uses: hoverkraft-tech/ci-github-publish/.github/workflows/release-actions.yml@ea6682de88070c254dac619a6623a242a6d57cd3 # 1.0.3 + uses: hoverkraft-tech/ci-github-publish/.github/workflows/release-actions.yml@f06e0e72a76bfca57c34d08d03e5c1914275fd4c # 2.0.0 permissions: contents: read with: diff --git a/.github/workflows/__need-fix-to-issue.yml b/.github/workflows/__need-fix-to-issue.yml index df58bceb..e91c899a 100644 --- a/.github/workflows/__need-fix-to-issue.yml +++ b/.github/workflows/__need-fix-to-issue.yml @@ -18,7 +18,7 @@ permissions: {} jobs: main: - uses: hoverkraft-tech/ci-github-common/.github/workflows/need-fix-to-issue.yml@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/.github/workflows/need-fix-to-issue.yml@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 permissions: contents: read issues: write diff --git a/.github/workflows/__prepare-release.yml b/.github/workflows/__prepare-release.yml index 0656096b..70c1f302 100644 --- a/.github/workflows/__prepare-release.yml +++ b/.github/workflows/__prepare-release.yml @@ -10,7 +10,7 @@ permissions: {} jobs: release: - uses: hoverkraft-tech/ci-github-publish/.github/workflows/prepare-release.yml@ea6682de88070c254dac619a6623a242a6d57cd3 # 1.0.3 + uses: hoverkraft-tech/ci-github-publish/.github/workflows/prepare-release.yml@f06e0e72a76bfca57c34d08d03e5c1914275fd4c # 2.0.0 permissions: contents: read pull-requests: write diff --git a/.github/workflows/__release.yml b/.github/workflows/__release.yml index b6fbd96a..0215296f 100644 --- a/.github/workflows/__release.yml +++ b/.github/workflows/__release.yml @@ -53,14 +53,14 @@ jobs: - name: Plan release id: plan-release - uses: hoverkraft-tech/ci-github-publish/actions/release/plan@ea6682de88070c254dac619a6623a242a6d57cd3 # 1.0.3 + uses: hoverkraft-tech/ci-github-publish/actions/release/plan@f06e0e72a76bfca57c34d08d03e5c1914275fd4c # 2.0.0 with: target-sha: ${{ github.sha }} github-token: ${{ steps.generate-token.outputs.token }} - name: Create and publish release if: (steps.plan-release.outputs.has-changes == 'true' || (github.event_name == 'workflow_dispatch' && inputs.force)) && steps.plan-release.outputs.tag != '' - uses: hoverkraft-tech/ci-github-publish/actions/release/create@ea6682de88070c254dac619a6623a242a6d57cd3 # 1.0.3 + uses: hoverkraft-tech/ci-github-publish/actions/release/create@f06e0e72a76bfca57c34d08d03e5c1914275fd4c # 2.0.0 env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} # zizmor: ignore[secrets-outside-env] repository secret is scoped to the release summary step with: diff --git a/.github/workflows/__semantic-pull-request.yml b/.github/workflows/__semantic-pull-request.yml index 30bf08e6..6798cf6a 100644 --- a/.github/workflows/__semantic-pull-request.yml +++ b/.github/workflows/__semantic-pull-request.yml @@ -12,7 +12,7 @@ permissions: {} jobs: main: - uses: hoverkraft-tech/ci-github-common/.github/workflows/semantic-pull-request.yml@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/.github/workflows/semantic-pull-request.yml@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 permissions: contents: write pull-requests: write diff --git a/.github/workflows/__shared-ci.yml b/.github/workflows/__shared-ci.yml index bc33e30b..087215b6 100644 --- a/.github/workflows/__shared-ci.yml +++ b/.github/workflows/__shared-ci.yml @@ -7,7 +7,7 @@ permissions: {} jobs: linter: - uses: hoverkraft-tech/ci-github-common/.github/workflows/linter.yml@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/.github/workflows/linter.yml@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 permissions: actions: read contents: read @@ -56,6 +56,20 @@ jobs: permissions: contents: read + test-action-package-test: + name: Test action "package/test" + needs: linter + uses: $/.github/workflows/__test-action-package-test.yml + permissions: + contents: read + + test-action-package-discover: + name: Test action "package/discover" + needs: linter + uses: $/.github/workflows/__test-action-package-discover.yml + permissions: + contents: read + test-action-setup-node: name: Test action "setup-node" needs: linter diff --git a/.github/workflows/__stale.yml b/.github/workflows/__stale.yml index 8f31024e..e4513161 100644 --- a/.github/workflows/__stale.yml +++ b/.github/workflows/__stale.yml @@ -8,7 +8,7 @@ permissions: {} jobs: main: - uses: hoverkraft-tech/ci-github-common/.github/workflows/stale.yml@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/.github/workflows/stale.yml@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 permissions: issues: write pull-requests: write diff --git a/.github/workflows/__test-action-package-discover.yml b/.github/workflows/__test-action-package-discover.yml new file mode 100644 index 00000000..a44a462a --- /dev/null +++ b/.github/workflows/__test-action-package-discover.yml @@ -0,0 +1,171 @@ +name: Internal - Tests for "package/discover" action + +on: + workflow_call: + +permissions: {} + +jobs: + discover: + name: Package discovery (${{ matrix.fixture.name }}) + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + fail-fast: false + matrix: + fixture: + - name: standalone npm + root: { private: false } + expected: ["."] + - name: npm workspaces and exclusions + root: + private: true + workspaces: [packages/*, "!packages/excluded"] + expected: [packages/cli, packages/core] + - name: workspace root is not implicitly packaged + root: + private: false + workspaces: [packages/core] + expected: [packages/core] + - name: Yarn workspace object + manager: yarn + root: + workspaces: { packages: [packages/*, "!packages/excluded"] } + expected: [packages/cli, packages/core] + - name: pnpm workspaces + manager: pnpm + files: + pnpm-workspace.yaml: | + packages: + - 'packages/*' + - '!packages/excluded' + expected: [packages/cli, packages/core] + - name: pnpm 10 workspaces + manager: pnpm10 + files: + pnpm-workspace.yaml: | + packages: + - 'packages/*' + - '!packages/excluded' + expected: [packages/cli, packages/core] + - name: standalone pnpm with settings + manager: pnpm + root: { private: false } + files: + pnpm-workspace.yaml: "packages: []" + expected: ["."] + - name: Lerna package patterns take precedence + root: { workspaces: [packages/excluded] } + files: + lerna.json: '{"version":"independent","packages":["packages/*","!packages/excluded"]}' + expected: [packages/cli, packages/core] + - name: explicit subset + root: { workspaces: [packages/*] } + directories: '["packages/core"]' + expected: [packages/core] + - name: explicit root + root: { private: false, workspaces: [packages/*] } + directories: '["."]' + expected: ["."] + - name: private standalone + expected: failure + - name: private workspaces only + root: { workspaces: [packages/private] } + expected: failure + - name: unmatched workspaces + root: { workspaces: [missing/*] } + expected: failure + - name: explicit private package + directories: '["packages/private"]' + expected: failure + - name: empty override + directories: "[]" + expected: failure + - name: invalid override + directories: '{"directory":"packages/core"}' + expected: failure + - name: escaping directory + directories: '["../outside"]' + expected: failure + - name: duplicate normalized directories + directories: '["packages/core", "./packages/core/"]' + expected: failure + - name: symlink outside the project + directories: '["linked"]' + expected: failure + steps: + - name: Arrange - Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Arrange - Create uninstalled project fixtures + id: fixture + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + FIXTURE: ${{ toJSON(matrix.fixture) }} + with: + script: | + const fs = require('node:fs'); + const path = require('node:path'); + const fixture = JSON.parse(process.env.FIXTURE); + const temporary = fs.mkdtempSync(path.join(process.env.RUNNER_TEMP, 'package-discovery-')); + const directory = path.join(temporary, 'project'); + const write = async (file, content) => { + const target = path.join(directory, file); + await io.mkdirP(path.dirname(target)); + fs.writeFileSync(target, typeof content === 'string' ? content : JSON.stringify(content)); + }; + const manifest = { name: 'discovery-root', version: '1.0.0', private: true, ...fixture.root, + scripts: { preinstall: 'exit 91', postinstall: 'exit 91', prepare: 'exit 91' } }; + if (fixture.manager?.startsWith('pnpm')) { + const lock = JSON.parse(fs.readFileSync('actions/release/prepare/package-lock.json', 'utf8')); + manifest.packageManager = `pnpm@${lock.packages[`node_modules/${fixture.manager}`].version}`; + await write('pnpm-lock.yaml', "lockfileVersion: '9.0'\n"); + } else if (fixture.manager === 'yarn') { + await write('yarn.lock', '# yarn lockfile v1\n'); + } else { + await write('package-lock.json', { name: manifest.name, version: manifest.version, lockfileVersion: 3, packages: { '': manifest } }); + } + await write('package.json', manifest); + await write('.node-version', fs.readFileSync('.node-version', 'utf8')); + for (const name of ['core', 'cli', 'private', 'excluded']) { + await write(`packages/${name}/package.json`, { name: `@example/${name}`, version: '1.0.0', private: name === 'private', scripts: manifest.scripts }); + } + await write('examples/unconfigured/package.json', { name: 'unconfigured', version: '1.0.0' }); + for (const [file, content] of Object.entries(fixture.files || {})) await write(file, content); + await io.mkdirP(path.join(temporary, 'outside')); + fs.writeFileSync(path.join(temporary, 'outside/package.json'), JSON.stringify({ name: 'outside', version: '1.0.0' })); + fs.symlinkSync(path.join(temporary, 'outside'), path.join(directory, 'linked'), 'dir'); + core.setOutput('directory', directory); + const lockfile = fixture.manager?.startsWith('pnpm') ? 'pnpm-lock.yaml' : fixture.manager === 'yarn' ? 'yarn.lock' : 'package-lock.json'; + core.setOutput('lockfile', path.join(directory, lockfile)); + core.setOutput('lock-content', fs.readFileSync(path.join(directory, lockfile), 'utf8')); + - name: Act - Discover packages without installing dependencies + id: discover + continue-on-error: true + uses: $/actions/package/discover + with: + working-directory: ${{ steps.fixture.outputs.directory }} + directories: ${{ matrix.fixture.directories || '' }} + - name: Assert - Selection and dependency installation + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + EXPECTED: ${{ toJSON(matrix.fixture.expected) }} + OUTCOME: ${{ steps.discover.outcome }} + DIRECTORIES: ${{ steps.discover.outputs.directories }} + PROJECT: ${{ steps.fixture.outputs.directory }} + LOCKFILE: ${{ steps.fixture.outputs.lockfile }} + LOCK_CONTENT: ${{ steps.fixture.outputs.lock-content }} + with: + script: | + const assert = require('node:assert/strict'); + const fs = require('node:fs'); + const path = require('node:path'); + const expected = JSON.parse(process.env.EXPECTED); + assert.equal(process.env.OUTCOME, expected === 'failure' ? 'failure' : 'success'); + if (expected !== 'failure') assert.deepEqual(JSON.parse(process.env.DIRECTORIES), expected); + const installed = await (await glob.create(`${process.env.PROJECT}/**/node_modules`, { followSymbolicLinks: false })).glob(); + assert.deepEqual(installed, []); + assert.equal(fs.existsSync(path.join(process.env.PROJECT, 'package.tgz')), false); + assert.equal(fs.readFileSync(process.env.LOCKFILE, 'utf8'), process.env.LOCK_CONTENT); diff --git a/.github/workflows/__test-action-package-publish.yml b/.github/workflows/__test-action-package-publish.yml index 711d140e..1ed5e98e 100644 --- a/.github/workflows/__test-action-package-publish.yml +++ b/.github/workflows/__test-action-package-publish.yml @@ -13,6 +13,7 @@ jobs: contents: read outputs: package-tarball-artifact-id: ${{ steps.package.outputs.package-tarball-artifact-id }} + published-version-artifact-id: ${{ steps.published-version.outputs.package-tarball-artifact-id }} steps: - name: Arrange - Checkout sources uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -31,6 +32,24 @@ jobs: with: working-directory: tests/npm + - id: published-fixture + name: Arrange - Use an existing npm name and version + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const fs = require('node:fs'); + const path = require('node:path'); + const { stdout } = await exec.getExecOutput('npm', ['--version'], { silent: true }); + const version = stdout.trim(); + const directory = fs.mkdtempSync(path.join(process.env.RUNNER_TEMP, 'published-version-')); + fs.writeFileSync(path.join(directory, 'package.json'), JSON.stringify({ name: 'npm', version, packageManager: `npm@${version}` })); + core.setOutput('directory', directory); + - id: published-version + name: Arrange - Pack the existing-version fixture + uses: $/actions/package/create + with: + working-directory: ${{ steps.published-fixture.outputs.directory }} + publish: name: Publish dry run (${{ matrix.os }}, ${{ matrix.tag || 'default tag' }}) needs: package @@ -57,6 +76,13 @@ jobs: dry-run: "true" provenance: "false" + - name: Act - Validate an already-published version without publishing + uses: $/actions/package/publish + with: + package-tarball-artifact-id: ${{ needs.package.outputs.published-version-artifact-id }} + dry-run: "true" + provenance: "false" + - id: invalid-artifact name: Act - Reject multiple artifact IDs continue-on-error: true diff --git a/.github/workflows/__test-action-package-test.yml b/.github/workflows/__test-action-package-test.yml new file mode 100644 index 00000000..63007487 --- /dev/null +++ b/.github/workflows/__test-action-package-test.yml @@ -0,0 +1,291 @@ +name: Internal - Tests for "package/test" action + +on: + workflow_call: + +permissions: {} + +jobs: + entry-points: + name: Package defaults (${{ matrix.fixture.name }}) + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + fail-fast: false + matrix: + fixture: + - name: data-only package + manifest: {} + files: { README.md: Package data } + outcome: success + - name: library with declarations and binaries + manifest: + main: lib/index.cjs + module: lib/index.mjs + types: lib/index.d.ts + bin: { example: bin/cli.js } + files: + lib/index.cjs: throw new Error('Do not execute package code'); + lib/index.mjs: throw new Error('Do not execute package code'); + lib/index.d.ts: export {}; + bin/cli.js: throw new Error('Do not execute package code'); + outcome: success + - name: direct conditional exports and typings + manifest: + typings: lib/index.d.ts + exports: { import: ./lib/index.mjs, require: ./lib/index.cjs } + files: + lib/index.mjs: export {}; + lib/index.cjs: module.exports = {}; + lib/index.d.ts: export {}; + outcome: success + - name: extensionless main + manifest: { main: lib/index } + files: { lib/index.js: "module.exports = {};" } + outcome: success + - name: directory main + manifest: { main: lib } + files: + lib/package.json: '{"main":"entry.cjs"}' + lib/entry.cjs: module.exports = {}; + outcome: success + - name: string export and binary + manifest: { exports: ./index.js, bin: cli.js } + files: { index.js: "export {};", cli.js: "console.log('ok');" } + outcome: success + - name: nested conditions and excluded subpaths + manifest: + exports: + ".": + types: ./index.d.ts + node: { import: ./index.mjs, require: ./index.cjs } + default: ./index.mjs + ./package.json: ./package.json + ./private: null + files: + index.d.ts: export {}; + index.mjs: export {}; + index.cjs: module.exports = {}; + outcome: success + - name: wildcard exports with nested and repeated replacements + manifest: + exports: + ./features/*: ./lib/*.js + ./repeated/*: ./repeat/*/index-*.js + files: + lib/nested/feature.js: export {}; + repeat/feature/index-feature.js: export {}; + outcome: success + - name: export fallback array + manifest: + exports: [unsupported:target, null, { default: ./index.js }] + files: { index.js: "export {};" } + outcome: success + - name: missing main + manifest: { main: missing.js } + files: { index.js: "module.exports = {};" } + outcome: failure + - name: missing declaration + manifest: { types: missing.d.ts } + files: {} + outcome: failure + - name: missing binary + manifest: { bin: { example: missing.js } } + files: {} + outcome: failure + - name: missing module + manifest: { module: missing.mjs } + files: {} + outcome: failure + - name: missing nested export + manifest: + exports: + ".": { import: ./index.js, require: ./missing.cjs } + files: { index.js: "export {};" } + outcome: failure + - name: unmatched export pattern + manifest: { exports: { "./features/*": "./missing/*.js" } } + files: { lib/index.js: "export {};" } + outcome: failure + - name: mismatched wildcard replacements + manifest: { exports: { "./features/*": "./lib/*/index-*.js" } } + files: { lib/one/index-two.js: "export {};" } + outcome: failure + - name: directory export + manifest: { exports: ./lib } + files: { lib/index.js: "export {};" } + outcome: failure + - name: entry outside the package + manifest: { main: ../outside.js } + files: {} + outcome: failure + - name: missing name + manifest: { name: null } + files: {} + outcome: failure + - name: missing version + manifest: { version: null } + files: {} + outcome: failure + steps: + - name: Arrange - Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Arrange - Pack the fixture + id: fixture + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MANIFEST: ${{ toJSON(matrix.fixture.manifest) }} + FILES: ${{ toJSON(matrix.fixture.files) }} + with: + script: | + const fs = require('node:fs'); + const path = require('node:path'); + const { createHash } = require('node:crypto'); + const directory = fs.mkdtempSync(path.join(process.env.RUNNER_TEMP, 'package-fixture-')); + const packageDirectory = path.join(directory, 'package'); + await io.mkdirP(packageDirectory); + fs.writeFileSync(path.join(packageDirectory, 'package.json'), JSON.stringify({ + name: 'example-package', version: '1.2.3', ...JSON.parse(process.env.MANIFEST), + scripts: { prepare: 'exit 91', prepack: 'exit 91', test: 'exit 91' }, + })); + for (const [entry, content] of Object.entries(JSON.parse(process.env.FILES))) { + const file = path.join(packageDirectory, entry); + await io.mkdirP(path.dirname(file)); + fs.writeFileSync(file, content); + } + const tarball = path.join(directory, 'package.tgz'); + await exec.exec('tar', ['-czf', tarball, '-C', directory, 'package']); + core.setOutput('tarball', tarball); + core.setOutput('digest', createHash('sha256').update(fs.readFileSync(tarball)).digest('hex')); + - name: Act - Run default checks without custom commands + id: test + continue-on-error: true + uses: $/actions/package/test + with: + tarball-path: ${{ steps.fixture.outputs.tarball }} + - name: Assert - Outcome, original bytes and cleanup + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + OUTCOME: ${{ steps.test.outcome }} + EXPECTED_OUTCOME: ${{ matrix.fixture.outcome }} + TARBALL: ${{ steps.fixture.outputs.tarball }} + EXPECTED_DIGEST: ${{ steps.fixture.outputs.digest }} + with: + script: | + const assert = require('node:assert/strict'); + const fs = require('node:fs'); + const { createHash } = require('node:crypto'); + assert.equal(process.env.OUTCOME, process.env.EXPECTED_OUTCOME); + assert.equal(createHash('sha256').update(fs.readFileSync(process.env.TARBALL)).digest('hex'), process.env.EXPECTED_DIGEST); + assert.deepEqual(fs.readdirSync(process.env.RUNNER_TEMP).filter(name => name.startsWith('package-test-')), []); + + consumer: + name: Installed consumer (${{ matrix.fixture.name }}) + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + fail-fast: false + matrix: + fixture: + - name: empty consumer + project: "" + outcome: success + - name: example project + project: consumer-fixture + outcome: success + - name: failed installation + project: "" + outcome: failure + steps: + - name: Arrange - Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Arrange - Package and example project with failing install hooks + id: fixture + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + FAIL_INSTALL: ${{ matrix.fixture.outcome == 'failure' }} + with: + script: | + const fs = require('node:fs'); + const path = require('node:path'); + const { createHash } = require('node:crypto'); + const directory = fs.mkdtempSync(path.join(process.env.RUNNER_TEMP, 'consumer-fixture-')); + const packageDirectory = path.join(directory, 'package'); + await io.mkdirP(packageDirectory); + const scripts = { preinstall: 'exit 91', install: 'exit 91', postinstall: 'exit 91', prepare: 'exit 91' }; + fs.writeFileSync(path.join(packageDirectory, 'package.json'), JSON.stringify({ + name: '@example/consumer-fixture', version: '1.2.3', main: 'index.js', + bin: { 'fixture-cli': 'cli.js' }, scripts, + dependencies: process.env.FAIL_INSTALL === 'true' ? { missing: `file:${directory}/missing.tgz` } : {}, + })); + fs.writeFileSync(path.join(packageDirectory, 'index.js'), 'module.exports = 42;'); + fs.writeFileSync(path.join(packageDirectory, 'cli.js'), '#!/usr/bin/env node\nconsole.log(require("./package.json").version);'); + const tarball = path.join(directory, 'fixture.tgz'); + await exec.exec('tar', ['-czf', tarball, '-C', directory, 'package']); + const project = path.join(process.env.GITHUB_WORKSPACE, 'consumer-fixture'); + await io.mkdirP(path.join(project, 'node_modules', 'must-not-copy')); + await io.mkdirP(path.join(project, '.git')); + fs.writeFileSync(path.join(project, 'node_modules', 'must-not-copy', 'sentinel'), 'preserve'); + fs.writeFileSync(path.join(project, '.git', 'sentinel'), 'preserve'); + fs.writeFileSync(path.join(project, '.fixture-config'), 'copied'); + fs.writeFileSync(path.join(project, 'package.json'), JSON.stringify({ + name: 'example-consumer', private: true, + dependencies: { '@example/consumer-fixture': 'file:../must-not-resolve' }, + scripts: { ...scripts, build: 'node build.cjs' }, + })); + fs.writeFileSync(path.join(project, 'build.cjs'), 'require("node:assert/strict").equal(require("@example/consumer-fixture"), 42);'); + core.setOutput('tarball', tarball); + core.setOutput('digest', createHash('sha256').update(fs.readFileSync(tarball)).digest('hex')); + core.setOutput('project-manifest', fs.readFileSync(path.join(project, 'package.json'), 'utf8')); + - name: Act - Exercise the installed package + id: test + continue-on-error: true + uses: $/actions/package/test + env: + TEST_PROJECT: ${{ matrix.fixture.project }} + with: + tarball-path: ${{ steps.fixture.outputs.tarball }} + test-project: ${{ matrix.fixture.project }} + commands: | + test "$PWD" != "$GITHUB_WORKSPACE" + test "$PACKAGE_NAME" = @example/consumer-fixture + test "$PACKAGE_VERSION" = 1.2.3 + test "$(fixture-cli)" = "$PACKAGE_VERSION" + test -f "$PACKAGE_INSTALL_PATH/index.js" + test ! -e node_modules/must-not-copy + test ! -e .git + node -e 'require("node:assert/strict").equal(require(process.env.PACKAGE_NAME), 42)' + if [ -n "$TEST_PROJECT" ]; then + test "$(cat .fixture-config)" = copied + npm run build + fi + touch "$RUNNER_TEMP/consumer-command-ran" + - name: Assert - Outcome, cleanup and unchanged source files + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + OUTCOME: ${{ steps.test.outcome }} + EXPECTED_OUTCOME: ${{ matrix.fixture.outcome }} + TARBALL: ${{ steps.fixture.outputs.tarball }} + EXPECTED_DIGEST: ${{ steps.fixture.outputs.digest }} + PROJECT_MANIFEST: ${{ steps.fixture.outputs.project-manifest }} + with: + script: | + const assert = require('node:assert/strict'); + const fs = require('node:fs'); + const path = require('node:path'); + const { createHash } = require('node:crypto'); + assert.equal(process.env.OUTCOME, process.env.EXPECTED_OUTCOME); + assert.equal(fs.existsSync(path.join(process.env.RUNNER_TEMP, 'consumer-command-ran')), process.env.OUTCOME === 'success'); + assert.equal(createHash('sha256').update(fs.readFileSync(process.env.TARBALL)).digest('hex'), process.env.EXPECTED_DIGEST); + assert.deepEqual(fs.readdirSync(process.env.RUNNER_TEMP).filter(name => name.startsWith('package-test-')), []); + const project = path.join(process.env.GITHUB_WORKSPACE, 'consumer-fixture'); + assert.equal(fs.readFileSync(path.join(project, 'package.json'), 'utf8'), process.env.PROJECT_MANIFEST); + assert.equal(fs.readFileSync(path.join(project, 'node_modules', 'must-not-copy', 'sentinel'), 'utf8'), 'preserve'); + assert.equal(fs.readFileSync(path.join(project, '.git', 'sentinel'), 'utf8'), 'preserve'); + assert.equal(fs.existsSync(path.join(project, 'package-lock.json')), false); diff --git a/.github/workflows/__test-release.yml b/.github/workflows/__test-release.yml index aa800ad4..29929364 100644 --- a/.github/workflows/__test-release.yml +++ b/.github/workflows/__test-release.yml @@ -146,12 +146,10 @@ jobs: working-directory: actions/package/test tarball-path: ${{ steps.fixture.outputs.tarball }} commands: | - test "$PWD" = "$GITHUB_WORKSPACE/actions/package/test" + test "$PWD" != "$GITHUB_WORKSPACE/actions/package/test" test "$PACKAGE_NAME" = "$EXPECTED_PACKAGE_NAME" test "$PACKAGE_VERSION" = 1.2.3 - consumer=$(mktemp -d) - npm install --prefix "$consumer" --ignore-scripts --offline --no-audit --no-fund "$PACKAGE_TARBALL_PATH" - test "$("$consumer/node_modules/.bin/release-fixture")" = "$PACKAGE_VERSION" + test "$(release-fixture)" = "$PACKAGE_VERSION" touch "$RUNNER_TEMP/release-smoke-ran" - name: Act - Reject a failing package command id: failing-command @@ -177,3 +175,90 @@ jobs: test -f "$RUNNER_TEMP/release-smoke-ran" test "$COMMAND_OUTCOME" = failure test "$TARBALL_OUTCOME" = failure + + prepared-workspaces: + name: Test prepared workspace packaging (${{ matrix.manager }}) + runs-on: ubuntu-latest + permissions: + contents: read + strategy: + fail-fast: false + matrix: + manager: [pnpm10, pnpm] + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Arrange - Create a workspace with compiled output and failing hooks + id: workspace + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + MANAGER: ${{ matrix.manager }} + with: + script: | + const fs = require('node:fs'); + const path = require('node:path'); + const lock = JSON.parse(fs.readFileSync('actions/release/prepare/package-lock.json', 'utf8')); + const version = lock.packages[`node_modules/${process.env.MANAGER}`].version; + const directory = fs.mkdtempSync(path.join(process.env.RUNNER_TEMP, 'pnpm-release-')); + const write = async (file, value) => { + const target = path.join(directory, file); + await io.mkdirP(path.dirname(target)); + fs.writeFileSync(target, typeof value === 'string' ? value : JSON.stringify(value)); + }; + const scripts = Object.fromEntries(['preinstall', 'postinstall', 'prepare', 'prepack', 'postpack'].map(name => [name, 'exit 91'])); + await write('.node-version', fs.readFileSync('.node-version', 'utf8')); + await write('package.json', { private: true, packageManager: `pnpm@${version}` }); + await write('pnpm-workspace.yaml', "packages:\n - packages/*\n"); + await write('packages/core/package.json', { name: '@example/core', version: '1.4.0' }); + await write('packages/cli/package.json', { + name: '@example/cli', version: '2.3.0', files: ['dist'], scripts, + dependencies: { '@example/core': 'workspace:^' }, + }); + await write('packages/cli/dist/index.js', 'compiled by CI\n'); + core.setOutput('directory', directory); + - uses: $/actions/setup-node + with: + working-directory: ${{ steps.workspace.outputs.directory }} + install-dependencies: "false" + - name: Arrange - Resolve the workspace lockfile + working-directory: ${{ steps.workspace.outputs.directory }} + run: pnpm install --lockfile-only --ignore-scripts + - name: Arrange - Upload the prepared build + id: build + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + path: ${{ steps.workspace.outputs.directory }} + name: workspace-build-${{ matrix.manager }}-${{ github.run_id }}-${{ github.run_attempt }} + if-no-files-found: error + - name: Arrange - Remove local build output before restoration + working-directory: ${{ steps.workspace.outputs.directory }} + run: rm packages/cli/dist/index.js + - name: Arrange - Link the prepared workspace without running hooks + working-directory: ${{ steps.workspace.outputs.directory }} + run: pnpm install --frozen-lockfile --ignore-scripts + - name: Act - Package the selected workspace from CI output + id: package + uses: $/actions/package/create + with: + working-directory: ${{ steps.workspace.outputs.directory }} + package-directory: packages/cli + build-artifact-id: ${{ steps.build.outputs.artifact-id }} + build-artifact-path: ${{ steps.workspace.outputs.directory }} + artifact-name: workspace-package-${{ matrix.manager }}-${{ github.run_id }}-${{ github.run_attempt }} + - name: Assert - Compiled bytes and native workspace dependency resolution + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + TARBALL: ${{ steps.package.outputs.package-tarball-path }} + ARTIFACT_ID: ${{ steps.package.outputs.package-tarball-artifact-id }} + with: + script: | + const assert = require('node:assert/strict'); + const { stdout: compiled } = await exec.getExecOutput('tar', ['-xOf', process.env.TARBALL, 'package/dist/index.js']); + const { stdout: manifest } = await exec.getExecOutput('tar', ['-xOf', process.env.TARBALL, 'package/package.json']); + const parsed = JSON.parse(manifest); + assert.equal(compiled, 'compiled by CI\n'); + assert.equal(parsed.name, '@example/cli'); + assert.equal(parsed.version, '2.3.0'); + assert.equal(parsed.dependencies['@example/core'], '^1.4.0'); + assert.match(process.env.ARTIFACT_ID, /^[1-9][0-9]*$/); diff --git a/.github/workflows/__test-workflow-continuous-integration.yml b/.github/workflows/__test-workflow-continuous-integration.yml index d73ac609..fc54ba29 100644 --- a/.github/workflows/__test-workflow-continuous-integration.yml +++ b/.github/workflows/__test-workflow-continuous-integration.yml @@ -6,6 +6,79 @@ on: permissions: {} jobs: + package-configuration: + name: Assert - Package configuration + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const assert = require('node:assert/strict'); + const fs = require('node:fs'); + const workflow = fs.readFileSync('.github/workflows/continuous-integration.yml', 'utf8'); + const step = workflow.split(' - id: package-config\n')[1]?.split('\n - ')[0]; + assert.ok(step, 'Package configuration step must exist'); + const script = step.split(' script: |\n')[1]?.replace(/^ {12}/gm, ''); + assert.ok(script, 'Package configuration script must exist'); + const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor; + const configure = new AsyncFunction('core', 'process', 'require', script); + async function parse(input, container = '') { + const outputs = {}; + await configure({ + setOutput: (name, value) => { outputs[name] = typeof value === 'string' ? value : JSON.stringify(value); } + }, { env: { PACKAGE_INPUT: input, CONTAINER_INPUT: container } }, require); + return outputs; + } + for (const input of ['', 'false', ' false ']) { + const outputs = await parse(input); + assert.equal(outputs.enabled, 'false', input); + assert.equal(outputs.tests, ''); + } + for (const input of ['true', '{}', ' true ']) { + const outputs = await parse(input); + assert.equal(outputs.enabled, 'true', input); + assert.equal(outputs.tests, ''); + } + const commands = 'echo "$PACKAGE_NAME"\necho "consumer check"\n'; + const custom = await parse(JSON.stringify({ tests: commands })); + assert.equal(custom.enabled, 'true'); + assert.equal(custom.tests, commands, 'Preserve multiline commands and shell quoting'); + for (const input of ['no', 'null', '[]', '0', '"true"', '{']) { + await assert.rejects(() => parse(input), /package must be true, false, or a JSON object/, input); + } + await assert.rejects(() => parse('{"unknown":true}'), /Unknown package options/); + for (const tests of [true, 1, [], {}, null]) { + await assert.rejects(() => parse(JSON.stringify({ tests })), /must be.*string/); + } + assert.equal((await parse('true')).directories, '', 'Default enables automatic discovery'); + assert.equal((await parse('true'))['test-project'], ''); + const configured = await parse(JSON.stringify({ directories: ['packages/core', 'packages/cli'], tests: commands, 'test-project': 'examples/app' })); + assert.deepEqual(JSON.parse(configured.directories), ['packages/core', 'packages/cli']); + assert.equal(configured['test-project'], 'examples/app'); + assert.equal(configured.tests, commands); + assert.equal((await parse('{"directories":["."]}')).enabled, 'true'); + for (const directories of [null, '.', [], ['.','.'], [42], [''], [' '], ['/tmp'], ['../outside'], ['a/../b'], ['C:\\outside'], ['..\\outside'], ['a\nb'], Array.from({ length: 257 }, (_, index) => `packages/${index}`)]) { + await assert.rejects(() => parse(JSON.stringify({ directories })), /package.directories/); + } + for (const project of [null, 42, true, []]) { + await assert.rejects(() => parse(JSON.stringify({ 'test-project': project, tests: commands })), /must be strings/); + } + for (const project of [' ', '/tmp', '../outside', 'C:\\outside', '..\\outside']) { + await assert.rejects(() => parse(JSON.stringify({ 'test-project': project, tests: commands })), /package.test-project must be a directory/); + } + for (const tests of ['', ' ']) { + await assert.rejects(() => parse(JSON.stringify({ 'test-project': 'example', tests })), /requires package.tests/); + } + await assert.rejects(() => parse('true', 'node:alpine'), /requires non-container CI/); + await assert.rejects(() => parse('{}', '{"image":"node:alpine"}'), /requires non-container CI/); + assert.equal((await parse('false', 'node:alpine')).enabled, 'false'); + core.info('Package configuration checks passed.'); + act-without-container: name: Act - Run the continuous integration workflow (without container) uses: $/.github/workflows/continuous-integration.yml @@ -17,14 +90,8 @@ jobs: security-events: write with: working-directory: tests/npm - package: true - package-tests: | - tar -xOf "$PACKAGE_TARBALL_PATH" package/dist/test.txt > "$RUNNER_TEMP/package-build.txt" - test "$(cat "$RUNNER_TEMP/package-build.txt")" = 'build test' - test "$PACKAGE_NAME" = test-for-ci-with-npm - consumer=$(mktemp -d) - npm install --prefix "$consumer" --ignore-scripts --offline --no-audit --no-fund "$PACKAGE_TARBALL_PATH" - test -f "$consumer/node_modules/$PACKAGE_NAME/dist/test.txt" + package: | + {"tests": "tar -xOf \"$PACKAGE_TARBALL_PATH\" package/dist/test.txt > \"$RUNNER_TEMP/package-build.txt\"\ntest \"$(cat \"$RUNNER_TEMP/package-build.txt\")\" = 'build test'\ntest \"$PACKAGE_NAME\" = test-for-ci-with-npm\ntest -f \"$PACKAGE_INSTALL_PATH/dist/test.txt\"\n"} code-ql: | { "languages": "javascript-typescript", @@ -54,6 +121,17 @@ jobs: - name: Check the build artifacts run: test -f tests/npm/dist/test.txt + - name: Assert - Package outputs identify the validated tarball + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + ARTIFACTS: ${{ needs.act-without-container.outputs.package-tarball-artifacts }} + EXPECTED_ARTIFACT_ID: ${{ needs.act-without-container.outputs.package-tarball-artifact-id }} + with: + script: | + const assert = require('node:assert/strict'); + assert.match(process.env.EXPECTED_ARTIFACT_ID, /^[1-9][0-9]*$/); + assert.deepEqual(JSON.parse(process.env.ARTIFACTS), { '.': process.env.EXPECTED_ARTIFACT_ID }); + - name: Act - Consume the validated CI package without repacking id: validated-package uses: $/actions/package/publish @@ -106,22 +184,10 @@ jobs: tar -xOf "$TARBALL" package/dist/test.txt > "$RUNNER_TEMP/packaged-build.txt" cmp "$RUNNER_TEMP/prepared-fixture/dist/test.txt" "$RUNNER_TEMP/packaged-build.txt" test ! -d tests/npm/node_modules - - name: Run package smoke checks with the project runtime + - name: Check the package without installing its unavailable dependencies uses: $/actions/package/test with: - working-directory: tests/npm tarball-path: ${{ steps.package.outputs.package-tarball-path }} - commands: | - node <<'NODE' - const assert = require('node:assert/strict'); - const fs = require('node:fs'); - const expected = fs.readFileSync('.node-version', 'utf8').trim().split('.'); - assert.deepEqual(process.versions.node.split('.').slice(0, expected.length), expected); - NODE - test "$PACKAGE_NAME" = prepared-fixture - test "$PACKAGE_VERSION" = 1.2.3 - tar -xOf "$PACKAGE_TARBALL_PATH" package/dist/test.txt > "$RUNNER_TEMP/smoke-output.txt" - cmp "$RUNNER_TEMP/prepared-fixture/dist/test.txt" "$RUNNER_TEMP/smoke-output.txt" - name: Validate publishing the prepared tarball uses: $/actions/package/publish with: diff --git a/.github/workflows/continuous-integration.md b/.github/workflows/continuous-integration.md index 04cf8ee2..6cfa6c45 100644 --- a/.github/workflows/continuous-integration.md +++ b/.github/workflows/continuous-integration.md @@ -58,7 +58,7 @@ on: permissions: {} jobs: continuous-integration: - uses: hoverkraft-tech/ci-github-nodejs/.github/workflows/continuous-integration.yml@97f64bc080b78daca8fb3ece49de448aaac83429 # 1.1.0 + uses: hoverkraft-tech/ci-github-nodejs/.github/workflows/continuous-integration.yml@ permissions: contents: read id-token: write @@ -124,15 +124,23 @@ jobs: # Default: `build` build: build - # Create and validate an npm package from the checked-out source and build output. - # Disabled by default for projects that do not distribute npm packages. + # Create and validate public npm packages from the checked-out source and build output. + # Pass "false" (the default) to disable, "true" for automatic package discovery and default checks, + # or a JSON object to enable packaging with specific options. Quote booleans because this input has type string. + # + # - `directories`: Optional array of public package directories relative to working-directory. Omit to discover workspaces or the standalone root package. Private packages are excluded. + # - `tests`: Optional shell commands after default manifest and entry-point checks. Runs in an isolated npm consumer with lifecycle scripts disabled and installed binaries on PATH. + # - `test-project`: Optional consumer fixture directory relative to working-directory. Its dependencies and scripts are copied without node_modules or Git metadata. Requires `tests`. + # + # PACKAGE_INSTALL_PATH, PACKAGE_TARBALL_PATH, PACKAGE_NAME and PACKAGE_VERSION identify the tested package. + # The consumer is cleaned up on success or failure. Commands must not modify the tarball. # Requires non-container CI with the default Ubuntu artifact layout. - package: false - - # Optional shell commands testing the packed tarball when package is enabled. - # Runs from working-directory with PACKAGE_TARBALL_PATH, PACKAGE_NAME and PACKAGE_VERSION. - # Install consumer test dependencies explicitly. Commands must not modify the tarball. - package-tests: "" + # Unknown options and invalid field types are rejected before build or packaging. + # + # Example: `{"tests":"my-cli --help"}` + # + # Default: `false` + package: "false" # Optional flag to enable check steps. # Default: `true` @@ -273,12 +281,20 @@ jobs: | | | | | | | | Example: | | | | | |
{
 "commands": [
 "build",
 "generate-artifacts"
 ],
 "env": {
 "CUSTOM_ENV_VAR": "value"
 },
 "artifact": [
 "dist/",
 "packages/package-a/build/"
 ]
}
| | | | -| **`package`** | Create and validate an npm package from the checked-out source and build output. | **false** | **boolean** | `false` | -| | Disabled by default for projects that do not distribute npm packages. | | | | +| **`package`** | Create and validate public npm packages from the checked-out source and build output. | **false** | **string** | `false` | +| | Pass "false" (the default) to disable, "true" for automatic package discovery and default checks, | | | | +| | or a JSON object to enable packaging with specific options. Quote booleans because this input has type string. | | | | +| | | | | | +| | - `directories`: Optional array of public package directories relative to working-directory. Omit to discover workspaces or the standalone root package. Private packages are excluded. | | | | +| | - `tests`: Optional shell commands after default manifest and entry-point checks. Runs in an isolated npm consumer with lifecycle scripts disabled and installed binaries on PATH. | | | | +| | - `test-project`: Optional consumer fixture directory relative to working-directory. Its dependencies and scripts are copied without node_modules or Git metadata. Requires `tests`. | | | | +| | | | | | +| | PACKAGE_INSTALL_PATH, PACKAGE_TARBALL_PATH, PACKAGE_NAME and PACKAGE_VERSION identify the tested package. | | | | +| | The consumer is cleaned up on success or failure. Commands must not modify the tarball. | | | | | | Requires non-container CI with the default Ubuntu artifact layout. | | | | -| **`package-tests`** | Optional shell commands testing the packed tarball when package is enabled. | **false** | **string** | - | -| | Runs from working-directory with PACKAGE_TARBALL_PATH, PACKAGE_NAME and PACKAGE_VERSION. | | | | -| | Install consumer test dependencies explicitly. Commands must not modify the tarball. | | | | +| | Unknown options and invalid field types are rejected before build or packaging. | | | | +| | | | | | +| | Example: `{"tests":"my-cli --help"}` | | | | | **`checks`** | Optional flag to enable check steps. | **false** | **boolean** | `true` | | **`lint`** | Whether to enable linting. | **false** | **string** | `true` | | | Set to `null` or empty to disable. | | | | @@ -421,10 +437,11 @@ When specified, steps will execute inside this container instead of checking out ## Outputs -| **Output** | **Description** | -| --------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| **`package-tarball-artifact-id`** | Artifact ID of the package validated by the packaging job. Available when package is enabled and validation succeeds. | -| **`build-artifact-id`** | ID of the build artifact) uploaded during the build step. | +| **Output** | **Description** | +| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| **`package-tarball-artifact-id`** | Artifact ID of the package validated by the packaging job. Available when package is enabled and validation succeeds. | +| **`package-tarball-artifacts`** | JSON object mapping package directories to validated tarball artifact IDs when packaging is enabled. Require successful CI before publication. | +| **`build-artifact-id`** | ID of the build artifact) uploaded during the build step. | diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 6efd046e..d5ca4f07 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -55,18 +55,22 @@ on: default: "build" package: description: | - Create and validate an npm package from the checked-out source and build output. - Disabled by default for projects that do not distribute npm packages. + Create and validate public npm packages from the checked-out source and build output. + Pass "false" (the default) to disable, "true" for automatic package discovery and default checks, + or a JSON object to enable packaging with specific options. Quote booleans because this input has type string. + + - `directories`: Optional array of public package directories relative to working-directory. Omit to discover workspaces or the standalone root package. Private packages are excluded. + - `tests`: Optional shell commands after default manifest and entry-point checks. Runs in an isolated npm consumer with lifecycle scripts disabled and installed binaries on PATH. + - `test-project`: Optional consumer fixture directory relative to working-directory. Its dependencies and scripts are copied without node_modules or Git metadata. Requires `tests`. + + PACKAGE_INSTALL_PATH, PACKAGE_TARBALL_PATH, PACKAGE_NAME and PACKAGE_VERSION identify the tested package. + The consumer is cleaned up on success or failure. Commands must not modify the tarball. Requires non-container CI with the default Ubuntu artifact layout. - type: boolean - default: false - package-tests: - description: | - Optional shell commands testing the packed tarball when package is enabled. - Runs from working-directory with PACKAGE_TARBALL_PATH, PACKAGE_NAME and PACKAGE_VERSION. - Install consumer test dependencies explicitly. Commands must not modify the tarball. + Unknown options and invalid field types are rejected before build or packaging. + + Example: `{"tests":"my-cli --help"}` type: string - default: "" + default: "false" checks: description: "Optional flag to enable check steps." type: boolean @@ -211,7 +215,10 @@ on: outputs: package-tarball-artifact-id: description: Artifact ID of the package validated by the packaging job. Available when package is enabled and validation succeeds. - value: ${{ jobs.package.outputs.artifact-id }} + value: ${{ jobs.package-results.outputs.artifact-id }} + package-tarball-artifacts: + description: JSON object mapping package directories to validated tarball artifact IDs when packaging is enabled. Require successful CI before publication. + value: ${{ jobs.package-results.outputs.artifacts }} build-artifact-id: description: "ID of the build artifact) uploaded during the build step." value: ${{ jobs.build.outputs.artifact-id }} @@ -231,23 +238,61 @@ jobs: container-volumes: ${{ steps.parse.outputs.container-volumes }} container-username: ${{ steps.parse.outputs.container-username }} path-mapping: ${{ steps.parse.outputs.path-mapping }} + package-enabled: ${{ steps.package-config.outputs.enabled }} + package-directories: ${{ steps.package-config.outputs.directories }} + package-tests: ${{ steps.package-config.outputs.tests }} + package-test-project: ${{ steps.package-config.outputs.test-project }} steps: + - id: package-config + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + PACKAGE_INPUT: ${{ inputs.package }} + CONTAINER_INPUT: ${{ inputs.container }} + with: + script: | + let config; + try { + config = JSON.parse(process.env.PACKAGE_INPUT.trim() || 'false'); + } catch { + throw new Error('package must be true, false, or a JSON object.'); + } + if (typeof config !== 'boolean' && (!config || typeof config !== 'object' || Array.isArray(config))) { + throw new Error('package must be true, false, or a JSON object.'); + } + const enabled = config !== false; + if (typeof config === 'boolean') config = {}; + const allowed = ['directories', 'tests', 'test-project']; + const unknown = Object.keys(config).filter(key => !allowed.includes(key)); + if (unknown.length) throw new Error(`Unknown package options: ${unknown.join(', ')}.`); + const { directories, tests = '', 'test-project': testProject = '' } = config; + if (typeof tests !== 'string' || typeof testProject !== 'string') { + throw new Error('package.tests and package.test-project must be strings.'); + } + const path = require('node:path'); + const relativeDirectory = value => typeof value === 'string' && value.trim() !== '' && + !path.posix.isAbsolute(value) && !path.win32.isAbsolute(value) && + !value.split(/[\\/]/).includes('..') && !/[\r\n\0]/.test(value); + if (directories !== undefined && (!Array.isArray(directories) || !directories.length || directories.length > 256 || + directories.some(directory => !relativeDirectory(directory)) || new Set(directories).size !== directories.length)) { + throw new Error('package.directories must contain 1 to 256 unique relative directories inside the project.'); + } + if (testProject && !relativeDirectory(testProject)) throw new Error('package.test-project must be a directory inside the project.'); + if (testProject && !tests.trim()) throw new Error('package.test-project requires package.tests.'); + if (enabled && process.env.CONTAINER_INPUT.trim()) { + throw new Error('Package validation requires non-container CI with the default Ubuntu artifact layout.'); + } + core.setOutput('enabled', enabled); + core.setOutput('directories', directories === undefined ? '' : directories); + core.setOutput('tests', tests); + core.setOutput('test-project', testProject); - id: parse uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: CONTAINER_INPUT: ${{ inputs.container }} CONTAINER_PASSWORD: ${{ secrets.container-password }} WORKING_DIRECTORY: ${{ inputs.working-directory }} - PACKAGE_ENABLED: ${{ inputs.package }} - PACKAGE_TESTS: ${{ inputs.package-tests }} with: script: | - if (process.env.PACKAGE_TESTS.trim() && process.env.PACKAGE_ENABLED !== 'true') { - return core.setFailed('package-tests requires package: true.'); - } - if (process.env.PACKAGE_ENABLED === 'true' && process.env.CONTAINER_INPUT.trim()) { - return core.setFailed('Package validation requires non-container CI with the default Ubuntu artifact layout.'); - } const containerInput = process.env.CONTAINER_INPUT.trim(); if (!containerInput) { return; @@ -338,7 +383,7 @@ jobs: security-events: write runs-on: *ci-runner steps: - - uses: hoverkraft-tech/ci-github-common/actions/checkout@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + - uses: hoverkraft-tech/ci-github-common/actions/checkout@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 - id: code-ql-options uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: @@ -381,7 +426,7 @@ jobs: contents: read runs-on: *ci-runner steps: - - uses: hoverkraft-tech/ci-github-common/actions/checkout@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + - uses: hoverkraft-tech/ci-github-common/actions/checkout@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 setup: @@ -404,17 +449,26 @@ jobs: build-env: ${{ steps.build-variables.outputs.env }} build-commands: ${{ steps.build-variables.outputs.commands }} build-artifact: ${{ steps.build-variables.outputs.artifact }} + package-directories: ${{ steps.packages.outputs.directories }} steps: - name: Checkout repository if: inputs.container == '' - uses: hoverkraft-tech/ci-github-common/actions/checkout@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/actions/checkout@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 - id: working-directory - uses: hoverkraft-tech/ci-github-common/actions/working-directory@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/actions/working-directory@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 with: working-directory: ${{ inputs.working-directory }} enforce-path-in-workspace: ${{ inputs.container && 'false' || 'true' }} + - name: Discover public packages + id: packages + if: inputs.checks == true && needs.prepare.outputs.package-enabled == 'true' + uses: $/actions/package/discover + with: + working-directory: ${{ steps.working-directory.outputs.absolute-path }} + directories: ${{ needs.prepare.outputs.package-directories }} + - id: build-variables if: inputs.build != '' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -516,7 +570,7 @@ jobs: contents: read packages: read steps: - - uses: hoverkraft-tech/ci-github-common/actions/checkout@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + - uses: hoverkraft-tech/ci-github-common/actions/checkout@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 if: inputs.container == '' - id: preparel-lint-options @@ -565,7 +619,7 @@ jobs: outputs: artifact-id: ${{ steps.build.outputs.artifact-id }} steps: - - uses: hoverkraft-tech/ci-github-common/actions/checkout@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + - uses: hoverkraft-tech/ci-github-common/actions/checkout@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 if: needs.setup.outputs.build-commands && inputs.container == '' - id: build @@ -595,7 +649,7 @@ jobs: packages: read pull-requests: write steps: - - uses: hoverkraft-tech/ci-github-common/actions/checkout@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + - uses: hoverkraft-tech/ci-github-common/actions/checkout@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 if: inputs.container == '' - if: needs.build.outputs.artifact-id && inputs.container == '' @@ -643,27 +697,35 @@ jobs: github-token: ${{ secrets.github-token || github.token }} package: - name: Validate package - if: inputs.checks == true && inputs.package - needs: build + name: Validate package ${{ matrix.directory }} + if: inputs.checks == true && needs.prepare.outputs.package-enabled == 'true' + needs: [prepare, setup, build] runs-on: ubuntu-latest permissions: contents: read - outputs: - artifact-id: ${{ steps.package.outputs.package-tarball-artifact-id }} + strategy: + fail-fast: false + matrix: + directory: ${{ fromJSON(needs.setup.outputs.package-directories) }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: $/actions/setup-node + - id: setup-node + uses: $/actions/setup-node with: working-directory: ${{ inputs.working-directory }} install-dependencies: "false" + - name: Link pnpm workspace dependencies + if: steps.setup-node.outputs.package-manager == 'pnpm' + working-directory: ${{ inputs.working-directory }} + run: pnpm install --frozen-lockfile --ignore-scripts - name: Package prepared files id: package uses: $/actions/package/create with: working-directory: ${{ inputs.working-directory }} + package-directory: ${{ matrix.directory }} build-artifact-id: ${{ needs.build.outputs.artifact-id }} build-artifact-path: / - name: Test the package as a consumer @@ -671,7 +733,8 @@ jobs: with: working-directory: ${{ inputs.working-directory }} tarball-path: ${{ steps.package.outputs.package-tarball-path }} - commands: ${{ inputs.package-tests }} + commands: ${{ needs.prepare.outputs.package-tests }} + test-project: ${{ needs.prepare.outputs.package-test-project }} - name: Validate npm publication without publishing uses: $/actions/package/publish with: @@ -679,3 +742,64 @@ jobs: dry-run: "true" provenance: "false" github-token: "" + - name: Record successful validation + id: report + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + DIRECTORY: ${{ matrix.directory }} + PROJECT: ${{ inputs.working-directory }} + ARTIFACT_ID: ${{ steps.package.outputs.package-tarball-artifact-id }} + with: + script: | + const fs = require('node:fs'); + const path = require('node:path'); + const report = path.join(process.env.RUNNER_TEMP, `${process.env.ARTIFACT_ID}.json`); + fs.writeFileSync(report, JSON.stringify({ directory: process.env.DIRECTORY, project: process.env.PROJECT, artifactId: process.env.ARTIFACT_ID, attempt: Number(process.env.GITHUB_RUN_ATTEMPT) })); + core.setOutput('path', report); + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: ci-package-validation-${{ steps.package.outputs.package-tarball-artifact-id }} + path: ${{ steps.report.outputs.path }} + if-no-files-found: error + + package-results: + name: Collect validated packages + if: inputs.checks == true && needs.prepare.outputs.package-enabled == 'true' + needs: [prepare, setup, package] + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + artifact-id: ${{ steps.collect.outputs.artifact-id }} + artifacts: ${{ steps.collect.outputs.artifacts }} + steps: + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + pattern: ci-package-validation-* + merge-multiple: true + path: ${{ runner.temp }}/package-validation + - id: collect + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + DIRECTORIES: ${{ needs.setup.outputs.package-directories }} + PROJECT: ${{ inputs.working-directory }} + REPORT_DIRECTORY: ${{ runner.temp }}/package-validation + with: + script: | + const fs = require('node:fs'); + const path = require('node:path'); + const directories = JSON.parse(process.env.DIRECTORIES); + const files = await (await glob.create(path.join(process.env.REPORT_DIRECTORY, '*.json'), { followSymbolicLinks: false, matchDirectories: false })).glob(); + const reports = files.map(file => JSON.parse(fs.readFileSync(file, 'utf8'))).filter(report => report.project === process.env.PROJECT); + const artifacts = Object.fromEntries(directories.map(directory => { + const matches = reports.filter(report => report.directory === directory).sort((a, b) => b.attempt - a.attempt); + const latest = matches[0]; + if (!latest || !/^[1-9][0-9]*$/.test(latest.artifactId) || !Number.isSafeInteger(Number(latest.artifactId)) || + !Number.isSafeInteger(latest.attempt) || latest.attempt < 1 || + (matches[1] && latest.attempt === matches[1].attempt)) { + throw new Error(`Expected one validated package result for ${directory}.`); + } + return [directory, latest.artifactId]; + })); + core.setOutput('artifacts', artifacts); + if (directories.length === 1) core.setOutput('artifact-id', artifacts[directories[0]]); diff --git a/.github/workflows/prepare-package-release.md b/.github/workflows/prepare-package-release.md index 71a82e57..40e753b5 100644 --- a/.github/workflows/prepare-package-release.md +++ b/.github/workflows/prepare-package-release.md @@ -39,10 +39,16 @@ See [the release guide](release.md). ### Workflow Call Inputs -| **Input** | **Description** | **Required** | **Type** | **Default** | -| -------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------ | ---------- | ----------- | -| **`working-directory`** | Public npm project directory containing its package.json and lockfile. | **false** | **string** | `.` | -| **`github-app-client-id`** | Optional GitHub App client ID, paired with the github-app-key secret to generate a repository-scoped preparation token. | **false** | **string** | - | +| **Input** | **Description** | **Required** | **Type** | **Default** | +| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | ----------- | +| **`release-config`** | Optional partial release configuration as a YAML or JSON mapping, forwarded to ci-github-publish/actions/release/plan. | **false** | **string** | - | +| | See for supported settings and override behavior. | | | | +| | Pass the same configuration to preparation and release. Independent Lerna retains native version planning. | | | | +| **`versioning`** | Versioning tool, npm for one public package or lerna for fixed or independent workspaces. | **false** | **string** | `npm` | +| **`working-directory`** | Project or workspace root containing the lockfile and package manager configuration. | **false** | **string** | `.` | +| **`package-directory`** | Public npm package directory relative to working-directory. Leave as . for a root package or Lerna workspaces. | **false** | **string** | `.` | +| **`force`** | Force a new version on manual default-branch runs, including unchanged public packages. Uses a patch bump when no version increase is planned. Ignored for schedules and tag recovery. | **false** | **boolean** | `false` | +| **`github-app-client-id`** | Optional GitHub App client ID, paired with the github-app-key secret to generate a repository-scoped preparation token. | **false** | **string** | - | @@ -106,7 +112,7 @@ on: permissions: {} jobs: prepare-package-release: - uses: hoverkraft-tech/ci-github-nodejs/.github/workflows/prepare-package-release.yml@97f64bc080b78daca8fb3ece49de448aaac83429 # 1.1.0 + uses: hoverkraft-tech/ci-github-nodejs/.github/workflows/prepare-package-release.yml@ permissions: actions: write contents: write @@ -115,10 +121,26 @@ jobs: # Optional GitHub App private key, paired with the github-app-client-id input. The identity must be permitted by branch and tag rules. github-app-key: "" with: - # Public npm project directory containing its package.json and lockfile. + # Optional partial release configuration as a YAML or JSON mapping, forwarded to ci-github-publish/actions/release/plan. + # See https://github.com/hoverkraft-tech/ci-github-publish/tree/main/actions/release/plan for supported settings and override behavior. + # Pass the same configuration to preparation and release. Independent Lerna retains native version planning. + release-config: "" + + # Versioning tool, npm for one public package or lerna for fixed or independent workspaces. + # Default: `npm` + versioning: npm + + # Project or workspace root containing the lockfile and package manager configuration. # Default: `.` working-directory: . + # Public npm package directory relative to working-directory. Leave as . for a root package or Lerna workspaces. + # Default: `.` + package-directory: . + + # Force a new version on manual default-branch runs, including unchanged public packages. Uses a patch bump when no version increase is planned. Ignored for schedules and tag recovery. + force: false + # Optional GitHub App client ID, paired with the github-app-key secret to generate a repository-scoped preparation token. github-app-client-id: "" ``` diff --git a/.github/workflows/prepare-package-release.yml b/.github/workflows/prepare-package-release.yml index 30c7d91b..4b19d4c8 100644 --- a/.github/workflows/prepare-package-release.yml +++ b/.github/workflows/prepare-package-release.yml @@ -6,10 +6,29 @@ name: Prepare Node.js Package Release on: workflow_call: inputs: + release-config: + description: | + Optional partial release configuration as a YAML or JSON mapping, forwarded to ci-github-publish/actions/release/plan. + See https://github.com/hoverkraft-tech/ci-github-publish/tree/main/actions/release/plan for supported settings and override behavior. + Pass the same configuration to preparation and release. Independent Lerna retains native version planning. + type: string + default: "" + versioning: + description: Versioning tool, npm for one public package or lerna for fixed or independent workspaces. + type: string + default: npm working-directory: - description: Public npm project directory containing its package.json and lockfile. + description: Project or workspace root containing the lockfile and package manager configuration. + type: string + default: . + package-directory: + description: Public npm package directory relative to working-directory. Leave as . for a root package or Lerna workspaces. type: string default: . + force: + description: Force a new version on manual default-branch runs, including unchanged public packages. Uses a patch bump when no version increase is planned. Ignored for schedules and tag recovery. + type: boolean + default: false github-app-client-id: description: Optional GitHub App client ID, paired with the github-app-key secret to generate a repository-scoped preparation token. type: string @@ -60,5 +79,9 @@ jobs: - id: prepare uses: $/actions/release/prepare with: + release-config: ${{ inputs.release-config }} + versioning: ${{ inputs.versioning }} working-directory: ${{ inputs.working-directory }} + package-directory: ${{ inputs.package-directory }} + force: ${{ inputs.force }} github-token: ${{ steps.release-token.outputs.token || github.token }} diff --git a/.github/workflows/release.md b/.github/workflows/release.md index 959e2f33..7bdeefea 100644 --- a/.github/workflows/release.md +++ b/.github/workflows/release.md @@ -22,8 +22,38 @@ ## Overview -Publish an already validated package tarball without rebuilding, repacking or running tests. -Call after successful package validation at the prepared tag, using an artifact from the same run and commit. +Publish validated package tarballs without rebuilding, repacking or running tests. +Call after successful package validation at the prepared tags, using artifacts from this run and commit. + +### Force a manual release + +Add an optional boolean `force` input, defaulting to `false`, under the caller's +`on.workflow_dispatch.inputs`. Forward it to the preparation job with +`force: ${{ github.event_name == 'workflow_dispatch' && inputs.force }}`. + +On a manual default-branch run, `force: true` prepares a new version even without +changes. Higher Conventional Commit plans are preserved; an unchanged planned +version receives a patch bump. Lerna includes every public package, using native +Conventional Commit increments in independent mode and patch increments for +unchanged packages. Private packages remain excluded. + +Schedules keep normal no-change behavior. Explicit tag runs always recover that +version, even when force is supplied. Preparation does not forward force to the +dispatched tag run. Builds, validation, repository protections and existing-tag +checks still apply. Force never overwrites a published version or release tag. + +### Override release configuration + +Pass the same partial YAML or JSON `release-config` to preparation and release. +For example, `tag-prefix: v` with `tag-template: v$RESOLVED_VERSION` preserves v-prefixed tags. +Alternatively, put partial overrides in `.github/release-config.yml`, or +`.github/release-configs/{slug}.yml` for a scoped release. Shared defaults come first, +repository settings override them, and workflow input overrides repository settings. +Supplied top-level keys replace inherited values, including entire lists and mappings; +omitted keys remain inherited. Files with their own `_extends` chain retain that base. +`_extends` is reserved in the workflow input. +Override only repository-specific settings to inherit upstream version rules and notes. +Independent Lerna still plans versions natively; keep tag settings consistent with its package tags. ### Permissions @@ -48,22 +78,34 @@ consumer test commands. The separate [`ci-github-publish` preparation workflow](https://github.com/hoverkraft-tech/ci-github-publish/blob/main/.github/workflows/prepare-release.yml) manages release labels on pull requests. -## Supported project +## Choose the package layout -Use one public npm package with a stable version and an npm lockfile. Set -`working-directory` to its directory, using the same value for preparation, building and -release. The default is the repository root. The package must have its own lockfile; -workspace roots, selected workspace packages and Lerna projects require a different -pipeline. Use [custom composition](#custom-release-pipelines) for other package layouts, -registries, prereleases, applications or containers. +| Layout | Preparation inputs | Version authority | +| -------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------- | +| One public root package | Defaults | HoverKraft Conventional Commit plan | +| One public package inside an npm or pnpm workspace | `package-directory: packages/cli` | HoverKraft Conventional Commit plan | +| Fixed-version Lerna workspaces | `versioning: lerna` | HoverKraft Conventional Commit plan | +| Independent Lerna workspaces | `versioning: lerna`, with `lerna.json.version: independent` | Lerna Conventional Commits for each changed package | + +`working-directory` is the project or workspace root containing the lockfile and +package manager configuration. Use the same value for preparation, building and release. +`package-directory` is relative to that root and selects the one public npm package; +private workspace packages remain private. Lerna selects its public packages itself. +Install Lerna in the project and set `npmClient: pnpm` in `lerna.json` when applicable. +Pin the package manager in the root `packageManager` field. + +Stable versions and public npm publication with the `latest` dist-tag are supported. +For apps, containers, GitHub Actions, private registries and other publication +policies, use [a custom release pipeline](#custom-release-pipelines). ## Set up -1. Align the committed version, existing tags, GitHub releases and npm versions. - HoverKraft plans from GitHub release history; the next version must not be older - than the committed version. Follow the [readiness guide](#establish-the-release-baseline). +1. Align committed package versions, existing tags, GitHub releases and npm versions. + HoverKraft plans from GitHub release history. An older plan fails; + independent Lerna uses existing `package-name@version` tags. Follow the + [release readiness guide](#establish-the-release-baseline) before the first automated run. 2. Configure [npm trusted publishing](../../actions/package/publish/README.md#npm-trusted-publishing) - for the package, repository and caller workflow filename. Leave npm's environment + for each public package, the repository and caller workflow filename. Leave npm's environment field empty unless opting into a [release environment](#optional-release-environment). A new package needs its first version published before configuring its trusted publisher. Publication uses GitHub-hosted runners and `id-token: write`. @@ -119,7 +161,7 @@ jobs: id-token: write with: build: '{"commands":["build"],"artifact":["dist"]}' - package: true + package: "true" release: needs: [prepare, ci] @@ -136,11 +178,11 @@ jobs: The caller concurrency group serializes the whole pipeline. Keep it distinct from those used by the reusable workflows. Any package validation job can replace the example's -shared CI job if it meets the [artifact requirements](#build-once-and-test-the-package). +shared CI job if it meets the [artifact requirements](#build-once-and-test-the-packages). Shared CI wrappers expose `package-tarball-artifact-id` for release and can retain `build-artifact-id` for documentation or other build outputs. CI has no release version-planning inputs and does not publish packages. -The `packages` output contains exactly one package for the release workflow. +The `packages` output identifies the packages selected for release. The caller chooses where to store App credentials and maps them explicitly as shown. The example uses repository or organization variables and secrets; their names are @@ -149,22 +191,46 @@ environment, so environment-scoped credentials are unavailable. A partially configured App fails before preparation. Without App credentials, omit both mappings to use `GITHUB_TOKEN` where repository rules allow it. +### Workspace caller adjustments + +Use the same caller with these changes: + +- For one public package inside a private npm or pnpm workspace, pass + `package-directory: packages/cli` to preparation and include `packages/cli/dist` + in the build artifact. Keep `working-directory` at the workspace root. +- For fixed-version Lerna, pass `versioning: lerna` to preparation and include the + generated output of every public package, for example `packages/*/dist`. +- For independent Lerna, use the same `versioning: lerna` input and set + `"version": "independent"` in `lerna.json`. Preparation selects changed public + packages; one build serves the whole release batch. + +Configure CI with `package: "true"`; public packages are discovered automatically. +Pass `needs.ci.outputs.package-tarball-artifacts` to release through +`package-tarball-artifacts`, replacing the single-artifact input. +Adjust optional CI `package.tests` using `PACKAGE_NAME` when installed-package +checks differ. Include documentation output in the same build +artifact when deploying it after release. + ## Establish the release baseline -Compare the public package's manifest and lockfile version with its published npm +Compare each public package's manifest and lockfile version with its published npm version and the corresponding Git tag and GitHub release. Use the commit that actually produced the published package as evidence. Do not create a historical tag on an unrelated current commit, lower an already published version, or overwrite a tag. -The next HoverKraft plan must not be older than the committed version. An equal -plan can release an already-versioned package when its tag does not exist. -Resolve manifest versions ahead of the plan before enabling the schedule: establish -missing releases from verified historical source, or reconcile an unpublished -manifest version in a reviewed change. -Fetch full history; preparation does this automatically. A GitHub release does not -prove npm publication succeeded. Resume its original tag only when that source -contains the caller workflow and supported configuration. Otherwise use a reviewed -recovery pipeline against the immutable source; never guess the published bytes. +For single and fixed-version projects, the next plan must not be older than the +committed version. An equal plan can release an already-versioned package when its +tag does not exist. Resolve manifest versions ahead of the plan before enabling +the schedule, using verified source history or a reviewed version correction. +Independent Lerna projects require their existing `name@version` tags and correct +per-package versions. Fetch full history; preparation does this automatically. + +Check every public workspace individually. A GitHub release for a fixed-version +monorepo does not prove every package reached npm. For a missing npm version, verify +that its source, generated output and dependencies can be reproduced from the +original tag. Resume that tag only when it contains the caller workflow and the +supported configuration. Otherwise use a reviewed recovery pipeline against that +immutable source; do not re-publish existing versions or guess their bytes. For a package that has never been published, establish its first version and matching release history before enabling trusted publication. The shared workflow deliberately @@ -188,28 +254,44 @@ an empty string. No release environment setup is required for npm trusted publis To use environment approvals or deployment restrictions, pass its name through the release job's `with.environment`, for example `environment: release`. Configure -that environment to permit the release tags. If npm's trusted publisher specifies +that environment to permit the release tags. Scoped Lerna tags contain `/`; match +the full tag rather than assuming a bare version. If npm's trusted publisher specifies an environment, its name must match this input; leave that field empty when using the default. Callers relying on an environment's protections or secrets must explicitly pass its name. ## Versions and release notes -HoverKraft selects the next version. npm applies that exact version to the manifest -and lockfile with lifecycle scripts disabled. Release notes and optional AI summaries -are published on GitHub. Preparation does not create or modify `CHANGELOG.md` files. -Repository release configuration is preserved. -Preparation creates version commits through GitHub's API, which signs them for -the authenticated App or bot. It requires GitHub to verify the signature before -atomically pushing the commit and tag, then dispatches the caller at the tag. -An already-committed planned version is accepted when its tag does not exist; -if npm leaves the manifest and lockfile unchanged, preparation tags the existing -commit without creating an empty commit. A no-change plan creates neither. Concurrent branch updates or existing -tag conflicts stop the push; tags are never overwritten. - -Stable bare tags, `v`-prefixed tags and directory-slug prefixes are supported. -Version planning, release notes, optional AI summaries and GitHub publication -use `ci-github-publish`; npm only applies the planned version. +For a single package or fixed-version Lerna project, HoverKraft selects the next +version. npm applies the exact planned version to the selected package and lockfile. +Lerna applies that version to changed public packages and updates dependency ranges +and lockfiles. Unchanged packages can keep their previous versions. Repository +release configuration takes precedence over generated defaults. + +Independent Lerna projects use Lerna's native change detection, Conventional Commit +increments and dependent-package updates. Preparation commits all changes once and +atomically pushes the branch and every `package-name@version` tag. One dispatched tag builds the batch. Release creates one GitHub release +per changed public package, with package-scoped HoverKraft notes, optional AI summaries +and its own tarball asset. Existing `.github/release-configs/.yml` +files take precedence; configure their tag prefix for the package's Lerna tags. + +Release notes and optional AI summaries are published on GitHub. Preparation does +not create or modify `CHANGELOG.md` files, including package changelogs. Lerna runs +with `--no-changelog` while retaining Conventional Commit versioning. + +Preparation installs dependencies with lifecycle scripts disabled. Versioning and +packaging hooks also remain disabled. No-change preparation creates no release +commit or tag. Concurrent branch updates or existing tag conflicts stop the atomic +push without creating remote release tags. Tags are never overwritten. + +Bare stable tags, `v`-prefixed tags and directory-slug prefixes are supported for +single and fixed-version projects. Independent packages use `name@version`, including +scoped names such as `@example/core@1.2.3`. Prereleases are unsupported. + +Preparation uses GitHub API bot signing and requires a verified signature before +pushing the version commit and tags atomically. An already-committed planned +version is accepted if its tag does not exist. When version application leaves +no changes, preparation tags the existing source without an empty commit. ## Changelog summaries @@ -236,20 +318,24 @@ the non-empty secret replaces the entire input; their properties are not merged. Omit both to keep the full release notes without an AI summary. Invalid configuration or summary generation failures stop publication. Already published notes are preserved. -## Build once and test the package +## Build once and test the packages Build and pack from the prepared tag because earlier artifacts can embed an old -version. Enable `package: true` on the shared CI workflow to create a tarball, -run optional `package-tests`, and validate it with `npm publish --dry-run`. +version. Enable `package: "true"` on the shared CI workflow to create a tarball, +validate its manifest and declared entry points, run optional `package.tests`, +and validate publication with `npm publish --dry-run`. The packaging job has read-only repository permissions, no release environment and no publication credentials. The option is disabled by default for applications -and other projects that do not distribute npm packages. Supplying `package-tests` -without enabling packaging is an error. +and other projects that do not distribute npm packages. Use `package: "false"` to +disable it, `package: "true"` for defaults, or a JSON object to enable packaging +with specific options. Boolean values must be quoted because the input is a string. +Unknown options and invalid field types fail configuration validation. Include every generated package file in the build artifact, preserving absolute paths for the default Ubuntu runner without a build container. Shared CI already produces this layout. Its packaging job restores those files without rebuilding, -installing project dependencies or running lifecycle hooks. Include files normally +rebuilding package files or running lifecycle hooks. For pnpm, CI first installs +the frozen workspace dependencies with lifecycle scripts disabled. Include files normally generated by `prepare` or `prepack` in the build. CI packaging does not support container mode; custom validation jobs can use the standalone package actions. @@ -258,22 +344,71 @@ If your build needs installation hooks, set `ignore-scripts: false` and configur any script approvals required by your package manager. Explicit build, lint and test commands still run. Packaging always disables lifecycle scripts. -Configure consumer checks on **CI**, not on the release workflow: +CI discovers packages before building, without installing project dependencies. +Explicit `lerna.json.packages` patterns take precedence; otherwise it uses pnpm's +workspace configuration or `package.json.workspaces`, including exclusions. +When child workspaces are present, the root is not implicitly packaged. A +standalone public package uses `.`. Packages with `private: true` are excluded; +discovery fails if there are no public packages. + +Use the `directories` option only to override discovery, for example +`package: '{"directories":["packages/core","packages/cli"]}'`. Paths are relative to `working-directory`. +Explicit selections must contain public packages; duplicate paths and paths +outside the project are rejected. The resolved selection is shared by the +package matrix and artifact collector. + +CI tests every selected package before exposing `package-tarball-artifacts`, a JSON +map from directory to artifact ID. Pass that output to release's input of the same +name instead of `package-tarball-artifact-id`. Preparation may select only a subset +of changed packages; release selects the matching artifacts from the map. The +single-artifact input remains available when preparation selects exactly one package. +Successful package validation results survive reruns of failed matrix jobs. + +Default package checks require a manifest with a name and version. They check +declared `main`, `module`, `types`, `typings`, `bin` and local `exports` targets +against the archive, including nested conditions and local fallback targets. +Export patterns must match at least one packaged file; repeated `*` replacements +must match the same text. Null exports and absent optional entry fields are +allowed. Node's file and directory resolution applies to `main`; other targets +must identify files. Checks do not execute package code or lifecycle scripts, +install dependencies, or prove runtime imports work. + +Configure additional consumer checks on **CI**, not on the release workflow: ```yaml with: - package: true - package-tests: | - test_project=$(mktemp -d) - npm install --prefix "$test_project" --ignore-scripts "$PACKAGE_TARBALL_PATH" - "$test_project/node_modules/.bin/my-cli" --help + package: '{"tests":"my-cli --help"}' ``` -Commands run from `working-directory`; `PACKAGE_TARBALL_PATH`, `PACKAGE_NAME` and -`PACKAGE_VERSION` describe the actual tarball. Install consumer test dependencies -explicitly. Tests must not change the tarball bytes. A failed check or dry run -fails CI, preventing dependent release jobs from starting. A dry run does not -validate npm authentication. Use this same CI configuration on pull requests. +The action creates a temporary npm consumer, installs the tarball with lifecycle +scripts disabled, runs commands there and cleans up on success or failure. +Installed binaries are on `PATH`. `PACKAGE_INSTALL_PATH`, `PACKAGE_TARBALL_PATH`, +`PACKAGE_NAME` and `PACKAGE_VERSION` identify the installed package and original +archive. Use `GITHUB_WORKSPACE` to access checked-out source. CLI flags and runtime +assertions remain explicit because they depend on the package's public interface. + +For a theme or plugin, provide its example project and the command that exercises +it: + +```yaml +with: + package: | + {"test-project":"packages/docs","tests":"npm run build"} +``` + +The project is relative to `working-directory` and must contain `package.json`. +Its files are copied without `node_modules` or Git metadata; the original stays +unchanged. npm installs its dependencies and replaces the tested dependency with +the tarball. The example must work as a standalone npm consumer: other dependencies +must resolve outside the source workspace. `package.test-project` requires +`package.tests`. With no custom commands, only the default archive checks run, +so unpublished sibling dependencies do not require registry access. + +Tests must not change the tarball bytes. A failed check or dry run +fails CI, preventing dependent release jobs from starting. Dry runs use npm's force +mode to validate tarballs whose committed version is already published. They do not +check registry version availability or npm authentication. Actual publication keeps +npm's version checks enabled. Use this same CI configuration on pull requests. Pass the validated artifact ID through `package-tarball-artifact-id` and require successful validation through `needs`. Release accepts one raw `.tgz` uploaded @@ -287,16 +422,21 @@ build layout because release downloads only the finished tarball. Run the caller from the default branch or wait for its schedule. Preparation reports: -| Status | Meaning | -| ------------ | ---------------------------------------------------- | -| `no-changes` | No release needed; building and release are skipped. | -| `dispatched` | Follow the new caller run at the prepared tag. | -| `released` | This version already has a published GitHub release. | -| `ready` | The tag run can build, then release. | +| Status | Meaning | +| ------------ | -------------------------------------------------------- | +| `no-changes` | No release needed; building and release are skipped. | +| `dispatched` | Follow the new caller run at the prepared tag. | +| `released` | Every release in this batch is already public on GitHub. | +| `ready` | This tag run can build, then release. | -The release job succeeds only after npm publication and GitHub release assets +On default-branch manual or scheduled runs, `no-changes` and `released` skip +version updates, tag creation, building, publication and dependent deployment +jobs. Explicit tag runs return `ready` to allow release recovery. + +The release job succeeds only after all npm packages and GitHub release assets succeed. Use `needs.release.result == 'success'` for dependent jobs. Preparation success alone is not publication. +Review AI summaries for accuracy. Retries preserve already published release notes. Rerun a failed release job to reuse the validated tarball without rebuilding, repacking or repeating consumer tests. Artifact and package identity checks still run. @@ -309,11 +449,15 @@ Published release notes are preserved. To build again and resume the same version, dispatch the caller at its original tag: ```sh -gh workflow run release.yml --ref v1.2.3 +gh workflow run release.yml --ref '@example/core@1.2.3' ``` -A tag run never plans another version. If source needs changing, make a new commit -and release; never move a released tag. Review AI summaries for accuracy. +A tag run never plans another version. Existing npm versions are skipped and their +original tarballs restored with SHA-512 verification for assets. Existing GitHub +assets are reused only when SHA-256 digests match. Conflicts and unavailable digests +fail instead of replacing assets. npm publication cannot be atomic; registry errors +stop recovery, and only an absent version permits publication. If source needs +changing, make a new commit and release. Never move a released tag. Documentation deployment remains a separate caller job. Require `needs.release.result == 'success'` and consume the producer's `build-artifact-id`. @@ -332,7 +476,7 @@ Use HoverKraft [`release/plan`](https://github.com/hoverkraft-tech/ci-github-pub for version selection. Commit version and lockfile updates before building the source, especially when compiled files embed the version. Keep the planned tag fixed throughout the pipeline. The [Node.js preparation action](../../actions/release/prepare/README.md) -handles this for one public npm package and dispatches the caller at the tag. +handles this for npm and Lerna projects and dispatches the caller at the tag. ### Packaging and publication @@ -355,6 +499,10 @@ different restoration path or omit the build artifact inputs. Run consumer tests and a publication dry run in this validation job, then pass the tarball artifact ID to a separate publication job that depends on validation succeeding. +For pnpm workspace packages, run `pnpm install --frozen-lockfile --ignore-scripts` +before packaging to resolve workspace dependencies. The shared CI packaging job +performs this preparation before the action restores the compiled build output. + The [Publish action](../../actions/package/publish/README.md) consumes the resulting tarball artifact ID. A dry run validates `npm publish` inputs but does not prove registry authentication or trusted publishing. See its @@ -376,9 +524,9 @@ publishing an npm library. Keep these consumers on the appropriate and container tooling. Attach their build artifacts or deployment jobs to the release caller. Do not introduce an npm package just to use shared release automation. -For custom package checks, [Test package](../../actions/package/test/README.md) -exposes the tarball path and rejects tests that change its bytes. Complete package -checks before publication. +[Test package](../../actions/package/test/README.md) checks the manifest and +declared entry points by default. Optional custom commands receive the tarball +path and must preserve its bytes. Complete package checks before publication. ### Recovery @@ -390,7 +538,9 @@ remaining release steps can resume. Registry and integrity errors still fail the Pass that `package-tarball-path` directly to `release/finalize`, with the desired `.tgz` filename as `asset-name`. Finalization checks the existing asset's digest and stages the file only if it is missing. Callers do not need to copy the tarball -into a predefined directory. +into a predefined directory. For workspace releases, pass the downloaded tarball +glob as `package-tarball-path` and leave `asset-name` empty to preserve the names +set before transfer between jobs. npm publication across several packages cannot be atomic. Check exact package versions before retrying; published versions cannot be overwritten. Restore original @@ -432,6 +582,12 @@ push rejection after concurrent branch changes. GitHub API responses, including signature verification, are mocked; these tests do not prove live App signing or branch-rule authorization. +`release/prepare/ReleaseProject.test.js` imports the workspace preparation implementation +directly and uses temporary Git repositories with native npm, pnpm and Lerna. It checks selected-package and +lockfile updates, dependent-package versions, changelog preservation, invalid plans +and discovery of every public package in an independent release batch. It does not +reproduce the workflow's step ordering, conditions or dispatch logic. + The [release test workflow](__test-release.yml) is called by [shared CI](__shared-ci.yml). It runs the unit tests through the existing Node.js CI workflow in a separate matrix job per action and invokes the actual release actions in separate @@ -445,6 +601,12 @@ The CI workflow tests exercise optional packaging, installed-package checks and consuming the validated tarball in a separate job. Publish action tests reject unexpected package identities before publication. +A matrix invokes the actual Package action with pnpm 10 and 12, using versions from +the action test lockfile. It restores uploaded compiled bytes, resolves `workspace:` +ranges through native packing and rejects accidental lifecycle execution through +failing hooks. The CI workflow integration test checks that the package artifact map +matches the single-package output and consumes that validated tarball in a separate job. + The [CI workflow integration test](__test-workflow-continuous-integration.yml) checks real artifact restoration and packaging of prepared files with failing lifecycle hooks and unavailable dependencies. It verifies the compiled bytes and exercises publication @@ -469,16 +631,20 @@ serves as its template; regeneration preserves that authored content. ### Workflow Call Inputs -| **Input** | **Description** | **Required** | **Type** | **Default** | -| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ---------- | ----------- | -| **`packages`** | JSON array containing one public npm package from prepare-package-release. | **true** | **string** | - | -| **`package-tarball-artifact-id`** | Validated package tarball artifact ID from this workflow run and the prepared tag's commit. Upload one raw .tgz with archive: false. The caller must depend on successful validation; any job can produce the artifact. | **true** | **string** | - | -| **`working-directory`** | Project directory relative to the caller repository root, matching release preparation. | **false** | **string** | `.` | -| **`changelog-summary`** | Optional JSON configuration forwarded unchanged to ci-github-publish/actions/release/create. | **false** | **string** | - | -| | Supports all upstream summary options. Use the changelog-summary secret when the configuration contains credentials. | | | | -| | A non-empty secret overrides this input; omit both to disable summarization. Summary failures stop publication. | | | | -| | See . | | | | -| **`environment`** | Optional GitHub environment for release jobs. Leave empty to run without an environment. When set, its protection rules apply; use the same name if restricting npm trusted publishing to an environment. | **false** | **string** | - | +| **Input** | **Description** | **Required** | **Type** | **Default** | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ---------- | ----------- | +| **`release-config`** | Optional partial release configuration as a YAML or JSON mapping, forwarded to ci-github-publish/actions/release/create. | **false** | **string** | - | +| | See for supported settings and override behavior. | | | | +| | Pass the same configuration to preparation and release. | | | | +| **`packages`** | JSON public package list from prepare-package-release, with name, version, tag, directory and slug. | **true** | **string** | - | +| **`package-tarball-artifact-id`** | Validated raw tarball artifact ID for one prepared package. Supply this or package-tarball-artifacts. | **false** | **string** | - | +| **`package-tarball-artifacts`** | JSON object mapping package directories relative to working-directory to validated raw tarball artifact IDs. Supply this or package-tarball-artifact-id. Artifacts must belong to this run and prepared commit; the caller must require successful validation. | **false** | **string** | - | +| **`working-directory`** | Project directory relative to the caller repository root, matching release preparation. | **false** | **string** | `.` | +| **`changelog-summary`** | Optional JSON configuration forwarded unchanged to ci-github-publish/actions/release/create. | **false** | **string** | - | +| | Supports all upstream summary options. Use the changelog-summary secret when the configuration contains credentials. | | | | +| | A non-empty secret overrides this input; omit both to disable summarization. Summary failures stop publication. | | | | +| | See . | | | | +| **`environment`** | Optional GitHub environment for release jobs. Leave empty to run without an environment. When set, its protection rules apply; use the same name if restricting npm trusted publishing to an environment. | **false** | **string** | - | @@ -534,7 +700,7 @@ on: permissions: {} jobs: release: - uses: hoverkraft-tech/ci-github-nodejs/.github/workflows/release.yml@97f64bc080b78daca8fb3ece49de448aaac83429 # 1.1.0 + uses: hoverkraft-tech/ci-github-nodejs/.github/workflows/release.yml@ permissions: actions: read contents: write @@ -544,15 +710,21 @@ jobs: # Optional complete JSON summary configuration, including provider credentials. Overrides the changelog-summary input and is forwarded unchanged to ci-github-publish/actions/release/create. changelog-summary: "" with: - # JSON array containing one public npm package from prepare-package-release. + # Optional partial release configuration as a YAML or JSON mapping, forwarded to ci-github-publish/actions/release/create. + # See https://github.com/hoverkraft-tech/ci-github-publish/tree/main/actions/release/create for supported settings and override behavior. + # Pass the same configuration to preparation and release. + release-config: "" + + # JSON public package list from prepare-package-release, with name, version, tag, directory and slug. # This input is required. packages: "" - # Validated package tarball artifact ID from this workflow run and the prepared tag's commit. Upload one raw .tgz with archive: false. The caller must depend on successful validation; any job can produce the artifact. - # - # This input is required. + # Validated raw tarball artifact ID for one prepared package. Supply this or package-tarball-artifacts. package-tarball-artifact-id: "" + # JSON object mapping package directories relative to working-directory to validated raw tarball artifact IDs. Supply this or package-tarball-artifact-id. Artifacts must belong to this run and prepared commit; the caller must require successful validation. + package-tarball-artifacts: "" + # Project directory relative to the caller repository root, matching release preparation. # Default: `.` working-directory: . diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 88ce863d..f5b53796 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,19 +1,59 @@ -# Publish an already validated package tarball without rebuilding, repacking or running tests. -# Call after successful package validation at the prepared tag, using an artifact from the same run and commit. +# Publish validated package tarballs without rebuilding, repacking or running tests. +# Call after successful package validation at the prepared tags, using artifacts from this run and commit. +# +# ### Force a manual release +# +# Add an optional boolean `force` input, defaulting to `false`, under the caller's +# `on.workflow_dispatch.inputs`. Forward it to the preparation job with +# `force: ${{ github.event_name == 'workflow_dispatch' && inputs.force }}`. +# +# On a manual default-branch run, `force: true` prepares a new version even without +# changes. Higher Conventional Commit plans are preserved; an unchanged planned +# version receives a patch bump. Lerna includes every public package, using native +# Conventional Commit increments in independent mode and patch increments for +# unchanged packages. Private packages remain excluded. +# +# Schedules keep normal no-change behavior. Explicit tag runs always recover that +# version, even when force is supplied. Preparation does not forward force to the +# dispatched tag run. Builds, validation, repository protections and existing-tag +# checks still apply. Force never overwrites a published version or release tag. +# +# ### Override release configuration +# +# Pass the same partial YAML or JSON `release-config` to preparation and release. +# For example, `tag-prefix: v` with `tag-template: v$RESOLVED_VERSION` preserves v-prefixed tags. +# Alternatively, put partial overrides in `.github/release-config.yml`, or +# `.github/release-configs/{slug}.yml` for a scoped release. Shared defaults come first, +# repository settings override them, and workflow input overrides repository settings. +# Supplied top-level keys replace inherited values, including entire lists and mappings; +# omitted keys remain inherited. Files with their own `_extends` chain retain that base. +# `_extends` is reserved in the workflow input. +# Override only repository-specific settings to inherit upstream version rules and notes. +# Independent Lerna still plans versions natively; keep tag settings consistent with its package tags. name: Node.js Release on: workflow_call: inputs: + release-config: + description: | + Optional partial release configuration as a YAML or JSON mapping, forwarded to ci-github-publish/actions/release/create. + See https://github.com/hoverkraft-tech/ci-github-publish/tree/main/actions/release/create for supported settings and override behavior. + Pass the same configuration to preparation and release. + type: string + default: "" packages: - description: JSON array containing one public npm package from prepare-package-release. + description: JSON public package list from prepare-package-release, with name, version, tag, directory and slug. type: string required: true package-tarball-artifact-id: - description: | - Validated package tarball artifact ID from this workflow run and the prepared tag's commit. Upload one raw .tgz with archive: false. The caller must depend on successful validation; any job can produce the artifact. + description: Validated raw tarball artifact ID for one prepared package. Supply this or package-tarball-artifacts. type: string - required: true + default: "" + package-tarball-artifacts: + description: JSON object mapping package directories relative to working-directory to validated raw tarball artifact IDs. Supply this or package-tarball-artifact-id. Artifacts must belong to this run and prepared commit; the caller must require successful validation. + type: string + default: "" working-directory: description: Project directory relative to the caller repository root, matching release preparation. type: string @@ -42,46 +82,115 @@ concurrency: cancel-in-progress: false jobs: - publish: - name: Release npm package + verify: + name: Verify package artifacts runs-on: ubuntu-latest - timeout-minutes: 45 + timeout-minutes: 10 environment: ${{ inputs.environment }} permissions: actions: read - contents: write - pull-requests: read - id-token: write + contents: read + outputs: + releases: ${{ steps.verify.outputs.releases }} + packages: ${{ steps.verify.outputs.packages }} steps: - id: verify uses: $/actions/release/verify with: packages: ${{ inputs.packages }} package-tarball-artifact-id: ${{ inputs.package-tarball-artifact-id }} + package-tarball-artifacts: ${{ inputs.package-tarball-artifacts }} working-directory: ${{ inputs.working-directory }} - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - uses: hoverkraft-tech/ci-github-publish/actions/release/create@ea6682de88070c254dac619a6623a242a6d57cd3 # 1.0.3 + + draft: + name: Draft release notes + needs: verify + runs-on: ubuntu-latest + timeout-minutes: 20 + environment: ${{ inputs.environment }} + permissions: + contents: write + pull-requests: read + strategy: + fail-fast: false + matrix: + release: ${{ fromJSON(needs.verify.outputs.releases) }} + steps: + - name: Create draft and optional AI summary + if: "!matrix.release.published" + uses: $/actions/release/draft with: - tag: ${{ github.ref_name }} - target-sha: ${{ github.sha }} - working-directory: ${{ inputs.working-directory != '.' && inputs.working-directory || '' }} - publish: "false" + tag: ${{ matrix.release.tag }} + working-directory: ${{ matrix.release.workingDirectory }} + release-config: ${{ inputs.release-config }} + package-name: ${{ matrix.release.packageName }} changelog-summary: ${{ secrets.changelog-summary || inputs.changelog-summary }} - name: Create draft and optional AI summary - if: steps.verify.outputs.published != 'true' + + npm: + name: Publish ${{ matrix.package.name }} + needs: [verify, draft] + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: ${{ inputs.environment }} + permissions: + actions: read + contents: read + id-token: write + strategy: + fail-fast: false + matrix: + package: ${{ fromJSON(needs.verify.outputs.packages) }} + steps: - name: Publish missing npm version id: publish uses: $/actions/package/publish with: - package-tarball-artifact-id: ${{ inputs.package-tarball-artifact-id }} - name: ${{ fromJSON(inputs.packages)[0].name }} - version: ${{ fromJSON(inputs.packages)[0].version }} + package-tarball-artifact-id: ${{ matrix.package.artifactId }} + name: ${{ matrix.package.name }} + version: ${{ matrix.package.version }} recover-existing: "true" tag: latest + - name: Name tarball for transfer between jobs + id: asset + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + PACKAGE_TARBALL: ${{ steps.publish.outputs.package-tarball-path }} + ASSET_NAME: ${{ matrix.package.slug }}-${{ matrix.package.version }}.tgz + with: + script: | + const path = require('node:path'); + const source = process.env.PACKAGE_TARBALL; + const destination = path.join(path.dirname(source), process.env.ASSET_NAME); + if (source !== destination) await io.mv(source, destination); + core.setOutput('path', destination); + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: release-assets-${{ matrix.package.slug }} + path: ${{ steps.asset.outputs.path }} + overwrite: true + if-no-files-found: error + + publish: + name: Publish GitHub release + needs: [verify, npm] + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: ${{ inputs.environment }} + permissions: + contents: write + actions: read + strategy: + fail-fast: false + matrix: + release: ${{ fromJSON(needs.verify.outputs.releases) }} + steps: + - id: release-assets + uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + pattern: ${{ matrix.release.artifactPattern }} + merge-multiple: true + path: ${{ runner.temp }}/release-assets - uses: $/actions/release/finalize with: - tag: ${{ github.ref_name }} - package-tarball-path: ${{ steps.publish.outputs.package-tarball-path }} - asset-name: ${{ fromJSON(inputs.packages)[0].slug }}-${{ fromJSON(inputs.packages)[0].version }}.tgz + tag: ${{ matrix.release.tag }} + package-tarball-path: ${{ steps.release-assets.outputs.download-path }}/*.tgz diff --git a/.gitignore b/.gitignore index 2efd1cf5..6768817c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,3 @@ /dist .pnpm-store /actions/**/node_modules -/actions/**/coverage diff --git a/README.md b/README.md index 44bfaea5..a0d85d44 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,13 @@ _Actions for continuous integration steps: build, lint, and test._ #### - [Build](actions/build/README.md) -#### - [Codecov](actions/codecov/README.md) - #### - [Lint](actions/lint/README.md) #### - [Test](actions/test/README.md) ### Package Actions +- [Discover public packages](actions/package/discover/README.md) - [Create a package tarball](actions/package/create/README.md) - [Test a package tarball](actions/package/test/README.md) - [Publish a package tarball](actions/package/publish/README.md) @@ -46,6 +45,7 @@ _Actions for continuous integration steps: build, lint, and test._ - [Prepare a package release](actions/release/prepare/README.md) - [Verify a prepared release](actions/release/verify/README.md) +- [Draft a package release](actions/release/draft/README.md) - [Finalize a release](actions/release/finalize/README.md) ### Dependencies diff --git a/actions/build/action.yml b/actions/build/action.yml index 860244b7..08ee1489 100644 --- a/actions/build/action.yml +++ b/actions/build/action.yml @@ -74,7 +74,7 @@ runs: steps: # jscpd:ignore-start - id: working-directory - uses: hoverkraft-tech/ci-github-common/actions/working-directory@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/actions/working-directory@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 with: working-directory: ${{ inputs.working-directory }} enforce-path-in-workspace: ${{ inputs.container && 'false' || 'true' }} diff --git a/actions/codecov/README.md b/actions/codecov/README.md deleted file mode 100644 index 6effa16a..00000000 --- a/actions/codecov/README.md +++ /dev/null @@ -1,100 +0,0 @@ - - -# ![Icon](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXItdXBsb2FkLWNsb3VkIiBjb2xvcj0iYmx1ZSI+PHBvbHlsaW5lIHBvaW50cz0iMTYgMTYgMTIgMTIgOCAxNiI+PC9wb2x5bGluZT48bGluZSB4MT0iMTIiIHkxPSIxMiIgeDI9IjEyIiB5Mj0iMjEiPjwvbGluZT48cGF0aCBkPSJNMjAuMzkgMTguMzlBNSA1IDAgMCAwIDE4IDloLTEuMjZBOCA4IDAgMSAwIDMgMTYuMyI+PC9wYXRoPjxwb2x5bGluZSBwb2ludHM9IjE2IDE2IDEyIDEyIDggMTYiPjwvcG9seWxpbmU+PC9zdmc+) GitHub Action: Codecov - -
- Codecov -
- ---- - - - - -[![Marketplace](https://img.shields.io/badge/Marketplace-codecov-blue?logo=github-actions)](https://github.com/marketplace/actions/codecov) -[![Release](https://img.shields.io/github/v/release/hoverkraft-tech/ci-github-nodejs)](https://github.com/hoverkraft-tech/ci-github-nodejs/releases) -[![License](https://img.shields.io/github/license/hoverkraft-tech/ci-github-nodejs)](http://choosealicense.com/licenses/mit/) -[![Stars](https://img.shields.io/github/stars/hoverkraft-tech/ci-github-nodejs?style=social)](https://img.shields.io/github/stars/hoverkraft-tech/ci-github-nodejs?style=social) -[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/hoverkraft-tech/ci-github-nodejs/blob/main/CONTRIBUTING.md) -![GitHub Verified Creator](https://img.shields.io/badge/GitHub-Verified%20Creator-4493F8?logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiBmaWxsPSJyZ2IoNjgsIDE0NywgMjQ4KSI+CiAgPHBhdGggZD0ibTkuNTg1LjUyLjkyOS42OGMuMTUzLjExMi4zMzEuMTg2LjUxOC4yMTVsMS4xMzguMTc1YTIuNjc4IDIuNjc4IDAgMCAxIDIuMjQgMi4yNGwuMTc0IDEuMTM5Yy4wMjkuMTg3LjEwMy4zNjUuMjE1LjUxOGwuNjguOTI4YTIuNjc3IDIuNjc3IDAgMCAxIDAgMy4xN2wtLjY4LjkyOGExLjE3NCAxLjE3NCAwIDAgMC0uMjE1LjUxOGwtLjE3NSAxLjEzOGEyLjY3OCAyLjY3OCAwIDAgMS0yLjI0MSAyLjI0MWwtMS4xMzguMTc1YTEuMTcgMS4xNyAwIDAgMC0uNTE4LjIxNWwtLjkyOC42OGEyLjY3NyAyLjY3NyAwIDAgMS0zLjE3IDBsLS45MjgtLjY4YTEuMTc0IDEuMTc0IDAgMCAwLS41MTgtLjIxNUwzLjgzIDE0LjQxYTIuNjc4IDIuNjc4IDAgMCAxLTIuMjQtMi4yNGwtLjE3NS0xLjEzOGExLjE3IDEuMTcgMCAwIDAtLjIxNS0uNTE4bC0uNjgtLjkyOGEyLjY3NyAyLjY3NyAwIDAgMSAwLTMuMTdsLjY4LS45MjhjLjExMi0uMTUzLjE4Ni0uMzMxLjIxNS0uNTE4bC4xNzUtMS4xNGEyLjY3OCAyLjY3OCAwIDAgMSAyLjI0LTIuMjRsMS4xMzktLjE3NWMuMTg3LS4wMjkuMzY1LS4xMDMuNTE4LS4yMTVsLjkyOC0uNjhhMi42NzcgMi42NzcgMCAwIDEgMy4xNyAwWk03LjMwMyAxLjcyOGwtLjkyNy42OGEyLjY3IDIuNjcgMCAwIDEtMS4xOC40ODlsLTEuMTM3LjE3NGExLjE3OSAxLjE3OSAwIDAgMC0uOTg3Ljk4N2wtLjE3NCAxLjEzNmEyLjY3NyAyLjY3NyAwIDAgMS0uNDg5IDEuMThsLS42OC45MjhhMS4xOCAxLjE4IDAgMCAwIDAgMS4zOTRsLjY4LjkyN2MuMjU2LjM0OC40MjQuNzUzLjQ4OSAxLjE4bC4xNzQgMS4xMzdjLjA3OC41MDkuNDc4LjkwOS45ODcuOTg3bDEuMTM2LjE3NGEyLjY3IDIuNjcgMCAwIDEgMS4xOC40ODlsLjkyOC42OGMuNDE0LjMwNS45NzkuMzA1IDEuMzk0IDBsLjkyNy0uNjhhMi42NyAyLjY3IDAgMCAxIDEuMTgtLjQ4OWwxLjEzNy0uMTc0YTEuMTggMS4xOCAwIDAgMCAuOTg3LS45ODdsLjE3NC0xLjEzNmEyLjY3IDIuNjcgMCAwIDEgLjQ4OS0xLjE4bC42OC0uOTI4YTEuMTc2IDEuMTc2IDAgMCAwIDAtMS4zOTRsLS42OC0uOTI3YTIuNjg2IDIuNjg2IDAgMCAxLS40ODktMS4xOGwtLjE3NC0xLjEzN2ExLjE3OSAxLjE3OSAwIDAgMC0uOTg3LS45ODdsLTEuMTM2LS4xNzRhMi42NzcgMi42NzcgMCAwIDEtMS4xOC0uNDg5bC0uOTI4LS42OGExLjE3NiAxLjE3NiAwIDAgMC0xLjM5NCAwWk0xMS4yOCA2Ljc4bC0zLjc1IDMuNzVhLjc1Ljc1IDAgMCAxLTEuMDYgMEw0LjcyIDguNzhhLjc1MS43NTEgMCAwIDEgLjAxOC0xLjA0Mi43NTEuNzUxIDAgMCAxIDEuMDQyLS4wMThMNyA4Ljk0bDMuMjItMy4yMmEuNzUxLjc1MSAwIDAgMSAxLjA0Mi4wMTguNzUxLjc1MSAwIDAgMSAuMDE4IDEuMDQyWiI+PC9wYXRoPgo8L3N2Zz4K) - - - - -## Overview - -Action to upload coverage to Codecov. - -- **Automatic dependency management**: Automatically detects and installs missing dependencies (Git, cURL, gnupg) using pkgxdev -- **Configuration fixes**: Automatically fixes pkgxdev configuration issues with unexpanded environment variables in gpgconf.ctl and .curlrc files -- **Cleanup**: Uninstalls dependencies after Codecov upload is complete -- **OIDC support**: Uses OIDC authentication with Codecov for secure uploads - -Note that this action must have write permission for id-token for this to work: - -```yml -permissions: - id-token: write -``` - - - - -## Usage - -```yaml -- uses: hoverkraft-tech/ci-github-nodejs/actions/codecov@81a4bca8445b7b273455ff26a9a5c3c31c86b2a1 # 0.25.0 - with: - # Working directory where coverage files are located. - # Can be absolute or relative to the repository root. - # - # Default: `.` - working-directory: . -``` - - - - -## Inputs - -| **Input** | **Description** | **Required** | **Default** | -| ----------------------- | --------------------------------------------------- | ------------ | ----------- | -| **`working-directory`** | Working directory where coverage files are located. | **false** | `.` | -| | Can be absolute or relative to the repository root. | | | - - - - - - - - - - -## Contributing - -Contributions are welcome! Please see the [contributing guidelines](https://github.com/hoverkraft-tech/ci-github-nodejs/blob/main/CONTRIBUTING.md) for more details. - - - - - - -## License - -This project is licensed under the MIT License. - -SPDX-License-Identifier: MIT - -Copyright © 2026 hoverkraft - -For more details, see the [license](http://choosealicense.com/licenses/mit/). - - - - ---- - -This documentation was automatically generated by [CI Dokumentor](https://github.com/hoverkraft-tech/ci-dokumentor). - - diff --git a/actions/codecov/action.yml b/actions/codecov/action.yml deleted file mode 100644 index 7dbda82f..00000000 --- a/actions/codecov/action.yml +++ /dev/null @@ -1,190 +0,0 @@ -name: "Codecov" -description: | - Action to upload coverage to Codecov. - - - **Automatic dependency management**: Automatically detects and installs missing dependencies (Git, cURL, gnupg) using pkgxdev - - **Configuration fixes**: Automatically fixes pkgxdev configuration issues with unexpanded environment variables in gpgconf.ctl and .curlrc files - - **Cleanup**: Uninstalls dependencies after Codecov upload is complete - - **OIDC support**: Uses OIDC authentication with Codecov for secure uploads - - Note that this action must have write permission for id-token for this to work: - - ```yml - permissions: - id-token: write - ``` - -author: hoverkraft -branding: - icon: upload-cloud - color: blue - -inputs: - working-directory: - description: | - Working directory where coverage files are located. - Can be absolute or relative to the repository root. - required: false - default: "." - -runs: - using: "composite" - steps: - # jscpd:ignore-start - - id: working-directory - uses: hoverkraft-tech/ci-github-common/actions/working-directory@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 - with: - working-directory: ${{ inputs.working-directory }} - enforce-path-in-workspace: "false" - # jscpd:ignore-end - - # Check and install dependencies for codecov - - name: Check Codecov dependencies - id: check-codecov-deps - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - // Check which dependencies are missing - const deps = {"git": "git", "curl": "curl", "gpg": "gnupg.org"}; - const missingDeps = []; - - for (const [dep, pkg] of Object.entries(deps)) { - try { - await io.which(dep, true); - } catch { - missingDeps.push(pkg); - } - } - - if (missingDeps.length > 0) { - core.setOutput('missing-deps', missingDeps.join(' ')); - } - - - name: Install missing Codecov dependencies - if: steps.check-codecov-deps.outputs.missing-deps - uses: pkgxdev/setup@4d4ae97af87ccb39ab8be4e073dea697fef2c6f7 # v5.0.0 - with: - +: ${{ steps.check-codecov-deps.outputs.missing-deps }} - - - name: Fix unexpanded environment variables - if: steps.check-codecov-deps.outputs.missing-deps - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const templatePattern = /\$\{([^}:]+)(?::-([^}]*))?\}/g; - const unsafeSyntaxPattern = /`|\$\(/; - const resolving = new Set(); - const resolvedCache = new Map(); - - const hasTemplate = (value) => { - if (typeof value !== 'string') { - return false; - } - - templatePattern.lastIndex = 0; - return templatePattern.test(value); - }; - - const getReferencedVariables = (value) => { - templatePattern.lastIndex = 0; - return Array.from(value.matchAll(templatePattern), (match) => match[1]); - }; - - const sanitizeForDoubleQuotes = (value) => value.replace(/(["\\])/g, '\\$1'); - - const resolveEnvVariable = async (envKey) => { - const rawValue = process.env[envKey]; - - if (!hasTemplate(rawValue)) { - core.debug(`Skipping ${envKey} because it does not match the template pattern.`); - return rawValue; - } - - if (unsafeSyntaxPattern.test(rawValue)) { - core.debug(`Skipping ${envKey} because it contains unsupported command substitution syntax.`); - return rawValue; - } - - if (resolvedCache.has(envKey)) { - return resolvedCache.get(envKey); - } - - if (resolving.has(envKey)) { - core.debug(`Detected circular reference while resolving ${envKey}, falling back to defaults.`); - return rawValue; - } - - resolving.add(envKey); - - const referencedVariables = getReferencedVariables(rawValue); - for (const variableName of referencedVariables) { - if (variableName === envKey) { - continue; - } - - await resolveEnvVariable(variableName); - } - - const envForBash = { ...process.env }; - delete envForBash[envKey]; - - const scriptLines = [ - 'set -eo pipefail', - `resolved_value="${sanitizeForDoubleQuotes(rawValue)}"`, - "printf '%s' \"${resolved_value}\"", - ]; - - let resolvedValue = rawValue; - try { - const { stdout } = await exec.getExecOutput( - 'bash', - ['-c', scriptLines.join('\n')], - { env: envForBash, silent: true } - ); - - resolvedValue = stdout.replace(/\r?\n$/, ''); - } catch (error) { - core.warning(`Failed to resolve ${envKey}: ${error.message}`); - } - - resolving.delete(envKey); - resolvedCache.set(envKey, resolvedValue); - - if (resolvedValue !== rawValue) { - core.debug(`Resolved ${envKey}: "${rawValue}" -> "${resolvedValue}"`); - process.env[envKey] = resolvedValue; - core.exportVariable(envKey, resolvedValue); - } else { - core.debug(`No changes for ${envKey}, skipping update.`); - } - - return resolvedValue; - }; - - for (const envKey of Object.keys(process.env)) { - await resolveEnvVariable(envKey); - } - - - name: 📊 Upload coverage to Codecov - uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 - with: - directory: ${{ steps.working-directory.outputs.workspace-relative-path }} - root_dir: ${{ steps.working-directory.outputs.workspace-relative-path }} - working-directory: ${{ steps.working-directory.outputs.workspace-relative-path }} - use_oidc: true - disable_telem: true - fail_ci_if_error: false - - # Uninstall pkgxdev dependencies after codecov is done - - name: Uninstall Codecov dependencies - if: always() && steps.check-codecov-deps.outputs.missing-deps - shell: bash - env: - MISSING_DEPS: ${{ steps.check-codecov-deps.outputs.missing-deps }} - run: | - if command -v pkgx &> /dev/null; then - for dep in $MISSING_DEPS; do - echo "Uninstalling $dep..." - pkgx uninstall "$dep" 2>/dev/null || true - done - fi diff --git a/actions/dependencies-cache/action.yml b/actions/dependencies-cache/action.yml index 4b325e1f..d9b32dd3 100644 --- a/actions/dependencies-cache/action.yml +++ b/actions/dependencies-cache/action.yml @@ -34,7 +34,7 @@ runs: steps: # jscpd:ignore-start - id: working-directory - uses: hoverkraft-tech/ci-github-common/actions/working-directory@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/actions/working-directory@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 with: working-directory: ${{ inputs.working-directory }} enforce-path-in-workspace: "false" diff --git a/actions/get-package-manager/action.yml b/actions/get-package-manager/action.yml index 694cc0bc..01fad3fd 100644 --- a/actions/get-package-manager/action.yml +++ b/actions/get-package-manager/action.yml @@ -37,7 +37,7 @@ runs: using: "composite" steps: - id: working-directory - uses: hoverkraft-tech/ci-github-common/actions/working-directory@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/actions/working-directory@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 with: working-directory: ${{ inputs.working-directory }} enforce-path-in-workspace: "false" diff --git a/actions/has-installed-dependencies/action.yml b/actions/has-installed-dependencies/action.yml index 9b4d77e1..e1fd664a 100644 --- a/actions/has-installed-dependencies/action.yml +++ b/actions/has-installed-dependencies/action.yml @@ -26,7 +26,7 @@ runs: steps: # jscpd:ignore-start - id: working-directory - uses: hoverkraft-tech/ci-github-common/actions/working-directory@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/actions/working-directory@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 with: working-directory: ${{ inputs.working-directory }} enforce-path-in-workspace: "false" diff --git a/actions/lint/README.md b/actions/lint/README.md index 9d0f6306..8d5c7b0b 100644 --- a/actions/lint/README.md +++ b/actions/lint/README.md @@ -2,12 +2,6 @@ # ![Icon](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXItY2hlY2stY2lyY2xlIiBjb2xvcj0iYmx1ZSI+PHBhdGggZD0iTTIyIDExLjA4VjEyYTEwIDEwIDAgMSAxLTUuOTMtOS4xNCI+PC9wYXRoPjxwb2x5bGluZSBwb2ludHM9IjIyIDQgMTIgMTQuMDEgOSAxMS4wMSI+PC9wb2x5bGluZT48L3N2Zz4=) GitHub Action: Lint -
- Lint -
- ---- - @@ -35,7 +29,7 @@ Action to lint Node.js projects with support for pull request reporting and anno ## Usage ```yaml -- uses: hoverkraft-tech/ci-github-nodejs/actions/lint@97f64bc080b78daca8fb3ece49de448aaac83429 # 1.1.0 +- uses: hoverkraft-tech/ci-github-nodejs/actions/lint@ with: # Disable project and dependency lifecycle scripts during installation. # Set false to allow installation hooks, subject to the package manager's script approval settings. @@ -66,13 +60,13 @@ Action to lint Node.js projects with support for pull request reporting and anno # Default: `lint:ci` command: lint:ci - # Optional lint report path forwarded to the [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/parse-ci-reports) action. + # Optional lint report path forwarded to the [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) action. # Provide an absolute path or one relative to the working directory. # When omitted, the action falls back to `auto:lint` detection. report-file: "" # Optional path mapping to adjust file paths in test and coverage reports. - # See the [parse-ci-reports documentation](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/parse-ci-reports) for details. + # See the [parse-ci-reports documentation](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) for details. path-mapping: "" ``` @@ -81,27 +75,27 @@ Action to lint Node.js projects with support for pull request reporting and anno ## Inputs -| **Input** | **Description** | **Required** | **Default** | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | -| **`ignore-scripts`** | Disable project and dependency lifecycle scripts during installation. | **false** | `true` | -| | Set false to allow installation hooks, subject to the package manager's script approval settings. | | | -| | Explicit commands are unaffected. Does not apply in container mode, which skips installation. | | | -| **`working-directory`** | Working directory where lint commands are executed. | **false** | `.` | -| | Can be absolute or relative to the repository root. | | | -| **`container`** | Whether running in container mode (skips checkout and node setup) | **false** | `false` | -| **`command`** | npm/pnpm/Yarn script command to run for linting. | **false** | `lint:ci` | -| | This should be a script defined in your `package.json`. | | | -| | The command should generate lint report files in a standard format. | | | -| | | | | -| | ESLint: `eslint --format json -o eslint-report.json .` | | | -| | Prettier: `prettier --check . \| tee prettier-report.txt` | | | -| | Biome: `biome check --assist-enabled=false . --reporter=sarif --reporter-file=biome-report.sarif --reporter=default` | | | -| | Astro: `astro check \| tee astro-report.txt` | | | -| **`report-file`** | Optional lint report path forwarded to the [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/parse-ci-reports) action. | **false** | - | -| | Provide an absolute path or one relative to the working directory. | | | -| | When omitted, the action falls back to `auto:lint` detection. | | | -| **`path-mapping`** | Optional path mapping to adjust file paths in test and coverage reports. | **false** | - | -| | See the [parse-ci-reports documentation](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/parse-ci-reports) for details. | | | +| **Input** | **Description** | **Required** | **Default** | +| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | +| **`ignore-scripts`** | Disable project and dependency lifecycle scripts during installation. | **false** | `true` | +| | Set false to allow installation hooks, subject to the package manager's script approval settings. | | | +| | Explicit commands are unaffected. Does not apply in container mode, which skips installation. | | | +| **`working-directory`** | Working directory where lint commands are executed. | **false** | `.` | +| | Can be absolute or relative to the repository root. | | | +| **`container`** | Whether running in container mode (skips checkout and node setup) | **false** | `false` | +| **`command`** | npm/pnpm/Yarn script command to run for linting. | **false** | `lint:ci` | +| | This should be a script defined in your `package.json`. | | | +| | The command should generate lint report files in a standard format. | | | +| | | | | +| | ESLint: `eslint --format json -o eslint-report.json .` | | | +| | Prettier: `prettier --check . \| tee prettier-report.txt` | | | +| | Biome: `biome check --assist-enabled=false . --reporter=sarif --reporter-file=biome-report.sarif --reporter=default` | | | +| | Astro: `astro check \| tee astro-report.txt` | | | +| **`report-file`** | Optional lint report path forwarded to the [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) action. | **false** | - | +| | Provide an absolute path or one relative to the working directory. | | | +| | When omitted, the action falls back to `auto:lint` detection. | | | +| **`path-mapping`** | Optional path mapping to adjust file paths in test and coverage reports. | **false** | - | +| | See the [parse-ci-reports documentation](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) for details. | | | diff --git a/actions/lint/action.yml b/actions/lint/action.yml index e538e0c3..4179829f 100644 --- a/actions/lint/action.yml +++ b/actions/lint/action.yml @@ -36,7 +36,7 @@ inputs: default: "lint:ci" report-file: description: | - Optional lint report path forwarded to the [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/parse-ci-reports) action. + Optional lint report path forwarded to the [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) action. Provide an absolute path or one relative to the working directory. When omitted, the action falls back to `auto:lint` detection. required: false @@ -44,7 +44,7 @@ inputs: path-mapping: description: | Optional path mapping to adjust file paths in test and coverage reports. - See the [parse-ci-reports documentation](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/parse-ci-reports) for details. + See the [parse-ci-reports documentation](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) for details. required: false default: "" @@ -53,7 +53,7 @@ runs: steps: # jscpd:ignore-start - id: working-directory - uses: hoverkraft-tech/ci-github-common/actions/working-directory@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/actions/working-directory@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 with: working-directory: ${{ inputs.working-directory }} enforce-path-in-workspace: ${{ inputs.container && 'false' || 'true' }} @@ -112,7 +112,7 @@ runs: - name: 📊 Parse lint reports if: always() - uses: hoverkraft-tech/ci-github-common/actions/parse-ci-reports@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/actions/quality/parse-ci-reports@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 with: working-directory: ${{ steps.working-directory.outputs.absolute-path }} report-paths: ${{ inputs.report-file || 'auto:lint' }} diff --git a/actions/package/create/action.yml b/actions/package/create/action.yml index 765e34b4..c8f9d6a4 100644 --- a/actions/package/create/action.yml +++ b/actions/package/create/action.yml @@ -65,7 +65,7 @@ runs: # jscpd:ignore-start - id: working-directory - uses: hoverkraft-tech/ci-github-common/actions/working-directory@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/actions/working-directory@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 with: working-directory: ${{ inputs.working-directory }} enforce-path-in-workspace: "false" diff --git a/actions/package/discover/README.md b/actions/package/discover/README.md new file mode 100644 index 00000000..5b03d91e --- /dev/null +++ b/actions/package/discover/README.md @@ -0,0 +1,95 @@ + + +# ![Icon](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXItcGFja2FnZSIgY29sb3I9ImJsdWUiPjxsaW5lIHgxPSIxNi41IiB5MT0iOS40IiB4Mj0iNy41IiB5Mj0iNC4yMSI+PC9saW5lPjxwYXRoIGQ9Ik0yMSAxNlY4YTIgMiAwIDAgMC0xLTEuNzNsLTctNGEyIDIgMCAwIDAtMiAwbC03IDRBMiAyIDAgMCAwIDMgOHY4YTIgMiAwIDAgMCAxIDEuNzNsNyA0YTIgMiAwIDAgMCAyIDBsNy00QTIgMiAwIDAgMCAyMSAxNnoiPjwvcGF0aD48cG9seWxpbmUgcG9pbnRzPSIzLjI3IDYuOTYgMTIgMTIuMDEgMjAuNzMgNi45NiI+PC9wb2x5bGluZT48bGluZSB4MT0iMTIiIHkxPSIyMi4wOCIgeDI9IjEyIiB5Mj0iMTIiPjwvbGluZT48L3N2Zz4=) GitHub Action: Discover packages + +
+ Discover packages +
+ +--- + + + + +[![Marketplace](https://img.shields.io/badge/Marketplace-discover--packages-blue?logo=github-actions)](https://github.com/marketplace/actions/discover-packages) +[![Release](https://img.shields.io/github/v/release/hoverkraft-tech/ci-github-nodejs)](https://github.com/hoverkraft-tech/ci-github-nodejs/releases) +[![License](https://img.shields.io/github/license/hoverkraft-tech/ci-github-nodejs)](http://choosealicense.com/licenses/mit/) +[![Stars](https://img.shields.io/github/stars/hoverkraft-tech/ci-github-nodejs?style=social)](https://img.shields.io/github/stars/hoverkraft-tech/ci-github-nodejs?style=social) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/hoverkraft-tech/ci-github-nodejs/blob/main/CONTRIBUTING.md) + + + + +## Overview + +Select public packages from workspace configuration or an explicit directory list without installing dependencies. + + + + +## Usage + +```yaml +- uses: hoverkraft-tech/ci-github-nodejs/actions/package/discover@ + with: + # Project root containing package.json and package manager configuration. + # Default: `.` + working-directory: . + + # Optional JSON array of public package directories relative to working-directory. Empty discovers workspaces, or the standalone root package. + directories: "" +``` + + + + +## Inputs + +| **Input** | **Description** | **Required** | **Default** | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | +| **`working-directory`** | Project root containing package.json and package manager configuration. | **false** | `.` | +| **`directories`** | Optional JSON array of public package directories relative to working-directory. Empty discovers workspaces, or the standalone root package. | **false** | - | + + + + + + +## Outputs + +| **Output** | **Description** | +| ----------------- | ----------------------------------------------------------------------- | +| **`directories`** | JSON array of public package directories relative to working-directory. | + + + + + + +## Contributing + +Contributions are welcome! Please see the [contributing guidelines](https://github.com/hoverkraft-tech/ci-github-nodejs/blob/main/CONTRIBUTING.md) for more details. + + + + + + +## License + +This project is licensed under the MIT License. + +SPDX-License-Identifier: MIT + +Copyright © 2026 hoverkraft + +For more details, see the [license](http://choosealicense.com/licenses/mit/). + + + + +--- + +This documentation was automatically generated by [CI Dokumentor](https://github.com/hoverkraft-tech/ci-dokumentor). + + diff --git a/actions/package/discover/action.yml b/actions/package/discover/action.yml new file mode 100644 index 00000000..1a3d93c8 --- /dev/null +++ b/actions/package/discover/action.yml @@ -0,0 +1,94 @@ +name: Discover packages +description: Select public packages from workspace configuration or an explicit directory list without installing dependencies. +author: hoverkraft +branding: + icon: package + color: blue +inputs: + working-directory: + description: Project root containing package.json and package manager configuration. + default: . + directories: + description: Optional JSON array of public package directories relative to working-directory. Empty discovers workspaces, or the standalone root package. + default: "" +outputs: + directories: + description: JSON array of public package directories relative to working-directory. + value: ${{ steps.discover.outputs.directories }} +runs: + using: composite + steps: + - id: setup-node + uses: $/actions/setup-node + with: + working-directory: ${{ inputs.working-directory }} + install-dependencies: "false" + - id: discover + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + WORKING_DIRECTORY: ${{ inputs.working-directory }} + DIRECTORIES: ${{ inputs.directories }} + PACKAGE_MANAGER: ${{ steps.setup-node.outputs.package-manager }} + with: + script: | + const fs = require('node:fs'); + const path = require('node:path'); + const directory = fs.realpathSync(path.resolve(process.env.GITHUB_WORKSPACE, process.env.WORKING_DIRECTORY)); + const read = file => JSON.parse(fs.readFileSync(path.join(directory, file), 'utf8')); + const manifest = read('package.json'); + const explicit = process.env.DIRECTORIES.trim() !== ''; + const relativePath = value => typeof value === 'string' && value.trim() !== '' && + !path.isAbsolute(value) && !path.win32.isAbsolute(value) && !value.split(/[\\/]/).includes('..'); + let directories; + if (explicit) { + directories = JSON.parse(process.env.DIRECTORIES); + if (!Array.isArray(directories) || !directories.length || directories.some(value => !relativePath(value))) { + throw new Error('directories must be a nonempty JSON array of relative package directories inside the project.'); + } + } else { + const lerna = fs.existsSync(path.join(directory, 'lerna.json')) ? read('lerna.json') : {}; + if (lerna.packages !== undefined) { + if (!Array.isArray(lerna.packages) || !lerna.packages.length) throw new Error('lerna.json packages must be a nonempty array.'); + const patterns = lerna.packages.map(pattern => { + if (typeof pattern !== 'string') throw new Error('Lerna package patterns must be strings.'); + const exclude = pattern.startsWith('!'); + const value = exclude ? pattern.slice(1) : pattern; + if (!relativePath(value)) throw new Error('Lerna package patterns must remain inside the project.'); + return `${exclude ? '!' : ''}${directory}/${value.replace(/\/$/, '')}/package.json`; + }); + patterns.push(`!${directory}/**/node_modules/**`, `!${directory}/**/.git/**`); + const files = await (await glob.create(patterns.join('\n'), { followSymbolicLinks: false, matchDirectories: false })).glob(); + directories = files.map(file => path.dirname(file)); + } else if (process.env.PACKAGE_MANAGER === 'pnpm' && fs.existsSync(path.join(directory, 'pnpm-workspace.yaml'))) { + const { stdout } = await exec.getExecOutput('pnpm', ['list', '--recursive', '--depth', '-1', '--json', '--config.lockfile=false'], { cwd: directory }); + directories = JSON.parse(stdout).map(project => project.path); + const workspaces = directories.filter(project => path.resolve(project) !== directory); + if (workspaces.length) directories = workspaces; + } else if (manifest.workspaces !== undefined) { + const { stdout } = await exec.getExecOutput('npm', [ + 'exec', '--workspaces', '--include-workspace-root=false', '--offline', '--yes=false', '--ignore-scripts', + '--', process.execPath, '-p', 'JSON.stringify(process.cwd())', + ], { cwd: directory }); + directories = stdout.trim().split(/\r?\n/).filter(Boolean).map(line => JSON.parse(line)); + } else { + directories = ['.']; + } + } + const selected = []; + for (const candidate of directories) { + const absolute = fs.realpathSync(path.resolve(directory, candidate)); + const relative = path.relative(directory, absolute) || '.'; + if (!relativePath(relative)) throw new Error(`Package directory escapes the project: ${candidate}.`); + const packaged = read(path.join(relative, 'package.json')); + if (packaged.private === true) { + if (explicit) throw new Error(`Package directory is private: ${candidate}.`); + continue; + } + const normalized = relative.split(path.sep).join('/'); + if (selected.includes(normalized)) throw new Error(`Duplicate package directory: ${normalized}.`); + selected.push(normalized); + } + if (!selected.length) throw new Error('No public packages found. Check workspace configuration or provide directories explicitly.'); + if (selected.length > 256) throw new Error('Package validation supports at most 256 package directories.'); + core.info(`Selected public packages: ${selected.join(', ')}`); + core.setOutput('directories', selected.sort()); diff --git a/actions/package/publish/README.md b/actions/package/publish/README.md index 276ed7ff..b011bfc4 100644 --- a/actions/package/publish/README.md +++ b/actions/package/publish/README.md @@ -25,7 +25,7 @@ Publish an existing Node.js package tarball to an npm-compatible registry ## Usage ```yaml -- uses: hoverkraft-tech/ci-github-nodejs/actions/package/publish@97f64bc080b78daca8fb3ece49de448aaac83429 # 1.1.0 +- uses: hoverkraft-tech/ci-github-nodejs/actions/package/publish@ with: # Expected package name, paired with version to reject an unintended release tarball before publication. name: "" @@ -56,7 +56,7 @@ Publish an existing Node.js package tarball to an npm-compatible registry # Default: `true` provenance: "true" - # Validate publishing without uploading the package (true or false) + # Validate the tarball without uploading (true or false). Enables npm force mode only during dry runs so CI can check already-published versions. Live publication keeps npm's version checks. # Default: `false` dry-run: "false" @@ -70,18 +70,18 @@ Publish an existing Node.js package tarball to an npm-compatible registry ## Inputs -| **Input** | **Description** | **Required** | **Default** | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | ---------------------------- | -| **`name`** | Expected package name, paired with version to reject an unintended release tarball before publication. | **false** | - | -| **`version`** | Expected package version, paired with name to reject an unintended release tarball before publication. | **false** | - | -| **`recover-existing`** | Recover an existing npmjs.org version by restoring its original tarball instead of publishing again. Checks the registry on every attempt. | **false** | `false` | -| **`package-tarball-artifact-id`** | Artifact ID of one package tarball uploaded by the package action | **true** | - | -| **`registry-url`** | Registry URL used by npm publish | **false** | `https://registry.npmjs.org` | -| **`access`** | Package access: public, restricted, or empty to use npm defaults | **false** | `public` | -| **`tag`** | npm distribution tag, such as latest, next, or canary; empty uses npm defaults | **false** | - | -| **`provenance`** | Whether to request provenance for npmjs.org publishes (true or false) | **false** | `true` | -| **`dry-run`** | Validate publishing without uploading the package (true or false) | **false** | `false` | -| **`github-token`** | GitHub token for downloading the artifact; registry authentication uses NODE_AUTH_TOKEN or OIDC | **false** | `${{ github.token }}` | +| **Input** | **Description** | **Required** | **Default** | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ---------------------------- | +| **`name`** | Expected package name, paired with version to reject an unintended release tarball before publication. | **false** | - | +| **`version`** | Expected package version, paired with name to reject an unintended release tarball before publication. | **false** | - | +| **`recover-existing`** | Recover an existing npmjs.org version by restoring its original tarball instead of publishing again. Checks the registry on every attempt. | **false** | `false` | +| **`package-tarball-artifact-id`** | Artifact ID of one package tarball uploaded by the package action | **true** | - | +| **`registry-url`** | Registry URL used by npm publish | **false** | `https://registry.npmjs.org` | +| **`access`** | Package access: public, restricted, or empty to use npm defaults | **false** | `public` | +| **`tag`** | npm distribution tag, such as latest, next, or canary; empty uses npm defaults | **false** | - | +| **`provenance`** | Whether to request provenance for npmjs.org publishes (true or false) | **false** | `true` | +| **`dry-run`** | Validate the tarball without uploading (true or false). Enables npm force mode only during dry runs so CI can check already-published versions. Live publication keeps npm's version checks. | **false** | `false` | +| **`github-token`** | GitHub token for downloading the artifact; registry authentication uses NODE_AUTH_TOKEN or OIDC | **false** | `${{ github.token }}` | diff --git a/actions/package/publish/action.yml b/actions/package/publish/action.yml index 3d4bf5dc..fce2c96d 100644 --- a/actions/package/publish/action.yml +++ b/actions/package/publish/action.yml @@ -35,7 +35,7 @@ inputs: required: false default: "true" dry-run: - description: "Validate publishing without uploading the package (true or false)" + description: "Validate the tarball without uploading (true or false). Enables npm force mode only during dry runs so CI can check already-published versions. Live publication keeps npm's version checks." required: false default: "false" github-token: @@ -224,7 +224,9 @@ runs: } if (dryRun) { - args.push('--dry-run'); + // CI validates committed versions that can already exist on npm. + // Keep registry conflict checks enabled for actual publication. + args.push('--dry-run', '--force'); } core.info(`Publishing ${path.basename(packageTarballPath)} to ${registryUrl} with npm ${args.join(' ')}`); diff --git a/actions/package/restore/coverage/.gitignore b/actions/package/restore/coverage/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/actions/package/restore/coverage/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/actions/package/restore/package.json b/actions/package/restore/package.json index 72b92d46..1e61ad0a 100644 --- a/actions/package/restore/package.json +++ b/actions/package/restore/package.json @@ -3,7 +3,8 @@ "private": true, "scripts": { "test": "node --test", - "test:ci": "node --test" + "test:watch": "node --test --watch", + "test:ci": "node --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=coverage/lcov.info --test" }, "devDependencies": { "@actions/exec": "^1.1.1" diff --git a/actions/package/test/README.md b/actions/package/test/README.md index 2e659bd6..77c81f16 100644 --- a/actions/package/test/README.md +++ b/actions/package/test/README.md @@ -23,18 +23,19 @@ ## Overview -Run consumer smoke tests against the package tarball and reject changes to its bytes. +Validate packaged entry points and run optional tests in an isolated consumer with the tarball installed, preserving the original package bytes. ## Inputs -| **Input** | **Description** | **Required** | **Default** | -| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | -| **`commands`** | Shell commands run from working-directory, with PACKAGE_TARBALL_PATH, PACKAGE_NAME and PACKAGE_VERSION. Install test dependencies explicitly when needed. | **false** | - | -| **`working-directory`** | Project or workspace root used by package tests. | **false** | `.` | -| **`tarball-path`** | Absolute path to the package tarball. | **true** | - | +| **Input** | **Description** | **Required** | **Default** | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | +| **`commands`** | Optional shell commands run in a temporary npm consumer after installing the tarball with lifecycle scripts disabled. Installed binaries are on PATH; PACKAGE_INSTALL_PATH, PACKAGE_TARBALL_PATH, PACKAGE_NAME and PACKAGE_VERSION describe the tested package. The consumer is removed after success or failure. | **false** | - | +| **`working-directory`** | Project or workspace root used to resolve test-project. Commands run in the temporary consumer; use GITHUB_WORKSPACE to access checked-out source. | **false** | `.` | +| **`test-project`** | Optional consumer project directory relative to working-directory, copied before installing the tarball. Must contain package.json. Copies files without node_modules or Git metadata; the original project is unchanged. Requires commands. | **false** | - | +| **`tarball-path`** | Absolute path to the package tarball. | **true** | - | @@ -75,15 +76,18 @@ This documentation was automatically generated by [CI Dokumentor](https://github ## Usage ```yaml -- uses: hoverkraft-tech/ci-github-nodejs/actions/package/test@97f64bc080b78daca8fb3ece49de448aaac83429 # 1.1.0 +- uses: hoverkraft-tech/ci-github-nodejs/actions/package/test@ with: - # Shell commands run from working-directory, with PACKAGE_TARBALL_PATH, PACKAGE_NAME and PACKAGE_VERSION. Install test dependencies explicitly when needed. + # Optional shell commands run in a temporary npm consumer after installing the tarball with lifecycle scripts disabled. Installed binaries are on PATH; PACKAGE_INSTALL_PATH, PACKAGE_TARBALL_PATH, PACKAGE_NAME and PACKAGE_VERSION describe the tested package. The consumer is removed after success or failure. commands: "" - # Project or workspace root used by package tests. + # Project or workspace root used to resolve test-project. Commands run in the temporary consumer; use GITHUB_WORKSPACE to access checked-out source. # Default: `.` working-directory: . + # Optional consumer project directory relative to working-directory, copied before installing the tarball. Must contain package.json. Copies files without node_modules or Git metadata; the original project is unchanged. Requires commands. + test-project: "" + # Absolute path to the package tarball. # This input is required. tarball-path: "" diff --git a/actions/package/test/action.yml b/actions/package/test/action.yml index 1bc807d5..94ceadc9 100644 --- a/actions/package/test/action.yml +++ b/actions/package/test/action.yml @@ -1,16 +1,19 @@ name: Test package -description: Run consumer smoke tests against the package tarball and reject changes to its bytes. +description: Validate packaged entry points and run optional tests in an isolated consumer with the tarball installed, preserving the original package bytes. author: hoverkraft branding: icon: check-circle color: blue inputs: commands: - description: Shell commands run from working-directory, with PACKAGE_TARBALL_PATH, PACKAGE_NAME and PACKAGE_VERSION. Install test dependencies explicitly when needed. + description: Optional shell commands run in a temporary npm consumer after installing the tarball with lifecycle scripts disabled. Installed binaries are on PATH; PACKAGE_INSTALL_PATH, PACKAGE_TARBALL_PATH, PACKAGE_NAME and PACKAGE_VERSION describe the tested package. The consumer is removed after success or failure. default: "" working-directory: - description: Project or workspace root used by package tests. + description: Project or workspace root used to resolve test-project. Commands run in the temporary consumer; use GITHUB_WORKSPACE to access checked-out source. default: . + test-project: + description: Optional consumer project directory relative to working-directory, copied before installing the tarball. Must contain package.json. Copies files without node_modules or Git metadata; the original project is unchanged. Requires commands. + default: "" tarball-path: description: Absolute path to the package tarball. required: true @@ -22,23 +25,110 @@ runs: env: PACKAGE_TEST_COMMANDS: ${{ inputs.commands }} WORKING_DIRECTORY: ${{ inputs.working-directory }} + PACKAGE_TEST_PROJECT: ${{ inputs.test-project }} PACKAGE_TARBALL_PATH: ${{ inputs.tarball-path }} with: script: | const fs = require('node:fs'); const path = require('node:path'); const { createHash } = require('node:crypto'); - if (!process.env.PACKAGE_TEST_COMMANDS.trim()) return; + const { createRequire } = require('node:module'); + const commands = process.env.PACKAGE_TEST_COMMANDS.trim(); + const testProject = process.env.PACKAGE_TEST_PROJECT.trim(); + if (testProject && !commands) throw new Error('test-project requires commands.'); const tarball = path.resolve(process.env.PACKAGE_TARBALL_PATH); const digest = () => createHash('sha256').update(fs.readFileSync(tarball)).digest('hex'); const expected = digest(); - const { stdout } = await exec.getExecOutput('tar', ['-xOf', tarball, 'package/package.json'], { silent: true }); - const { name, version } = JSON.parse(stdout); - if (typeof name !== 'string' || !name || typeof version !== 'string' || !version) { - throw new Error('Package tarball must declare its name and version.'); + const directory = fs.mkdtempSync(path.join(process.env.RUNNER_TEMP, 'package-test-')); + try { + await exec.exec('tar', ['-xzf', tarball, '-C', directory]); + const packageDirectory = path.join(directory, 'package'); + const manifestPath = path.join(packageDirectory, 'package.json'); + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + const { name, version } = manifest ?? {}; + if (typeof name !== 'string' || !name.trim() || typeof version !== 'string' || !version.trim()) { + throw new Error('Package tarball must declare its name and version.'); + } + const packageRequire = createRequire(manifestPath); + let packagedFiles; + const checkEntry = async (entry, field, pattern = false) => { + if (typeof entry !== 'string' || !entry) throw new Error(`Invalid package entry point: ${field}.`); + let file = path.resolve(packageDirectory, entry); + if (file !== packageDirectory && !file.startsWith(`${packageDirectory}${path.sep}`)) { + throw new Error(`Package entry point escapes the package: ${field} (${entry}).`); + } + if (pattern && entry.includes('*')) { + packagedFiles ??= await (await glob.create(`${packageDirectory}/**`, { matchDirectories: false, followSymbolicLinks: false })).glob(); + const parts = file.split('*').map(part => RegExp.escape(part)); + const matcher = new RegExp(`^${parts.shift()}(.*)${parts.join('\\1')}$`); + if (packagedFiles.some(candidate => matcher.test(candidate))) return; + } else { + if (field === 'main') { + try { file = packageRequire.resolve(file); } catch (error) { + if (error.code !== 'MODULE_NOT_FOUND') throw error; + } + } + if (file.startsWith(`${packageDirectory}${path.sep}`) && fs.statSync(file, { throwIfNoEntry: false })?.isFile()) return; + } + throw new Error(`Missing package entry point: ${field} (${entry}).`); + }; + const checkExports = async (value, field = 'exports', fallback = false) => { + if (value === null) return; + if (Array.isArray(value)) { + for (const [index, target] of value.entries()) await checkExports(target, `${field}[${index}]`, true); + } else if (typeof value === 'object') { + for (const [key, target] of Object.entries(value)) await checkExports(target, `${field}.${key}`, fallback); + } else if (typeof value === 'string' && value.startsWith('./')) { + await checkEntry(value, field, true); + } else if (!fallback) { + throw new Error(`Invalid package export target: ${field}.`); + } + }; + for (const field of ['main', 'module', 'types', 'typings']) { + if (manifest[field] != null) await checkEntry(manifest[field], field); + } + if (manifest.exports !== undefined) await checkExports(manifest.exports); + if (typeof manifest.bin === 'string') await checkEntry(manifest.bin, 'bin'); + else if (manifest.bin != null) { + for (const [command, entry] of Object.entries(manifest.bin)) await checkEntry(entry, `bin.${command}`); + } + core.info(`Validated packaged entry points for ${name}@${version}.`); + if (commands) { + const consumer = path.join(directory, 'consumer'); + await io.mkdirP(consumer); + if (testProject) { + const root = fs.realpathSync(path.resolve(process.env.GITHUB_WORKSPACE, process.env.WORKING_DIRECTORY)); + const source = fs.realpathSync(path.resolve(root, testProject)); + if (source !== root && !source.startsWith(`${root}${path.sep}`)) throw new Error('test-project must be inside working-directory.'); + if (!fs.statSync(path.join(source, 'package.json'), { throwIfNoEntry: false })?.isFile()) throw new Error('test-project must contain package.json.'); + const files = await (await glob.create([ + `${source}/**`, `!${source}/**/node_modules`, `!${source}/**/node_modules/**`, + `!${source}/**/.git`, `!${source}/**/.git/**`, + ].join('\n'), { matchDirectories: false, followSymbolicLinks: false })).glob(); + for (const file of files) { + const resolved = fs.realpathSync(file); + if (!resolved.startsWith(`${source}${path.sep}`)) throw new Error('test-project files must stay inside the project.'); + const destination = path.join(consumer, path.relative(source, file)); + await io.mkdirP(path.dirname(destination)); + await io.cp(resolved, destination); + } + } else { + fs.writeFileSync(path.join(consumer, 'package.json'), JSON.stringify({ name: 'package-consumer', version: '0.0.0', private: true })); + } + await exec.exec('npm', ['install', '--ignore-scripts', '--no-audit', '--no-fund', '--workspaces=false', tarball], { cwd: consumer }); + const installed = path.resolve(consumer, 'node_modules', name); + if (!installed.startsWith(`${consumer}${path.sep}`)) throw new Error('Installed package must stay inside the consumer.'); + const installedManifest = JSON.parse(fs.readFileSync(path.join(installed, 'package.json'), 'utf8')); + if (installedManifest.name !== name || installedManifest.version !== version) throw new Error('Installed package does not match the tarball.'); + await exec.exec('bash', ['--noprofile', '--norc', '-e', '-o', 'pipefail', '-c', commands], { + cwd: consumer, + env: { + ...process.env, PATH: `${path.join(consumer, 'node_modules', '.bin')}${path.delimiter}${process.env.PATH || ''}`, + PACKAGE_INSTALL_PATH: installed, PACKAGE_TARBALL_PATH: tarball, PACKAGE_NAME: name, PACKAGE_VERSION: version, + }, + }); + } + if (digest() !== expected) throw new Error('Package tests must not modify the package tarball.'); + } finally { + await io.rmRF(directory); } - await exec.exec('bash', ['--noprofile', '--norc', '-e', '-o', 'pipefail', '-c', process.env.PACKAGE_TEST_COMMANDS], { - cwd: path.resolve(process.env.GITHUB_WORKSPACE, process.env.WORKING_DIRECTORY), - env: { ...process.env, PACKAGE_TARBALL_PATH: tarball, PACKAGE_NAME: name, PACKAGE_VERSION: version }, - }); - if (digest() !== expected) throw new Error('Package tests must not modify the package tarball.'); diff --git a/actions/release/draft/README.md b/actions/release/draft/README.md new file mode 100644 index 00000000..ee820cd7 --- /dev/null +++ b/actions/release/draft/README.md @@ -0,0 +1,107 @@ + + +# ![Icon](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXItZWRpdCIgY29sb3I9ImJsdWUiPjxwYXRoIGQ9Ik0xMSA0SDRhMiAyIDAgMCAwLTIgMnYxNGEyIDIgMCAwIDAgMiAyaDE0YTIgMiAwIDAgMCAyLTJ2LTciPjwvcGF0aD48cGF0aCBkPSJNMTguNSAyLjVhMi4xMjEgMi4xMjEgMCAwIDEgMyAzTDEyIDE1bC00IDEgMS00IDkuNS05LjV6Ij48L3BhdGg+PC9zdmc+) GitHub Action: Draft Node.js release + +
+ Draft Node.js release +
+ +--- + + + + +[![Marketplace](https://img.shields.io/badge/Marketplace-draft--node.js--release-blue?logo=github-actions)](https://github.com/marketplace/actions/draft-node.js-release) +[![Release](https://img.shields.io/github/v/release/hoverkraft-tech/ci-github-nodejs)](https://github.com/hoverkraft-tech/ci-github-nodejs/releases) +[![License](https://img.shields.io/github/license/hoverkraft-tech/ci-github-nodejs)](http://choosealicense.com/licenses/mit/) +[![Stars](https://img.shields.io/github/stars/hoverkraft-tech/ci-github-nodejs?style=social)](https://img.shields.io/github/stars/hoverkraft-tech/ci-github-nodejs?style=social) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/hoverkraft-tech/ci-github-nodejs/blob/main/CONTRIBUTING.md) + + + + +## Overview + +Create scoped release notes and an optional AI summary with HoverKraft, preserving independent package tag history. + + + + +## Inputs + +| **Input** | **Description** | **Required** | **Default** | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | +| **`release-config`** | Optional partial release configuration as a YAML or JSON mapping, forwarded to ci-github-publish/actions/release/create. | **false** | - | +| | See for supported settings and override behavior. | | | +| | Use the same configuration as release preparation. | | | +| **`tag`** | Prepared release tag. | **true** | - | +| **`working-directory`** | Release notes scope relative to the repository root. | **false** | `.` | +| **`package-name`** | Package name for an independent Lerna release. Empty for repository-wide releases. | **false** | - | +| **`changelog-summary`** | Optional complete JSON summary configuration forwarded unchanged to ci-github-publish/actions/release/create. | **false** | - | +| | Supports all upstream provider, model, credential, model configuration, working directory and template options. | | | +| | An empty value disables summarization. See . | | | + + + + + + + + +## Contributing + +Contributions are welcome! Please see the [contributing guidelines](https://github.com/hoverkraft-tech/ci-github-nodejs/blob/main/CONTRIBUTING.md) for more details. + + + + + + +## License + +This project is licensed under the MIT License. + +SPDX-License-Identifier: MIT + +Copyright © 2026 hoverkraft + +For more details, see the [license](http://choosealicense.com/licenses/mit/). + + + + +--- + +This documentation was automatically generated by [CI Dokumentor](https://github.com/hoverkraft-tech/ci-dokumentor). + + + + +## Usage + +```yaml +- uses: hoverkraft-tech/ci-github-nodejs/actions/release/draft@ + with: + # Optional partial release configuration as a YAML or JSON mapping, forwarded to ci-github-publish/actions/release/create. + # See https://github.com/hoverkraft-tech/ci-github-publish/tree/main/actions/release/create for supported settings and override behavior. + # Use the same configuration as release preparation. + release-config: "" + + # Prepared release tag. + # This input is required. + tag: "" + + # Release notes scope relative to the repository root. + # Default: `.` + working-directory: . + + # Package name for an independent Lerna release. Empty for repository-wide releases. + package-name: "" + + # Optional complete JSON summary configuration forwarded unchanged to ci-github-publish/actions/release/create. + # Supports all upstream provider, model, credential, model configuration, working directory and template options. + # An empty value disables summarization. See https://github.com/hoverkraft-tech/ci-github-publish/tree/main/actions/release/create. + changelog-summary: "" +``` + + diff --git a/actions/release/draft/action.yml b/actions/release/draft/action.yml new file mode 100644 index 00000000..91537e34 --- /dev/null +++ b/actions/release/draft/action.yml @@ -0,0 +1,93 @@ +name: Draft Node.js release +description: Create scoped release notes and an optional AI summary with HoverKraft, preserving independent package tag history. +author: hoverkraft +branding: + icon: edit + color: blue +inputs: + release-config: + description: | + Optional partial release configuration as a YAML or JSON mapping, forwarded to ci-github-publish/actions/release/create. + See https://github.com/hoverkraft-tech/ci-github-publish/tree/main/actions/release/create for supported settings and override behavior. + Use the same configuration as release preparation. + default: "" + tag: + description: Prepared release tag. + required: true + working-directory: + description: Release notes scope relative to the repository root. + default: . + package-name: + description: Package name for an independent Lerna release. Empty for repository-wide releases. + default: "" + changelog-summary: + description: | + Optional complete JSON summary configuration forwarded unchanged to ci-github-publish/actions/release/create. + Supports all upstream provider, model, credential, model configuration, working directory and template options. + An empty value disables summarization. See https://github.com/hoverkraft-tech/ci-github-publish/tree/main/actions/release/create. + default: "" +runs: + using: composite + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - id: configuration + if: inputs.package-name != '' + uses: hoverkraft-tech/ci-github-publish/actions/release/get-configuration@f06e0e72a76bfca57c34d08d03e5c1914275fd4c # 2.0.0 + with: + working-directory: ${{ inputs.working-directory }} + - id: independent-configuration + if: inputs.package-name != '' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + WORKING_DIRECTORY: ${{ inputs.working-directory }} + PACKAGE_NAME: ${{ inputs.package-name }} + CONFIGURATION_PATH: ${{ steps.configuration.outputs.config-path }} + with: + script: | + const fs = require('node:fs'); + const path = require('node:path'); + const workspace = process.env.GITHUB_WORKSPACE; + const workingDirectory = process.env.WORKING_DIRECTORY; + const packageName = process.env.PACKAGE_NAME; + const configurationPath = process.env.CONFIGURATION_PATH; + if (!/^(?:@[a-z0-9-]+\/)?[a-z0-9][a-z0-9._-]*$/.test(packageName)) + throw new Error("Expected a valid independent package name."); + const slug = path + .basename(workingDirectory) + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, ""); + const destination = path.join( + workspace, + ".github", + "release-configs", + `${slug}.yml`, + ); + // A caller's release configuration always takes precedence. + if (fs.existsSync(destination)) return; + const source = fs.readFileSync( + path.resolve(workspace, ".github", configurationPath), + "utf8", + ); + const configuration = source + .replaceAll( + `${slug}-$RESOLVED_VERSION`, + `${packageName}@$RESOLVED_VERSION`, + ) + .replace(/^tag-prefix:.*$/m, `tag-prefix: "${packageName}@"`) + .replace( + /^tag-template:.*$/m, + `tag-template: "${packageName}@$RESOLVED_VERSION"`, + ); + await io.mkdirP(path.dirname(destination)); + fs.writeFileSync(destination, configuration); + - uses: hoverkraft-tech/ci-github-publish/actions/release/create@f06e0e72a76bfca57c34d08d03e5c1914275fd4c # 2.0.0 + with: + release-config: ${{ inputs.release-config }} + tag: ${{ inputs.tag }} + target-sha: ${{ github.sha }} + working-directory: ${{ inputs.working-directory != '.' && inputs.working-directory || '' }} + publish: "false" + changelog-summary: ${{ inputs.changelog-summary }} diff --git a/actions/release/finalize/README.md b/actions/release/finalize/README.md index 31281a9e..b39e35ee 100644 --- a/actions/release/finalize/README.md +++ b/actions/release/finalize/README.md @@ -23,18 +23,18 @@ ## Overview -Verify a published npm tarball against the named release asset, then stage it only if missing and publish the draft with HoverKraft release/update using GITHUB_TOKEN. +Verify published npm tarballs against release assets, then stage only missing files and publish the draft with HoverKraft release/update using GITHUB_TOKEN. ## Inputs -| **Input** | **Description** | **Required** | **Default** | -| -------------------------- | ---------------------------------------------------------------------------------------------------------- | ------------ | ----------- | -| **`tag`** | Committed release tag. | **true** | - | -| **`package-tarball-path`** | Local tarball path returned by package/publish, including the original npm bytes restored during recovery. | **true** | - | -| **`asset-name`** | Plain .tgz filename for the GitHub release asset. | **true** | - | +| **Input** | **Description** | **Required** | **Default** | +| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | ----------- | +| **`tag`** | Committed release tag. | **true** | - | +| **`package-tarball-path`** | Local tarball path returned by package/publish, or a glob matching downloaded workspace tarballs. Use the original npm bytes restored during recovery. | **true** | - | +| **`asset-name`** | Plain .tgz filename for a single tarball. Leave empty to preserve matched tarball filenames. | **false** | - | @@ -75,18 +75,17 @@ This documentation was automatically generated by [CI Dokumentor](https://github ## Usage ```yaml -- uses: hoverkraft-tech/ci-github-nodejs/actions/release/finalize@97f64bc080b78daca8fb3ece49de448aaac83429 # 1.1.0 +- uses: hoverkraft-tech/ci-github-nodejs/actions/release/finalize@ with: # Committed release tag. # This input is required. tag: "" - # Local tarball path returned by package/publish, including the original npm bytes restored during recovery. + # Local tarball path returned by package/publish, or a glob matching downloaded workspace tarballs. Use the original npm bytes restored during recovery. # This input is required. package-tarball-path: "" - # Plain .tgz filename for the GitHub release asset. - # This input is required. + # Plain .tgz filename for a single tarball. Leave empty to preserve matched tarball filenames. asset-name: "" ``` diff --git a/actions/release/finalize/action.yml b/actions/release/finalize/action.yml index 64d5dfbf..12c183f6 100644 --- a/actions/release/finalize/action.yml +++ b/actions/release/finalize/action.yml @@ -1,5 +1,5 @@ name: Finalize Node.js release -description: Verify a published npm tarball against the named release asset, then stage it only if missing and publish the draft with HoverKraft release/update using GITHUB_TOKEN. +description: Verify published npm tarballs against release assets, then stage only missing files and publish the draft with HoverKraft release/update using GITHUB_TOKEN. author: hoverkraft branding: icon: tag @@ -9,11 +9,11 @@ inputs: description: Committed release tag. required: true package-tarball-path: - description: Local tarball path returned by package/publish, including the original npm bytes restored during recovery. + description: Local tarball path returned by package/publish, or a glob matching downloaded workspace tarballs. Use the original npm bytes restored during recovery. required: true asset-name: - description: Plain .tgz filename for the GitHub release asset. - required: true + description: Plain .tgz filename for a single tarball. Leave empty to preserve matched tarball filenames. + default: "" runs: using: composite steps: @@ -29,7 +29,7 @@ runs: script: | const releaseAssets = require(`${process.env.ACTION_PATH}/index.js`); const { release, pendingDirectory, hasAssets } = await releaseAssets.prepareAssets({ - github, repo: context.repo, tag: process.env.RELEASE_TAG, + github, glob, repo: context.repo, tag: process.env.RELEASE_TAG, tarballPath: process.env.PACKAGE_TARBALL_PATH, assetName: process.env.ASSET_NAME, tempDirectory: process.env.RUNNER_TEMP, }); @@ -44,7 +44,7 @@ runs: name: release-pending-assets-${{ steps.assets.outputs.release-id }}-${{ github.run_id }}-${{ github.run_attempt }} path: ${{ steps.assets.outputs.pending-directory }}/*.tgz if-no-files-found: error - - uses: hoverkraft-tech/ci-github-publish/actions/release/update@ea6682de88070c254dac619a6623a242a6d57cd3 # 1.0.3 + - uses: hoverkraft-tech/ci-github-publish/actions/release/update@f06e0e72a76bfca57c34d08d03e5c1914275fd4c # 2.0.0 if: steps.assets.outputs.published != 'true' with: tag: ${{ inputs.tag }} diff --git a/actions/release/finalize/coverage/.gitignore b/actions/release/finalize/coverage/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/actions/release/finalize/coverage/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/actions/release/finalize/index.js b/actions/release/finalize/index.js index 65a3e0bd..a1a8ff76 100644 --- a/actions/release/finalize/index.js +++ b/actions/release/finalize/index.js @@ -21,19 +21,41 @@ async function findRelease(github, repo, tag) { async function prepareAssets({ github, + glob, repo, tag, tarballPath, assetName, tempDirectory, }) { - if ( - typeof assetName !== "string" || - !/^[A-Za-z0-9][A-Za-z0-9._-]*\.tgz$/.test(assetName) - ) - throw new Error("asset-name must be a plain .tgz filename."); - if (!fs.lstatSync(tarballPath).isFile()) - throw new Error("package-tarball-path must point to a regular file."); + let files; + if (assetName) { + files = [{ file: tarballPath, name: assetName }]; + } else { + const globber = await glob.create(tarballPath, { + followSymbolicLinks: false, + matchDirectories: false, + implicitDescendants: false, + }); + files = (await globber.glob()) + .filter((file) => fs.lstatSync(file).isFile()) + .map((file) => ({ file, name: path.basename(file) })); + } + if (!files.length) + throw new Error("No package tarballs available to attach."); + const names = new Set(); + for (const { file, name } of files) { + if ( + typeof name !== "string" || + !/^[A-Za-z0-9][A-Za-z0-9._-]*\.tgz$/.test(name) + ) + throw new Error("asset-name must be a plain .tgz filename."); + if (!fs.lstatSync(file).isFile()) + throw new Error("package-tarball-path must point to a regular file."); + if (names.has(name)) + throw new Error(`Duplicate release asset name ${name}.`); + names.add(name); + } const release = await findRelease(github, repo, tag); if (!release) throw new Error(`No release found for ${tag}.`); const assets = await github.paginate(github.rest.repos.listReleaseAssets, { @@ -41,24 +63,29 @@ async function prepareAssets({ release_id: release.id, per_page: 100, }); - const data = fs.readFileSync(tarballPath); - const digest = `sha256:${createHash("sha256").update(data).digest("hex")}`; - const existing = assets.find((asset) => asset.name === assetName); - if (existing) { - if (existing.digest !== digest) - throw new Error( - `Release asset ${assetName} has a different or unavailable digest.`, - ); - return { release, pendingDirectory: "", hasAssets: false }; + const missing = []; + for (const { file, name } of files) { + const data = fs.readFileSync(file); + const digest = `sha256:${createHash("sha256").update(data).digest("hex")}`; + const existing = assets.find((asset) => asset.name === name); + if (existing) { + if (existing.digest !== digest) + throw new Error( + `Release asset ${name} has a different or unavailable digest.`, + ); + continue; + } + if (!release.draft) + throw new Error(`Published release is missing expected asset ${name}.`); + missing.push({ name, data }); } - if (!release.draft) - throw new Error( - `Published release is missing expected asset ${assetName}.`, - ); + if (!missing.length) + return { release, pendingDirectory: "", hasAssets: false }; const pendingDirectory = fs.mkdtempSync( path.join(tempDirectory, "release-pending-"), ); - fs.writeFileSync(path.join(pendingDirectory, assetName), data); + for (const { name, data } of missing) + fs.writeFileSync(path.join(pendingDirectory, name), data); return { release, pendingDirectory, hasAssets: true }; } diff --git a/actions/release/finalize/index.test.js b/actions/release/finalize/index.test.js index 39228193..30b71883 100644 --- a/actions/release/finalize/index.test.js +++ b/actions/release/finalize/index.test.js @@ -1,4 +1,5 @@ const assert = require("node:assert/strict"); +const glob = require("@actions/glob"); const { test } = require("node:test"); const { createHash } = require("node:crypto"); const fs = require("node:fs"); @@ -32,6 +33,7 @@ function assetsFixture(t) { data, args: { github, + glob, repo: { owner: "example", repo: "project" }, tag: "v1.2.0", tarballPath, @@ -92,7 +94,6 @@ test("attaches only the requested file even when other tarballs are present", as test("rejects asset names that could escape staging or are not tarball filenames", async (t) => { const f = assetsFixture(t); for (const assetName of [ - "", "../outside.tgz", "/outside.tgz", "dir\\outside.tgz", @@ -145,3 +146,56 @@ test("recovers draft assets through release listing and propagates lookup failur }; await assert.rejects(releaseAssets.prepareAssets(f.args), /Forbidden/); }); + +test("workspace recovery stages only missing tarballs from the explicit glob", async (t) => { + const f = assetsFixture(t); + const root = f.args.tempDirectory; + fs.renameSync(f.args.tarballPath, path.join(root, f.args.assetName)); + f.assets.push({ + name: f.args.assetName, + digest: `sha256:${createHash("sha256").update(f.data).digest("hex")}`, + }); + const missingName = "example-cli-1.2.0.tgz"; + fs.writeFileSync(path.join(root, missingName), "second package"); + const nested = path.join(root, "previous-attempt"); + fs.mkdirSync(nested); + fs.writeFileSync(path.join(nested, "ignored.tgz"), "previous attempt"); + fs.mkdirSync(path.join(root, "directory.tgz")); + fs.symlinkSync(path.join(nested, "ignored.tgz"), path.join(root, "link.tgz")); + const args = { + ...f.args, + tarballPath: path.join(root, "*.tgz"), + assetName: "", + }; + const result = await releaseAssets.prepareAssets(args); + assert.deepEqual(fs.readdirSync(result.pendingDirectory), [missingName]); + assert.equal( + fs.readFileSync(path.join(result.pendingDirectory, missingName), "utf8"), + "second package", + ); + await assert.rejects( + releaseAssets.prepareAssets({ + ...args, + tarballPath: path.join(root, "absent-*.tgz"), + }), + /No package tarballs/, + ); +}); + +test("rejects workspace tarballs with duplicate asset filenames", async (t) => { + const f = assetsFixture(t); + const nested = path.join(f.args.tempDirectory, "nested"); + fs.mkdirSync(nested); + fs.writeFileSync( + path.join(nested, path.basename(f.args.tarballPath)), + "other package", + ); + await assert.rejects( + releaseAssets.prepareAssets({ + ...f.args, + tarballPath: path.join(f.args.tempDirectory, "**/*.tgz"), + assetName: "", + }), + /Duplicate release asset name/, + ); +}); diff --git a/actions/release/finalize/package-lock.json b/actions/release/finalize/package-lock.json index c6ee39bb..471f9154 100644 --- a/actions/release/finalize/package-lock.json +++ b/actions/release/finalize/package-lock.json @@ -4,7 +4,131 @@ "requires": true, "packages": { "": { - "name": "release-finalize-tests" + "name": "release-finalize-tests", + "devDependencies": { + "@actions/glob": "^0.4.0" + } + }, + "node_modules/@actions/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/glob": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.4.0.tgz", + "integrity": "sha512-+eKIGFhsFa4EBwaf/GMyzCdWrXWymGXfFmZU3FHQvYS8mPcHtTtZONbkcqqUMzw9mJ/pImEBFET1JNifhqGsAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@actions/core": "^1.9.1", + "minimatch": "^3.0.4" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } } } } diff --git a/actions/release/finalize/package.json b/actions/release/finalize/package.json index fcf7eb4a..01842904 100644 --- a/actions/release/finalize/package.json +++ b/actions/release/finalize/package.json @@ -3,6 +3,10 @@ "private": true, "scripts": { "test": "node --test", - "test:ci": "node --test" + "test:watch": "node --test --watch", + "test:ci": "node --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=coverage/lcov.info --test" + }, + "devDependencies": { + "@actions/glob": "^0.4.0" } } diff --git a/actions/release/prepare/README.md b/actions/release/prepare/README.md index 69b297fd..11f90934 100644 --- a/actions/release/prepare/README.md +++ b/actions/release/prepare/README.md @@ -23,17 +23,23 @@ ## Overview -Apply the HoverKraft Conventional Commit plan to one public npm package and dispatch publication from the committed tag. +Prepare Conventional Commit versions with HoverKraft and npm or Lerna, then dispatch publication from the committed tag. ## Inputs -| **Input** | **Description** | **Required** | **Default** | -| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------ | --------------------- | -| **`working-directory`** | Public npm project directory containing its package.json and lockfile. | **false** | `.` | -| **`github-token`** | Token with contents and actions write and pull-requests read permissions. Its identity must be allowed to push release commits and tags. | **false** | `${{ github.token }}` | +| **Input** | **Description** | **Required** | **Default** | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | --------------------- | +| **`release-config`** | Optional partial release configuration as a YAML or JSON mapping, forwarded to ci-github-publish/actions/release/plan. | **false** | - | +| | See for supported settings and override behavior. | | | +| | Pass the same configuration to the release workflow. Independent Lerna retains native version planning. | | | +| **`versioning`** | Versioning tool, npm for one public package or lerna for fixed or independent workspaces. | **false** | `npm` | +| **`working-directory`** | Project or workspace root containing the lockfile and package manager configuration. | **false** | `.` | +| **`package-directory`** | Public npm package directory relative to working-directory. Leave as . for a single root package or Lerna workspaces. | **false** | `.` | +| **`force`** | Force a new version on manual default-branch runs, including unchanged public packages. Uses a patch bump when no version increase is planned. Ignored for schedules and tag recovery. | **false** | `false` | +| **`github-token`** | Token with contents and actions write and pull-requests read permissions. Its identity must be allowed to push release commits and tags. | **false** | `${{ github.token }}` | @@ -45,7 +51,7 @@ Apply the HoverKraft Conventional Commit plan to one public npm package and disp | **Output** | **Description** | | -------------- | ------------------------------------------------------------------------------------------------------------ | | **`status`** | One of no-changes, dispatched, ready or released. Only ready should start building, followed by publication. | -| **`packages`** | JSON array containing the public npm package at the committed release version. | +| **`packages`** | JSON array of public packages at the committed release version, with name, version, tag, directory and slug. | @@ -82,12 +88,29 @@ This documentation was automatically generated by [CI Dokumentor](https://github ## Usage ```yaml -- uses: hoverkraft-tech/ci-github-nodejs/actions/release/prepare@97f64bc080b78daca8fb3ece49de448aaac83429 # 1.1.0 +- uses: hoverkraft-tech/ci-github-nodejs/actions/release/prepare@ with: - # Public npm project directory containing its package.json and lockfile. + # Optional partial release configuration as a YAML or JSON mapping, forwarded to ci-github-publish/actions/release/plan. + # See https://github.com/hoverkraft-tech/ci-github-publish/tree/main/actions/release/plan for supported settings and override behavior. + # Pass the same configuration to the release workflow. Independent Lerna retains native version planning. + release-config: "" + + # Versioning tool, npm for one public package or lerna for fixed or independent workspaces. + # Default: `npm` + versioning: npm + + # Project or workspace root containing the lockfile and package manager configuration. # Default: `.` working-directory: . + # Public npm package directory relative to working-directory. Leave as . for a single root package or Lerna workspaces. + # Default: `.` + package-directory: . + + # Force a new version on manual default-branch runs, including unchanged public packages. Uses a patch bump when no version increase is planned. Ignored for schedules and tag recovery. + # Default: `false` + force: "false" + # Token with contents and actions write and pull-requests read permissions. Its identity must be allowed to push release commits and tags. # Default: `${{ github.token }}` github-token: ${{ github.token }} diff --git a/actions/release/prepare/ReleaseProject.js b/actions/release/prepare/ReleaseProject.js new file mode 100644 index 00000000..e970a086 --- /dev/null +++ b/actions/release/prepare/ReleaseProject.js @@ -0,0 +1,347 @@ +const fs = require("node:fs"); +const path = require("node:path"); +const { versionFromTag, validateVersion } = require("./index.js"); + +class ReleaseProject { + constructor({ + workspace, + workingDirectory = ".", + packageDirectory = ".", + versioning = "npm", + force = false, + context, + exec, + }) { + this.exec = exec; + this.workspace = fs.realpathSync(workspace); + this.directory = fs.realpathSync(path.resolve(workspace, workingDirectory)); + this.relativeDirectory = path.relative(this.workspace, this.directory); + if ( + this.relativeDirectory.startsWith("..") || + path.isAbsolute(this.relativeDirectory) + ) { + throw new Error( + "Release working directory must be inside the repository.", + ); + } + if (!["lerna", "npm"].includes(versioning)) { + throw new Error("versioning must be npm or lerna."); + } + this.versioning = versioning; + this.packageDirectory = fs.realpathSync( + path.resolve(this.directory, packageDirectory), + ); + this.relativePackageDirectory = path.relative( + this.directory, + this.packageDirectory, + ); + if ( + this.relativePackageDirectory.startsWith("..") || + path.isAbsolute(this.relativePackageDirectory) + ) + throw new Error( + "Package directory must be inside the release working directory.", + ); + if (versioning === "lerna" && this.relativePackageDirectory) + throw new Error( + "package-directory selects one npm package; Lerna selects its public workspaces.", + ); + this.context = context; + this.force = + force && + context.eventName === "workflow_dispatch" && + context.ref === `refs/heads/${context.payload.repository.default_branch}`; + const manifest = this.readJson( + path.join(this.relativePackageDirectory, "package.json"), + ); + if (versioning === "npm" && (manifest.workspaces || manifest.private)) { + throw new Error( + "npm mode requires one public package; select package-directory or use lerna for workspaces.", + ); + } + if ( + versioning === "npm" && + !this.relativePackageDirectory && + (!["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml"].some( + (file) => fs.existsSync(path.join(this.directory, file)), + ) || + (manifest.packageManager && + !/^(npm|pnpm)@/.test(manifest.packageManager))) + ) + throw new Error( + "A standalone release package requires its own npm or pnpm lockfile.", + ); + this.version(); + } + + get independent() { + return ( + this.versioning === "lerna" && + this.readJson("lerna.json").version === "independent" + ); + } + + readJson(file) { + return JSON.parse(fs.readFileSync(path.join(this.directory, file), "utf8")); + } + + async run(command, args) { + const { stdout, stderr, exitCode } = await this.exec.getExecOutput( + command, + args, + { + cwd: this.directory, + silent: true, + ignoreReturnCode: true, + env: { ...process.env, CI: "true" }, + }, + ); + if (exitCode !== 0) + throw new Error( + stderr.trim() || `${command} failed with exit code ${exitCode}.`, + ); + return stdout.trim(); + } + + git(...args) { + return this.run("git", args); + } + + version() { + const { version } = this.readJson( + this.versioning === "lerna" + ? "lerna.json" + : path.join(this.relativePackageDirectory, "package.json"), + ); + if (this.independent) return version; + if ( + typeof version !== "string" || + !/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(version) + ) { + throw new Error( + "Releases require a stable version; prereleases are not supported.", + ); + } + return version; + } + + async listPackages() { + return ( + this.versioning === "lerna" + ? JSON.parse( + await this.run("npm", [ + "exec", + "--no", + "--", + "lerna", + "list", + "--all", + "--json", + "--loglevel", + "silent", + ]), + ) + : [ + { + ...this.readJson( + path.join(this.relativePackageDirectory, "package.json"), + ), + location: this.packageDirectory, + }, + ] + ).filter((item) => !item.private); + } + + async packages(tag) { + const version = this.version(); + if (!this.independent && this.versionFromTag(tag) !== version) + throw new Error("Release tag must match the committed package version."); + const tags = ( + await this.git( + "tag", + "--points-at", + (await this.tagCommit(tag)) || this.context.sha, + ) + ).split("\n"); + const packages = (await this.listPackages()) + .filter((item) => + this.independent + ? tags.includes(`${item.name}@${item.version}`) + : item.version === version, + ) + .map((item) => { + if ( + typeof item.name !== "string" || + !/^(?:@[a-z0-9-]+\/)?[a-z0-9][a-z0-9._-]*$/.test(item.name) + ) { + throw new Error("Release packages must have valid npm names."); + } + const directory = path.relative( + this.directory, + fs.realpathSync(item.location), + ); + if (directory.startsWith("..") || path.isAbsolute(directory)) + throw new Error("Package is outside the release working directory."); + return { + name: item.name, + version: item.version, + tag: this.independent ? `${item.name}@${item.version}` : tag, + directory: directory || ".", + slug: item.name.replace(/^@/, "").replace("/", "-"), + }; + }); + if ( + !packages.length || + new Set(packages.map((item) => item.slug)).size !== packages.length + ) { + throw new Error( + "Expected uniquely named public packages at the release version.", + ); + } + if (!packages.some((item) => item.tag === tag)) + throw new Error( + "Release tag must identify a public package at the committed version.", + ); + return packages; + } + + async tagCommit(tag) { + try { + return await this.git("rev-parse", "--verify", `${tag}^{commit}`); + } catch { + return undefined; + } + } + + versionFromTag(tag) { + return versionFromTag(tag, this.directory, this.relativeDirectory); + } + + async currentTag() { + if (this.context.ref.startsWith("refs/tags/")) + return this.context.ref.slice("refs/tags/".length); + if ( + this.context.ref !== + `refs/heads/${this.context.payload.repository.default_branch}` + ) { + throw new Error( + "Run releases from the default branch or an existing release tag.", + ); + } + if (this.force) return undefined; + const tags = (await this.git("tag", "--points-at", this.context.sha)) + .split("\n") + .filter((tag) => { + try { + return this.independent + ? /^(?:@[a-z0-9-]+\/)?[a-z0-9][a-z0-9._-]*@\d+\.\d+\.\d+$/.test(tag) + : this.versionFromTag(tag) === this.version(); + } catch { + return false; + } + }); + if (tags.length > 1 && !this.independent) + throw new Error( + "Several release tags match this commit; resume the intended tag explicitly.", + ); + return tags[0]; + } + + async prepareVersion({ tag }) { + let version = this.versionFromTag(tag); + const current = this.version(); + validateVersion(version, current); + if (this.force && version === current) { + const [major, minor, patch] = current.split("."); + version = `${major}.${minor}.${BigInt(patch) + 1n}`; + tag = `${tag.slice(0, -current.length)}${version}`; + } + if (await this.tagCommit(tag)) + throw new Error(`Existing tag ${tag} must not be overwritten.`); + if (this.versioning === "lerna" && version === current) { + return { tag, tags: [tag] }; + } + if (this.versioning === "lerna") { + await this.versionLerna([ + version, + "--tag-version-prefix", + tag.slice(0, -version.length), + ...(this.force ? ["--no-private"] : []), + ]); + if (this.version() === current) return { status: "no-changes" }; + } else { + const args = [ + "version", + version, + "--allow-same-version", + "--no-git-tag-version", + "--ignore-scripts", + ]; + if (this.relativePackageDirectory) + args.push("--workspace", this.relativePackageDirectory); + const pnpm = fs.existsSync(path.join(this.directory, "pnpm-lock.yaml")); + if (pnpm) args.push("--no-workspaces-update"); + await this.run("npm", args); + if (pnpm) + await this.run("pnpm", [ + "install", + "--lockfile-only", + "--no-frozen-lockfile", + "--ignore-scripts", + ]); + } + if (this.version() !== version) + throw new Error("Version preparation did not apply the planned version."); + return { tag, tags: [tag] }; + } + + async versionLerna(args = []) { + await this.run("npm", [ + "exec", + "--no", + "--", + "lerna", + "version", + ...args, + ...(this.force ? ["--force-publish"] : []), + "--conventional-commits", + "--no-changelog", + "--no-git-tag-version", + "--no-push", + "--yes", + "--ignore-scripts", + ]); + } + + async prepareIndependent() { + const previous = new Map( + (await this.listPackages()).map((item) => [item.name, item.version]), + ); + if ( + [...previous.values()].some( + (version) => + !/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(version), + ) + ) + throw new Error("Independent releases require stable package versions."); + await this.versionLerna(["--no-private"]); + const changed = (await this.listPackages()).filter( + (item) => item.version !== previous.get(item.name), + ); + if (!changed.length) return { status: "no-changes" }; + const tags = []; + for (const item of changed) { + if (!/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(item.version)) + throw new Error( + "Independent releases require stable package versions.", + ); + const tag = `${item.name}@${item.version}`; + if (await this.tagCommit(tag)) + throw new Error(`Existing tag ${tag} must not be overwritten.`); + tags.push(tag); + } + return { tag: tags[0], tags }; + } +} + +module.exports = { ReleaseProject }; diff --git a/actions/release/prepare/ReleaseProject.test.js b/actions/release/prepare/ReleaseProject.test.js new file mode 100644 index 00000000..364f5a92 --- /dev/null +++ b/actions/release/prepare/ReleaseProject.test.js @@ -0,0 +1,571 @@ +const assert = require("node:assert/strict"); +const exec = require("@actions/exec"); +const { execFileSync } = require("node:child_process"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { test } = require("node:test"); +const { ReleaseProject } = require("./ReleaseProject.js"); + +Object.assign(process.env, { + CI: "true", + NX_DAEMON: "false", + NX_ISOLATE_PLUGINS: "false", + GIT_CONFIG_GLOBAL: "/dev/null", + GIT_CONFIG_NOSYSTEM: "1", + npm_config_offline: "true", + npm_config_audit: "false", + npm_config_fund: "false", +}); + +function fixture(t, versioning = "npm", initialTag = true, force = false) { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "nodejs-release-")); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + const directory = path.join(root, "project"); + const remote = path.join(root, "remote.git"); + fs.mkdirSync(directory); + const run = (command, args, cwd = directory) => + execFileSync(command, args, { + cwd, + encoding: "utf8", + stdio: "pipe", + }).trim(); + const git = (...args) => run("git", args); + const write = (file, content) => { + fs.mkdirSync(path.dirname(path.join(directory, file)), { recursive: true }); + fs.writeFileSync(path.join(directory, file), content); + }; + const json = (file, value) => + write(file, `${JSON.stringify(value, null, 2)}\n`); + const commit = (message) => { + git("add", "."); + git("commit", "-m", message); + return git("rev-parse", "HEAD"); + }; + git("init", "--bare", remote); + git("init", "-b", "main"); + git("config", "user.name", "Release test"); + git("config", "user.email", "test@example.invalid"); + git("remote", "add", "origin", remote); + write(".gitignore", "node_modules/\n.nx/\ndist/\n*.tgz\n"); + if (versioning === "lerna") { + json("package.json", { + name: "workspaces-test", + private: true, + workspaces: ["packages/*"], + }); + json("lerna.json", { version: "1.0.0", npmClient: "npm" }); + json("packages/core/package.json", { + name: "@example/core", + version: "1.0.0", + }); + json("packages/ui/package.json", { + name: "@example/ui", + version: "1.0.0", + dependencies: { "@example/core": "^1.0.0" }, + }); + json("packages/private/package.json", { + name: "@example/private", + version: "1.0.0", + private: true, + }); + write("packages/core/index.js", "module.exports = 1;\n"); + run("npm", ["install", "--package-lock-only", "--ignore-scripts"]); + fs.symlinkSync( + path.join(__dirname, "node_modules"), + path.join(directory, "node_modules"), + "dir", + ); + } else { + json("package.json", { + name: "@example/cli", + version: "1.0.0", + type: "module", + main: "dist/index.js", + bin: { "example-cli": "bin/example-cli" }, + files: ["dist", "bin"], + scripts: { build: "node build.js" }, + }); + write( + "bin/example-cli", + "#!/usr/bin/env node\nimport { version } from '../dist/index.js';\nconsole.log(version);\n", + ); + fs.chmodSync(path.join(directory, "bin/example-cli"), 0o755); + write( + "build.js", + "import fs from 'node:fs';\nconst { version } = JSON.parse(fs.readFileSync('package.json'));\nfs.mkdirSync('dist', { recursive: true });\nfs.writeFileSync('dist/index.js', 'export const version = ' + JSON.stringify(version) + ';');\n", + ); + run("npm", ["install", "--package-lock-only", "--ignore-scripts"]); + } + commit("chore: initial"); + if (initialTag) git("tag", "-a", "v1.0.0", "-m", "v1.0.0"); + git("push", "origin", "main", "--tags"); + const context = { + eventName: "workflow_dispatch", + ref: "refs/heads/main", + sha: git("rev-parse", "HEAD"), + payload: { repository: { default_branch: "main" } }, + }; + const project = new ReleaseProject({ + exec, + workspace: directory, + versioning, + force, + context, + }); + const change = (file, content, message = "feat: update package") => { + write(file, content); + context.sha = commit(message); + }; + return { + root, + directory, + remote, + run, + git, + write, + json, + commit, + context, + project, + change, + }; +} + +test("npm applies an exact planned version to the manifest and lockfile without Git writes or changelogs", async (t) => { + const f = fixture(t); + const history = "# Existing history\r\n"; + f.write("CHANGELOG.md", history); + const manifest = f.project.readJson("package.json"); + manifest.scripts.preversion = "exit 91"; + manifest.scripts.postversion = "exit 91"; + f.json("package.json", manifest); + f.change("feature.txt", "feature"); + const head = f.git("rev-parse", "HEAD"); + assert.deepEqual(await f.project.prepareVersion({ tag: "v3.2.1" }), { + tag: "v3.2.1", + tags: ["v3.2.1"], + }); + assert.equal(f.project.version(), "3.2.1"); + assert.equal( + f.project.readJson("package-lock.json").packages[""].version, + "3.2.1", + ); + assert.equal(f.git("rev-parse", "HEAD"), head); + assert.equal(await f.project.tagCommit("v3.2.1"), undefined); + assert.equal( + fs.readFileSync(path.join(f.directory, "CHANGELOG.md"), "utf8"), + history, + ); +}); + +test("planned version validation preserves source and existing tags", async (t) => { + const f = fixture(t); + f.git("tag", "v1.1.0"); + f.change("feature.txt", "feature"); + for (const tag of ["v0.9.0", "v1.0.0", "v1.0.1-rc.1", "latest", "v1.1.0"]) { + await assert.rejects( + () => f.project.prepareVersion({ tag }), + /older|stable version tag|Existing tag/, + ); + assert.equal(f.project.version(), "1.0.0"); + assert.equal(f.git("status", "--porcelain"), ""); + } +}); + +test("forced npm releases increment an unchanged version and preserve the tag prefix", async (t) => { + for (const prefix of ["", "v", "project-"]) { + const f = fixture(t, "npm", true, true); + if (prefix === "project-") { + f.project = new ReleaseProject({ + exec, + workspace: f.root, + workingDirectory: "project", + context: f.context, + force: true, + }); + } + const head = f.context.sha; + assert.equal(await f.project.currentTag(), undefined); + assert.deepEqual( + await f.project.prepareVersion({ tag: `${prefix}1.0.0` }), + { + tag: `${prefix}1.0.1`, + tags: [`${prefix}1.0.1`], + }, + ); + assert.equal(f.project.version(), "1.0.1"); + assert.equal( + f.project.readJson("package-lock.json").packages[""].version, + "1.0.1", + ); + assert.equal(f.git("rev-parse", "HEAD"), head); + assert.equal(await f.project.tagCommit("v1.0.0"), head); + assert.equal(await f.project.tagCommit(`${prefix}1.0.1`), undefined); + } +}); + +test("forced preparation preserves higher plans and rejects older versions and tag collisions", async (t) => { + const f = fixture(t, "npm", true, true); + f.git("tag", "v1.0.1"); + f.git("tag", "v1.1.0"); + for (const tag of ["v0.9.0", "v1.0.0", "v1.0.1", "v1.1.0"]) { + await assert.rejects( + () => f.project.prepareVersion({ tag }), + /older|Existing tag/, + ); + assert.equal(f.project.version(), "1.0.0"); + assert.equal(f.git("status", "--porcelain"), ""); + } + await f.project.prepareVersion({ tag: "v2.0.0" }); + assert.equal(f.project.version(), "2.0.0"); +}); + +test("force only applies to manual default-branch preparation and preserves tag recovery", async (t) => { + const f = fixture(t); + for (const eventName of ["schedule", "push", "workflow_dispatch"]) { + for (const ref of [ + "refs/heads/main", + "refs/heads/feature", + "refs/tags/v1.0.0", + ]) { + const project = new ReleaseProject({ + exec, + workspace: f.directory, + context: { ...f.context, eventName, ref }, + force: true, + }); + const forced = + eventName === "workflow_dispatch" && ref === "refs/heads/main"; + assert.equal(project.force, forced); + if (ref === "refs/heads/feature") { + await assert.rejects(() => project.currentTag(), /default branch/); + } else { + assert.equal(await project.currentTag(), forced ? undefined : "v1.0.0"); + if (ref.startsWith("refs/tags/")) { + assert.equal((await project.packages("v1.0.0"))[0].version, "1.0.0"); + } + } + } + } + assert.equal(await f.project.currentTag(), "v1.0.0"); + assert.equal(f.git("status", "--porcelain"), ""); +}); + +test("already-versioned npm and fixed Lerna projects reuse their source without new commits", async (t) => { + for (const versioning of ["npm", "lerna"]) { + const f = fixture(t, versioning, false); + const head = f.git("rev-parse", "HEAD"); + assert.deepEqual(await f.project.prepareVersion({ tag: "v1.0.0" }), { + tag: "v1.0.0", + tags: ["v1.0.0"], + }); + assert.equal(f.project.version(), "1.0.0"); + assert.equal(f.git("rev-parse", "HEAD"), head); + assert.equal(await f.project.tagCommit("v1.0.0"), undefined); + } +}); + +test("selected npm workspace updates its lockfile without versioning private siblings", async (t) => { + const f = fixture(t, "lerna"); + const project = new ReleaseProject({ + exec, + workspace: f.directory, + packageDirectory: "packages/core", + context: f.context, + }); + await project.prepareVersion({ tag: "v1.1.0" }); + assert.equal(project.version(), "1.1.0"); + assert.equal( + project.readJson("package-lock.json").packages["packages/core"].version, + "1.1.0", + ); + assert.equal( + project.readJson("packages/private/package.json").version, + "1.0.0", + ); + assert.equal(project.readJson("lerna.json").version, "1.0.0"); + assert.equal( + (await project.packages("v1.1.0"))[0].directory, + "packages/core", + ); +}); + +test("fixed Lerna applies the plan and propagates dependencies without generating changelogs", async (t) => { + const f = fixture(t, "lerna"); + const history = "# Existing history\n"; + f.write("packages/core/CHANGELOG.md", history); + f.change("packages/core/index.js", "module.exports = 2;\n"); + const result = await f.project.prepareVersion({ tag: "v1.1.0" }); + assert.deepEqual(result.tags, ["v1.1.0"]); + assert.equal(f.project.version(), "1.1.0"); + assert.equal( + f.project.readJson("packages/ui/package.json").dependencies[ + "@example/core" + ], + "^1.1.0", + ); + assert.equal( + f.project.readJson("package-lock.json").packages["packages/core"].version, + "1.1.0", + ); + assert.deepEqual( + (await f.project.packages(result.tag)).map((item) => item.name).sort(), + ["@example/core", "@example/ui"], + ); + assert.equal(f.git("rev-parse", "HEAD"), f.context.sha); + assert.equal(await f.project.tagCommit(result.tag), undefined); + assert.equal( + fs.readFileSync( + path.join(f.directory, "packages/core/CHANGELOG.md"), + "utf8", + ), + history, + ); + assert.equal( + fs.existsSync(path.join(f.directory, "packages/ui/CHANGELOG.md")), + false, + ); +}); + +test("independent Lerna returns changed public package tags and discovers the full committed batch", async (t) => { + const f = fixture(t, "lerna"); + f.json("lerna.json", { version: "independent", npmClient: "npm" }); + f.json("packages/ui/package.json", { + name: "@example/ui", + version: "2.3.0", + dependencies: { "@example/core": "^1.0.0" }, + }); + f.run("npm", ["install", "--package-lock-only", "--ignore-scripts"]); + const history = "# Existing history\r\n"; + f.write("CHANGELOG.md", history); + f.write("packages/core/CHANGELOG.md", history); + f.commit("chore: configure independent packages"); + f.git("tag", "@example/core@1.0.0"); + f.git("tag", "@example/ui@2.3.0"); + f.change( + "packages/core/index.js", + "module.exports = 2;\n", + "feat(core): expose API", + ); + const result = await f.project.prepareIndependent(); + const tags = ["@example/core@1.1.0", "@example/ui@2.3.1"]; + assert.deepEqual(result.tags.sort(), tags); + assert.equal( + f.project.readJson("packages/ui/package.json").dependencies[ + "@example/core" + ], + "^1.1.0", + ); + assert.equal(f.git("rev-parse", "HEAD"), f.context.sha); + for (const tag of tags) + assert.equal(await f.project.tagCommit(tag), undefined); + for (const directory of [".", "packages/core"]) + assert.equal( + fs.readFileSync( + path.join(f.directory, directory, "CHANGELOG.md"), + "utf8", + ), + history, + ); + assert.equal( + fs.existsSync(path.join(f.directory, "packages/ui/CHANGELOG.md")), + false, + ); + f.context.sha = f.commit("chore(release): prepare fixture batch"); + for (const tag of tags) f.git("tag", tag); + for (const tag of tags) { + f.context.ref = `refs/tags/${tag}`; + assert.deepEqual( + (await f.project.packages(tag)).map((item) => item.tag).sort(), + tags, + ); + } +}); + +test("fixed Lerna returns no changes without versioning or tagging unchanged packages", async (t) => { + const f = fixture(t, "lerna"); + f.change("README.md", "Documentation only.\n", "docs: describe setup"); + const source = f.context.sha; + assert.deepEqual(await f.project.prepareVersion({ tag: "v1.0.1" }), { + status: "no-changes", + }); + assert.equal(f.project.version(), "1.0.0"); + assert.equal(f.git("rev-parse", "HEAD"), source); + assert.equal(f.git("status", "--porcelain"), ""); + assert.equal(await f.project.tagCommit("v1.0.1"), undefined); +}); + +test("forced fixed Lerna versions every public package even at the last release commit", async (t) => { + for (const tag of ["v1.0.0", "v1.1.0"]) { + const f = fixture(t, "lerna", true, true); + const expected = tag === "v1.0.0" ? "1.0.1" : "1.1.0"; + const result = await f.project.prepareVersion({ tag }); + assert.equal(f.project.version(), expected); + assert.deepEqual(result.tags, [`v${expected}`]); + assert.deepEqual( + (await f.project.listPackages()).map((item) => item.version), + [expected, expected], + ); + assert.equal( + f.project.readJson("packages/private/package.json").version, + "1.0.0", + ); + assert.equal( + f.project.readJson("packages/ui/package.json").dependencies[ + "@example/core" + ], + `^${expected}`, + ); + assert.equal( + f.project.readJson("package-lock.json").packages["packages/core"].version, + expected, + ); + assert.equal(f.git("rev-parse", "HEAD"), f.context.sha); + assert.equal(await f.project.tagCommit(result.tag), undefined); + } +}); + +test("forced independent Lerna uses Conventional Commits and patch bumps unchanged public packages", async (t) => { + for (const changed of [false, true]) { + const f = fixture(t, "lerna", true, true); + f.json("lerna.json", { version: "independent", npmClient: "npm" }); + f.context.sha = f.commit("chore: configure independent packages"); + f.git("tag", "@example/core@1.0.0"); + f.git("tag", "@example/ui@1.0.0"); + if (changed) + f.change( + "packages/core/index.js", + "module.exports = 2;\n", + "feat(core): expose API", + ); + const result = await f.project.prepareIndependent(); + const coreVersion = changed ? "1.1.0" : "1.0.1"; + assert.deepEqual(result.tags.sort(), [ + `@example/core@${coreVersion}`, + "@example/ui@1.0.1", + ]); + assert.equal( + f.project.readJson("packages/private/package.json").version, + "1.0.0", + ); + assert.equal( + f.project.readJson("packages/ui/package.json").dependencies[ + "@example/core" + ], + `^${coreVersion}`, + ); + assert.equal( + f.project.readJson("package-lock.json").packages["packages/core"].version, + coreVersion, + ); + assert.equal(f.git("rev-parse", "HEAD"), f.context.sha); + for (const tag of result.tags) + assert.equal(await f.project.tagCommit(tag), undefined); + } +}); + +test("independent Lerna leaves packages unchanged for documentation-only commits", async (t) => { + const f = fixture(t, "lerna"); + f.json("lerna.json", { version: "independent", npmClient: "npm" }); + f.commit("chore: configure independent packages"); + f.git("tag", "@example/core@1.0.0"); + f.git("tag", "@example/ui@1.0.0"); + f.change("README.md", "Documentation only.", "docs: describe setup"); + assert.deepEqual(await f.project.prepareIndependent(), { + status: "no-changes", + }); + assert.equal(f.git("status", "--porcelain"), ""); +}); + +test("selected pnpm workspace retains workspace ranges and updates its own lockfile", async (t) => { + const f = fixture(t, "lerna"); + fs.unlinkSync(path.join(f.directory, "package-lock.json")); + f.write("pnpm-workspace.yaml", "packages:\n - packages/*\n"); + f.json("packages/ui/package.json", { + name: "@example/ui", + version: "1.0.0", + dependencies: { "@example/core": "workspace:^" }, + }); + f.run("pnpm", ["install", "--lockfile-only", "--ignore-scripts"]); + f.context.sha = f.commit("feat: configure workspace CLI"); + const project = new ReleaseProject({ + exec, + workspace: f.directory, + packageDirectory: "packages/ui", + context: f.context, + }); + await project.prepareVersion({ tag: "v1.1.0" }); + assert.equal(project.version(), "1.1.0"); + assert.equal( + project.readJson("packages/ui/package.json").dependencies["@example/core"], + "workspace:^", + ); + assert.equal( + fs.existsSync(path.join(f.directory, "package-lock.json")), + false, + ); + assert.equal( + f + .run("pnpm", [ + "install", + "--lockfile-only", + "--frozen-lockfile", + "--ignore-scripts", + ]) + .includes("ERR_PNPM"), + false, + ); +}); + +test("project discovery rejects unsupported directories, layouts, refs and ambiguous tags", async (t) => { + const f = fixture(t); + assert.throws( + () => + new ReleaseProject({ + exec, + workspace: f.directory, + workingDirectory: "..", + }), + /inside/, + ); + assert.throws( + () => + new ReleaseProject({ + exec, + workspace: f.directory, + packageDirectory: "..", + }), + /inside/, + ); + assert.throws( + () => + new ReleaseProject({ + exec, + workspace: f.directory, + versioning: "unknown", + }), + /versioning/, + ); + f.context.ref = "refs/heads/feature"; + await assert.rejects(() => f.project.currentTag(), /default branch/); + f.context.ref = "refs/heads/main"; + f.git("tag", "1.0.0"); + await assert.rejects(() => f.project.currentTag(), /Several release tags/); + f.context.ref = "refs/tags/v1.0.0"; + assert.equal(await f.project.currentTag(), "v1.0.0"); + await assert.rejects( + () => f.project.packages("v9.0.0"), + /committed package version/, + ); + fs.unlinkSync(path.join(f.directory, "package-lock.json")); + assert.throws( + () => new ReleaseProject({ exec, workspace: f.directory }), + /own npm or pnpm lockfile/, + ); + f.json("package.json", { name: "private", private: true, version: "1.0.0" }); + assert.throws( + () => new ReleaseProject({ exec, workspace: f.directory }), + /one public package/, + ); +}); diff --git a/actions/release/prepare/action.yml b/actions/release/prepare/action.yml index 18300174..602c4991 100644 --- a/actions/release/prepare/action.yml +++ b/actions/release/prepare/action.yml @@ -1,23 +1,38 @@ name: Prepare Node.js package release -description: Apply the HoverKraft Conventional Commit plan to one public npm package and dispatch publication from the committed tag. +description: Prepare Conventional Commit versions with HoverKraft and npm or Lerna, then dispatch publication from the committed tag. author: hoverkraft branding: icon: git-commit color: blue inputs: + release-config: + description: | + Optional partial release configuration as a YAML or JSON mapping, forwarded to ci-github-publish/actions/release/plan. + See https://github.com/hoverkraft-tech/ci-github-publish/tree/main/actions/release/plan for supported settings and override behavior. + Pass the same configuration to the release workflow. Independent Lerna retains native version planning. + default: "" + versioning: + description: Versioning tool, npm for one public package or lerna for fixed or independent workspaces. + default: npm working-directory: - description: Public npm project directory containing its package.json and lockfile. + description: Project or workspace root containing the lockfile and package manager configuration. default: . + package-directory: + description: Public npm package directory relative to working-directory. Leave as . for a single root package or Lerna workspaces. + default: . + force: + description: Force a new version on manual default-branch runs, including unchanged public packages. Uses a patch bump when no version increase is planned. Ignored for schedules and tag recovery. + default: "false" github-token: description: Token with contents and actions write and pull-requests read permissions. Its identity must be allowed to push release commits and tags. default: ${{ github.token }} outputs: status: description: One of no-changes, dispatched, ready or released. Only ready should start building, followed by publication. - value: ${{ steps.inspect.outputs.status || (steps.dispatch.outcome == 'success' && 'dispatched') || 'no-changes' }} + value: ${{ steps.existing.outputs.status || (steps.dispatch.outcome == 'success' && 'dispatched') || 'no-changes' }} packages: - description: JSON array containing the public npm package at the committed release version. - value: ${{ steps.inspect.outputs.packages || '[]' }} + description: JSON array of public packages at the committed release version, with name, version, tag, directory and slug. + value: ${{ steps.existing.outputs.packages || '[]' }} runs: using: composite steps: @@ -31,128 +46,125 @@ runs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: ACTION_PATH: ${{ github.action_path }} + VERSIONING: ${{ inputs.versioning }} + PACKAGE_DIRECTORY: ${{ inputs.package-directory }} WORKING_DIRECTORY: ${{ inputs.working-directory }} CALLER_WORKFLOW_REF: ${{ github.workflow_ref }} + FORCE_RELEASE: ${{ inputs.force }} with: github-token: ${{ inputs.github-token }} script: | - const fs = require('node:fs'); - const path = require('node:path'); - const releasePackage = require(`${process.env.ACTION_PATH}/index.js`); - const workspace = fs.realpathSync(process.env.GITHUB_WORKSPACE); - const directory = fs.realpathSync(path.resolve(workspace, process.env.WORKING_DIRECTORY)); - const relativeDirectory = path.relative(workspace, directory); - if (relativeDirectory.startsWith('..') || path.isAbsolute(relativeDirectory)) throw new Error('Release directory must be inside the repository.'); - const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'package.json'), 'utf8')); - if (manifest.private || manifest.workspaces || !['package-lock.json', 'npm-shrinkwrap.json'].some(file => fs.existsSync(path.join(directory, file))) || (manifest.packageManager && !manifest.packageManager.startsWith('npm@')) || ['lerna.json', 'pnpm-workspace.yaml', 'pnpm-lock.yaml', 'yarn.lock'].some(file => fs.existsSync(path.join(directory, file)))) { - throw new Error('Release preparation requires one public npm package with its own lockfile.'); - } - const { name, version } = manifest; - const item = { name, version, directory: '.', slug: typeof name === 'string' ? name.replace(/^@/, '').replace('/', '-') : '' }; - releasePackage.parsePackages(JSON.stringify([item])); - const versionFromTag = tag => releasePackage.versionFromTag(tag, directory, relativeDirectory); - let tag; - if (context.ref.startsWith('refs/tags/')) { - tag = context.ref.slice('refs/tags/'.length); - if (versionFromTag(tag) !== version) throw new Error('Release tag must match the committed package version.'); - core.setOutput('status', 'ready'); - core.setOutput('packages', [{ ...item, tag }]); - } else { - if (context.ref !== `refs/heads/${context.payload.repository.default_branch}`) throw new Error('Run releases from the default branch or an existing release tag.'); - const { stdout } = await exec.getExecOutput('git', ['tag', '--points-at', context.sha], { cwd: directory }); - const tags = stdout.trim().split('\n').filter(candidate => { - try { return versionFromTag(candidate) === version; } catch { return false; } - }); - if (tags.length > 1) throw new Error('Several release tags match this commit; resume the intended tag explicitly.'); - tag = tags[0]; + const { ReleaseProject } = require(`${process.env.ACTION_PATH}/ReleaseProject.js`); + const project = new ReleaseProject({ workspace: process.env.GITHUB_WORKSPACE, workingDirectory: process.env.WORKING_DIRECTORY, packageDirectory: process.env.PACKAGE_DIRECTORY, versioning: process.env.VERSIONING, force: process.env.FORCE_RELEASE === 'true', context, exec }); + core.setOutput('tag', (await project.currentTag()) || ''); + core.setOutput('independent', String(project.independent)); + core.setOutput('force', String(project.force)); + if (!context.ref.startsWith('refs/tags/')) { const prefix = `${context.repo.owner}/${context.repo.repo}/`; const workflow = process.env.CALLER_WORKFLOW_REF?.startsWith(prefix) ? process.env.CALLER_WORKFLOW_REF.slice(prefix.length).split('@')[0] : ''; if (!/^\.github\/workflows\/[^/]+\.ya?ml$/.test(workflow)) throw new Error('Cannot determine the caller workflow for publication dispatch.'); core.setOutput('workflow', workflow); } - if (tag) { - const release = await releasePackage.findRelease(github, context.repo, tag); - if (!context.ref.startsWith('refs/tags/') && release && !release.draft) core.setOutput('status', 'released'); - } - core.setOutput('tag', tag || ''); - core.setOutput('version', version); - name: Plan release with HoverKraft id: plan - if: steps.inspect.outputs.tag == '' - uses: hoverkraft-tech/ci-github-publish/actions/release/plan@ea6682de88070c254dac619a6623a242a6d57cd3 # 1.0.3 + if: steps.inspect.outputs.tag == '' && steps.inspect.outputs.independent != 'true' + uses: hoverkraft-tech/ci-github-publish/actions/release/plan@f06e0e72a76bfca57c34d08d03e5c1914275fd4c # 2.0.0 with: github-token: ${{ inputs.github-token }} target-sha: ${{ github.sha }} + release-config: ${{ inputs.release-config }} working-directory: ${{ inputs.working-directory != '.' && inputs.working-directory || '' }} - name: Restore source checkout for version preparation - if: steps.plan.outputs.has-changes == 'true' + if: steps.plan.outputs.has-changes == 'true' || steps.inspect.outputs.force == 'true' uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 token: ${{ inputs.github-token }} persist-credentials: true - uses: $/actions/setup-node - if: steps.plan.outputs.has-changes == 'true' + if: steps.plan.outputs.has-changes == 'true' || steps.inspect.outputs.force == 'true' || inputs.versioning == 'lerna' with: working-directory: ${{ inputs.working-directory }} ignore-scripts: "true" - - name: Validate planned version - id: version - if: steps.plan.outputs.has-changes == 'true' + - name: Inspect existing package releases + id: existing + if: steps.inspect.outputs.tag != '' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: ACTION_PATH: ${{ github.action_path }} + VERSIONING: ${{ inputs.versioning }} + PACKAGE_DIRECTORY: ${{ inputs.package-directory }} WORKING_DIRECTORY: ${{ inputs.working-directory }} - RELEASE_TAG: ${{ steps.plan.outputs.tag }} - CURRENT_VERSION: ${{ steps.inspect.outputs.version }} + RELEASE_TAG: ${{ steps.inspect.outputs.tag }} with: + github-token: ${{ inputs.github-token }} script: | - const fs = require('node:fs'); - const path = require('node:path'); - const releasePackage = require(`${process.env.ACTION_PATH}/index.js`); - const workspace = fs.realpathSync(process.env.GITHUB_WORKSPACE); - const directory = fs.realpathSync(path.resolve(workspace, process.env.WORKING_DIRECTORY)); - const version = releasePackage.versionFromTag(process.env.RELEASE_TAG, directory, path.relative(workspace, directory)); - releasePackage.validateVersion(version, process.env.CURRENT_VERSION); - const existing = await exec.getExecOutput('git', ['show-ref', '--verify', '--quiet', `refs/tags/${process.env.RELEASE_TAG}`], { cwd: directory, ignoreReturnCode: true }); - if (existing.exitCode === 0) throw new Error(`Existing tag ${process.env.RELEASE_TAG} must not be overwritten.`); - if (existing.exitCode !== 1) throw new Error('Could not check the planned release tag.'); - core.setOutput('version', version); - - name: Apply the planned version - if: steps.plan.outputs.has-changes == 'true' + const { ReleaseProject } = require(`${process.env.ACTION_PATH}/ReleaseProject.js`); + const githubRelease = require(`${process.env.ACTION_PATH}/index.js`); + const project = new ReleaseProject({ workspace: process.env.GITHUB_WORKSPACE, workingDirectory: process.env.WORKING_DIRECTORY, packageDirectory: process.env.PACKAGE_DIRECTORY, versioning: process.env.VERSIONING, context, exec }); + const packages = await project.packages(process.env.RELEASE_TAG); + const tags = [...new Set(packages.map(item => item.tag))]; + const releases = await Promise.all(tags.map(tag => githubRelease.findRelease(github, context.repo, tag))); + if (context.ref.startsWith('refs/tags/')) { + core.setOutput('status', 'ready'); + core.setOutput('packages', packages); + } else if (releases.every(release => release && !release.draft)) { + core.setOutput('status', 'released'); + } + - name: Start version preparation from the planned source + id: source + if: steps.inspect.outputs.tag == '' && (steps.plan.outputs.has-changes == 'true' || steps.inspect.outputs.independent == 'true' || steps.inspect.outputs.force == 'true') shell: bash working-directory: ${{ inputs.working-directory }} env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - RELEASE_VERSION: ${{ steps.version.outputs.version }} run: | git switch --force-create "$DEFAULT_BRANCH" "$GITHUB_SHA" if [ -n "$(git status --porcelain)" ]; then echo "Release source must be clean before applying the planned version." >&2 exit 1 fi - npm version "$RELEASE_VERSION" --allow-same-version --no-git-tag-version --ignore-scripts - git add --all -- . - - name: Push the signed release commit and tag atomically - if: steps.plan.outputs.has-changes == 'true' + - name: Apply package versions with npm or Lerna + id: version + if: steps.inspect.outputs.tag == '' && (steps.plan.outputs.has-changes == 'true' || steps.inspect.outputs.independent == 'true' || steps.inspect.outputs.force == 'true') uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: ACTION_PATH: ${{ github.action_path }} + VERSIONING: ${{ inputs.versioning }} + PACKAGE_DIRECTORY: ${{ inputs.package-directory }} WORKING_DIRECTORY: ${{ inputs.working-directory }} RELEASE_TAG: ${{ steps.plan.outputs.tag }} + FORCE_RELEASE: ${{ steps.inspect.outputs.force }} + with: + script: | + const { ReleaseProject } = require(`${process.env.ACTION_PATH}/ReleaseProject.js`); + const project = new ReleaseProject({ workspace: process.env.GITHUB_WORKSPACE, workingDirectory: process.env.WORKING_DIRECTORY, packageDirectory: process.env.PACKAGE_DIRECTORY, versioning: process.env.VERSIONING, force: process.env.FORCE_RELEASE === 'true', context, exec }); + const result = project.independent ? await project.prepareIndependent() : await project.prepareVersion({ tag: process.env.RELEASE_TAG }); + core.setOutput('tag', result.tag || ''); + core.setOutput('tags', result.tags || []); + - name: Push the signed release commit and tags atomically + id: commit + if: steps.version.outputs.tag != '' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + ACTION_PATH: ${{ github.action_path }} + WORKING_DIRECTORY: ${{ inputs.working-directory }} + RELEASE_TAGS: ${{ steps.version.outputs.tags }} with: github-token: ${{ inputs.github-token }} script: | const path = require('node:path'); const { pushRelease } = require(`${process.env.ACTION_PATH}/index.js`); - await pushRelease({ github, context, exec, directory: path.resolve(process.env.GITHUB_WORKSPACE, process.env.WORKING_DIRECTORY), tags: [process.env.RELEASE_TAG] }); + const directory = path.resolve(process.env.GITHUB_WORKSPACE, process.env.WORKING_DIRECTORY); + await exec.exec('git', ['add', '--all', '--', '.'], { cwd: directory }); + await pushRelease({ github, context, exec, directory, tags: JSON.parse(process.env.RELEASE_TAGS) }); - name: Dispatch the caller at the prepared tag id: dispatch - if: steps.inspect.outputs.status == '' && (steps.inspect.outputs.tag != '' || steps.plan.outputs.has-changes == 'true') + if: steps.existing.outputs.status == '' && (steps.inspect.outputs.tag != '' || steps.version.outputs.tag != '') uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: CALLER_WORKFLOW: ${{ steps.inspect.outputs.workflow }} - RELEASE_TAG: ${{ steps.inspect.outputs.tag || steps.plan.outputs.tag }} + RELEASE_TAG: ${{ steps.inspect.outputs.tag || steps.version.outputs.tag }} with: github-token: ${{ inputs.github-token }} script: | diff --git a/actions/release/prepare/coverage/.gitignore b/actions/release/prepare/coverage/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/actions/release/prepare/coverage/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/actions/release/prepare/index.js b/actions/release/prepare/index.js index 1c97b7b6..04e8036f 100644 --- a/actions/release/prepare/index.js +++ b/actions/release/prepare/index.js @@ -21,8 +21,10 @@ function versionFromTag(tag, directory, relativeDirectory) { function parsePackages(input, version) { const packages = JSON.parse(input); - if (!Array.isArray(packages) || packages.length !== 1) - throw new Error("Expected exactly one public npm package."); + if (!Array.isArray(packages) || !packages.length || packages.length > 256) + throw new Error( + "Expected a nonempty package array with at most 256 entries.", + ); for (const item of packages) { if ( !item || @@ -31,12 +33,17 @@ function parsePackages(input, version) { item.slug !== item.name.replace(/^@/, "").replace("/", "-") || typeof item.version !== "string" || !/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(item.version) || - item.directory !== "." + typeof item.directory !== "string" || + !item.directory || + path.isAbsolute(item.directory) || + item.directory.split(/[\\/]/).includes("..") ) throw new Error( - "Expected a valid package name, slug, stable version and root package directory.", + "Expected valid package names, slugs, stable versions and relative directories.", ); } + if (new Set(packages.map((item) => item.slug)).size !== packages.length) + throw new Error("Package slugs must be unique."); if (version && packages.some((item) => item.version !== version)) throw new Error("Package versions must match the prepared release tag."); return packages; diff --git a/actions/release/prepare/index.test.js b/actions/release/prepare/index.test.js index e5a03f8d..b9e0bef4 100644 --- a/actions/release/prepare/index.test.js +++ b/actions/release/prepare/index.test.js @@ -218,7 +218,16 @@ const packageItem = { directory: ".", tag: "v1.2.3", }; -test("release accepts one prepared npm package without CI packaging metadata", () => { +test("release accepts prepared single-package and workspace lists without CI packaging metadata", () => { + const packages = [ + packageItem, + { + ...packageItem, + name: "other", + slug: "other", + directory: "packages/other", + }, + ]; assert.deepEqual( releasePreparation.parsePackages( JSON.stringify([packageItem]), @@ -226,6 +235,13 @@ test("release accepts one prepared npm package without CI packaging metadata", ( ), [packageItem], ); + assert.deepEqual( + releasePreparation.parsePackages( + JSON.stringify(packages), + packageItem.version, + ), + packages, + ); }); test("rejects ambiguous names, unsafe paths, mismatched versions and invalid package lists", () => { @@ -234,7 +250,6 @@ test("rejects ambiguous names, unsafe paths, mismatched versions and invalid pac {}, [null], [packageItem, packageItem], - [{ ...packageItem, directory: "packages/cli" }], [{ ...packageItem, slug: "wrong" }], [{ ...packageItem, name: "../bad" }], [{ ...packageItem, directory: "../outside" }], diff --git a/actions/release/prepare/package-lock.json b/actions/release/prepare/package-lock.json index 1e496fda..4c42bb12 100644 --- a/actions/release/prepare/package-lock.json +++ b/actions/release/prepare/package-lock.json @@ -6,7 +6,10 @@ "": { "name": "release-prepare-tests", "devDependencies": { - "@actions/exec": "^1.1.1" + "@actions/exec": "^1.1.1", + "lerna": "10.1.1", + "pnpm": "12.16.1", + "pnpm10": "npm:pnpm@10.33.0" } }, "node_modules/@actions/exec": { @@ -25,6 +28,5760 @@ "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", "dev": true, "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@clack/core": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@clack/core/-/core-1.4.3.tgz", + "integrity": "sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==", + "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.7.0", + "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.7.0.tgz", + "integrity": "sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/core": "1.4.3", + "fast-string-width": "^3.0.2", + "fast-wrap-ansi": "^0.2.0", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 20.12.0" + } + }, + "node_modules/@conventional-changelog/template": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/template/-/template-1.4.0.tgz", + "integrity": "sha512-aalGyl7dbB5PArRebDIX43ZvBlXrYm9uWzGJ26t+4SzJVPsOuvfILGGbw5X4yX7i50YEmJ8zvbiWnqH/AAnZqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@emnapi/core": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emnapi/wasi-threads": "1.0.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@gar/promise-retry": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz", + "integrity": "sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@inquirer/ansi": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.10.tgz", + "integrity": "sha512-dwrc+1aqVZpHNkNDswXGtn1typ+Pxpo5FHZRyBw1le7onEJUkPUkYOY1FIFC1LvFMYUqqLuWBwElhAmencL7MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.8.tgz", + "integrity": "sha512-NY/S1EalQhlonqhwwsarrcnkH7s9ByQuvjWYdk1jB2Gb9EPQJjKkhCTWjM3yQ0UpHaLm1XbIL89b+32G8rhO5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.10", + "@inquirer/core": "^12.0.6", + "@inquirer/figures": "^2.0.10", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/checkbox/node_modules/@inquirer/type": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.2.1.tgz", + "integrity": "sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.3.5.tgz", + "integrity": "sha512-BJ59BOyURiZiAxdr9AuRu38/k+rV+DcTIM2fUF/c/BXrab6H8zMZTTOl3LgkSJH96Xob2vOJxDiBkHiz32Xx2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm/node_modules/@inquirer/type": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.2.1.tgz", + "integrity": "sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "12.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-12.0.6.tgz", + "integrity": "sha512-5yxn2LsJ8Tu4ei2a+yNkmFRhM/cb+JMEsEs/AW6CCOnBnpMy4IjYiPMFyHtYq8weOmlTyqqCx40y0DsD7OsEPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.10", + "@inquirer/figures": "^2.0.10", + "@inquirer/type": "4.2.1", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/@inquirer/type": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.2.1.tgz", + "integrity": "sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.3.6.tgz", + "integrity": "sha512-Z5SOeRC5VNkINfKN4rY8BSaYKb3GYi8iSEqZZRqogsE2GhBBToQ0Xw25SbI+4bcatLzU009mZ5d9EuJNiJuUCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/external-editor": "^3.0.7", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor/node_modules/@inquirer/type": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.2.1.tgz", + "integrity": "sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.8.tgz", + "integrity": "sha512-dMEnTkpLqzM/sbILwL7QJgx9iX5hoEM8+bh6shgwd9mYJamdOnth7y2mMaFd08Glj9AXEFKllWcGYyBqnJsHrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand/node_modules/@inquirer/type": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.2.1.tgz", + "integrity": "sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.7.tgz", + "integrity": "sha512-enNqTJpojgISCG+a5acjnfJ02hcRTFUBVm8uXiCY40PRPZtSzFnNLskOTU+kDUUEODYma90syxuea1rRtUFh0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.2" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.10.tgz", + "integrity": "sha512-51elrUJV3jZpvD0ygz8+U10Kc6+Lzqln+/tYmnbhy6jo5oXpE6hMCcv+OTBC0enwJXsb3JIuT3VtEinRNCSK2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/input": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.9.tgz", + "integrity": "sha512-2Vd5T7lTxfgJMpAd3aOu6bQpbUnCU7tBgvTQIZkP/7juJzmRmTM2t5XfOCuirFduWLTSJI/WZp32Cq1E01TVeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/input/node_modules/@inquirer/type": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.2.1.tgz", + "integrity": "sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.2.6.tgz", + "integrity": "sha512-22LnGGCDm1M/pKXxxF8n6ppr/1IztdsxqeqeBRUMTTNnJTkjIBiAkewiDM4u/gjtazj/FvZS+nk9AbO/vEcBFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number/node_modules/@inquirer/type": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.2.1.tgz", + "integrity": "sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.2.5.tgz", + "integrity": "sha512-0v0wLQA4uhWbybmdH6oPl7ooxCiymNylkvi2gCSc+mA5ZQB6QoJLVhoHq12T82lhozpp9fpMJnJkC1nJrU/A+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.10", + "@inquirer/core": "^12.0.6", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password/node_modules/@inquirer/type": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.2.1.tgz", + "integrity": "sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "8.7.5", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.7.5.tgz", + "integrity": "sha512-l/IWwC+QhOyXgxkZloOh+n5quGzc1m5ZawyzBmoizCZaxbJeLRdS3pStoqg/JVInJRXaG5aHMhjK6H6oYiBC9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^5.2.8", + "@inquirer/confirm": "^6.3.5", + "@inquirer/editor": "^5.3.6", + "@inquirer/expand": "^5.1.8", + "@inquirer/input": "^5.1.9", + "@inquirer/number": "^4.2.6", + "@inquirer/password": "^5.2.5", + "@inquirer/rawlist": "^5.3.8", + "@inquirer/search": "^4.3.6", + "@inquirer/select": "^5.2.8" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.8.tgz", + "integrity": "sha512-h+6Y4pPvYvuRbksn1kIBdD9tCYD+E468W5sIcVfLmoLP5IasnY0r33N+r+46ej/P8b2PCREjyKR+MnnfPGdTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist/node_modules/@inquirer/type": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.2.1.tgz", + "integrity": "sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.3.6.tgz", + "integrity": "sha512-JZj9ttkIvuaFQIc2oxsyqC1ad05WL02ku6W/INVKTCzNaVq1KkhP3AfTRC0m7OWlKZyw+EWwPBFZfYiG9jF6Yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.6", + "@inquirer/figures": "^2.0.10", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search/node_modules/@inquirer/type": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.2.1.tgz", + "integrity": "sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.8.tgz", + "integrity": "sha512-BklkkAAOpjdCfuQw5fVGcDuhKxA7T1iBpn6b2JUvDDdkIsO3Ftlg+WnWUCVpkUBFagL2o036OFW+xL4lC9wIPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.10", + "@inquirer/core": "^12.0.6", + "@inquirer/figures": "^2.0.10", + "@inquirer/type": "4.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select/node_modules/@inquirer/type": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.2.1.tgz", + "integrity": "sha512-lDkPsnv5M5CcYB2T1qTc3dobNTPkVr4DU3Ic2Q6SW8/qVRHgvzdRdmVZ6YqG9Ag1aJTeMsLU0s0WPRh3CZWDQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.1.1.tgz", + "integrity": "sha512-yJoHYrMnxIsJZCY+0Vb66Dy3he3kL3e2wOBKhoSwWWAzZAY82emlxwgprCtp6yRixvNRNq9ztfRWQYPNr3Go7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", + "integrity": "sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@emnapi/core": "^1.1.0", + "@emnapi/runtime": "^1.1.0", + "@tybys/wasm-util": "^0.9.0" + } + }, + "node_modules/@npmcli/agent": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-5.0.2.tgz", + "integrity": "sha512-EkzGmEsgbQ1rqWkRJe2P0oQHx/ylZozDUNPMXCklLuSFL3GY+QyEfBUjhjCsgGXzh4OGpnHvkboSQgczjP/jJg==", + "dev": true, + "license": "ISC", + "dependencies": { + "agent-base": "^9.0.0", + "http-proxy-agent": "^9.0.0", + "https-proxy-agent": "^9.0.0", + "lru-cache": "^11.2.1", + "socks-proxy-agent": "^10.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@npmcli/arborist": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-10.0.3.tgz", + "integrity": "sha512-QuGpR01YV/4RuJ+5S0W5qh4uljQygmk7b50Jkx+zLErOMMUpKhpbLu/TqQlKfsvuR3SOwXIn3ht+G4bcxrv1WA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^6.0.0", + "@npmcli/installed-package-contents": "^5.0.0", + "@npmcli/map-workspaces": "^6.0.0", + "@npmcli/metavuln-calculator": "^10.0.0", + "@npmcli/name-from-folder": "^5.0.0", + "@npmcli/node-gyp": "^6.0.0", + "@npmcli/package-json": "^8.0.0", + "@npmcli/query": "^5.0.0", + "@npmcli/redact": "^5.0.0", + "@npmcli/run-script": "^11.0.0", + "bin-links": "^7.0.0", + "cacache": "^21.0.1", + "common-ancestor-path": "^2.0.0", + "diff": "^8.0.2", + "hosted-git-info": "^10.1.1", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^11.2.1", + "minimatch": "^10.0.3", + "nopt": "^10.0.1", + "npm-install-checks": "^9.0.0", + "npm-package-arg": "^14.0.0", + "npm-pick-manifest": "^12.0.0", + "npm-registry-fetch": "^20.0.1", + "pacote": "^22.0.0", + "parse-conflict-json": "^6.0.0", + "proc-log": "^7.0.0", + "proggy": "^4.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "semver": "^7.3.7", + "ssri": "^14.0.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^8.0.0", + "walk-up-path": "^4.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-6.0.0.tgz", + "integrity": "sha512-AheOs4swKka/XLtht6xxJDPezlQ7K2IYQ9Y8lST4JLDjnralnWuMM9AE2CdVcgQJ5omrXhsRzM7F7aYmeZBvKQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-8.0.0.tgz", + "integrity": "sha512-5P1oo+TbxZNAiiMBtpzHA8QyEGh5D69LYLexNWJEDXLdxnAZvT/SLitGJBXxjtCE4ftAcFOS/Tu2185MeIjooQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/promise-spawn": "^10.0.0", + "ini": "^7.0.0", + "lru-cache": "^11.2.1", + "npm-pick-manifest": "^12.0.0", + "proc-log": "^7.0.0", + "semver": "^7.3.5", + "which": "^7.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-5.0.0.tgz", + "integrity": "sha512-6Ay12sf2Lh7U1ifvnS1mq7TZFeh/rXHMXye+kV7jQrANIubaoVcleeh4HdFumxhsRYwm9OaHycB5lYmSwGrcIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^6.0.0", + "npm-normalize-package-bin": "^6.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@npmcli/map-workspaces": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-6.0.0.tgz", + "integrity": "sha512-Dsex7XBea0BoFRbfALMbNuaNqeI3kbZJxiBMQhFvPUNluv0yEeKMd5FLIXy0NCsOuGMYQkLa+i219CadnoqLbg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^5.0.0", + "@npmcli/package-json": "^8.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-10.0.0.tgz", + "integrity": "sha512-fC56lpGOae+unrFx6hoiWeWa0/4q+bLhOQTSrB65qcy+Zm34i1OdBcevAQFJJdUB/3i9uXw7PjdrrRjt0KSKXw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cacache": "^21.0.1", + "json-parse-even-better-errors": "^6.0.0", + "pacote": "^22.0.0", + "proc-log": "^7.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@npmcli/name-from-folder": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-5.0.0.tgz", + "integrity": "sha512-Mua2i5asmOn4sGGAsANS9Guamo7sgUd4BihhQWzlNGz6l/Ki23CzC2oGe4I94r2k/39ImCjDLbyu/rSJr/SXoA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-6.0.0.tgz", + "integrity": "sha512-MFakpea4pcZNlHSTbMi15HK8RY8zl2UpgDtxhZCWOer+KRN3x7HFIMk/fKpOMgR55L4LIcA2qn8IHeyABhIFtw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-8.0.0.tgz", + "integrity": "sha512-agNZzYQ18MR0wKp3Emg1q5QbcC8CXigYp3Z3CvB0Sax9Ge9aF4cVyyuSG+5SbACSrZUKTvMjVULWiE1RJA38wg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^8.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^10.1.1", + "json-parse-even-better-errors": "^6.0.0", + "proc-log": "^7.0.0", + "semver": "^7.5.3", + "spdx-expression-parse": "^4.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-10.0.1.tgz", + "integrity": "sha512-IstFcKAoEIZ7olS9iTwBsyNOZkIturafO2da9ocSP3LifRlm1+sxuo8IVM5phg5sNe0tNYLT0ZqCBRTLNzz0AA==", + "dev": true, + "license": "ISC", + "dependencies": { + "which": "^7.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@npmcli/query": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-5.0.0.tgz", + "integrity": "sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-5.0.0.tgz", + "integrity": "sha512-3zcN5Q3yEmeyxXBzqB6fXPQFzYa2ROsGFSr69W0ArXIAGJqxl/aFECOVPD2kbkYPm0U/EHxFKgclK3UA9WQg5A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-11.0.0.tgz", + "integrity": "sha512-leBRl6F5F0TvWut8m1/aZcMTUHi2vXjKeMJ/Ik1lW7Q7Yy16Dhtkklu+cEqQww1p1NeLnUNzV3+uwpzqRcy9vw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^6.0.0", + "@npmcli/package-json": "^8.0.0", + "@npmcli/promise-spawn": "^10.0.0", + "node-gyp": "^13.0.0", + "proc-log": "^7.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@nx/devkit": { + "version": "23.3.1", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-23.3.1.tgz", + "integrity": "sha512-Cw6YISZUjit22Trfo/p6rnrNklIKHYgRyp2IyOgr0i9naDgO3Ufllib9D/v0SInwX2zKevaWJWMyreqVvHRU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ejs": "5.0.1", + "minimatch": "10.2.5", + "semver": "^7.6.3", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 22 <= 24 || ^23.0.0-0" + } + }, + "node_modules/@nx/devkit/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@nx/nx-darwin-arm64": { + "version": "23.3.1", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-23.3.1.tgz", + "integrity": "sha512-T3KOTrUIdWaqlIIMyusbpKHujXTumoltkYwBjqjw9Py6Fb3c8Eb7uvWVW6dP8ODknqr9oEcTlk+C8Nid+bunAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nx/nx-darwin-x64": { + "version": "23.3.1", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-23.3.1.tgz", + "integrity": "sha512-GnbMF9xEGIdUI/7KoQdcTILWmRUrMLbbFi3CO8KgKcA1Be7eaoeKdgBQoCQ2yF7r7DyQOR1PXp7sF3s7gqUATQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@nx/nx-freebsd-x64": { + "version": "23.3.1", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-23.3.1.tgz", + "integrity": "sha512-RzLx1pVL/MQQ/fSreMFIufAfsrj/TpD1+69n5kBcr3An2FaqKdQ6BhKLWRlAeOSKq6Su75Y0la8e2AU5zlmdpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@nx/nx-linux-arm-gnueabihf": { + "version": "23.3.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-23.3.1.tgz", + "integrity": "sha512-/K7ig9xL4FMO2eQovTSLtk4QR7d0phbKoWf1wYQZ2r0ZrHNo2gPYmS8wU0Ci4d6oSiqa2fdeHKVDtEvWldTBaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-linux-arm64-gnu": { + "version": "23.3.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-23.3.1.tgz", + "integrity": "sha512-Uj3xl5aLXUV6EQ/vWbZBqMqZO9K58CxRIsMTISC1+LxjvGnf4uUL73ObTi3K1eih3f1CVCGsU95LF3Kf6T2Frw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-linux-arm64-musl": { + "version": "23.3.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-23.3.1.tgz", + "integrity": "sha512-woeNHutXYgiWU0SQnvKQ4VNgVbqwy1uer6bS0HF2RbfN80HglsjhtzTAUZY2pZiWBLbHcHZwTNmDGTAXSNMLAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-linux-x64-gnu": { + "version": "23.3.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-23.3.1.tgz", + "integrity": "sha512-GckqfXo7BAIrcR07CoI0IM06h1wbUim+4AyUYosokrI4Gx1zHZFRwrE3vpPrMpSKVVYZD7BGQp2bhZBPvgGaxg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-linux-x64-musl": { + "version": "23.3.1", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-23.3.1.tgz", + "integrity": "sha512-QjOj5S/X39OxVykltLFSvNxaZtnI9uRk0Uqfyt69kOrZn+/Ap2SM/bT70Xqn8PFxj5hfA0viI2vU1AlOM4JQxg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@nx/nx-win32-arm64-msvc": { + "version": "23.3.1", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-23.3.1.tgz", + "integrity": "sha512-Jf5kc3zhek8Fz06DaMxaJTJxgNJIN2tddE8yxt+XaIHtulmW+MMjy6Z91QPKPf9iZA70N+oT9vwgvYPmOsdO8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@nx/nx-win32-x64-msvc": { + "version": "23.3.1", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-23.3.1.tgz", + "integrity": "sha512-RKQ5t6u4fgOWQyVt61y5Jn/xzXCYxqXOKz2okDbuWccBShQddF8jsqRaLMZDlGT5d8CJTLIso8Y9BiY12/7x7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.8.tgz", + "integrity": "sha512-L7y8eYc+AwxGr2PWI4WFt1VG4TiJ66c26BD16mXpYIlXxG0SMigM1+m4aTSlYyBr5BlQsGAlz8uDCoZN4SEMcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.5", + "@octokit/request": "^10.0.16", + "@octokit/request-error": "^7.1.2", + "@octokit/types": "^18.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/endpoint": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.5.tgz", + "integrity": "sha512-iXa654H3yFafF/ieHkukfbgWo2rmXD2ceD0ZOtrPhw1bc3FDch1d9N/TNs0FQ1/cIbwb7kspUX8jzIs8nzb9DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^18.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/graphql": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.5.tgz", + "integrity": "sha512-bt/hm03LeU6Vy7FwTrkkC9p3XGT/lBwClglMqxBSe5/q0E5CdJTXeAqEI0vlw89/LF/G6tryTIH8HirZ3prMVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.16", + "@octokit/types": "^18.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-29.0.1.tgz", + "integrity": "sha512-9qWOMFNxxLokERcms42rU0PTLqQmVs7g5E41TI4mCOxmpFayD1rfC7XxOL55cG9MBZLFlC31BrR37myMKardwg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-enterprise-rest": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz", + "integrity": "sha512-93uGjlhUD+iNg1iWhUENAtJata6w5nE+V4urXOAlIXdco6xNZtUSfYY8dzp3Udy74aqO/B5UZL80x/YMa5PKRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-6.0.0.tgz", + "integrity": "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", + "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "10.0.16", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.16.tgz", + "integrity": "sha512-A0zWGjHzISIb+9ccG8s0dq7LKO5zVpJLRICjgUb+sJxEWqn8RUHB1rD3AE51+PECvXHIxqZ1VVvs4fHTSD9nUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.5", + "@octokit/request-error": "^7.1.2", + "@octokit/types": "^18.0.0", + "content-type": "^3.0.0", + "json-with-bigint": "^3.5.12", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/request-error": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.2.tgz", + "integrity": "sha512-XZRuT3xZ84D3gYErI1DZvhJ33dCWVV6uzBtWkaBB4TvA/L6eOeTZodxLFVB44bBEEo3vEx7y00UfX1tBLrtLRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^18.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/rest": { + "version": "22.0.1", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-22.0.1.tgz", + "integrity": "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/core": "^7.0.6", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-request-log": "^6.0.0", + "@octokit/plugin-rest-endpoint-methods": "^17.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/types": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-18.0.0.tgz", + "integrity": "sha512-l6bAF43PNxkJp6g+W4PjoUSSkxHomXw2nOum5CTftJz1NlV3vu93NImgOYtLf6CbBUb5j+fiuzW0PPQ5JTSvZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^29.0.1" + } + }, + "node_modules/@pnpm/exe.android-arm64": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/@pnpm/exe.android-arm64/-/exe.android-arm64-12.16.1.tgz", + "integrity": "sha512-PKf78Ew+i0CBYf0Xc+eMcqQiqyBT+u8XCVg1F5roc98/6wWfdYlWhrUMmEBiKWaJ3JqHxrtt8l1NwZ7qfJt2/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@pnpm/exe.android-x64": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/@pnpm/exe.android-x64/-/exe.android-x64-12.16.1.tgz", + "integrity": "sha512-xdu7gMbB98pdvhwMeA6612Of515FDlRUtfA/1f7nWPZfPLnEdSRBO2u+1N0XBtQ/ennhPH/lrxB1gC6gnR/cdg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@pnpm/exe.darwin-arm64": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/@pnpm/exe.darwin-arm64/-/exe.darwin-arm64-12.16.1.tgz", + "integrity": "sha512-pVbmIIbhixiLvADLu1WA3TaW8FZy/D72R1De27WbbFx8/2mxvnsTyFVm4fO9kjpjMY7RzJd48CiQ96p8fO3ebQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pnpm/exe.darwin-x64": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/@pnpm/exe.darwin-x64/-/exe.darwin-x64-12.16.1.tgz", + "integrity": "sha512-xZvQBFqcZjO7vFKTK3XsJpqY6lnBL9EWSLCzVUPq3kZ94+D96gwJCa76xxGNK8Cj32f4eeM91Qg+yNRR4sRpDw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@pnpm/exe.freebsd-x64": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/@pnpm/exe.freebsd-x64/-/exe.freebsd-x64-12.16.1.tgz", + "integrity": "sha512-zTtunfi9mBhRXiXEJYfwiJj0tk7MgTSfqDSRh+Az9FZ9THazVIiQiVNS8jpcsftDOqu7LKNffQdjmgwHDThfUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@pnpm/exe.linux-arm64": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/@pnpm/exe.linux-arm64/-/exe.linux-arm64-12.16.1.tgz", + "integrity": "sha512-JaRd5gNxwdNGBBkrZFt8bknrK4YmHVJBW0JMhUQ0fJ0dEYp6C2GT9ExaCqOO8EwYEyqM+jpKgJIK+8WFaJy3ew==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pnpm/exe.linux-arm64-musl": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/@pnpm/exe.linux-arm64-musl/-/exe.linux-arm64-musl-12.16.1.tgz", + "integrity": "sha512-BJ3mOmcmQgcUo92Ee/WfEOt1pRiv3y2MMAkhEiYHd85UiPpHTfFhv+SpCbALmfExzXk3UlaRtMYwHlkQymc4VA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pnpm/exe.linux-ppc64": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/@pnpm/exe.linux-ppc64/-/exe.linux-ppc64-12.16.1.tgz", + "integrity": "sha512-FnSV0Jv40S1cMv7vFNWwvu0X567Lec4L4HCRead3CFcD6zQDNbGw81s8N5bcKPahtPD9ApVuhsbmFCx4rw6RMw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pnpm/exe.linux-riscv64": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/@pnpm/exe.linux-riscv64/-/exe.linux-riscv64-12.16.1.tgz", + "integrity": "sha512-C1Sww4FN5IttD4atRRhAKmQtSGWVp04ZAKNxO8pfu2bkRPwOW+7v+2DH1Xs1W+kZYYpO86BYDvUcadefF+AgaQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pnpm/exe.linux-s390x": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/@pnpm/exe.linux-s390x/-/exe.linux-s390x-12.16.1.tgz", + "integrity": "sha512-kn2fautsTcxIMODoovQVqZyfpDRdNgGkoeVqNdrKqbeBBvrEl+fXLkmZiq5xDht1LMr4sYiCNcsMfNMT6JPFmg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pnpm/exe.linux-x64": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/@pnpm/exe.linux-x64/-/exe.linux-x64-12.16.1.tgz", + "integrity": "sha512-RK//8ZLcXGW8A2k8bQOXZDBN1Qu92zN4dYnG1bzj1zoQ1fkZ6jNzZ1AvXtsfGVma795CJtvoaF5bLXf4l/FIUw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pnpm/exe.linux-x64-musl": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/@pnpm/exe.linux-x64-musl/-/exe.linux-x64-musl-12.16.1.tgz", + "integrity": "sha512-PM6ccHCOe/O9SsTflTJTYWfwogJZa6EmUvpnwZgiPguJBddh20wDQKgxUPXf48rDsPm9vMsIij0uHswwwbXL8g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@pnpm/exe.win32-arm64": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/@pnpm/exe.win32-arm64/-/exe.win32-arm64-12.16.1.tgz", + "integrity": "sha512-Ox3kNNVxtGw6pK7SVejmeOUgHhUTZgAM7NQQ6cU7frPw3ix9+j7d2MGbRg3fyEFAaKWq8ZAZLTQGcDyLnAu+qA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@pnpm/exe.win32-x64": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/@pnpm/exe.win32-x64/-/exe.win32-x64-12.16.1.tgz", + "integrity": "sha512-k+FhRJ0ttphFoFIVB1Sk4v76YJAulafmA/Ly57cysesUhhIzVlApeOShUz0jXubKx317TThlEfpQrukldnqORA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sigstore/bundle": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-5.0.0.tgz", + "integrity": "sha512-wefjygudENbzbQMks1t5u34EP0fFoD0XvaEP7DOUP/sXKvogzEJYFw5E6pegGyp3onGWzVEYKVa3bNZWyTYX+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@sigstore/core": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-4.0.1.tgz", + "integrity": "sha512-9v5hRjujn5NXq8o7XFEUgLyAtdr5Iisb4pzM05u3K61IS5q3hP3luWAndk0RkPPLTUFoTbg7Vb84UQ1ZQeajWQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.2.tgz", + "integrity": "sha512-SQqvFMt4V78fdjcDdYX6HbiVSOR4QK3ZgwCa2KOsopAgPIHy1rU5UDUmzLl02r5oyyaYcYHR1hpwDRk/yUe+Mw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-5.0.0.tgz", + "integrity": "sha512-DSFivqz9/i5AkwZ5fq0YdjaJlc4o1WeS2Zffon0kqtChx0vy4W9NOjkEet9bF2vkzOufX72eVH8kZBIGtcBp1w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@gar/promise-retry": "^1.0.2", + "@sigstore/bundle": "^5.0.0", + "@sigstore/core": "^4.0.0", + "@sigstore/protobuf-specs": "^0.5.0", + "make-fetch-happen": "^16.0.0", + "proc-log": "^7.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-5.0.0.tgz", + "integrity": "sha512-Zyqg9tcHps3uRAlKHLNmsW4ohsUZAjb9G+31r7lg0ICh/JOcadzmJsIRdjKljlRHpaR0K4aJ2kXXIdywdcdMlA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.5.0", + "tuf-js": "^6.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-4.1.2.tgz", + "integrity": "sha512-BfD9eLrz3A/DG58aSgfgZYmIR6V9Yw96QVN/frtu2bEH7ctSTk2TDvHU12un3JsDPBTqTNkqkIkucjxljpOFqQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^5.0.0", + "@sigstore/core": "^4.0.1", + "@sigstore/protobuf-specs": "^0.5.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@simple-libs/child-process-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-2.0.0.tgz", + "integrity": "sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^2.0.0" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/@simple-libs/hosted-git-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@simple-libs/hosted-git-info/-/hosted-git-info-2.0.0.tgz", + "integrity": "sha512-55XwK/GYgV58PuN8lZFhI1qRxdKoMLJocXl/yM1EPqazFDmM4QWY7q6BxPCPDNKTNunj794DSD4h0H7SrqUdMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/@simple-libs/normalize-package-data": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@simple-libs/normalize-package-data/-/normalize-package-data-1.0.0.tgz", + "integrity": "sha512-/EOmFBekV9tGee8gac/k+5Ox3twkypNqh5TfxA9vTkmcJkjm6f1xqrlstDNOrEhTvnYyVtU6Du2ZhY/IV1+9GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/hosted-git-info": "^2.0.0", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/@simple-libs/stream-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-2.0.0.tgz", + "integrity": "sha512-fCTuZK4QBa+39Oz9l4OGfJfz+GpwCp3AqO7Zch3to99xHPgstVsRFpeQ8LNd2o1Gv8raL2mCFwiaHh7bFSp5DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-5.0.1.tgz", + "integrity": "sha512-wacy5twbJk36R0dTcGE3qmPNNH095bFselAYNrVCzf0Byd0t3+Qzk3+3gn/rHGP10FuHBVk4swg4aczr1c2z/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^10.2.6" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", + "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/parse-path": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/parse-path/-/parse-path-7.0.3.tgz", + "integrity": "sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@zkochan/js-yaml": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", + "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/abbrev": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-5.0.0.tgz", + "integrity": "sha512-/XrFJgzQQQHpti1raDJC6m4ws6aNktmjBlhk8Fdlk7LwCEuDoieEJJY9OFHjfiFJFFRM2tK+Ky/IsfbbmlMu1w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/agent-base": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-9.0.0.tgz", + "integrity": "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/argue-cli": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/argue-cli/-/argue-cli-3.2.0.tgz", + "integrity": "sha512-VipTB0gXgGIFO2Rg9yEVN5wLt2AurJcZqDbgmYSwPwsykhLrQhs240/bfceev4w68lI8JshoOJIk9uW7tFzROw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://ko-fi.com/dangreen" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bin-links": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-7.0.1.tgz", + "integrity": "sha512-lgxd7FtxW02eSuafotQOGEQf1iGfDExMz2BRaL+e1tU5xTLx+vvSVA5oHIO6B1tR91LDKcWqYGsktUHdFMtHHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^9.0.0", + "npm-normalize-package-bin": "^6.0.0", + "proc-log": "^7.0.0", + "read-cmd-shim": "^7.0.0", + "write-file-atomic": "^8.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacache": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-21.0.1.tgz", + "integrity": "sha512-pTwz/uj3Jyp6WXdJ6fWhR+7LVxVs6RyroQSn7KJwHsSxXuyGSp0pcMVcwSwTpCFq1X2YG8QBe0W+vN+cr0SwzA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^6.0.0", + "fs-minipass": "^3.0.0", + "glob": "^13.0.0", + "lru-cache": "^11.1.0", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^14.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", + "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/cmd-shim": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-9.0.2.tgz", + "integrity": "sha512-xVHoI+wNrM4tDB9iC1idf/8D0tYnVimlBp/5zHW+x1sGjjRD69NvR9th3Z1JAYGN/BTW4he6aZFYV6kzy+k+jw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/columnify": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.6.0.tgz", + "integrity": "sha512-lomjuFZKfM6MSAnV9aCZC9sc0qGbmZdfygNv+nCpqVkSKdCxCklLtd16O0EILGkImHw9ZpHkAnHaB+8Zxq5W6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/common-ancestor-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", + "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/content-type": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-3.1.1.tgz", + "integrity": "sha512-GW4qUsfFo59d0HbUibDlWv5wPz+vAAcaTWbKIuKCf0JkC7wWkSyf8f13IpXn5JkeMlB2P8iTSSIjwXljorg2vA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/conventional-changelog": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-8.1.3.tgz", + "integrity": "sha512-fOMwLCxm46znJKPB08jLxl1FD8SOZOQHktlzCVhvhhVUfq0KgQSryoYXsRRHs1aRlZPR1/QK5wztnY8sBNC2AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@conventional-changelog/git-client": "^3.1.2", + "@simple-libs/hosted-git-info": "^2.0.0", + "@simple-libs/normalize-package-data": "^1.0.0", + "argue-cli": "^3.1.0", + "conventional-changelog-preset-loader": "^6.0.1", + "conventional-changelog-writer": "^9.2.1", + "conventional-commits-filter": "^6.0.1", + "conventional-commits-parser": "^7.1.2", + "fd-package-json": "^2.0.0" + }, + "bin": { + "conventional-changelog": "dist/cli/index.js" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-9.4.0.tgz", + "integrity": "sha512-HdxRxuS8bBXVIuo4V82gvSwAXT0vYQUizrjs/izmPg5JdDstr8v8I5hduGL3iQbG+o310dUDxC4+LetuS5hu9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@conventional-changelog/template": "^1.4.0" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/conventional-changelog-preset-loader": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-6.0.1.tgz", + "integrity": "sha512-GZ8E3RQzXQb3JFy0pKl8oeSf7ENIhvAMvJr+PlWkavZOesLHHdhkfNJ4SvW35eo1Fu2+EyVxWQ1tVvtzAG2iWg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/conventional-changelog-writer": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-9.3.0.tgz", + "integrity": "sha512-lxiECDlvy880e7JIYAtAhxg0wD/CGCVbZCdeua5aOlQVFTEd39zAeOzwK22i/FO4xnEOURK1gbUTETNEYW7zxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@conventional-changelog/template": "^1.4.0", + "@simple-libs/stream-utils": "^2.0.0", + "argue-cli": "^3.1.0", + "conventional-commits-filter": "^6.0.1", + "verkit": "^0.5.0" + }, + "bin": { + "conventional-changelog-writer": "dist/cli/index.js" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/conventional-changelog/node_modules/@conventional-changelog/git-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-3.2.0.tgz", + "integrity": "sha512-sD/E0vzpkoDz/dqM1Hhv0ihC4KBw6RXjefeO8SG1XrX+wGkMxe1pKeedkKDPCtr2JjuhbuS9aJNgOvnT8hbeBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/child-process-utils": "^2.0.0", + "@simple-libs/stream-utils": "^2.0.0", + "verkit": "^0.5.0" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "conventional-commits-filter": "^6.0.1", + "conventional-commits-parser": "^7.1.3" + }, + "peerDependenciesMeta": { + "conventional-commits-filter": { + "optional": true + }, + "conventional-commits-parser": { + "optional": true + } + } + }, + "node_modules/conventional-changelog/node_modules/conventional-commits-parser": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-7.1.3.tgz", + "integrity": "sha512-Q9oWm1o9uy/oEsZQIraoFMZ4RuPD+79KgwWj6mJVP356tazolCB0piRLZ5Kz9te9p027uSEcUW+Gu44Eh+X79g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^2.0.0", + "argue-cli": "^3.1.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/conventional-commits-filter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-6.0.1.tgz", + "integrity": "sha512-cs+LadpH7Kpw0M3k8wurk+sOVVDAENA0iK4OBOrkL94j5lEVYRJ4j3zd2bhY9qgzyrPqthdcYT3axzRN7AliMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/conventional-commits-parser": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-7.1.2.tgz", + "integrity": "sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^2.0.0", + "argue-cli": "^3.1.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/conventional-recommended-bump": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-12.1.0.tgz", + "integrity": "sha512-HTNG3kEZXOOfKwrEx54ljURU9bG4nVPQzXMyCSeUcA/PE8EpNpQNujSrbXNBI8QZyN35zM+pVw+FuQk4KqHSHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@conventional-changelog/git-client": "^3.1.0", + "argue-cli": "^3.1.0", + "conventional-changelog-preset-loader": "^6.0.1", + "conventional-commits-filter": "^6.0.1", + "conventional-commits-parser": "^7.1.0" + }, + "bin": { + "conventional-recommended-bump": "dist/cli/index.js" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/conventional-recommended-bump/node_modules/@conventional-changelog/git-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-3.2.0.tgz", + "integrity": "sha512-sD/E0vzpkoDz/dqM1Hhv0ihC4KBw6RXjefeO8SG1XrX+wGkMxe1pKeedkKDPCtr2JjuhbuS9aJNgOvnT8hbeBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/child-process-utils": "^2.0.0", + "@simple-libs/stream-utils": "^2.0.0", + "verkit": "^0.5.0" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "conventional-commits-filter": "^6.0.1", + "conventional-commits-parser": "^7.1.3" + }, + "peerDependenciesMeta": { + "conventional-commits-filter": { + "optional": true + }, + "conventional-commits-parser": { + "optional": true + } + } + }, + "node_modules/conventional-recommended-bump/node_modules/conventional-commits-parser": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-7.1.3.tgz", + "integrity": "sha512-Q9oWm1o9uy/oEsZQIraoFMZ4RuPD+79KgwWj6mJVP356tazolCB0piRLZ5Kz9te9p027uSEcUW+Gu44Eh+X79g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@simple-libs/stream-utils": "^2.0.0", + "argue-cli": "^3.1.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", + "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ejs": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-5.0.1.tgz", + "integrity": "sha512-COqBPFMxuPTPspXl2DkVYaDS3HtrD1GpzOGkNTJ1IYkifq/r9h8SVEFrjA3D9/VJGOEoMQcrlhpntcSUrM8k6A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.12.18" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-10.0.1.tgz", + "integrity": "sha512-ge98qjkRK4IB7tL7Ju/6qmm5LHoH1eEMt5FNZrz3f4UIYhF28lggX20z3FaX1sgc67msLEn0N0BscOs29iuwyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "figures": "^6.1.0", + "get-stream": "^9.0.1", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.3.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "which-command": "^0.1.0", + "yoctocolors": "^2.1.2" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "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/fd-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", + "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "walk-up-path": "^4.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.1.tgz", + "integrity": "sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.7.0.tgz", + "integrity": "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/git-up": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/git-up/-/git-up-8.1.1.tgz", + "integrity": "sha512-FDenSF3fVqBYSaJoYy1KSc2wosx0gCvKP+c+PRBht7cAaiCeQlBtfBDX9vgnNOHmdePlSFITVcn4pFfcgNvx3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-ssh": "^1.4.0", + "parse-url": "^9.2.0" + } + }, + "node_modules/git-url-parse": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/git-url-parse/-/git-url-parse-16.1.0.tgz", + "integrity": "sha512-cPLz4HuK86wClEW7iDdeAKcCVlWXmrLpb2L+G9goW0Z1dtpNS6BXXSOckUTlJT/LDQViE1QZKstNORzHsLnobw==", + "dev": true, + "license": "MIT", + "dependencies": { + "git-up": "^8.1.0" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/handlebars": { + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.10.tgz", + "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.8", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hosted-git-info": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-10.1.1.tgz", + "integrity": "sha512-DeOnSPAvOndYKfw075gt8yZzQ7S2hNztw34zBTfhIzLhmBTswIBg5/y+pqu/VD5cYWm5goAFTusDmUEmKZ0PEQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^11.1.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-9.1.0.tgz", + "integrity": "sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/https-proxy-agent": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-9.1.0.tgz", + "integrity": "sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "proxy-agent-negotiate": "1.1.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-9.0.0.tgz", + "integrity": "sha512-tCBEZV2z2FNpIDl2vrhiWzIHzs4qOAuIDEO85eS02vZ3L1U3P56qpPL8GuGGAijDktAEaq2swMkO/Fmbo7YmfQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-7.0.0.tgz", + "integrity": "sha512-ifK0CgjALofS5bkrcTy4RaQ9Vx2Knf/eLeIO+NaswQEpH1UblrtTSCIvN71qQDMq0PeQ/SSPojvEJp9vvvfr+w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/init-package-json": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-9.0.0.tgz", + "integrity": "sha512-aYDqz4jgX1plB2GDCFYStlOh67QXVpAIeNgDq8rRGVDYMvLE39yYlf8+CjYY2chVOVfqLsSIycFYznHWozZ9CA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^8.0.0", + "npm-package-arg": "^14.0.0", + "promzard": "^4.0.0", + "read": "^6.0.0", + "semver": "^7.7.2", + "validate-npm-package-name": "^8.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/inquirer": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-14.2.2.tgz", + "integrity": "sha512-WGerZIIb7mTtaFFskkD/P0europ8xBWf1OXACMiugzKPSmVzoqhcPSuSUzbzMch/t3rf1ncBT3v/0WU73cLqJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.8", + "@inquirer/core": "^12.0.3", + "@inquirer/prompts": "^8.7.2", + "@inquirer/type": "4.1.1", + "mute-stream": "^3.0.0", + "run-async": "^4.0.6" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/ip-address": { + "version": "10.7.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.5.tgz", + "integrity": "sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-ssh": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/is-ssh/-/is-ssh-1.4.1.tgz", + "integrity": "sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "protocols": "^2.0.1" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.2.tgz", + "integrity": "sha512-m+aqu+LwO1O6sIopafj8HUVl5aawITwZQe/yHpMCKjaWBaA/d07B/QdMb3529REftiU+RMMHL3Vlsw3hON7vWg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.mjs" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-6.0.0.tgz", + "integrity": "sha512-2/8adwnK1/+Fdjyts4r6wSpfANWw8zdNhU9U/Llk59c6O+DjSisPWPykwoL8gZmocP9Dy64S7oie2g+Mia123A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/json-stringify-nice": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", + "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", + "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/json-with-bigint": { + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.12.tgz", + "integrity": "sha512-uwbF/wSSuOgC7qqlq27Xp5B6a2MHVug3t0idZdTqu0JnlFvgJuH7ju+KAk/J06C7GfhoYy2gnb9wz2INqcne7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "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/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/just-diff": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", + "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==", + "dev": true, + "license": "MIT" + }, + "node_modules/just-diff-apply": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz", + "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lerna": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/lerna/-/lerna-10.1.1.tgz", + "integrity": "sha512-WDGaMGDGj5jV0n6Yx2zuBDWH0ym6toUE6w+maaPLqeMd3ncDRN+NJMcVWImVdUKCIHRk2Xk+X7QQkFXdHKsscA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@npmcli/arborist": "10.0.3", + "@npmcli/package-json": "8.0.0", + "@npmcli/run-script": "11.0.0", + "@nx/devkit": ">=23.1.0 < 24.0.0", + "@octokit/plugin-enterprise-rest": "6.0.1", + "@octokit/rest": "22.0.1", + "ci-info": "4.4.0", + "columnify": "1.6.0", + "conventional-changelog": "8.1.3", + "conventional-changelog-angular": "9.4.0", + "conventional-commits-filter": "6.0.1", + "conventional-commits-parser": "7.1.2", + "conventional-recommended-bump": "12.1.0", + "cosmiconfig": "9.0.2", + "dedent": "1.7.2", + "envinfo": "7.21.0", + "execa": "10.0.1", + "fs-extra": "^11.2.0", + "git-url-parse": "16.1.0", + "handlebars": "4.7.10", + "import-local": "3.2.0", + "ini": "^7.0.0", + "init-package-json": "9.0.0", + "inquirer": "14.2.2", + "js-yaml": "5.4.2", + "libnpmaccess": "11.0.0", + "libnpmpublish": "12.0.1", + "load-json-file": "7.0.1", + "make-fetch-happen": "16.0.1", + "minimatch": "10.2.6", + "npm-package-arg": "14.0.0", + "npm-packlist": "11.3.0", + "npm-registry-fetch": "20.0.1", + "nx": ">=23.1.0 < 24.0.0", + "p-map": "7.0.8", + "p-queue": "9.3.3", + "pacote": "22.0.0", + "semver": "7.8.5", + "signal-exit": "4.1.0", + "ssri": "14.0.0", + "string-width": "^8.0.0", + "tar": "7.5.22", + "tinyglobby": "0.2.17", + "validate-npm-package-name": "8.0.0", + "write-file-atomic": "8.0.0", + "yargs": "18.2.0" + }, + "bin": { + "lerna": "dist/cli.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || ^26.0.0" + } + }, + "node_modules/libnpmaccess": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/libnpmaccess/-/libnpmaccess-11.0.0.tgz", + "integrity": "sha512-qku5OaTtCv7f4xq5lmZMh4H6pd9WRalww3WF4PEk/aFo1Bpi7/IRCzy28EQBvyrprhzZGifbpM378U4waB9/0Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^14.0.0", + "npm-registry-fetch": "^20.0.1" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/libnpmpublish": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/libnpmpublish/-/libnpmpublish-12.0.1.tgz", + "integrity": "sha512-gG32Hgikbb8mx+c+b2MKseuQcwKbiIl7wM4a5+scxuRtu/Q7L/fbywMyLD4naUfL0yexdKWzfi6kglMz6BGOWQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^8.0.0", + "ci-info": "^4.0.0", + "npm-package-arg": "^14.0.0", + "npm-registry-fetch": "^20.0.1", + "proc-log": "^7.0.0", + "semver": "^7.3.7", + "sigstore": "^5.0.0", + "ssri": "^14.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/lines-and-columns": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.3.tgz", + "integrity": "sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/load-json-file": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-7.0.1.tgz", + "integrity": "sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "11.5.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", + "integrity": "sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/make-fetch-happen": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-16.0.1.tgz", + "integrity": "sha512-uUv1yxHzaKVVEPfcFeGSNov/Cehjv08ovlY8ImTljgL7Q+SiA0dAYLQ6SYVa2kkKqNj4Y3aZEI7xv2teadie0A==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/agent": "^5.0.0", + "@npmcli/redact": "^5.0.0", + "cacache": "^21.0.0", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^6.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^7.0.0", + "ssri": "^14.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-6.0.0.tgz", + "integrity": "sha512-AWI8bKapGmgx/J0E6IGYSKj8TiHebZkmKWSs8raPSw8KXwgEAJ+Bw3+LSdXHR6T/RHKAWCOYk2MiLrYluaUU6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^2.0.0", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + }, + "optionalDependencies": { + "iconv-lite": "^0.7.2" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minipass-sized": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", + "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-gyp": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-13.1.0.tgz", + "integrity": "sha512-FpNzOLFACKG0oRBYbZ5WOeWD1EqRTIWX90MaRIWTO18lVYPx8fqIjrdS3pihjnCkSmgop6zVGkSqg1vXZLksuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^10.0.0", + "proc-log": "^7.0.0", + "semver": "^7.3.5", + "tar": "^7.5.7", + "tinyglobby": "^0.2.12", + "undici": "^8.4.1", + "which": "^7.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/nopt": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-10.0.1.tgz", + "integrity": "sha512-df3sBr/6ax9hSGuC3CspvLlbnX8cP5L5nZwXF8cGN8l0zSWR6BvzmQ6jPUKjvo6+/xdpkNvEcucBNUdBeeV13g==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^5.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/npm-bundled": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-6.0.0.tgz", + "integrity": "sha512-EqdodKEW6pYM+dPxA66TZQfMEqVDiuzjDM9edSjuPI1mXUbUJwVxkgqMZSJvs8RTXz2CGq8HUol/AffTZX5g8w==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^6.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-9.0.0.tgz", + "integrity": "sha512-t05Izcgi7p15cpldqoiXYpjzlkTTvBw33sgjmL/JjcvtV0ydbm2O4iEXO8A6smqComu5FAQhUas86HTMQ6Z1Uw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-6.0.0.tgz", + "integrity": "sha512-tdt4aFn9QamlhdN3HV2D2ccpBwO5/fyjjbXUxYA6uBjyekMZcZvDq0aSj9t5Jo+tih6AYFnt/cuIRn9013e0Uw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-14.0.0.tgz", + "integrity": "sha512-69XQh3k+dtGa1p+7RaR57IuG3rCko96xr/nUfN4yDYBXbTYICiWcOpsFKLN2GtGE9cyIljE+f1exnaYt9MvM+Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^10.1.0", + "proc-log": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^8.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-11.3.0.tgz", + "integrity": "sha512-cS1yVkyriZgQAbiK8PtwhZHEtsFOsKHsCg5Ww2ONckAvXIspgqd6o4WirOzvkupU24iMRZ4xtO4kb2iK2rbnag==", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^13.0.6", + "ignore-walk": "^9.0.0", + "proc-log": "^7.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-12.0.0.tgz", + "integrity": "sha512-8Fs3YLrnNOhrCdPNZy18MzNgVC58LTDAFzq1FdZO/p3BHeCC/coz+t4F5Pxabys8HJpyTUorMea26GkXsb4J/Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^9.0.0", + "npm-normalize-package-bin": "^6.0.0", + "npm-package-arg": "^14.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-20.0.1.tgz", + "integrity": "sha512-vzc1svxw/kw1IRjFsLi6gaxe1Olqm88V0tIfu2u5raL0b1gChe6ZEXNkyUlKxUC7s/egt5NxZHkbY18tMKKLfQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^5.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^16.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^6.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^14.0.0", + "proc-log": "^7.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nx": { + "version": "23.3.1", + "resolved": "https://registry.npmjs.org/nx/-/nx-23.3.1.tgz", + "integrity": "sha512-dufPxsiUazuSejFYdtLAotKXeda9ufz3B9gVtsrvgxkjWjpAM23rOpIJoqZj3FFauig64lYsaMX+nX2hh+YhYQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@clack/core": "1.4.3", + "@clack/prompts": "1.7.0", + "@emnapi/core": "1.4.5", + "@emnapi/runtime": "1.4.5", + "@emnapi/wasi-threads": "1.0.4", + "@jest/diff-sequences": "30.0.1", + "@napi-rs/wasm-runtime": "0.2.4", + "@tybys/wasm-util": "0.9.0", + "@yarnpkg/lockfile": "1.1.0", + "@zkochan/js-yaml": "0.0.7", + "ansi-regex": "5.0.1", + "ansi-styles": "4.3.0", + "argparse": "2.0.1", + "balanced-match": "4.0.3", + "base64-js": "1.5.1", + "bl": "4.1.0", + "brace-expansion": "5.0.12", + "buffer": "5.7.1", + "bundle-name": "4.1.0", + "chalk": "4.1.2", + "cli-cursor": "3.1.0", + "cli-spinners": "2.6.1", + "cliui": "8.0.1", + "clone": "1.0.4", + "color-convert": "2.0.1", + "color-name": "1.1.4", + "default-browser": "5.2.1", + "default-browser-id": "5.0.0", + "defaults": "1.0.4", + "define-lazy-prop": "3.0.0", + "dotenv": "16.4.7", + "dotenv-expand": "12.0.3", + "ejs": "5.0.1", + "emoji-regex": "8.0.0", + "end-of-stream": "1.4.5", + "escalade": "3.2.0", + "escape-string-regexp": "1.0.5", + "fast-string-truncated-width": "3.0.3", + "fast-string-width": "3.0.2", + "fast-wrap-ansi": "0.2.0", + "figures": "3.2.0", + "fs-constants": "1.0.0", + "get-caller-file": "2.0.5", + "has-flag": "4.0.0", + "ieee754": "1.2.1", + "ignore": "7.0.6", + "inherits": "2.0.4", + "is-docker": "3.0.0", + "is-fullwidth-code-point": "3.0.0", + "is-inside-container": "1.0.0", + "is-interactive": "1.0.0", + "is-unicode-supported": "0.1.0", + "is-wsl": "3.1.0", + "isexe": "2.0.0", + "json5": "2.2.3", + "jsonc-parser": "3.3.1", + "lines-and-columns": "2.0.3", + "log-symbols": "4.1.0", + "mimic-fn": "2.1.0", + "minimatch": "10.2.5", + "minimist": "1.2.8", + "npm-run-path": "4.0.1", + "once": "1.4.0", + "onetime": "5.1.2", + "open": "10.1.0", + "ora": "5.4.1", + "path-key": "3.1.1", + "picocolors": "1.1.1", + "readable-stream": "3.6.2", + "require-directory": "2.1.1", + "resolve.exports": "2.0.3", + "restore-cursor": "3.1.0", + "run-applescript": "7.0.0", + "safe-buffer": "5.2.1", + "semver": "7.8.5", + "signal-exit": "3.0.7", + "sisteransi": "1.0.5", + "smol-toml": "1.9.0", + "string_decoder": "1.3.0", + "string-width": "4.2.3", + "strip-ansi": "6.0.1", + "strip-bom": "3.0.0", + "supports-color": "7.2.0", + "tar-stream": "2.2.0", + "tmp": "0.2.7", + "tsconfig-paths": "4.2.0", + "tslib": "2.8.1", + "undici": "7.29.0", + "util-deprecate": "1.0.2", + "wcwidth": "1.0.1", + "which": "3.0.1", + "wrap-ansi": "7.0.0", + "wrappy": "1.0.2", + "y18n": "5.0.8", + "yaml": "2.9.0", + "yargs": "17.7.2", + "yargs-parser": "21.1.1" + }, + "bin": { + "nx": "dist/bin/nx.js", + "nx-cloud": "dist/bin/nx-cloud.js" + }, + "optionalDependencies": { + "@nx/nx-darwin-arm64": "23.3.1", + "@nx/nx-darwin-x64": "23.3.1", + "@nx/nx-freebsd-x64": "23.3.1", + "@nx/nx-linux-arm-gnueabihf": "23.3.1", + "@nx/nx-linux-arm64-gnu": "23.3.1", + "@nx/nx-linux-arm64-musl": "23.3.1", + "@nx/nx-linux-x64-gnu": "23.3.1", + "@nx/nx-linux-x64-musl": "23.3.1", + "@nx/nx-win32-arm64-msvc": "23.3.1", + "@nx/nx-win32-x64-msvc": "23.3.1" + }, + "peerDependencies": { + "@swc-node/register": "^1.11.1", + "@swc/core": "^1.15.8" + }, + "peerDependenciesMeta": { + "@swc-node/register": { + "optional": true + }, + "@swc/core": { + "optional": true + } + } + }, + "node_modules/nx/node_modules/balanced-match": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", + "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/nx/node_modules/fast-wrap-ansi": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz", + "integrity": "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/nx/node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nx/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nx/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/nx/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nx/node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/nx/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/nx/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nx/node_modules/undici": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/nx/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/nx/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.8.tgz", + "integrity": "sha512-MitaVsCuCFIvOLLPIU7NnfrZvS9H9h7kwMUkDo+T2pEISaJD48IV9S8iIdXB7PsvvdxyYcsSTTrr90XKsbulNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-9.3.3.tgz", + "integrity": "sha512-NXAOdnEe5FsZJfT4oK84lE1Y5cFFdWlRuOo5tww8DyNMxyRXwn39fIkUtNLKppcPC+UYU/bXujNCUGDv01y7CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^5.0.4", + "p-timeout": "^7.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-7.0.2.tgz", + "integrity": "sha512-prbX4Z3YszrFNgH+MW5Zoeq3baXrMtP/MQnFeET90UB/GtGcGDQ5Usg9OCy6ETjTTntOw1SL2z9fMPUppN3Guw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pacote": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-22.0.0.tgz", + "integrity": "sha512-++VqeOZeL03uGM2MFLk96jGCSt1owBGkyFKoPr+trwNlZhCpjN2RrvwYxt8nTbs1wNMqSFYurq0TafVWkAIHig==", + "dev": true, + "license": "ISC", + "dependencies": { + "@gar/promise-retry": "^1.0.0", + "@npmcli/git": "^8.0.0", + "@npmcli/installed-package-contents": "^5.0.0", + "@npmcli/package-json": "^8.0.0", + "@npmcli/promise-spawn": "^10.0.0", + "@npmcli/run-script": "^11.0.0", + "cacache": "^21.0.1", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^14.0.0", + "npm-packlist": "^11.2.0", + "npm-pick-manifest": "^12.0.0", + "npm-registry-fetch": "^20.0.1", + "proc-log": "^7.0.0", + "sigstore": "^5.0.0", + "ssri": "^14.0.0", + "tar": "^7.4.3" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-conflict-json": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-6.0.0.tgz", + "integrity": "sha512-JIdsITvN0yVRpYxiip9DpCduYi75AryIDSCf6ez/Yul6PV46L0B/8SxSvLiMmWiUZKopLrD1BZgexxCfufoT9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^6.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-json/node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-path": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.1.0.tgz", + "integrity": "sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "protocols": "^2.0.0" + } + }, + "node_modules/parse-url": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/parse-url/-/parse-url-9.2.0.tgz", + "integrity": "sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/parse-path": "^7.0.0", + "parse-path": "^7.0.0" + }, + "engines": { + "node": ">=14.13.0" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pnpm": { + "version": "12.16.1", + "resolved": "https://registry.npmjs.org/pnpm/-/pnpm-12.16.1.tgz", + "integrity": "sha512-CR0d7nknV1Zhsl2rPCrjHK/BxU1rce1qZgDPDByxBDSkR+rhQBXxsj4ua+3u1535SpMuIWTSgyKA3OP7MvZW0Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "pn": "pn", + "pnpm": "pnpm", + "pnpx": "pnpx", + "pnx": "pnx" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@pnpm/exe.android-arm64": "12.16.1", + "@pnpm/exe.android-x64": "12.16.1", + "@pnpm/exe.darwin-arm64": "12.16.1", + "@pnpm/exe.darwin-x64": "12.16.1", + "@pnpm/exe.freebsd-x64": "12.16.1", + "@pnpm/exe.linux-arm64": "12.16.1", + "@pnpm/exe.linux-arm64-musl": "12.16.1", + "@pnpm/exe.linux-ppc64": "12.16.1", + "@pnpm/exe.linux-riscv64": "12.16.1", + "@pnpm/exe.linux-s390x": "12.16.1", + "@pnpm/exe.linux-x64": "12.16.1", + "@pnpm/exe.linux-x64-musl": "12.16.1", + "@pnpm/exe.win32-arm64": "12.16.1", + "@pnpm/exe.win32-x64": "12.16.1" + } + }, + "node_modules/pnpm10": { + "name": "pnpm", + "version": "10.33.0", + "resolved": "https://registry.npmjs.org/pnpm/-/pnpm-10.33.0.tgz", + "integrity": "sha512-EFaLtKavtYyes2MNqQzJUWQXq+vT+rvmc58K55VyjaFJHp21pUTHatjrdXD1xLs9bGN7LLQb/c20f6gjyGSTGQ==", + "dev": true, + "license": "MIT", + "bin": { + "pnpm": "bin/pnpm.cjs", + "pnpx": "bin/pnpx.cjs" + }, + "engines": { + "node": ">=18.12" + }, + "funding": { + "url": "https://opencollective.com/pnpm" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.7.tgz", + "integrity": "sha512-wWBro/7hJ40ye6TPiXkUQoXKCw23xSsjriUvqd7Uls9Sso3oLiON1flc18cCdnjgCRXw4fBVqUJJeOdDTh3P3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.1.tgz", + "integrity": "sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proc-log": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-7.0.0.tgz", + "integrity": "sha512-FYgfaA69XZ93zaXLoMNQ+ViDXGGBgR8aLh03txzcFhV+9xOXx7+8DLCULrKKpR9+GsH9ZfHm82aSUPpozX0Ztg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/proggy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/proggy/-/proggy-4.0.0.tgz", + "integrity": "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/promise-all-reject-late": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", + "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", + "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/promise-call-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.2.tgz", + "integrity": "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==", + "dev": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/promzard": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/promzard/-/promzard-4.0.0.tgz", + "integrity": "sha512-zqf38XGznB+ECFHQPGt0RNQCh0YF+r5iac0EZloSQlEWDxt0vCS39PEtUShBJOZU0tcJ+USPvmnyGo+3CEFwcQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "read": "^6.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/protocols": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.2.tgz", + "integrity": "sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-agent-negotiate": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-agent-negotiate/-/proxy-agent-negotiate-1.1.0.tgz", + "integrity": "sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "kerberos": "^2.0.0" + }, + "peerDependenciesMeta": { + "kerberos": { + "optional": true + } + } + }, + "node_modules/read": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/read/-/read-6.0.0.tgz", + "integrity": "sha512-Tg/LaEke7h9SDbp0xJJj53oa4s1VPPySVnxQF9nUS9DgA25M/ZCLk7RVF1kHjytC87XbkgQKmQ/79+DQgEIqLA==", + "dev": true, + "license": "ISC", + "dependencies": { + "mute-stream": "^4.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/read-cmd-shim": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-7.0.1.tgz", + "integrity": "sha512-GhAsW4iJS0xNqv8P6HocT3gb5HCKHtEBKLIN7fE2oWr27jGIEihu48lXyGGeexkbiLv1XAbwyXvgjYMfT81kZA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/read/node_modules/mute-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-4.0.0.tgz", + "integrity": "sha512-gSrprq0fJ3EiOErzjdIZrjysVVmJ4uu1QWfCDss5LypA5OXvrMje5Ym5z6V6RLyJ2eF87lasX7t6a0AnFvZblg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-async": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.6.tgz", + "integrity": "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-5.0.0.tgz", + "integrity": "sha512-hJqJfoG/e4qFQaauQL00c6J6FrHLBGKtkFvW3JbTSIEFOhLrSjdSM/gWd/yUOfYo/gsERehTXGC1VZWX+9X4Dg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^5.0.0", + "@sigstore/core": "^4.0.0", + "@sigstore/protobuf-specs": "^0.5.0", + "@sigstore/sign": "^5.0.0", + "@sigstore/tuf": "^5.0.0", + "@sigstore/verify": "^4.0.0" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "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/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/smol-toml": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.0.tgz", + "integrity": "sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, + "node_modules/socks": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.10.tgz", + "integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-10.1.0.tgz", + "integrity": "sha512-WlMj/67cEJ6MDI1OcsnjuYKDNDoyPCCYZ249kuuXPiMDw9F8PXkVaQ7YWu3siTydfQ/4BEZcvGzu+aYvz7dDCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "9.0.0", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.24", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.24.tgz", + "integrity": "sha512-cLS9TtWkIQFyLkJ3/5aFQAOHOSKTlOs/7WDut/XPSdjom1fJhUdkepeJAKXa9Y05+FabHd0sti+Et559vDtkpQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/ssri": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-14.0.0.tgz", + "integrity": "sha512-jQxKI0yx0ZnTKrqjKkLDV2DXkBQn3k49JVmVqDGcDwKDtGDbImD/GXsq04KD0VVzCQQ9wZJYal3RwR1GzWTSow==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.3.0.tgz", + "integrity": "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.5.0.tgz", + "integrity": "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/treeverse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz", + "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tuf-js": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-6.0.1.tgz", + "integrity": "sha512-o59N1PU8xk0bBV3HpJ4jjwldIeze17yXW1XTv02CEEKdC3E/tnJDU3CPWDNT4IZpkx9Srv9v1DJZ+JTdo46THg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@gar/promise-retry": "^1.0.3", + "@tufjs/models": "5.0.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici": { + "version": "8.12.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.12.3.tgz", + "integrity": "sha512-+fdndUTwBP86cVjihTp21hZHo7d6oj1c/47XPA3lL8mM0XHs+C4EkcjmgRsFnFk2f/UONGDy9ItBItmXsLOQHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22.19.0" + } + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/validate-npm-package-name": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-8.0.0.tgz", + "integrity": "sha512-SCv6OOV6Xj2/3cXy3dGmADluJTNcL3o7hZAglNPTe+WYuEuvxgJzxPrSDLZhF+CwyQOubqgecjMmTJGMVLWjYQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/verkit": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.5.0.tgz", + "integrity": "sha512-u4J1hr6Y+C/hit34GwL3eIcUf3wngT8TlYUjK7tk0jxydKLAq3sYvifjdTDA6U3KCKRcQCQDGf/srjxfZF3l7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/which": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-7.0.0.tgz", + "integrity": "sha512-RancgH2dmbLdHl6LRhEqvklWMgl/Hdnun0Y90KhBOLkMefg8Qa7/Zel8Sm+8HEcP6DEjzsWzpkuBQEZok58isA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/which-command": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/which-command/-/which-command-0.1.0.tgz", + "integrity": "sha512-XZyoF5/5hZtXitIwzrU4NKK+Wtbb9aB9CezUEw2Q0wlYK8NUYQxC1rRXgNueYLtBAJwXIb+/tFVk4dozciNJMA==", + "dev": true, + "license": "MIT", + "bin": { + "which-command": "cli.js" + }, + "engines": { + "node": ">=22" + }, + "funding": { + "url": "https://github.com/sindresorhus/which-command?sponsor=1" + } + }, + "node_modules/which/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-8.0.0.tgz", + "integrity": "sha512-dYwyZredl67GyLLIHJnRM3h2PcOmN5SkcgC7eM5DPDEOEl6dLFqVrMg3F1Ea32usj4VSVZtd2H4MtKTNOf6nPg==", + "dev": true, + "license": "ISC", + "dependencies": { + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.2.0.tgz", + "integrity": "sha512-9OpKOLeaoNFecEp7P6iYbzze/5CqWoH7N3SMs/6y1XF4nMvFMspEGZzJ6uFi9MmQwXhyzqYoSEKO3tvA3Z/o2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^9.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "string-width": "^8.2.1", + "y18n": "^5.0.5", + "yargs-parser": "^22.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.5.0.tgz", + "integrity": "sha512-zhvbbbdt1kbw7EJ9OQoxmQcuaNR9P5kTj7e/YdmOSU10aHWbycofJxA6jKfoEy3vlLdctOF9ZHaxLtmSm998wQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/yargs/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/yargs/node_modules/cliui": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", + "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/yargs/node_modules/cliui/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/yargs/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/yargs/node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/yoctocolors": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/actions/release/prepare/package.json b/actions/release/prepare/package.json index c5277706..5f97237d 100644 --- a/actions/release/prepare/package.json +++ b/actions/release/prepare/package.json @@ -3,9 +3,13 @@ "private": true, "scripts": { "test": "node --test", - "test:ci": "node --test" + "test:watch": "node --test --watch", + "test:ci": "node --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=coverage/lcov.info --test" }, "devDependencies": { - "@actions/exec": "^1.1.1" + "@actions/exec": "^1.1.1", + "lerna": "10.1.1", + "pnpm": "12.16.1", + "pnpm10": "npm:pnpm@10.33.0" } } diff --git a/actions/release/verify/README.md b/actions/release/verify/README.md index 0090cd10..edaa777a 100644 --- a/actions/release/verify/README.md +++ b/actions/release/verify/README.md @@ -2,12 +2,6 @@ # ![Icon](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXItY2hlY2stY2lyY2xlIiBjb2xvcj0iYmx1ZSI+PHBhdGggZD0iTTIyIDExLjA4VjEyYTEwIDEwIDAgMSAxLTUuOTMtOS4xNCI+PC9wYXRoPjxwb2x5bGluZSBwb2ludHM9IjIyIDQgMTIgMTQuMDEgOSAxMS4wMSI+PC9wb2x5bGluZT48L3N2Zz4=) GitHub Action: Verify release inputs -
- Verify release inputs -
- ---- - @@ -30,11 +24,12 @@ Verify the prepared npm package and package tarball artifact source before creat ## Inputs -| **Input** | **Description** | **Required** | **Default** | -| --------------------------------- | ----------------------------------------------------------------------------------------------- | ------------ | ----------- | -| **`packages`** | JSON array containing one public root package from release preparation. | **true** | - | -| **`package-tarball-artifact-id`** | Package tarball artifact ID from this run and prepared commit, regardless of the producing job. | **true** | - | -| **`working-directory`** | Project directory relative to the repository root, matching release preparation. | **false** | `.` | +| **Input** | **Description** | **Required** | **Default** | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | +| **`packages`** | JSON package list from release preparation. | **true** | - | +| **`package-tarball-artifact-id`** | Package tarball artifact ID from this run and prepared commit, regardless of the producing job. | **false** | - | +| **`package-tarball-artifacts`** | JSON object mapping prepared package directories to validated tarball artifact IDs. Supply this or package-tarball-artifact-id. | **false** | - | +| **`working-directory`** | Project directory relative to the repository root, matching release preparation. | **false** | `.` | @@ -43,9 +38,10 @@ Verify the prepared npm package and package tarball artifact source before creat ## Outputs -| **Output** | **Description** | -| --------------- | -------------------------------------------------------- | -| **`published`** | Whether this tag already has a published GitHub release. | +| **Output** | **Description** | +| -------------- | ----------------------------------------------------------------------------- | +| **`packages`** | Prepared packages paired with their validated tarball artifact IDs. | +| **`releases`** | Verified release identities, note scopes and asset selectors for publication. | @@ -82,16 +78,18 @@ This documentation was automatically generated by [CI Dokumentor](https://github ## Usage ```yaml -- uses: hoverkraft-tech/ci-github-nodejs/actions/release/verify@97f64bc080b78daca8fb3ece49de448aaac83429 # 1.1.0 +- uses: hoverkraft-tech/ci-github-nodejs/actions/release/verify@ with: - # JSON array containing one public root package from release preparation. + # JSON package list from release preparation. # This input is required. packages: "" # Package tarball artifact ID from this run and prepared commit, regardless of the producing job. - # This input is required. package-tarball-artifact-id: "" + # JSON object mapping prepared package directories to validated tarball artifact IDs. Supply this or package-tarball-artifact-id. + package-tarball-artifacts: "" + # Project directory relative to the repository root, matching release preparation. # Default: `.` working-directory: . diff --git a/actions/release/verify/action.yml b/actions/release/verify/action.yml index d93e6af7..0dcdfc46 100644 --- a/actions/release/verify/action.yml +++ b/actions/release/verify/action.yml @@ -6,32 +6,63 @@ branding: color: blue inputs: packages: - description: JSON array containing one public root package from release preparation. + description: JSON package list from release preparation. required: true package-tarball-artifact-id: description: Package tarball artifact ID from this run and prepared commit, regardless of the producing job. - required: true + default: "" + package-tarball-artifacts: + description: JSON object mapping prepared package directories to validated tarball artifact IDs. Supply this or package-tarball-artifact-id. + default: "" working-directory: description: Project directory relative to the repository root, matching release preparation. default: . outputs: - published: - description: Whether this tag already has a published GitHub release. - value: ${{ steps.verify.outputs.published }} + packages: + description: Prepared packages paired with their validated tarball artifact IDs. + value: ${{ steps.verify.outputs.packages }} + releases: + description: Verified release identities, note scopes and asset selectors for publication. + value: ${{ steps.verify.outputs.releases }} runs: using: composite steps: - - id: verify + - id: prepare uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: ACTION_PATH: ${{ github.action_path }} PACKAGES: ${{ inputs.packages }} + PACKAGE_ARTIFACTS: ${{ inputs.package-tarball-artifacts }} PACKAGE_ARTIFACT_ID: ${{ inputs.package-tarball-artifact-id }} WORKING_DIRECTORY: ${{ inputs.working-directory }} with: script: | const releaseVerification = require(`${process.env.ACTION_PATH}/index.js`); - const packages = releaseVerification.parsePackages(process.env.PACKAGES); - await releaseVerification.verifyArtifact({ github, context, artifactId: process.env.PACKAGE_ARTIFACT_ID }); - const releases = await releaseVerification.verifyReleases({ github, context, packages, workingDirectory: process.env.WORKING_DIRECTORY }); - core.setOutput('published', String(releases[0].published)); + const packages = releaseVerification.bindArtifacts(releaseVerification.parsePackages(process.env.PACKAGES), process.env.PACKAGE_ARTIFACT_ID, process.env.PACKAGE_ARTIFACTS); + const releases = releaseVerification.prepareReleases({ context, packages, workingDirectory: process.env.WORKING_DIRECTORY }); + core.setOutput('artifact-ids', packages.map(item => item.artifactId)); + core.setOutput('tags', releases.map(release => release.tag)); + core.setOutput('releases', releases); + core.setOutput('packages', packages); + - id: identity + uses: hoverkraft-tech/ci-github-publish/actions/release/verify@fe4393361d3f5d9f2443ba1ff61d86d5c94c7a63 + with: + artifact-ids: ${{ steps.prepare.outputs.artifact-ids }} + tags: ${{ steps.prepare.outputs.tags }} + require-tags: "true" + - id: verify + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + PACKAGES: ${{ steps.prepare.outputs.packages }} + RELEASES: ${{ steps.prepare.outputs.releases }} + VERIFIED_RELEASES: ${{ steps.identity.outputs.releases }} + with: + script: | + const verified = new Map(JSON.parse(process.env.VERIFIED_RELEASES).map(release => [release.tag, release.published])); + const releases = JSON.parse(process.env.RELEASES).map(release => { + const published = verified.get(release.tag); + if (typeof published !== 'boolean') throw new Error('Missing verified release state for ' + release.tag); + return { ...release, published }; + }); + core.setOutput('releases', releases); + core.setOutput('packages', JSON.parse(process.env.PACKAGES)); diff --git a/actions/release/verify/coverage/.gitignore b/actions/release/verify/coverage/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/actions/release/verify/coverage/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/actions/release/verify/index.js b/actions/release/verify/index.js index deaea44e..08b45dbb 100644 --- a/actions/release/verify/index.js +++ b/actions/release/verify/index.js @@ -21,8 +21,10 @@ function versionFromTag(tag, directory, relativeDirectory) { function parsePackages(input, version) { const packages = JSON.parse(input); - if (!Array.isArray(packages) || packages.length !== 1) - throw new Error("Expected exactly one public npm package."); + if (!Array.isArray(packages) || !packages.length || packages.length > 256) + throw new Error( + "Expected a nonempty package array with at most 256 entries.", + ); for (const item of packages) { if ( !item || @@ -31,40 +33,50 @@ function parsePackages(input, version) { item.slug !== item.name.replace(/^@/, "").replace("/", "-") || typeof item.version !== "string" || !/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(item.version) || - item.directory !== "." + typeof item.directory !== "string" || + !item.directory || + path.isAbsolute(item.directory) || + item.directory.split(/[\\/]/).includes("..") ) throw new Error( - "Expected a valid package name, slug, stable version and root package directory.", + "Expected valid package names, slugs, stable versions and relative directories.", ); } + if (new Set(packages.map((item) => item.slug)).size !== packages.length) + throw new Error("Package slugs must be unique."); if (version && packages.some((item) => item.version !== version)) throw new Error("Package versions must match the prepared release tag."); return packages; } -async function findRelease(github, repo, tag) { - try { - const { data } = await github.rest.repos.getReleaseByTag({ - ...repo, - tag, - }); - return data; - } catch (error) { - if (error.status !== 404) throw error; - const releases = await github.paginate(github.rest.repos.listReleases, { - ...repo, - per_page: 100, - }); - return releases.find((release) => release.tag_name === tag); - } +function bindArtifacts(packages, artifactId = "", artifactsInput = "") { + if (Boolean(artifactId) === Boolean(artifactsInput)) + throw new Error( + "Supply either one package artifact ID or a directory-to-artifact map.", + ); + if (artifactId && packages.length !== 1) + throw new Error("Multiple packages require a directory-to-artifact map."); + const artifacts = artifactsInput + ? JSON.parse(artifactsInput) + : { [packages[0].directory]: artifactId }; + if (!artifacts || Array.isArray(artifacts) || typeof artifacts !== "object") + throw new Error("Package artifacts must be a directory-to-artifact map."); + const result = packages.map((item) => { + const id = Object.hasOwn(artifacts, item.directory) + ? String(artifacts[item.directory]) + : ""; + if (!/^[1-9][0-9]*$/.test(id) || !Number.isSafeInteger(Number(id))) + throw new Error( + `Missing or invalid package artifact for ${item.directory}.`, + ); + return { ...item, artifactId: id }; + }); + if (new Set(result.map((item) => item.artifactId)).size !== result.length) + throw new Error("Each package must have its own tarball artifact."); + return result; } -async function verifyReleases({ - github, - context, - packages, - workingDirectory = ".", -}) { +function prepareReleases({ context, packages, workingDirectory = "." }) { if (!context.ref.startsWith("refs/tags/")) throw new Error("Publish from the prepared release tag."); const sourceTag = context.ref.slice("refs/tags/".length); @@ -78,39 +90,40 @@ async function verifyReleases({ ); if (relativeDirectory.startsWith("..") || path.isAbsolute(relativeDirectory)) throw new Error("Release directory must be inside the repository."); - const [item] = parsePackages(JSON.stringify(packages)); - if (item.tag !== sourceTag) - throw new Error("Package tag must match the workflow source tag."); - if (versionFromTag(sourceTag, directory, relativeDirectory) !== item.version) - throw new Error("Package version must match the release tag."); - const { data: commit } = await github.rest.repos.getCommit({ - ...context.repo, - ref: sourceTag, - }); - if (commit.sha !== context.sha) - throw new Error("Release tag must identify the prepared source commit."); - const release = await findRelease(github, context.repo, sourceTag); - return [{ tag: sourceTag, published: Boolean(release && !release.draft) }]; -} - -async function verifyArtifact({ github, context, artifactId }) { - if ( - !/^[1-9][0-9]*$/.test(artifactId) || - !Number.isSafeInteger(Number(artifactId)) - ) - throw new Error("Expected one positive package tarball artifact ID."); - const { data: artifact } = await github.rest.actions.getArtifact({ - ...context.repo, - artifact_id: Number(artifactId), - }); + const releases = new Map(); + for (const item of packages) { + const independent = item.tag === `${item.name}@${item.version}`; + if ( + typeof item.tag !== "string" || + (!independent && + versionFromTag(item.tag, directory, relativeDirectory) !== item.version) + ) + throw new Error("Package versions must match their release tags."); + if (!releases.has(item.tag)) { + releases.set(item.tag, { + tag: item.tag, + packageName: independent ? item.name : "", + workingDirectory: independent + ? path.posix.join(workingDirectory, item.directory) + : workingDirectory, + artifactPattern: independent + ? `release-assets-${item.slug}` + : "release-assets-*", + }); + } + } + if (!releases.has(sourceTag)) + throw new Error("Package list must include the workflow source tag."); if ( - artifact.expired || - artifact.workflow_run?.id !== context.runId || - artifact.workflow_run?.head_sha !== context.sha + releases.size > 1 && + [...releases.values()].some((release) => !release.packageName) ) - throw new Error( - "Package tarball artifact must be unexpired and belong to this run and prepared commit.", - ); + throw new Error("Multiple releases require independent package tags."); + return [...releases.values()]; } -module.exports = { parsePackages, verifyReleases, verifyArtifact }; +module.exports = { + parsePackages, + bindArtifacts, + prepareReleases, +}; diff --git a/actions/release/verify/index.test.js b/actions/release/verify/index.test.js index 6618b303..1fcd0bcd 100644 --- a/actions/release/verify/index.test.js +++ b/actions/release/verify/index.test.js @@ -14,118 +14,50 @@ const item = { directory: ".", tag: "v1.2.3", }; -const artifact = { - id: 10, - expired: false, - workflow_run: { id: 42, head_sha: context.sha }, -}; -const verifyArtifact = (data = artifact, artifactId = "10") => - releaseVerification.verifyArtifact({ - context, - artifactId, - github: { - rest: { - actions: { - getArtifact: async ({ artifact_id }) => { - assert.equal(artifact_id, 10); - return { data }; - }, - }, - }, +test("prepares independent release scopes and requires the workflow source tag", () => { + const packages = [ + { ...item, tag: "@example/cli@1.2.3", directory: "packages/cli" }, + { + name: "@example/core", + slug: "example-core", + version: "2.0.1", + tag: "@example/core@2.0.1", + directory: "packages/core", }, - }); - -test("release verifies the source tag, committed version and publication state", async () => { - const github = { - rest: { - repos: { - getCommit: async () => ({ data: { sha: context.sha } }), - getReleaseByTag: async () => ({ data: { draft: false } }), + ]; + const prepare = (values = packages) => + releaseVerification.prepareReleases({ + context: { + ...context, + workspace: "/tmp/project", + ref: `refs/tags/${packages[0].tag}`, }, - }, - }; - const verify = ( - changes = {}, - ref = `refs/tags/${item.tag}`, - workingDirectory = ".", - ) => - releaseVerification.verifyReleases({ - github, - context: { ...context, workspace: "/tmp/project", ref }, - packages: [{ ...item, ...changes }], - workingDirectory, + packages: values, }); - assert.deepEqual(await verify(), [{ tag: item.tag, published: true }]); - github.rest.repos.getReleaseByTag = async () => ({ data: { draft: true } }); - assert.equal((await verify())[0].published, false); - github.rest.repos.getReleaseByTag = async () => { - throw Object.assign(new Error("Not found"), { status: 404 }); - }; - github.paginate = async () => [{ tag_name: item.tag, draft: true }]; - assert.equal((await verify())[0].published, false); - github.paginate = async () => []; - assert.equal((await verify())[0].published, false); - github.rest.repos.getReleaseByTag = async () => { - throw Object.assign(new Error("Forbidden"), { status: 403 }); - }; - await assert.rejects(verify(), /Forbidden/); - - await assert.rejects(verify({}, "refs/heads/main"), /prepared release tag/); - await assert.rejects(verify({ tag: "v2.0.0" }), /source tag/); - await assert.rejects(verify({ version: "1.2.4" }), /match the release tag/); - await assert.rejects( - verify({}, `refs/tags/${item.tag}`, ".."), - /inside the repository/, + const releases = prepare(); + assert.equal(releases.length, 2); + assert.equal(releases[1].workingDirectory, "packages/core"); + assert.equal(releases[1].artifactPattern, "release-assets-example-core"); + assert.throws(() => prepare([packages[1]]), /source tag/); + assert.throws( + () => prepare([{ ...packages[0], tag: "@other/name@1.2.3" }]), + /stable version tag/, ); - github.rest.repos.getCommit = async () => ({ data: { sha: "other-commit" } }); - await assert.rejects(verify(), /prepared source commit/); -}); - -test("release accepts a package tarball artifact from this run and prepared commit, including reruns", async () => { - await assert.doesNotReject(verifyArtifact()); }); -test("rejects expired artifacts, other runs, older commits and unavailable source identity", async () => { - for (const data of [ - { ...artifact, expired: true }, - { ...artifact, workflow_run: { id: 41, head_sha: context.sha } }, - { ...artifact, workflow_run: { id: 42, head_sha: "old" } }, - { ...artifact, workflow_run: undefined }, - ]) { - await assert.rejects(verifyArtifact(data), /this run and prepared commit/); - } -}); - -test("rejects missing, multiple or invalid package tarball artifact IDs before downloading", async () => { - for (const id of ["", "0", "1,2", "NaN", "-1", "9007199254740992"]) { - await assert.rejects( - verifyArtifact(artifact, id), - /positive package tarball artifact ID/, - ); - } -}); - -test("missing or inaccessible package tarball artifacts stop release", async () => { - const github = { - rest: { - actions: { - getArtifact: async () => { - throw new Error("Artifact not found"); - }, - }, - }, - }; - await assert.rejects( - releaseVerification.verifyArtifact({ github, context, artifactId: "10" }), - /Artifact not found/, - ); -}); - -test("release accepts one prepared npm package with its prepared identity", () => { +test("release accepts prepared single-package and workspace lists with their prepared identities", () => { + const packages = [ + item, + { ...item, name: "other", slug: "other", directory: "packages/other" }, + ]; assert.deepEqual( releaseVerification.parsePackages(JSON.stringify([item]), item.version), [item], ); + assert.deepEqual( + releaseVerification.parsePackages(JSON.stringify(packages), item.version), + packages, + ); }); test("rejects ambiguous names, unsafe paths, mismatched versions and invalid package lists", () => { @@ -134,7 +66,6 @@ test("rejects ambiguous names, unsafe paths, mismatched versions and invalid pac {}, [null], [item, item], - [{ ...item, directory: "packages/cli" }], [{ ...item, slug: "wrong" }], [{ ...item, name: "../bad" }], [{ ...item, directory: "../outside" }], @@ -153,21 +84,72 @@ test("rejects ambiguous names, unsafe paths, mismatched versions and invalid pac } }); -test("verifies tags derived from nested project directories containing punctuation", async () => { +test("prepares tags derived from nested project directories containing punctuation", () => { const tag = "my-package-1.2.3"; - const releases = await releaseVerification.verifyReleases({ + const releases = releaseVerification.prepareReleases({ context: { ...context, workspace: "/tmp/project", ref: `refs/tags/${tag}` }, packages: [{ ...item, tag }], workingDirectory: " ---My Package--- ", - github: { - rest: { - repos: { - getCommit: async () => ({ data: { sha: context.sha } }), - getReleaseByTag: async () => ({ data: { draft: true } }), - }, - }, - }, }); assert.equal(releases[0].tag, tag); - assert.equal(releases[0].published, false); +}); + +test("binds validated CI tarballs to selected packages and rejects ambiguous artifacts", () => { + const packages = [ + item, + { ...item, name: "other", slug: "other", directory: "packages/other" }, + ]; + assert.equal( + releaseVerification.bindArtifacts([item], "10")[0].artifactId, + "10", + ); + assert.deepEqual( + releaseVerification + .bindArtifacts( + packages, + "", + JSON.stringify({ ".": "10", "packages/other": "11", unchanged: "12" }), + ) + .map((p) => p.artifactId), + ["10", "11"], + ); + for (const [selected, single, map] of [ + [packages, "10", ""], + [[item], "10", '{".":"10"}'], + [[item], "", ""], + [packages, "", '{".":"10"}'], + [packages, "", '{".":"10","packages/other":"10"}'], + [[item], "", "[]"], + [[item], "", '{".":"0"}'], + [[item], "", '{".":"9007199254740992"}'], + ]) + assert.throws( + () => releaseVerification.bindArtifacts(selected, single, map), + /artifact|map/, + ); +}); + +test("rejects unprepared refs, escaped scopes and mixed release modes", () => { + const prepare = (overrides = {}) => + releaseVerification.prepareReleases({ + context: { + ...context, + workspace: "/tmp/project", + ref: "refs/tags/v1.2.3", + }, + packages: [item], + ...overrides, + }); + assert.throws( + () => prepare({ context: { ...context, ref: "refs/heads/main" } }), + /prepared release tag/, + ); + assert.throws( + () => prepare({ workingDirectory: "../outside" }), + /inside the repository/, + ); + assert.throws( + () => prepare({ packages: [item, { ...item, tag: "@example/cli@1.2.3" }] }), + /independent package tags/, + ); }); diff --git a/actions/release/verify/package.json b/actions/release/verify/package.json index 91134a23..0e26e7f0 100644 --- a/actions/release/verify/package.json +++ b/actions/release/verify/package.json @@ -3,6 +3,7 @@ "private": true, "scripts": { "test": "node --test", - "test:ci": "node --test" + "test:watch": "node --test --watch", + "test:ci": "node --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout --test-reporter=lcov --test-reporter-destination=coverage/lcov.info --test" } } diff --git a/actions/setup-node/README.md b/actions/setup-node/README.md index e2830b4d..68e24368 100644 --- a/actions/setup-node/README.md +++ b/actions/setup-node/README.md @@ -34,7 +34,7 @@ Action to setup Node.js and install dependencies according to the package manage ## Usage ```yaml -- uses: hoverkraft-tech/ci-github-nodejs/actions/setup-node@97f64bc080b78daca8fb3ece49de448aaac83429 # 1.1.0 +- uses: hoverkraft-tech/ci-github-nodejs/actions/setup-node@ with: # Install project dependencies after configuring Node.js and the package manager. # Set false when only tooling is needed, such as packaging prepared build files, @@ -95,6 +95,7 @@ Action to setup Node.js and install dependencies according to the package manage | **Output** | **Description** | | ------------------------ | ---------------------------------------------------- | +| **`package-manager`** | Detected package manager (`npm`, `pnpm` or `yarn`). | | **`run-script-command`** | The command to run a script in the package.json file | diff --git a/actions/setup-node/action.yml b/actions/setup-node/action.yml index 265b35f5..d3927f2e 100644 --- a/actions/setup-node/action.yml +++ b/actions/setup-node/action.yml @@ -36,6 +36,9 @@ inputs: default: "" outputs: + package-manager: + description: Detected package manager (`npm`, `pnpm` or `yarn`). + value: ${{ steps.get-package-manager.outputs.package-manager }} run-script-command: description: "The command to run a script in the package.json file" value: ${{ steps.get-package-manager.outputs.run-script-command }} @@ -56,7 +59,7 @@ runs: done # jscpd:ignore-start - id: working-directory - uses: hoverkraft-tech/ci-github-common/actions/working-directory@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/actions/working-directory@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 with: working-directory: ${{ inputs.working-directory }} enforce-path-in-workspace: "false" diff --git a/actions/test/README.md b/actions/test/README.md index a5259846..d7a6c305 100644 --- a/actions/test/README.md +++ b/actions/test/README.md @@ -2,12 +2,6 @@ # ![Icon](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJmZWF0aGVyIGZlYXRoZXItY2hlY2stc3F1YXJlIiBjb2xvcj0iYmx1ZSI+PHBvbHlsaW5lIHBvaW50cz0iOSAxMSAxMiAxNCAyMiA0Ij48L3BvbHlsaW5lPjxwYXRoIGQ9Ik0yMSAxMnY3YTIgMiAwIDAgMS0yIDJINWEyIDIgMCAwIDEtMi0yVjVhMiAyIDAgMCAxIDItMmgxMSI+PC9wYXRoPjwvc3ZnPg==) GitHub Action: Test -
- Test -
- ---- - @@ -42,7 +36,7 @@ permissions: ## Usage ```yaml -- uses: hoverkraft-tech/ci-github-nodejs/actions/test@97f64bc080b78daca8fb3ece49de448aaac83429 # 1.1.0 +- uses: hoverkraft-tech/ci-github-nodejs/actions/test@ with: # Disable project and dependency lifecycle scripts during installation. # Set false to allow installation hooks, subject to the package manager's script approval settings. @@ -72,14 +66,14 @@ permissions: command: test:ci # Code coverage reporter to use. Supported values: - # - `github`: Parse coverage reports via [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/parse-ci-reports) action, with GitHub summaries/PR comments + # - `github`: Parse coverage reports via [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) action, with GitHub summaries/PR comments # - `codecov`: Upload coverage to Codecov # - `""` or `null`: No coverage reporting # # Default: `github` coverage: github - # Optional test and coverage report paths forwarded to the [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/parse-ci-reports) action. + # Optional test and coverage report paths forwarded to the [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) action. # Supports multiple formats (Cobertura, OpenCover, lcov, etc.). # Provide absolute paths or paths relative to the working directory. # Multiple entries can be separated by newlines, commas, or semicolons. @@ -87,7 +81,7 @@ permissions: report-file: "" # Optional path mapping to adjust file paths in test and coverage reports. - # See the [parse-ci-reports documentation](https://hoverkraft-tech/ci-github-common/actions/parse-ci-reports) for details. + # See the [parse-ci-reports documentation](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) for details. path-mapping: "" # GitHub token for coverage PR comments. @@ -104,37 +98,37 @@ permissions: ## Inputs -| **Input** | **Description** | **Required** | **Default** | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | -| **`ignore-scripts`** | Disable project and dependency lifecycle scripts during installation. | **false** | `true` | -| | Set false to allow installation hooks, subject to the package manager's script approval settings. | | | -| | Explicit commands are unaffected. Does not apply in container mode, which skips installation. | | | -| **`working-directory`** | Working directory where test commands are executed. | **false** | `.` | -| | Can be absolute or relative to the repository root. | | | -| **`container`** | Whether running in container mode (skips checkout and node setup) | **false** | `false` | -| **`command`** | npm/pnpm/Yarn script command to run for testing. | **false** | `test:ci` | -| | This should be a script defined in your `package.json`. | | | -| | The command should generate coverage report files in a standard format (Cobertura XML, lcov, etc.). | | | -| | | | | -| | Vitest: `vitest run --reporter=default --reporter=junit --outputFile=junit.xml --coverage.enabled --coverage.reporter=lcov --coverage.reporter=text` | | | -| | Jest: `jest --ci --reporters=default --reporters=jest-junit --coverage` | | | -| **`coverage`** | Code coverage reporter to use. Supported values: | **false** | `github` | -| | - `github`: Parse coverage reports via [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/parse-ci-reports) action, with GitHub summaries/PR comments | | | -| | - `codecov`: Upload coverage to Codecov | | | -| | - `""` or `null`: No coverage reporting | | | -| **`report-file`** | Optional test and coverage report paths forwarded to the [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/parse-ci-reports) action. | **false** | - | -| | Supports multiple formats (Cobertura, OpenCover, lcov, etc.). | | | -| | Provide absolute paths or paths relative to the working directory. | | | -| | Multiple entries can be separated by newlines, commas, or semicolons. | | | -| | When omitted, the action falls back to `auto:test,auto:coverage` detection. | | | -| **`path-mapping`** | Optional path mapping to adjust file paths in test and coverage reports. | **false** | - | -| | See the [parse-ci-reports documentation](https://hoverkraft-tech/ci-github-common/actions/parse-ci-reports) for details. | | | -| **`github-token`** | GitHub token for coverage PR comments. | **false** | - | -| | Required when coverage is set to `github`. | | | -| | Requires permissions to create and update PR comments: | | | -| | | | | -| | - `issues: write` | | | -| | - `pull-requests: write` | | | +| **Input** | **Description** | **Required** | **Default** | +| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ----------- | +| **`ignore-scripts`** | Disable project and dependency lifecycle scripts during installation. | **false** | `true` | +| | Set false to allow installation hooks, subject to the package manager's script approval settings. | | | +| | Explicit commands are unaffected. Does not apply in container mode, which skips installation. | | | +| **`working-directory`** | Working directory where test commands are executed. | **false** | `.` | +| | Can be absolute or relative to the repository root. | | | +| **`container`** | Whether running in container mode (skips checkout and node setup) | **false** | `false` | +| **`command`** | npm/pnpm/Yarn script command to run for testing. | **false** | `test:ci` | +| | This should be a script defined in your `package.json`. | | | +| | The command should generate coverage report files in a standard format (Cobertura XML, lcov, etc.). | | | +| | | | | +| | Vitest: `vitest run --reporter=default --reporter=junit --outputFile=junit.xml --coverage.enabled --coverage.reporter=lcov --coverage.reporter=text` | | | +| | Jest: `jest --ci --reporters=default --reporters=jest-junit --coverage` | | | +| **`coverage`** | Code coverage reporter to use. Supported values: | **false** | `github` | +| | - `github`: Parse coverage reports via [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) action, with GitHub summaries/PR comments | | | +| | - `codecov`: Upload coverage to Codecov | | | +| | - `""` or `null`: No coverage reporting | | | +| **`report-file`** | Optional test and coverage report paths forwarded to the [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) action. | **false** | - | +| | Supports multiple formats (Cobertura, OpenCover, lcov, etc.). | | | +| | Provide absolute paths or paths relative to the working directory. | | | +| | Multiple entries can be separated by newlines, commas, or semicolons. | | | +| | When omitted, the action falls back to `auto:test,auto:coverage` detection. | | | +| **`path-mapping`** | Optional path mapping to adjust file paths in test and coverage reports. | **false** | - | +| | See the [parse-ci-reports documentation](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) for details. | | | +| **`github-token`** | GitHub token for coverage PR comments. | **false** | - | +| | Required when coverage is set to `github`. | | | +| | Requires permissions to create and update PR comments: | | | +| | | | | +| | - `issues: write` | | | +| | - `pull-requests: write` | | | diff --git a/actions/test/action.yml b/actions/test/action.yml index 9b4d3b95..6c91552e 100644 --- a/actions/test/action.yml +++ b/actions/test/action.yml @@ -44,14 +44,14 @@ inputs: coverage: description: | Code coverage reporter to use. Supported values: - - `github`: Parse coverage reports via [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/parse-ci-reports) action, with GitHub summaries/PR comments + - `github`: Parse coverage reports via [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) action, with GitHub summaries/PR comments - `codecov`: Upload coverage to Codecov - `""` or `null`: No coverage reporting required: false default: "github" report-file: description: | - Optional test and coverage report paths forwarded to the [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/parse-ci-reports) action. + Optional test and coverage report paths forwarded to the [parse-ci-reports](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) action. Supports multiple formats (Cobertura, OpenCover, lcov, etc.). Provide absolute paths or paths relative to the working directory. Multiple entries can be separated by newlines, commas, or semicolons. @@ -61,7 +61,7 @@ inputs: path-mapping: description: | Optional path mapping to adjust file paths in test and coverage reports. - See the [parse-ci-reports documentation](https://hoverkraft-tech/ci-github-common/actions/parse-ci-reports) for details. + See the [parse-ci-reports documentation](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) for details. required: false default: "" github-token: @@ -80,7 +80,7 @@ runs: steps: # jscpd:ignore-start - id: working-directory - uses: hoverkraft-tech/ci-github-common/actions/working-directory@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/actions/working-directory@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 with: working-directory: ${{ inputs.working-directory }} enforce-path-in-workspace: ${{ inputs.container && 'false' || 'true' }} @@ -141,7 +141,7 @@ runs: - name: 📊 Parse coverage reports if: always() && inputs.coverage == 'github' id: parse-coverage-reports - uses: hoverkraft-tech/ci-github-common/actions/parse-ci-reports@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/actions/quality/parse-ci-reports@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 with: working-directory: ${{ steps.working-directory.outputs.absolute-path }} report-name: "Coverage Results" @@ -151,7 +151,7 @@ runs: - name: 📊 Add coverage PR comment if: always() && inputs.coverage == 'github' && github.event_name == 'pull_request' && steps.parse-coverage-reports.outputs.markdown - uses: hoverkraft-tech/ci-github-common/actions/create-or-update-comment@44f6108cef0e9884ea7735f3d62932c2310745d4 # 0.39.1 + uses: hoverkraft-tech/ci-github-common/actions/create-or-update-comment@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 with: title: "Code Coverage Report" token: ${{ inputs.github-token }} @@ -159,6 +159,6 @@ runs: - name: 📊 Upload coverage to Codecov if: always() && inputs.coverage == 'codecov' - uses: $/actions/codecov + uses: hoverkraft-tech/ci-github-common/actions/quality/codecov@5fbb531db839a26ebcc35c4946be279f945f7633 # 1.0.0 with: working-directory: ${{ steps.working-directory.outputs.absolute-path }} diff --git a/docs/adr/0001-release-orchestration.md b/docs/adr/0001-release-orchestration.md index 0b67a780..fe15f7e7 100644 --- a/docs/adr/0001-release-orchestration.md +++ b/docs/adr/0001-release-orchestration.md @@ -62,6 +62,14 @@ belongs to `release/verify`, npm recovery to `package/restore`, and asset handli rules and GitHub release lookup, which have multiple consumers. Specialized package preparation belongs to `prepare` in the workspace extension. +Follow the [parse-ci-reports action pattern](https://github.com/hoverkraft-tech/ci-github-common/tree/main/actions/quality/parse-ci-reports) +for dependency setup and tests. Keep each action's manifest and lockfile local. +Use `setup-node` with `github.action_path` when runtime dependencies are needed; +install test-only dependencies through shared CI. Colocated Node.js tests expose +`test`, `test:watch` and `test:ci`; the CI command emits console results and LCOV +into a tracked coverage directory that ignores generated reports. Keep unit-test +CI separate from integration jobs that exercise the actual actions. + ### Version preparation and release notes Use the public `ci-github-publish/release/plan` action as the version authority for @@ -83,6 +91,22 @@ the same committed identity. A concurrent branch update fails the atomic push. The preparation identity must satisfy branch and tag rules; preparation neither creates release PRs nor bypasses protections. +The manual override accepted on 2026-11-05 extends the no-change decision: an +optional `force` input creates a new version only on manual default-branch runs. +Keep higher HoverKraft plans and use a patch increment when the plan equals the +committed version. Native Lerna force versioning includes all public packages; +independent packages retain Conventional Commit increments with a patch fallback. +Schedules and explicit tag recovery ignore the override. Do not forward it to the +dispatched tag run or relax validation, version-history or tag-conflict checks. + +The generic configuration override accepted on 2026-11-05 exposes the same +`release-config` YAML or JSON mapping to preparation and drafting. Forward it to +ci-github-publish rather than adding an input for each upstream release setting. +Use native configuration inheritance: supplied top-level keys replace inherited +values. Apply shared defaults, then partial repository settings, then workflow +input. Preserve repository files; an explicit `_extends` chain keeps its own base. Independent +Lerna retains native version planning. Both phases must receive the same overrides. + Keep release notes on GitHub. Preparation never creates or modifies `CHANGELOG.md`, including existing files. Use `release/create` for the draft and optional AI summary, and `release/update` for missing assets and final publication. Preserve caller release @@ -97,11 +121,16 @@ or an enablement flag to the Node.js workflow. ### Build artifacts and packaging +GitHub artifact ownership and release tag identity checks are shared through +`ci-github-publish/actions/release/verify`. Node.js retains package/version validation +and release grouping. The test action calls `ci-github-common/actions/quality/codecov` +directly, so language-specific actions do not maintain wrappers or uploader setup. + Preparation commits versions before validation. CI builds, runs source tests, packs the distributable and optionally runs consumer checks against that tarball. The existing CI workflow adds an opt-in `package` job and a `package-tarball-artifact-id` output. General applications keep packaging disabled. -`package-tests` belongs to CI and has no publication credentials or environment. +The `package` configuration's `tests` option belongs to CI and has no publication credentials or environment. It derives package identity from the tarball and rejects changes to its bytes. A publication dry run also runs in CI. The standard packaging job supports the non-container Ubuntu artifact layout; custom producers can compose the actions. @@ -194,6 +223,91 @@ the alternatives and observations below preserve their history, not new validati Renovate updates. The compatibility finding at that time was Gatsby's exclusion of Node 26; it is historical evidence, not a permanent runtime constraint. +## Workspace and Lerna extension + +The extension in PR #563 applies the same preparation, validated package artifact and publication +contracts to these additional layouts. It adds to this record rather than creating +a separate release policy. + +### Native versioning and package selection + +Keep `working-directory` as the root containing the lockfile and package-manager +configuration. Use `package-directory` to select one public package in an npm or +pnpm workspace. npm updates the selected manifest and root lockfile; pnpm maintains +its own lockfile. Private sibling packages are not published. + +For fixed Lerna, apply HoverKraft's exact planned version through the consumer's +installed Lerna, preserving dependency propagation and lockfile updates. For +independent Lerna, use native Conventional Commit change detection and per-package +increments instead of a second dependency/version planner. Disable Lerna's Git +commits, tags and pushes, and use `--no-changelog` in both modes so existing root +and package changelogs remain unchanged. + +Preparation pushes one commit and every resulting package tag atomically, then +dispatches one tag to build and release the whole batch. Independent packages use +`package-name@version` tags and receive their own HoverKraft notes, optional AI +summaries and tarball assets. Preserve caller-supplied notes configuration. Recovery +from any matching package tag at the prepared commit resumes the full batch. + +### Native packing and validation before publication + +npm prebuilt packing still skips dependency installation. Native pnpm packing needs +workspace dependencies to resolve `workspace:` ranges, so install the frozen +workspace with lifecycle scripts disabled, restore the artifact's compiled bytes and pack with +hooks disabled. Reject unresolved workspace references in the tarball; do not replace +native packing with manual manifest rewrites. This adds dependency availability to +CI packaging; release consumes the resulting tarballs without packing again. + +CI must finish smoke tests and publication dry runs for all selected packages. +Release must finish optional AI summaries before any npm publication. Preserve successful validation artifact IDs across partial +matrix retries and recheck the registry in each publication attempt. Original npm +bytes and GitHub asset integrity remain protected. npm publication across packages +is non-atomic even with this validation barrier. + +Tests colocated with the release helpers cover native npm/pnpm version application, +fixed/independent Lerna dependency propagation and committed package discovery. +Dedicated integration workflows invoke the real actions to check npm/pnpm packaging, +artifact restoration, smoke-test failures and validation collection. Unit tests mock +registry and GitHub responses for integrity cases. Full remote preparation and +workflow dispatch require a release-capable test repository and are not covered by +these checks; see the validation guide for the explicit boundary. + +## Consumer audit (2026-11-02) + +The consumer audit on this date combined the +[GitHub dependency graph](https://github.com/hoverkraft-tech/ci-github-nodejs/network/dependents) +with code search and repository metadata. It identified 24 repositories with workflow +or action usage and `fork: false`. GitHub's dependent graph alone was incomplete; +validation copies with `fork: false` were retained. These counts are dated evidence, +not a continuously updated inventory. + +Six public npm publishers demonstrated these concrete requirements: + +| Repository | Package layout | Release requirement | +| ----------------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------- | +| [Reactionable](https://github.com/reactionable/reactionable) | Fixed Lerna, npm | Preserve workspace dependencies and shared build output | +| [Reactionable CLI](https://github.com/reactionable/reactionable-cli) | Single npm package | Preserve the installed CLI and documentation deployment | +| [ts-dev-tools](https://github.com/escemi-tech/ts-dev-tools) | Independent Lerna, pnpm | Keep package tags, independent versions and `workspace:` dependencies | +| [CI Dokumentor](https://github.com/hoverkraft-tech/ci-dokumentor) | Private pnpm/Nx workspace, public `packages/cli` | Use the root lockfile; retain installed CLI tests and other distribution channels | +| [Docusaurus theme](https://github.com/hoverkraft-tech/docusaurus-theme) | Private npm workspace, public `packages/theme` | Include built static assets and test an installed theme | +| [Slidev theme](https://github.com/hoverkraft-tech/slidev-theme) | Private npm workspace, public `packages/theme` | Keep checked-in theme files and installed-theme slide builds | + +The remaining consumers include applications, sites, containers and GitHub Actions. +Using Node.js CI does not imply they should publish an npm package. An additional +unpublished resume theme needs an explicit initial release baseline. + +The audit also found manifest/release/npm version differences, a missing npm package +version in a fixed-version release, PR-protected release branches, and five Pages +environments restricted to `main`. These require repository-specific reconciliation; +changing a shared workflow cannot grant permissions or reconstruct release history. +The operational requirements belong in the [readiness guide](../../.github/workflows/release.md#establish-the-release-baseline). + +These findings justified workspace selection, native independent Lerna versioning +and installed-package validation. The subsequent 2026-11-03 decision removed source +changelog generation from those paths while retaining version and dependency updates. +Applications and other release products continue using their own publication +compositions. Repository migrations and settings changes remain each owner's work. + - **2026-11-03, consumer validation:** The CLI planned `1.3.0`, already committed but unpublished, and required signed commits on its default branch. This supersedes rejection of equal versions and local unsigned release commits: @@ -289,6 +403,12 @@ the alternatives and observations below preserve their history, not new validati retains source/identity checks and retry recovery. This avoids testing a different package from the one published and removes consumer commands from the job with publication credentials. The PR CI entry point remains unchanged. + +- **2026-11-04, workspace CI handoff:** CI packages selected directories in a + read-only matrix and collects their validated artifact IDs. Release pairs those + IDs with prepared packages, verifies all artifact sources, then publishes. + Validation reports retain successful matrix results across failed-job reruns. + The single-package artifact input remains available for one-package callers. - **2026-11-04, recovery input naming:** Rename Publish's `skip-existing` input to `recover-existing` to describe restoring original registry bytes when resuming a release. Keep the default false for standalone publication and enable recovery @@ -297,3 +417,11 @@ the alternatives and observations below preserve their history, not new validati tarball path and its asset name directly to finalization. This supersedes the implicit `RUNNER_TEMP/release-assets` contract and the workflow's extra copy. Finalization stages missing bytes once, preserving digest checks on retries. + +- **2026-11-05, isolated package consumers:** Custom package checks share temporary + consumer creation, npm installation with lifecycle scripts disabled, binary + lookup and cleanup inside `package/test`. Commands run in that consumer and + receive the installed package path and archive identity. An optional example + project supplies dependencies and scripts without modifying its source files. + Callers retain package-specific runtime assertions. Default archive checks + remain independent of installation for workspaces with unpublished siblings.