diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 000000000..5d1b929f2 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,11 @@ +# Changesets + +This directory is used by [Changesets](https://github.com/changesets/changesets) to manage versioning and changelogs. + +To add a changeset, run: + +```sh +bunx changeset +``` + +This will prompt you to select the packages that changed and the type of change (patch, minor, major). A markdown file will be created in this directory describing the change — commit it with your PR. diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 000000000..3f9f18799 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "linked": [], + "access": "public", + "baseBranch": "origin/main", + "updateInternalDependencies": "patch", + "ignore": ["@clerk/cli-core"], + "snapshot": { + "useCalculatedVersion": true, + "prereleaseTemplate": "{tag}.v{datetime}" + } +} diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml new file mode 100644 index 000000000..6511f5d02 --- /dev/null +++ b/.github/workflows/build-binaries.yml @@ -0,0 +1,79 @@ +name: Build Binaries + +on: + workflow_call: + inputs: + version: + required: true + type: string + ref: + required: true + type: string + artifact-prefix: + required: true + type: string + description: "Prefix for artifact names (e.g. 'clerk', 'clerk-canary', 'clerk-snapshot')" + +jobs: + build: + runs-on: blacksmith-2vcpu-ubuntu-2404 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + - uses: oven-sh/setup-bun@v2 + - run: bun install --frozen-lockfile + + - name: Build all targets + env: + CLI_VERSION: ${{ inputs.version }} + run: bun run scripts/build.ts --version="${CLI_VERSION}" + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-darwin-arm64 + path: dist/artifacts/darwin-arm64/clerk + retention-days: 1 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-darwin-x64 + path: dist/artifacts/darwin-x64/clerk + retention-days: 1 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-linux-arm64 + path: dist/artifacts/linux-arm64/clerk + retention-days: 1 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-linux-arm64-musl + path: dist/artifacts/linux-arm64-musl/clerk + retention-days: 1 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-linux-x64 + path: dist/artifacts/linux-x64/clerk + retention-days: 1 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-linux-x64-musl + path: dist/artifacts/linux-x64-musl/clerk + retention-days: 1 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-win32-arm64 + path: dist/artifacts/win32-arm64/clerk.exe + retention-days: 1 + + - uses: actions/upload-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-win32-x64 + path: dist/artifacts/win32-x64/clerk.exe + retention-days: 1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60c8fddad..b6cf4adea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,25 +7,20 @@ on: jobs: lint: runs-on: blacksmith-2vcpu-ubuntu-2404 + timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - run: bun install --frozen-lockfile - run: bun run format:check - run: bun run lint + - run: bun run build test: runs-on: blacksmith-2vcpu-ubuntu-2404 + timeout-minutes: 10 steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - run: bun install --frozen-lockfile - - name: Run tests - run: | - failed=0 - for f in $(find src -name '*.test.ts' | sort); do - if ! bun test "$f"; then - failed=1 - fi - done - exit $failed + - run: bun run --filter @clerk/cli-core test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..c9a2eb3fc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,188 @@ +name: Release + +on: + push: + branches: [main] + +concurrency: + group: release + cancel-in-progress: false + +permissions: + contents: read + +jobs: + versioning: + runs-on: blacksmith-2vcpu-ubuntu-2404 + timeout-minutes: 5 + permissions: + contents: write + pull-requests: write + outputs: + release_created: ${{ steps.check.outputs.release_created }} + version: ${{ steps.check.outputs.version }} + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + - run: bun install --frozen-lockfile + + - id: changesets + uses: changesets/action@v1 + with: + version: bun run version-packages + commit: "ci(repo): version packages" + title: "ci(repo): Version Packages" + env: + GITHUB_TOKEN: ${{ github.token }} + + - name: Check if release needed + id: check + run: bun run scripts/check-release.ts + + # ─── Stable release ──────────────────────────────────────────────── + + build: + needs: versioning + if: ${{ needs.versioning.outputs.release_created == 'true' }} + uses: ./.github/workflows/build-binaries.yml + with: + version: ${{ needs.versioning.outputs.version }} + ref: ${{ github.sha }} + artifact-prefix: clerk + + smoke-test: + needs: [versioning, build] + uses: ./.github/workflows/smoke-test.yml + with: + version: ${{ needs.versioning.outputs.version }} + artifact-prefix: clerk + preset: stable + + publish-npm: + needs: [versioning, build, smoke-test] + # Must run on GitHub-hosted runner for npm OIDC trusted publishing + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: write + id-token: write + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + - uses: actions/setup-node@v4 + with: + node-version: "22" + registry-url: "https://registry.npmjs.org" + - name: Upgrade npm for OIDC trusted publishing + run: npm install -g npm@11 + - run: bun install --frozen-lockfile + + - uses: actions/download-artifact@v4 + with: + pattern: clerk-* + path: dist/artifacts + + - name: Publish packages + run: bun run release + env: + ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts + GH_TOKEN: ${{ github.token }} + + upload-github-assets: + needs: [versioning, build, smoke-test, publish-npm] + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + steps: + - uses: actions/download-artifact@v4 + with: + pattern: clerk-* + path: dist/artifacts + + - name: Upload binaries to GitHub Release + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + tag="v${{ needs.versioning.outputs.version }}" + for dir in dist/artifacts/clerk-*/; do + target=${dir#dist/artifacts/clerk-} && target=${target%/} + ext=""; [[ "$target" == win32-* ]] && ext=".exe" + gh release upload "$tag" "${dir}clerk${ext}#clerk-${target}${ext}" + done + + # ─── Canary release ──────────────────────────────────────────────── + + canary-version: + needs: versioning + if: ${{ needs.versioning.outputs.release_created != 'true' }} + runs-on: blacksmith-2vcpu-ubuntu-2404 + timeout-minutes: 5 + outputs: + version: ${{ steps.version.outputs.version }} + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + - run: bun install --frozen-lockfile + + - name: Version packages for canary + id: version + run: | + bun run version-packages:canary + version=$(jq -r '.version' packages/cli/package.json) + echo "version=${version}" >> "$GITHUB_OUTPUT" + + canary-build: + needs: canary-version + uses: ./.github/workflows/build-binaries.yml + with: + version: ${{ needs.canary-version.outputs.version }} + ref: ${{ github.sha }} + artifact-prefix: clerk-canary + + canary-smoke-test: + needs: [canary-version, canary-build] + uses: ./.github/workflows/smoke-test.yml + with: + version: ${{ needs.canary-version.outputs.version }} + artifact-prefix: clerk-canary + preset: canary + + canary-publish: + needs: [canary-version, canary-build, canary-smoke-test] + # Must run on GitHub-hosted runner for npm OIDC trusted publishing + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + - uses: actions/setup-node@v4 + with: + node-version: "22" + registry-url: "https://registry.npmjs.org" + - name: Upgrade npm for OIDC trusted publishing + run: npm install -g npm@11 + - run: bun install --frozen-lockfile + + - uses: actions/download-artifact@v4 + with: + pattern: clerk-canary-* + path: dist/artifacts + + - name: Rename artifact directories + run: | + cd dist/artifacts + for dir in clerk-canary-*/; do + target=${dir#clerk-canary-} && target=${target%/} + mv "$dir" "clerk-${target}" + done + + - name: Publish canary packages + run: bun run release:canary --version "$CANARY_VERSION" + env: + CANARY_VERSION: ${{ needs.canary-version.outputs.version }} + ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml new file mode 100644 index 000000000..58f177d61 --- /dev/null +++ b/.github/workflows/smoke-test.yml @@ -0,0 +1,96 @@ +name: Smoke Test + +on: + workflow_call: + inputs: + version: + required: true + type: string + artifact-prefix: + required: true + type: string + preset: + required: true + type: string + description: > + Matrix preset name. One of: stable (all 7 testable targets), + canary (darwin-arm64 + linux-x64 + linux-x64-musl), or + snapshot (linux-x64 only). + +jobs: + resolve-matrix: + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + matrix: ${{ steps.resolve.outputs.matrix }} + steps: + - id: resolve + run: | + case "${{ inputs.preset }}" in + stable) + # All testable targets. win32-arm64 is excluded — no GitHub-hosted + # ARM Windows runner available. Build-time format verification + # confirms it is a valid PE32+/Aarch64 binary. + matrix='[ + {"target":"darwin-arm64","runner":"macos-latest","bin":"./clerk"}, + {"target":"darwin-x64","runner":"macos-15-large","bin":"./clerk"}, + {"target":"linux-arm64","runner":"ubuntu-24.04-arm","bin":"./clerk"}, + {"target":"linux-arm64-musl","runner":"ubuntu-24.04-arm","bin":"./clerk","docker":"alpine"}, + {"target":"linux-x64","runner":"ubuntu-latest","bin":"./clerk"}, + {"target":"linux-x64-musl","runner":"ubuntu-latest","bin":"./clerk","docker":"alpine"}, + {"target":"win32-x64","runner":"windows-latest","bin":"./clerk.exe"} + ]' + ;; + canary) + # Subset for speed/cost. darwin-arm64 + linux-x64 cover the two + # most common dev platforms; musl is included since it runs on the + # same ubuntu-latest runner. + matrix='[ + {"target":"darwin-arm64","runner":"macos-latest","bin":"./clerk"}, + {"target":"linux-x64","runner":"ubuntu-latest","bin":"./clerk"}, + {"target":"linux-x64-musl","runner":"ubuntu-latest","bin":"./clerk","docker":"alpine"} + ]' + ;; + snapshot) + matrix='[{"target":"linux-x64","runner":"ubuntu-latest","bin":"./clerk"}]' + ;; + *) + echo "::error::Unknown preset: ${{ inputs.preset }}" + exit 1 + ;; + esac + { + echo "matrix<> "$GITHUB_OUTPUT" + + smoke-test: + needs: resolve-matrix + strategy: + fail-fast: false + matrix: + include: ${{ fromJSON(needs.resolve-matrix.outputs.matrix) }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 10 + steps: + - uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact-prefix }}-${{ matrix.target }} + - name: Make executable + if: runner.os != 'Windows' + run: chmod +x ${{ matrix.bin }} + - name: Smoke test + shell: bash + run: | + expected="${{ inputs.version }}" + if [ -n "${{ matrix.docker }}" ]; then + actual=$(docker run --rm -v "$PWD:/work" ${{ matrix.docker }} /work/${{ matrix.bin }} --version) + else + actual=$(${{ matrix.bin }} --version) + fi + echo "Binary reports $actual (expected $expected)" + if [ "$actual" != "$expected" ]; then + echo "::error::Version mismatch: expected $expected, got $actual" + exit 1 + fi diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml new file mode 100644 index 000000000..076864c1f --- /dev/null +++ b/.github/workflows/snapshot.yml @@ -0,0 +1,179 @@ +name: Snapshot Release + +on: + issue_comment: + types: [created] + +permissions: + contents: read + pull-requests: write + +jobs: + snapshot: + # Intentionally restricted to MEMBER and OWNER — external collaborators + # (COLLABORATOR association) cannot trigger snapshots. This is a deliberate + # security boundary: snapshot publishing has npm write access via OIDC. + if: >- + github.event.issue.pull_request && + startsWith(github.event.comment.body, '!snapshot') && + contains(fromJSON('["MEMBER","OWNER"]'), github.event.comment.author_association) + runs-on: blacksmith-2vcpu-ubuntu-2404 + timeout-minutes: 5 + outputs: + version: ${{ steps.version.outputs.version }} + sha: ${{ steps.pr.outputs.sha }} + steps: + - name: React to comment + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + gh api "repos/${GH_REPO}/issues/comments/${{ github.event.comment.id }}/reactions" \ + -f content=eyes --silent + + - name: Get PR head SHA + id: pr + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + sha=$(gh api "repos/${GH_REPO}/pulls/${{ github.event.issue.number }}" --jq '.head.sha') + echo "sha=${sha}" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@v4 + with: + ref: ${{ steps.pr.outputs.sha }} + - uses: oven-sh/setup-bun@v2 + - run: bun install --frozen-lockfile + + - name: Compute snapshot version + id: version + env: + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + # Extract optional name from "!snapshot " — default to "snapshot" + name=$(echo "$COMMENT_BODY" | sed 's/^!snapshot[[:space:]]*//' | tr -cd 'a-zA-Z0-9-') + if [ -z "$name" ]; then name="snapshot"; fi + bun run version-packages:snapshot -- "$name" + version=$(jq -r '.version' packages/cli/package.json) + echo "version=${version}" >> "$GITHUB_OUTPUT" + + build: + needs: snapshot + uses: ./.github/workflows/build-binaries.yml + with: + version: ${{ needs.snapshot.outputs.version }} + ref: ${{ needs.snapshot.outputs.sha }} + artifact-prefix: clerk-snapshot + + smoke-test: + needs: [snapshot, build] + uses: ./.github/workflows/smoke-test.yml + with: + version: ${{ needs.snapshot.outputs.version }} + artifact-prefix: clerk-snapshot + preset: snapshot + + publish: + needs: [snapshot, build, smoke-test] + # Must run on GitHub-hosted runner for npm OIDC trusted publishing + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + pull-requests: write + id-token: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.snapshot.outputs.sha }} + - uses: oven-sh/setup-bun@v2 + - uses: actions/setup-node@v4 + with: + node-version: "22" + registry-url: "https://registry.npmjs.org" + - name: Upgrade npm for OIDC trusted publishing + run: npm install -g npm@11 + - run: bun install --frozen-lockfile + + - uses: actions/download-artifact@v4 + with: + pattern: clerk-snapshot-* + path: dist/artifacts + + - name: Rename artifact directories + run: | + cd dist/artifacts + for dir in clerk-snapshot-*/; do + target=${dir#clerk-snapshot-} && target=${target%/} + mv "$dir" "clerk-${target}" + done + + - name: Publish snapshot packages + run: bun run release:snapshot --version "$SNAPSHOT_VERSION" + env: + SNAPSHOT_VERSION: ${{ needs.snapshot.outputs.version }} + ARTIFACTS_DIR: ${{ github.workspace }}/dist/artifacts + + - name: React with rocket + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + gh api "repos/${GH_REPO}/issues/comments/${{ github.event.comment.id }}/reactions" \ + -f content=rocket --silent + + - name: Post installation comment + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + SNAPSHOT_VERSION: ${{ needs.snapshot.outputs.version }} + SNAPSHOT_SHA: ${{ needs.snapshot.outputs.sha }} + PR_NUMBER: ${{ github.event.issue.number }} + run: | + short_sha=$(echo "${SNAPSHOT_SHA}" | cut -c1-7) + { + echo '## Snapshot published' + echo '' + echo '```sh' + echo "npm install -g clerk@${SNAPSHOT_VERSION}" + echo '```' + echo '' + echo '| Package | Version |' + echo '|---------|---------|' + echo "| \`clerk\` | \`${SNAPSHOT_VERSION}\` |" + echo '' + echo "> Published from ${short_sha}" + } > /tmp/comment-body.md + gh pr comment "${PR_NUMBER}" --repo "${GH_REPO}" --body-file /tmp/comment-body.md + + notify-failure: + needs: [snapshot, build, smoke-test, publish] + if: failure() + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + pull-requests: write + steps: + - name: React with confused emoji + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + gh api "repos/${GH_REPO}/issues/comments/${{ github.event.comment.id }}/reactions" \ + -f content=confused --silent + + - name: Post failure comment + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + PR_NUMBER: ${{ github.event.issue.number }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + { + echo '## Snapshot failed' + echo '' + echo "The snapshot publish workflow failed. [View the workflow run](${RUN_URL}) for details." + } > /tmp/comment-body.md + gh pr comment "${PR_NUMBER}" --repo "${GH_REPO}" --body-file /tmp/comment-body.md diff --git a/.gitignore b/.gitignore index a14702c40..db941ea17 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # Finder (MacOS) folder config .DS_Store + +# Bun compile artifacts +*.bun-build diff --git a/.oxlintrc.json b/.oxlintrc.json index a58d866f8..49a8b8d73 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -4,7 +4,7 @@ }, "overrides": [ { - "files": ["src/cli.ts", "src/cli-program.ts"], + "files": ["packages/cli-core/src/cli.ts", "packages/cli-core/src/cli-program.ts"], "rules": { "unicorn/no-process-exit": "off" } diff --git a/CLAUDE.md b/CLAUDE.md index 27593e8f9..5f9f635c4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,6 +4,18 @@ globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json" alwaysApply: false --- +## Project Structure + +This is a Bun workspace monorepo: + +- `packages/cli-core/` — CLI source code, commands, and tests +- `packages/cli/` — npm wrapper package with platform binary shim (not run directly during development; do not add command logic here) +- `scripts/releaser/` — release publishing script that generates platform packages and publishes to npm + +See [docs/releasing.md](docs/releasing.md) for the full release flow, channels, and safeguards. + +## Bun + Default to using Bun instead of Node.js. - Use `bun ` instead of `node ` or `ts-node ` @@ -122,9 +134,13 @@ bun test # Run all tests CI runs `bun run format:check` (fails if unformatted), `bun run lint`, and `bun test` on every PR to `main`. +## Versioning + +The `CLI_VERSION` global is injected at compile time via `bun build --compile --define "CLI_VERSION=..."`. Local `build:compile` omits it, so the binary reports `0.0.0-dev`. The CI release workflow injects the real version. + ## Commands -Every CLI command lives in its own directory under `src/commands//`. Each directory must contain a `README.md` that documents: +Every CLI command lives in its own directory under `packages/cli-core/src/commands//`. Each directory must contain a `README.md` that documents: - What the command does - Usage and options @@ -133,15 +149,15 @@ Every CLI command lives in its own directory under `src/commands//`. Each When adding a new command, create its directory and README. When modifying a command's behavior, options, or API calls, update its README to match. -When creating or modifying a command, evaluate whether it needs an agent mode. Commands with interactive prompts (menus, wizards, multi-step flows) should check `isAgent()` from `src/mode.ts` and, when in agent mode, output a structured prompt that an AI agent can follow instead of running the interactive flow. Commands that are already non-interactive (e.g., single API calls, browser-based OAuth) typically don't need agent mode. +When creating or modifying a command, evaluate whether it needs an agent mode. Commands with interactive prompts (menus, wizards, multi-step flows) should check `isAgent()` from `packages/cli-core/src/mode.ts` and, when in agent mode, output a structured prompt that an AI agent can follow instead of running the interactive flow. Commands that are already non-interactive (e.g., single API calls, browser-based OAuth) typically don't need agent mode. ### Root README -`README.md` at the project root contains the CLI help output. When commands are added, removed, or their options change, update the help output in `README.md` to stay in sync. You can regenerate it by running `bun run src/cli.ts --help`. +`README.md` at the project root contains the CLI help output. When commands are added, removed, or their options change, update the help output in `README.md` to stay in sync. You can regenerate it by running `bun run dev -- --help`. ## Error Handling -All error classes and helpers live in `src/lib/errors.ts`. The global error handler in `src/cli.ts` catches thrown errors and formats them for the user. **Never call `console.error` + `process.exit` directly in commands** — throw an error instead and let the global handler deal with output and exit codes. +All error classes and helpers live in `packages/cli-core/src/lib/errors.ts`. The global error handler in `packages/cli-core/src/cli.ts` catches thrown errors and formats them for the user. **Never call `console.error` + `process.exit` directly in commands** — throw an error instead and let the global handler deal with output and exit codes. ### Known failures — `CliError` @@ -196,4 +212,4 @@ const config = await withApiContext( ### API error classes -`BapiError` and `PlapiError` (both extend `ApiError`) are thrown by the API helpers in `src/commands/api/bapi.ts` and `src/lib/plapi.ts` respectively. Don't construct these in commands — they're thrown automatically by the fetch wrappers. Use `withApiContext` to add context when calling those helpers. +`BapiError` and `PlapiError` (both extend `ApiError`) are thrown by the API helpers in `packages/cli-core/src/commands/api/bapi.ts` and `packages/cli-core/src/lib/plapi.ts` respectively. Don't construct these in commands — they're thrown automatically by the fetch wrappers. Use `withApiContext` to add context when calling those helpers. diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..daceccfbc --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Clerk, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 8a26fdbc2..2df9f28e6 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# @clerk/cli +# clerk The Clerk command-line interface. @@ -8,31 +8,36 @@ Usage: clerk [options] [command] Clerk CLI Options: - -V, --version Display version - --mode Force interaction mode (human or agent). - Defaults to auto-detect based on TTY. - -h, --help Display help for command + -V, --version output the version number + --mode Force interaction mode (human or agent). + Defaults to auto-detect based on TTY. + --verbose Show detailed error output + -h, --help display help for command Commands: - init [options] Initialize Clerk in your project - auth Manage authentication + init Initialize Clerk in your project + auth Manage authentication login|signup Log in to your Clerk account logout|signout Log out of your Clerk account - link [options] Link this project to a Clerk application - unlink [options] Unlink this project from its Clerk application - whoami Show the current logged-in user - config Manage instance configuration + link [options] Link this project to a Clerk application + unlink [options] Unlink this project from its Clerk + application + whoami Show the current logged-in user + env Manage environment variables + pull [options] Pull environment variables from Clerk to .env.local + config Manage instance configuration pull [options] Pull instance configuration from Clerk schema [options] Pull instance config schema from Clerk patch [options] Partially update instance configuration (PATCH) put [options] Replace entire instance configuration (PUT) - env Manage environment variables - pull [options] Pull environment variables from Clerk to .env.local - api [options] [endpoint] [filter] Make authenticated requests to the Clerk API + api [options] [endpoint] [filter] Make authenticated requests to the Clerk + API ls [filter] List available API endpoints (no args) Interactive request builder (TTY only) - doctor [options] Check your project's Clerk integration health + doctor [options] Check your project's Clerk integration + health deploy [options] Deploy your Clerk application (hidden) + help [command] display help for command clerk init --prompt Output a prompt for an AI agent to integrate Clerk diff --git a/bun.lock b/bun.lock index 1bc728390..8a48e12a4 100644 --- a/bun.lock +++ b/bun.lock @@ -1,9 +1,28 @@ { "lockfileVersion": 1, - "configVersion": 0, + "configVersion": 1, "workspaces": { "": { - "name": "marseille", + "name": "@clerk/cli-workspace", + "devDependencies": { + "@changesets/cli": "^2.29.4", + "@types/bun": "^1.3.9", + "nano-staged": "^0.9.0", + "oxfmt": "^0.36.0", + "oxlint": "^1.51.0", + "typescript": "^5", + }, + }, + "packages/cli": { + "name": "clerk", + "version": "0.0.1", + "bin": { + "clerk": "./bin/clerk", + }, + }, + "packages/cli-core": { + "name": "@clerk/cli-core", + "version": "0.0.0", "dependencies": { "@commander-js/extra-typings": "^14.0.0", "@inquirer/prompts": "^8.2.0", @@ -12,52 +31,85 @@ "env-paths": "^4.0.0", "yaml": "^2.8.2", }, - "devDependencies": { - "@types/bun": "latest", - "nano-staged": "^0.9.0", - "oxfmt": "^0.36.0", - "oxlint": "^1.51.0", - }, - "peerDependencies": { - "typescript": "^5", - }, }, }, "packages": { + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], + + "@changesets/apply-release-plan": ["@changesets/apply-release-plan@7.1.0", "", { "dependencies": { "@changesets/config": "^3.1.3", "@changesets/get-version-range-type": "^0.4.0", "@changesets/git": "^3.0.4", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "detect-indent": "^6.0.0", "fs-extra": "^7.0.1", "lodash.startcase": "^4.4.0", "outdent": "^0.5.0", "prettier": "^2.7.1", "resolve-from": "^5.0.0", "semver": "^7.5.3" } }, "sha512-yq8ML3YS7koKQ/9bk1PqO0HMzApIFNwjlwCnwFEXMzNe8NpzeeYYKCmnhWJGkN8g7E51MnWaSbqRcTcdIxUgnQ=="], + + "@changesets/assemble-release-plan": ["@changesets/assemble-release-plan@6.0.9", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.3", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "semver": "^7.5.3" } }, "sha512-tPgeeqCHIwNo8sypKlS3gOPmsS3wP0zHt67JDuL20P4QcXiw/O4Hl7oXiuLnP9yg+rXLQ2sScdV1Kkzde61iSQ=="], + + "@changesets/changelog-git": ["@changesets/changelog-git@0.2.1", "", { "dependencies": { "@changesets/types": "^6.1.0" } }, "sha512-x/xEleCFLH28c3bQeQIyeZf8lFXyDFVn1SgcBiR2Tw/r4IAWlk1fzxCEZ6NxQAjF2Nwtczoen3OA2qR+UawQ8Q=="], + + "@changesets/cli": ["@changesets/cli@2.30.0", "", { "dependencies": { "@changesets/apply-release-plan": "^7.1.0", "@changesets/assemble-release-plan": "^6.0.9", "@changesets/changelog-git": "^0.2.1", "@changesets/config": "^3.1.3", "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.3", "@changesets/get-release-plan": "^4.0.15", "@changesets/git": "^3.0.4", "@changesets/logger": "^0.1.1", "@changesets/pre": "^2.0.2", "@changesets/read": "^0.6.7", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@changesets/write": "^0.4.0", "@inquirer/external-editor": "^1.0.2", "@manypkg/get-packages": "^1.1.3", "ansi-colors": "^4.1.3", "enquirer": "^2.4.1", "fs-extra": "^7.0.1", "mri": "^1.2.0", "package-manager-detector": "^0.2.0", "picocolors": "^1.1.0", "resolve-from": "^5.0.0", "semver": "^7.5.3", "spawndamnit": "^3.0.1", "term-size": "^2.1.0" }, "bin": { "changeset": "bin.js" } }, "sha512-5D3Nk2JPqMI1wK25pEymeWRSlSMdo5QOGlyfrKg0AOufrUcjEE3RQgaCpHoBiM31CSNrtSgdJ0U6zL1rLDDfBA=="], + + "@changesets/config": ["@changesets/config@3.1.3", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.3", "@changesets/logger": "^0.1.1", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "fs-extra": "^7.0.1", "micromatch": "^4.0.8" } }, "sha512-vnXjcey8YgBn2L1OPWd3ORs0bGC4LoYcK/ubpgvzNVr53JXV5GiTVj7fWdMRsoKUH7hhhMAQnsJUqLr21EncNw=="], + + "@changesets/errors": ["@changesets/errors@0.2.0", "", { "dependencies": { "extendable-error": "^0.1.5" } }, "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow=="], + + "@changesets/get-dependents-graph": ["@changesets/get-dependents-graph@2.1.3", "", { "dependencies": { "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "picocolors": "^1.1.0", "semver": "^7.5.3" } }, "sha512-gphr+v0mv2I3Oxt19VdWRRUxq3sseyUpX9DaHpTUmLj92Y10AGy+XOtV+kbM6L/fDcpx7/ISDFK6T8A/P3lOdQ=="], + + "@changesets/get-release-plan": ["@changesets/get-release-plan@4.0.15", "", { "dependencies": { "@changesets/assemble-release-plan": "^6.0.9", "@changesets/config": "^3.1.3", "@changesets/pre": "^2.0.2", "@changesets/read": "^0.6.7", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3" } }, "sha512-Q04ZaRPuEVZtA+auOYgFaVQQSA98dXiVe/yFaZfY7hoSmQICHGvP0TF4u3EDNHWmmCS4ekA/XSpKlSM2PyTS2g=="], + + "@changesets/get-version-range-type": ["@changesets/get-version-range-type@0.4.0", "", {}, "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ=="], + + "@changesets/git": ["@changesets/git@3.0.4", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@manypkg/get-packages": "^1.1.3", "is-subdir": "^1.1.1", "micromatch": "^4.0.8", "spawndamnit": "^3.0.1" } }, "sha512-BXANzRFkX+XcC1q/d27NKvlJ1yf7PSAgi8JG6dt8EfbHFHi4neau7mufcSca5zRhwOL8j9s6EqsxmT+s+/E6Sw=="], + + "@changesets/logger": ["@changesets/logger@0.1.1", "", { "dependencies": { "picocolors": "^1.1.0" } }, "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg=="], + + "@changesets/parse": ["@changesets/parse@0.4.3", "", { "dependencies": { "@changesets/types": "^6.1.0", "js-yaml": "^4.1.1" } }, "sha512-ZDmNc53+dXdWEv7fqIUSgRQOLYoUom5Z40gmLgmATmYR9NbL6FJJHwakcCpzaeCy+1D0m0n7mT4jj2B/MQPl7A=="], + + "@changesets/pre": ["@changesets/pre@2.0.2", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "fs-extra": "^7.0.1" } }, "sha512-HaL/gEyFVvkf9KFg6484wR9s0qjAXlZ8qWPDkTyKF6+zqjBe/I2mygg3MbpZ++hdi0ToqNUF8cjj7fBy0dg8Ug=="], + + "@changesets/read": ["@changesets/read@0.6.7", "", { "dependencies": { "@changesets/git": "^3.0.4", "@changesets/logger": "^0.1.1", "@changesets/parse": "^0.4.3", "@changesets/types": "^6.1.0", "fs-extra": "^7.0.1", "p-filter": "^2.1.0", "picocolors": "^1.1.0" } }, "sha512-D1G4AUYGrBEk8vj8MGwf75k9GpN6XL3wg8i42P2jZZwFLXnlr2Pn7r9yuQNbaMCarP7ZQWNJbV6XLeysAIMhTA=="], + + "@changesets/should-skip-package": ["@changesets/should-skip-package@0.1.2", "", { "dependencies": { "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3" } }, "sha512-qAK/WrqWLNCP22UDdBTMPH5f41elVDlsNyat180A33dWxuUDyNpg6fPi/FyTZwRriVjg0L8gnjJn2F9XAoF0qw=="], + + "@changesets/types": ["@changesets/types@6.1.0", "", {}, "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA=="], + + "@changesets/write": ["@changesets/write@0.4.0", "", { "dependencies": { "@changesets/types": "^6.1.0", "fs-extra": "^7.0.1", "human-id": "^4.1.1", "prettier": "^2.7.1" } }, "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q=="], + + "@clerk/cli-core": ["@clerk/cli-core@workspace:packages/cli-core"], + "@commander-js/extra-typings": ["@commander-js/extra-typings@14.0.0", "", { "peerDependencies": { "commander": "~14.0.0" } }, "sha512-hIn0ncNaJRLkZrxBIp5AsW/eXEHNKYQBh0aPdoUqNgD+Io3NIykQqpKFyKcuasZhicGaEZJX/JBSIkZ4e5x8Dg=="], "@inquirer/ansi": ["@inquirer/ansi@2.0.3", "", {}, "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw=="], - "@inquirer/checkbox": ["@inquirer/checkbox@5.0.4", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.1", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-DrAMU3YBGMUAp6ArwTIp/25CNDtDbxk7UjIrrtM25JVVrlVYlVzHh5HR1BDFu9JMyUoZ4ZanzeaHqNDttf3gVg=="], + "@inquirer/checkbox": ["@inquirer/checkbox@5.1.0", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.5", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-/HjF1LN0a1h4/OFsbGKHNDtWICFU/dqXCdym719HFTyJo9IG7Otr+ziGWc9S0iQuohRZllh+WprSgd5UW5Fw0g=="], - "@inquirer/confirm": ["@inquirer/confirm@6.0.4", "", { "dependencies": { "@inquirer/core": "^11.1.1", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-WdaPe7foUnoGYvXzH4jp4wH/3l+dBhZ3uwhKjXjwdrq5tEIFaANxj6zrGHxLdsIA0yKM0kFPVcEalOZXBB5ISA=="], + "@inquirer/confirm": ["@inquirer/confirm@6.0.8", "", { "dependencies": { "@inquirer/core": "^11.1.5", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Di6dgmiZ9xCSUxWUReWTqDtbhXCuG2MQm2xmgSAIruzQzBqNf49b8E07/vbCYY506kDe8BiwJbegXweG8M1klw=="], - "@inquirer/core": ["@inquirer/core@11.1.1", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^9.0.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-hV9o15UxX46OyQAtaoMqAOxGR8RVl1aZtDx1jHbCtSJy1tBdTfKxLPKf7utsE4cRy4tcmCQ4+vdV+ca+oNxqNA=="], + "@inquirer/core": ["@inquirer/core@11.1.5", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A=="], - "@inquirer/editor": ["@inquirer/editor@5.0.4", "", { "dependencies": { "@inquirer/core": "^11.1.1", "@inquirer/external-editor": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QI3Jfqcv6UO2/VJaEFONH8Im1ll++Xn/AJTBn9Xf+qx2M+H8KZAdQ5sAe2vtYlo+mLW+d7JaMJB4qWtK4BG3pw=="], + "@inquirer/editor": ["@inquirer/editor@5.0.8", "", { "dependencies": { "@inquirer/core": "^11.1.5", "@inquirer/external-editor": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-sLcpbb9B3XqUEGrj1N66KwhDhEckzZ4nI/W6SvLXyBX8Wic3LDLENlWRvkOGpCPoserabe+MxQkpiMoI8irvyA=="], - "@inquirer/expand": ["@inquirer/expand@5.0.4", "", { "dependencies": { "@inquirer/core": "^11.1.1", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-0I/16YwPPP0Co7a5MsomlZLpch48NzYfToyqYAOWtBmaXSB80RiNQ1J+0xx2eG+Wfxt0nHtpEWSRr6CzNVnOGg=="], + "@inquirer/expand": ["@inquirer/expand@5.0.8", "", { "dependencies": { "@inquirer/core": "^11.1.5", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QieW3F1prNw3j+hxO7/NKkG1pk3oz7pOB6+5Upwu3OIwADfPX0oZVppsqlL+Vl/uBHHDSOBY0BirLctLnXwGGg=="], - "@inquirer/external-editor": ["@inquirer/external-editor@2.0.3", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-LgyI7Agbda74/cL5MvA88iDpvdXI2KuMBCGRkbCl2Dg1vzHeOgs+s0SDcXV7b+WZJrv2+ERpWSM65Fpi9VfY3w=="], + "@inquirer/external-editor": ["@inquirer/external-editor@1.0.3", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA=="], "@inquirer/figures": ["@inquirer/figures@2.0.3", "", {}, "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g=="], - "@inquirer/input": ["@inquirer/input@5.0.4", "", { "dependencies": { "@inquirer/core": "^11.1.1", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-4B3s3jvTREDFvXWit92Yc6jF1RJMDy2VpSqKtm4We2oVU65YOh2szY5/G14h4fHlyQdpUmazU5MPCFZPRJ0AOw=="], + "@inquirer/input": ["@inquirer/input@5.0.8", "", { "dependencies": { "@inquirer/core": "^11.1.5", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-p0IJslw0AmedLEkOU+yrEX3Aj2RTpQq7ZOf8nc1DIhjzaxRWrrgeuE5Kyh39fVRgtcACaMXx/9WNo8+GjgBOfw=="], - "@inquirer/number": ["@inquirer/number@4.0.4", "", { "dependencies": { "@inquirer/core": "^11.1.1", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-CmMp9LF5HwE+G/xWsC333TlCzYYbXMkcADkKzcawh49fg2a1ryLc7JL1NJYYt1lJ+8f4slikNjJM9TEL/AljYQ=="], + "@inquirer/number": ["@inquirer/number@4.0.8", "", { "dependencies": { "@inquirer/core": "^11.1.5", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-uGLiQah9A0F9UIvJBX52m0CnqtLaym0WpT9V4YZrjZ+YRDKZdwwoEPz06N6w8ChE2lrnsdyhY9sL+Y690Kh9gQ=="], - "@inquirer/password": ["@inquirer/password@5.0.4", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.1", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-ZCEPyVYvHK4W4p2Gy6sTp9nqsdHQCfiPXIP9LbJVW4yCinnxL/dDDmPaEZVysGrj8vxVReRnpfS2fOeODe9zjg=="], + "@inquirer/password": ["@inquirer/password@5.0.8", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.5", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-zt1sF4lYLdvPqvmvHdmjOzuUUjuCQ897pdUCO8RbXMUDKXJTTyOQgtn23le+jwcb+MpHl3VAFvzIdxRAf6aPlA=="], - "@inquirer/prompts": ["@inquirer/prompts@8.2.0", "", { "dependencies": { "@inquirer/checkbox": "^5.0.4", "@inquirer/confirm": "^6.0.4", "@inquirer/editor": "^5.0.4", "@inquirer/expand": "^5.0.4", "@inquirer/input": "^5.0.4", "@inquirer/number": "^4.0.4", "@inquirer/password": "^5.0.4", "@inquirer/rawlist": "^5.2.0", "@inquirer/search": "^4.1.0", "@inquirer/select": "^5.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-rqTzOprAj55a27jctS3vhvDDJzYXsr33WXTjODgVOru21NvBo9yIgLIAf7SBdSV0WERVly3dR6TWyp7ZHkvKFA=="], + "@inquirer/prompts": ["@inquirer/prompts@8.3.0", "", { "dependencies": { "@inquirer/checkbox": "^5.1.0", "@inquirer/confirm": "^6.0.8", "@inquirer/editor": "^5.0.8", "@inquirer/expand": "^5.0.8", "@inquirer/input": "^5.0.8", "@inquirer/number": "^4.0.8", "@inquirer/password": "^5.0.8", "@inquirer/rawlist": "^5.2.4", "@inquirer/search": "^4.1.4", "@inquirer/select": "^5.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-JAj66kjdH/F1+B7LCigjARbwstt3SNUOSzMdjpsvwJmzunK88gJeXmcm95L9nw1KynvFVuY4SzXh/3Y0lvtgSg=="], - "@inquirer/rawlist": ["@inquirer/rawlist@5.2.0", "", { "dependencies": { "@inquirer/core": "^11.1.1", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-CciqGoOUMrFo6HxvOtU5uL8fkjCmzyeB6fG7O1vdVAZVSopUBYECOwevDBlqNLyyYmzpm2Gsn/7nLrpruy9RFg=="], + "@inquirer/rawlist": ["@inquirer/rawlist@5.2.4", "", { "dependencies": { "@inquirer/core": "^11.1.5", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-fTuJ5Cq9W286isLxwj6GGyfTjx1Zdk4qppVEPexFuA6yioCCXS4V1zfKroQqw7QdbDPN73xs2DiIAlo55+kBqg=="], - "@inquirer/search": ["@inquirer/search@4.1.0", "", { "dependencies": { "@inquirer/core": "^11.1.1", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-EAzemfiP4IFvIuWnrHpgZs9lAhWDA0GM3l9F4t4mTQ22IFtzfrk8xbkMLcAN7gmVML9O/i+Hzu8yOUyAaL6BKA=="], + "@inquirer/search": ["@inquirer/search@4.1.4", "", { "dependencies": { "@inquirer/core": "^11.1.5", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-9yPTxq7LPmYjrGn3DRuaPuPbmC6u3fiWcsE9ggfLcdgO/ICHYgxq7mEy1yJ39brVvgXhtOtvDVjDh9slJxE4LQ=="], - "@inquirer/select": ["@inquirer/select@5.0.4", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.1", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-s8KoGpPYMEQ6WXc0dT9blX2NtIulMdLOO3LA1UKOiv7KFWzlJ6eLkEYTDBIi+JkyKXyn8t/CD6TinxGjyLt57g=="], + "@inquirer/select": ["@inquirer/select@5.1.0", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.5", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-OyYbKnchS1u+zRe14LpYrN8S0wH1vD0p2yKISvSsJdH2TpI87fh4eZdWnpdbrGauCRWDph3NwxRmM4Pcm/hx1Q=="], "@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="], + "@manypkg/find-root": ["@manypkg/find-root@1.1.0", "", { "dependencies": { "@babel/runtime": "^7.5.5", "@types/node": "^12.7.1", "find-up": "^4.1.0", "fs-extra": "^8.1.0" } }, "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA=="], + + "@manypkg/get-packages": ["@manypkg/get-packages@1.1.3", "", { "dependencies": { "@babel/runtime": "^7.5.5", "@changesets/types": "^4.0.1", "@manypkg/find-root": "^1.1.0", "fs-extra": "^8.1.0", "globby": "^11.0.0", "read-yaml-file": "^1.1.0" } }, "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A=="], + "@napi-rs/keyring": ["@napi-rs/keyring@1.2.0", "", { "optionalDependencies": { "@napi-rs/keyring-darwin-arm64": "1.2.0", "@napi-rs/keyring-darwin-x64": "1.2.0", "@napi-rs/keyring-freebsd-x64": "1.2.0", "@napi-rs/keyring-linux-arm-gnueabihf": "1.2.0", "@napi-rs/keyring-linux-arm64-gnu": "1.2.0", "@napi-rs/keyring-linux-arm64-musl": "1.2.0", "@napi-rs/keyring-linux-riscv64-gnu": "1.2.0", "@napi-rs/keyring-linux-x64-gnu": "1.2.0", "@napi-rs/keyring-linux-x64-musl": "1.2.0", "@napi-rs/keyring-win32-arm64-msvc": "1.2.0", "@napi-rs/keyring-win32-ia32-msvc": "1.2.0", "@napi-rs/keyring-win32-x64-msvc": "1.2.0" } }, "sha512-d0d4Oyxm+v980PEq1ZH2PmS6cvpMIRc17eYpiU47KgW+lzxklMu6+HOEOPmxrpnF/XQZ0+Q78I2mgMhbIIo/dg=="], "@napi-rs/keyring-darwin-arm64": ["@napi-rs/keyring-darwin-arm64@1.2.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CA83rDeyONDADO25JLZsh3eHY8yTEtm/RS6ecPsY+1v+dSawzT9GywBMu2r6uOp1IEhQs/xAfxgybGAFr17lSA=="], @@ -84,6 +136,12 @@ "@napi-rs/keyring-win32-x64-msvc": ["@napi-rs/keyring-win32-x64-msvc@1.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-xFlx/TsmqmCwNU9v+AVnEJgoEAlBYgzFF5Ihz1rMpPAt4qQWWkMd4sCyM1gMJ1A/GnRqRegDiQpwaxGUHFtFbA=="], + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + "@oxfmt/binding-android-arm-eabi": ["@oxfmt/binding-android-arm-eabi@0.36.0", "", { "os": "android", "cpu": "arm" }, "sha512-Z4yVHJWx/swHHjtr0dXrBZb6LxS+qNz1qdza222mWwPTUK4L790+5i3LTgjx3KYGBzcYpjaiZBw4vOx94dH7MQ=="], "@oxfmt/binding-android-arm64": ["@oxfmt/binding-android-arm64@0.36.0", "", { "os": "android", "cpu": "arm64" }, "sha512-3ElCJRFNPQl7jexf2CAa9XmAm8eC5JPrIDSjc9jSchkVSFTEqyL0NtZinBB2h1a4i4JgP1oGl/5G5n8YR4FN8Q=="], @@ -122,96 +180,236 @@ "@oxfmt/binding-win32-x64-msvc": ["@oxfmt/binding-win32-x64-msvc@0.36.0", "", { "os": "win32", "cpu": "x64" }, "sha512-MoyeQ9S36ZTz/4bDhOKJgOBIDROd4dQ5AkT9iezhEaUBxAPdNX9Oq0jD8OSnCj3G4wam/XNxVWKMA52kmzmPtQ=="], - "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.51.0", "", { "os": "android", "cpu": "arm" }, "sha512-jJYIqbx4sX+suIxWstc4P7SzhEwb4ArWA2KVrmEuu9vH2i0qM6QIHz/ehmbGE4/2fZbpuMuBzTl7UkfNoqiSgw=="], + "@oxlint/binding-android-arm-eabi": ["@oxlint/binding-android-arm-eabi@1.53.0", "", { "os": "android", "cpu": "arm" }, "sha512-JC89/jAx4d2zhDIbK8MC4L659FN1WiMXMBkNg7b33KXSkYpUgcbf+0nz7+EPRg+VwWiZVfaoFkNHJ7RXYb5Neg=="], + + "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.53.0", "", { "os": "android", "cpu": "arm64" }, "sha512-CY+pZfi+uyeU7AwFrEnjsNT+VfxYmKLMuk7bVxArd8f+09hQbJb8f7C7EpvTfNqrCK1J8zZlaYI4LltmEctgbQ=="], + + "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.53.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0aqsC4HDQ94oI6kMz64iaOJ1f3bCVArxvaHJGOScBvFz6CcQedXi5b70Xg09CYjKNaHA56dW0QJfoZ/111kz1A=="], - "@oxlint/binding-android-arm64": ["@oxlint/binding-android-arm64@1.51.0", "", { "os": "android", "cpu": "arm64" }, "sha512-GtXyBCcH4ti98YdiMNCrpBNGitx87EjEWxevnyhcBK12k/Vu4EzSB45rzSC4fGFUD6sQgeaxItRCEEWeVwPafw=="], + "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.53.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-e+KvuaWtnisyWojO/t5qKDbp2dvVpg+1dl4MGnTb21QpY4+4+9Y1XmZPaztcA2XNvy4BIaXFW+9JH9tMpSBqUg=="], - "@oxlint/binding-darwin-arm64": ["@oxlint/binding-darwin-arm64@1.51.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-3QJbeYaMHn6Bh2XeBXuITSsbnIctyTjvHf5nRjKYrT9pPeErNIpp5VDEeAXC0CZSwSVTsc8WOSDwgrAI24JolQ=="], + "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.53.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hpU0ZHVeblFjmZDfgi9BxhhCpURh0KjoFy5V+Tvp9sg/fRcnMUEfaJrgz+jQfOX4jctlVWrAs1ANs91+5iV+zA=="], - "@oxlint/binding-darwin-x64": ["@oxlint/binding-darwin-x64@1.51.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-NzErhMaTEN1cY0E8C5APy74lw5VwsNfJfVPBMWPVQLqAbO0k4FFLjvHURvkUL+Y18Wu+8Vs1kbqPh2hjXYA4pg=="], + "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.53.0", "", { "os": "linux", "cpu": "arm" }, "sha512-ccKxOpw+X4xa2pO+qbTOpxQ2x1+Ag3ViRQMnWt3gHp1LcpNgS1xd6GYc3OvehmHtrXqEV3YGczZ0I1qpBB4/2A=="], - "@oxlint/binding-freebsd-x64": ["@oxlint/binding-freebsd-x64@1.51.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-msAIh3vPAoKoHlOE/oe6Q5C/n9umypv/k81lED82ibrJotn+3YG2Qp1kiR8o/Dg5iOEU97c6tl0utxcyFenpFw=="], + "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.53.0", "", { "os": "linux", "cpu": "arm" }, "sha512-UBkBvmzSmlyH2ZObQMDKW/TuyTmUtP/XClPUyU2YLwj0qLopZTZxnDz4VG5d3wz1HQuZXO0o1QqsnQUW1v4a6Q=="], - "@oxlint/binding-linux-arm-gnueabihf": ["@oxlint/binding-linux-arm-gnueabihf@1.51.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CqQPcvqYyMe9ZBot2stjGogEzk1z8gGAngIX7srSzrzexmXixwVxBdFZyxTVM0CjGfDeV+Ru0w25/WNjlMM2Hw=="], + "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.53.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-PQJJ1izoH9p61las6rZ0BWOznAhTDMmdUPL2IEBLuXFwhy2mSloYHvRkk39PSYJ1DyG+trqU5Z9ZbtHSGH6plg=="], - "@oxlint/binding-linux-arm-musleabihf": ["@oxlint/binding-linux-arm-musleabihf@1.51.0", "", { "os": "linux", "cpu": "arm" }, "sha512-dstrlYQgZMnyOssxSbolGCge/sDbko12N/35RBNuqLpoPbft2aeBidBAb0dvQlyBd9RJ6u8D4o4Eh8Un6iTgyQ=="], + "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.53.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-GXI1o4Thn/rtnRIL38BwrDMwVcUbIHKCsOixIWf/CkU3fCG3MXFzFTtDMt+34ik0Qk452d8kcpksL0w/hUkMZA=="], - "@oxlint/binding-linux-arm64-gnu": ["@oxlint/binding-linux-arm64-gnu@1.51.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-QEjUpXO7d35rP1/raLGGbAsBLLGZIzV3ZbeSjqWlD3oRnxpRIZ6iL4o51XQHkconn3uKssc+1VKdtHJ81BBhDA=="], + "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.53.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Uahk7IVs2yBamCgeJ3XKpKT9Vh+de0pDKISFKnjEcI3c/w2CFHk1+W6Q6G3KI56HGwE9PWCp6ayhA9whXWkNIQ=="], - "@oxlint/binding-linux-arm64-musl": ["@oxlint/binding-linux-arm64-musl@1.51.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-YSJua5irtG4DoMAjUapDTPhkQLHhBIY0G9JqlZS6/SZPzqDkPku/1GdWs0D6h/wyx0Iz31lNCfIaWKBQhzP0wQ=="], + "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.53.0", "", { "os": "linux", "cpu": "none" }, "sha512-sWtcU9UkrKMWsGKdFy8R6jkm9Q0VVG1VCpxVuh0HzRQQi3ENI1Nh5CkpsdfUs2MKRcOoHKbXqTscunuXjhxoxQ=="], - "@oxlint/binding-linux-ppc64-gnu": ["@oxlint/binding-linux-ppc64-gnu@1.51.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-7L4Wj2IEUNDETKssB9IDYt16T6WlF+X2jgC/hBq3diGHda9vJLpAgb09+D3quFq7TdkFtI7hwz/jmuQmQFPc1Q=="], + "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.53.0", "", { "os": "linux", "cpu": "none" }, "sha512-aXew1+HDvCdExijX/8NBVC854zJwxhKP3l9AHFSHQNo4EanlHtzDMIlIvP3raUkL0vXtFCkTFYezzU5HjstB8A=="], - "@oxlint/binding-linux-riscv64-gnu": ["@oxlint/binding-linux-riscv64-gnu@1.51.0", "", { "os": "linux", "cpu": "none" }, "sha512-cBUHqtOXy76G41lOB401qpFoKx1xq17qYkhWrLSM7eEjiHM9sOtYqpr6ZdqCnN9s6ZpzudX4EkeHOFH2E9q0vA=="], + "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.53.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-rVpyBSqPGou9sITcsoXqUoGBUH74bxYLYOAGUqN599Zu6BQBlBU9hh3bJQ/20D1xrhhrsbiCpVPvXpLPM5nL1w=="], - "@oxlint/binding-linux-riscv64-musl": ["@oxlint/binding-linux-riscv64-musl@1.51.0", "", { "os": "linux", "cpu": "none" }, "sha512-WKbg8CysgZcHfZX0ixQFBRSBvFZUHa3SBnEjHY2FVYt2nbNJEjzTxA3ZR5wMU0NOCNKIAFUFvAh5/XJKPRJuJg=="], + "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.53.0", "", { "os": "linux", "cpu": "x64" }, "sha512-eOyeQ8qFQ2geXmlWJuXAOaek0hFhbMLlYsU457NMLKDRoC43Xf+eDPZ9Yk0n9jDaGJ5zBl/3Dy8wo41cnIXuLA=="], - "@oxlint/binding-linux-s390x-gnu": ["@oxlint/binding-linux-s390x-gnu@1.51.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-N1QRUvJTxqXNSu35YOufdjsAVmKVx5bkrggOWAhTWBc3J4qjcBwr1IfyLh/6YCg8sYRSR1GraldS9jUgJL/U4A=="], + "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.53.0", "", { "os": "linux", "cpu": "x64" }, "sha512-S6rBArW/zD1tob8M9PwKYrRmz+j1ss1+wjbRAJCWKd7TC3JB6noDiA95pIj9zOZVVp04MIzy5qymnYusrEyXzg=="], - "@oxlint/binding-linux-x64-gnu": ["@oxlint/binding-linux-x64-gnu@1.51.0", "", { "os": "linux", "cpu": "x64" }, "sha512-e0Mz0DizsCoqNIjeOg6OUKe8JKJWZ5zZlwsd05Bmr51Jo3AOL4UJnPvwKumr4BBtBrDZkCmOLhCvDGm95nJM2g=="], + "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.53.0", "", { "os": "none", "cpu": "arm64" }, "sha512-sd/A0Ny5sN0D/MJtlk7w2jGY4bJQou7gToa9WZF7Sj6HTyVzvlzKJWiOHfr4SulVk4ndiFQ8rKmF9rXP0EcF3A=="], - "@oxlint/binding-linux-x64-musl": ["@oxlint/binding-linux-x64-musl@1.51.0", "", { "os": "linux", "cpu": "x64" }, "sha512-wD8HGTWhYBKXvRDvoBVB1y+fEYV01samhWQSy1Zkxq2vpezvMnjaFKRuiP6tBNITLGuffbNDEXOwcAhJ3gI5Ug=="], + "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.53.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-QC3q7b51Er/ZurEFcFzc7RpQ/YEoEBLJuCp3WoOzhSHHH/nkUKFy+igOxlj1z3LayhEZPDQQ7sXvv2PM2cdG3Q=="], - "@oxlint/binding-openharmony-arm64": ["@oxlint/binding-openharmony-arm64@1.51.0", "", { "os": "none", "cpu": "arm64" }, "sha512-5NSwQ2hDEJ0GPXqikjWtwzgAQCsS7P9aLMNenjjKa+gknN3lTCwwwERsT6lKXSirfU3jLjexA2XQvQALh5h27w=="], + "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.53.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-3OvLgOqwd705hWHV2i8ni80pilvg6BUgpC2+xtVu++e/q28LKVohGh5J5QYJOrRMfWmxK0M/AUu43vUw62LAKQ=="], - "@oxlint/binding-win32-arm64-msvc": ["@oxlint/binding-win32-arm64-msvc@1.51.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-JEZyah1M0RHMw8d+jjSSJmSmO8sABA1J1RtrHYujGPeCkYg1NeH0TGuClpe2h5QtioRTaF57y/TZfn/2IFV6fA=="], + "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.53.0", "", { "os": "win32", "cpu": "x64" }, "sha512-xTiOkntexCdJytZ7ArIIgl3vGW5ujMM3sJNM7/+iqGAVJagCqjFFWn68HRWRLeyT66c95uR+CeFmQFI6mLQqDw=="], - "@oxlint/binding-win32-ia32-msvc": ["@oxlint/binding-win32-ia32-msvc@1.51.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-q3cEoKH6kwjz/WRyHwSf0nlD2F5Qw536kCXvmlSu+kaShzgrA0ojmh45CA81qL+7udfCaZL2SdKCZlLiGBVFlg=="], + "@types/bun": ["@types/bun@1.3.10", "", { "dependencies": { "bun-types": "1.3.10" } }, "sha512-0+rlrUrOrTSskibryHbvQkDOWRJwJZqZlxrUs1u4oOoTln8+WIXBPmAuCF35SWB2z4Zl3E84Nl/D0P7803nigQ=="], - "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.51.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Q14+fOGb9T28nWF/0EUsYqERiRA7cl1oy4TJrGmLaqhm+aO2cV+JttboHI3CbdeMCAyDI1+NoSlrM7Melhp/cw=="], + "@types/node": ["@types/node@25.4.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-9wLpoeWuBlcbBpOY3XmzSTG3oscB6xjBEEtn+pYXTfhyXhIxC5FsBer2KTopBlvKEiW9l13po9fq+SJY/5lkhw=="], - "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], - "@types/node": ["@types/node@25.2.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ=="], + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - "ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], - "bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], + "better-path-resolve": ["better-path-resolve@1.0.0", "", { "dependencies": { "is-windows": "^1.0.0" } }, "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "bun-types": ["bun-types@1.3.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-tcpfCCl6XWo6nCVnpcVrxQ+9AYN1iqMIzgrSKYMB/fjLtV2eyAVEg7AxQJuCq/26R6HpKWykQXuSOq/21RYcbg=="], "chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="], + "clerk": ["clerk@workspace:packages/cli"], + "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], "commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="], - "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "detect-indent": ["detect-indent@6.1.0", "", {}, "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA=="], + + "dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="], + + "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], "env-paths": ["env-paths@4.0.0", "", { "dependencies": { "is-safe-filename": "^0.1.0" } }, "sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw=="], - "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], + "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], + + "extendable-error": ["extendable-error@0.1.7", "", {}, "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="], + + "fast-string-width": ["fast-string-width@3.0.2", "", { "dependencies": { "fast-string-truncated-width": "^3.0.2" } }, "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg=="], + + "fast-wrap-ansi": ["fast-wrap-ansi@0.2.0", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w=="], + + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "fs-extra": ["fs-extra@7.0.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw=="], + + "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "human-id": ["human-id@4.1.3", "", { "bin": { "human-id": "dist/cli.js" } }, "sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q=="], "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + "is-safe-filename": ["is-safe-filename@0.1.1", "", {}, "sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g=="], + "is-subdir": ["is-subdir@1.2.0", "", { "dependencies": { "better-path-resolve": "1.0.0" } }, "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw=="], + + "is-windows": ["is-windows@1.0.2", "", {}, "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + + "jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], + + "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="], + "mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="], "nano-staged": ["nano-staged@0.9.0", "", { "dependencies": { "picocolors": "^1.0.0" }, "bin": { "nano-staged": "lib/bin.js" } }, "sha512-0JfyX4i0Vp5HhC9RDtJ1kp7psz8CFuS3Gya3Z6WZv//QCwA9dPzi1S803VdR0c0P6R7sSvweZ5mSJmYQ/N+loQ=="], + "outdent": ["outdent@0.5.0", "", {}, "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q=="], + "oxfmt": ["oxfmt@0.36.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/binding-android-arm-eabi": "0.36.0", "@oxfmt/binding-android-arm64": "0.36.0", "@oxfmt/binding-darwin-arm64": "0.36.0", "@oxfmt/binding-darwin-x64": "0.36.0", "@oxfmt/binding-freebsd-x64": "0.36.0", "@oxfmt/binding-linux-arm-gnueabihf": "0.36.0", "@oxfmt/binding-linux-arm-musleabihf": "0.36.0", "@oxfmt/binding-linux-arm64-gnu": "0.36.0", "@oxfmt/binding-linux-arm64-musl": "0.36.0", "@oxfmt/binding-linux-ppc64-gnu": "0.36.0", "@oxfmt/binding-linux-riscv64-gnu": "0.36.0", "@oxfmt/binding-linux-riscv64-musl": "0.36.0", "@oxfmt/binding-linux-s390x-gnu": "0.36.0", "@oxfmt/binding-linux-x64-gnu": "0.36.0", "@oxfmt/binding-linux-x64-musl": "0.36.0", "@oxfmt/binding-openharmony-arm64": "0.36.0", "@oxfmt/binding-win32-arm64-msvc": "0.36.0", "@oxfmt/binding-win32-ia32-msvc": "0.36.0", "@oxfmt/binding-win32-x64-msvc": "0.36.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-/ejJ+KoSW6J9bcNT9a9UtJSJNWhJ3yOLSBLbkoFHJs/8CZjmaZVZAJe4YgO1KMJlKpNQasrn/G9JQUEZI3p0EQ=="], - "oxlint": ["oxlint@1.51.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.51.0", "@oxlint/binding-android-arm64": "1.51.0", "@oxlint/binding-darwin-arm64": "1.51.0", "@oxlint/binding-darwin-x64": "1.51.0", "@oxlint/binding-freebsd-x64": "1.51.0", "@oxlint/binding-linux-arm-gnueabihf": "1.51.0", "@oxlint/binding-linux-arm-musleabihf": "1.51.0", "@oxlint/binding-linux-arm64-gnu": "1.51.0", "@oxlint/binding-linux-arm64-musl": "1.51.0", "@oxlint/binding-linux-ppc64-gnu": "1.51.0", "@oxlint/binding-linux-riscv64-gnu": "1.51.0", "@oxlint/binding-linux-riscv64-musl": "1.51.0", "@oxlint/binding-linux-s390x-gnu": "1.51.0", "@oxlint/binding-linux-x64-gnu": "1.51.0", "@oxlint/binding-linux-x64-musl": "1.51.0", "@oxlint/binding-openharmony-arm64": "1.51.0", "@oxlint/binding-win32-arm64-msvc": "1.51.0", "@oxlint/binding-win32-ia32-msvc": "1.51.0", "@oxlint/binding-win32-x64-msvc": "1.51.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.15.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-g6DNPaV9/WI9MoX2XllafxQuxwY1TV++j7hP8fTJByVBuCoVtm3dy9f/2vtH/HU40JztcgWF4G7ua+gkainklQ=="], + "oxlint": ["oxlint@1.53.0", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.53.0", "@oxlint/binding-android-arm64": "1.53.0", "@oxlint/binding-darwin-arm64": "1.53.0", "@oxlint/binding-darwin-x64": "1.53.0", "@oxlint/binding-freebsd-x64": "1.53.0", "@oxlint/binding-linux-arm-gnueabihf": "1.53.0", "@oxlint/binding-linux-arm-musleabihf": "1.53.0", "@oxlint/binding-linux-arm64-gnu": "1.53.0", "@oxlint/binding-linux-arm64-musl": "1.53.0", "@oxlint/binding-linux-ppc64-gnu": "1.53.0", "@oxlint/binding-linux-riscv64-gnu": "1.53.0", "@oxlint/binding-linux-riscv64-musl": "1.53.0", "@oxlint/binding-linux-s390x-gnu": "1.53.0", "@oxlint/binding-linux-x64-gnu": "1.53.0", "@oxlint/binding-linux-x64-musl": "1.53.0", "@oxlint/binding-openharmony-arm64": "1.53.0", "@oxlint/binding-win32-arm64-msvc": "1.53.0", "@oxlint/binding-win32-ia32-msvc": "1.53.0", "@oxlint/binding-win32-x64-msvc": "1.53.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.15.0" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-TLW0PzGbpO1JxUnuy1pIqVPjQUGh4fNfxu5XJbdFIRFVaJ0UFzTjjk/hSFTMRxN6lZub53xL/IwJNEkrh7VtDg=="], + + "p-filter": ["p-filter@2.1.0", "", { "dependencies": { "p-map": "^2.0.0" } }, "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw=="], + + "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + + "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "p-map": ["p-map@2.1.0", "", {}, "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw=="], + + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + + "package-manager-detector": ["package-manager-detector@0.2.11", "", { "dependencies": { "quansync": "^0.2.7" } }, "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "pify": ["pify@4.0.1", "", {}, "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="], + + "prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="], + + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "read-yaml-file": ["read-yaml-file@1.1.0", "", { "dependencies": { "graceful-fs": "^4.1.5", "js-yaml": "^3.6.1", "pify": "^4.0.1", "strip-bom": "^3.0.0" } }, "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA=="], + + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="], - "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "spawndamnit": ["spawndamnit@3.0.1", "", { "dependencies": { "cross-spawn": "^7.0.5", "signal-exit": "^4.0.1" } }, "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg=="], + + "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], + + "term-size": ["term-size@2.2.1", "", {}, "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg=="], "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], + + "universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], - "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="], + + "@inquirer/editor/@inquirer/external-editor": ["@inquirer/external-editor@2.0.3", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-LgyI7Agbda74/cL5MvA88iDpvdXI2KuMBCGRkbCl2Dg1vzHeOgs+s0SDcXV7b+WZJrv2+ERpWSM65Fpi9VfY3w=="], + + "@manypkg/find-root/@types/node": ["@types/node@12.20.55", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="], + + "@manypkg/find-root/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], + + "@manypkg/get-packages/@changesets/types": ["@changesets/types@4.1.0", "", {}, "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw=="], + + "@manypkg/get-packages/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], + + "read-yaml-file/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + + "read-yaml-file/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], } } diff --git a/docs/releasing.md b/docs/releasing.md new file mode 100644 index 000000000..a74c42e6f --- /dev/null +++ b/docs/releasing.md @@ -0,0 +1,204 @@ +# Release Flow + +This document describes how the Clerk CLI is built, versioned, and published. + +## Overview + +``` +push to main + → changesets/action creates/updates "Version Packages" PR + → merge "Version Packages" PR + → check-release.ts detects unpublished version + → build job: cross-compile all 8 targets (~5.5s total) + → smoke-test job: verify binaries on native runners + → publish-npm: generate platform packages + publish wrapper + → upload-github-assets: attach binaries to the GitHub Release + → (if no stable release needed) canary.ts versions packages + → build → smoke-test subset → publish @canary + +PR comment "!snapshot [name]" + → snapshot.ts versions packages from PR branch + → build job: cross-compile binaries + → smoke-test job: verify linux-x64 binary + → publish-npm: publish @snapshot packages + → post installation comment on PR +``` + +## Architecture + +The CLI is distributed as an npm wrapper package (`clerk`) plus one platform-specific package per target (e.g., `@clerk/cli-darwin-arm64`). The full list of platform targets is defined in [`scripts/releaser/targets.ts`](../scripts/releaser/targets.ts). + +When a user runs `npm install -g clerk`, npm installs the wrapper plus the matching platform package via `optionalDependencies`. The wrapper's `bin/clerk` shim resolves the binary from the platform package using `require.resolve()`. + +Target names follow Node.js's `${process.platform}-${process.arch}` convention so the shim can derive package names without a lookup table. + +## Release Channels + +### Stable (`@latest`) + +Published when the "Version Packages" PR (created by `changesets/action`) is merged and `check-release.ts` detects that the version in `packages/cli/package.json` is not yet published on npm. Includes full smoke testing on native runners before publishing. Binaries are also attached to a GitHub Release created by the releaser script. + +Install: `npm install -g clerk` + +### Canary (`@canary`) + +Published automatically on every push to `main` that does **not** trigger a stable release. `scripts/canary.ts` uses Changesets snapshot mode to produce versions in the format `x.y.z-canary.v` (e.g., `0.0.1-canary.v20260313145959`). A subset of smoke tests (darwin-arm64, linux-x64, linux-x64-musl) runs before publishing. + +Install: `npm install -g clerk@canary` + +### Snapshot (`@snapshot`) + +Published on-demand from PR branches by commenting `!snapshot` (or `!snapshot `) on a pull request. The commenter must be a member or owner of the repository's organization. `scripts/snapshot.ts` uses Changesets snapshot mode to produce versions in the format `x.y.z-.v` (e.g., `0.0.1-snapshot.v20260313145959` or `0.0.1-my-feature.v20260313145959`). The datetime format ensures multiple snapshots from the same PR sort monotonically in semver. + +Install: `npm install -g clerk@` (version is posted as a PR comment after publishing) + +## Versioning + +Versioning is managed by [Changesets](https://github.com/changesets/changesets). Contributors add changeset files to their PRs by running: + +```sh +bunx changeset +``` + +This launches an interactive prompt that asks which packages changed and whether it is a patch, minor, or major bump. The resulting markdown file is committed with the PR. + +On every push to `main`, the `changesets/action@v1` GitHub Action either creates a new "Version Packages" PR or updates an existing one. That PR aggregates all pending changesets, bumps versions, and updates changelogs. Merging it triggers a stable release (detected by `scripts/check-release.ts`). + +Configuration: + +- `.changeset/config.json` -- Changesets configuration (access, snapshot template, ignored packages) + +## Build Pipeline + +The release workflow (`.github/workflows/release.yml`) runs on every push to `main`. Binary compilation is handled by a reusable workflow (`.github/workflows/build-binaries.yml`) shared across stable, canary, and snapshot pipelines. Build jobs run on Blacksmith runners (`blacksmith-2vcpu-ubuntu-2404`); smoke tests run on platform-native GitHub-hosted runners. + +### 1. Build Job + +Defined in [`.github/workflows/build-binaries.yml`](../.github/workflows/build-binaries.yml) and called by the release, canary, and snapshot pipelines. Runs as a **single sequential job** on a Blacksmith runner that cross-compiles all 8 targets in ~5.5 seconds total using `scripts/build.ts`. For each target, the script: + +1. Cross-compiles the CLI using `bun build --compile --no-compile-autoload-dotenv --target=` +2. Injects the version via `--define "CLI_VERSION=\"$CLI_VERSION\""` +3. Verifies the binary format using `file` output +4. The workflow then uploads each binary as a separate GitHub Actions artifact + +### 2. Smoke Test Job (matrix) + +Downloads each compiled binary and runs `--version` to verify the binary actually executes. Smoke testing is handled by a reusable workflow (`.github/workflows/smoke-test.yml`) shared across stable, canary, and snapshot pipelines. Each caller passes a preset name (`stable`, `canary`, or `snapshot`); the reusable workflow resolves the preset to a target matrix internally. glibc targets run natively on a platform-matched GitHub-hosted runner; musl targets run inside an Alpine Docker container on a Linux runner. + +Not all targets have a native runner available. `win32-arm64` is published as best-effort -- the build job verifies it is a valid PE32+/Aarch64 binary via `file` output, but no execution-level smoke test runs because there is no GitHub-hosted ARM Windows runner. + +Publishing and GitHub Release upload are gated on all smoke tests passing. + +### 3. Publish npm Job + +Runs the releaser script (`scripts/releaser/index.ts`) via `bun run release` (stable), `bun run release:canary` (canary), or `bun run release:snapshot` (snapshot): + +1. Reads the version from `packages/cli/package.json` (or uses `--version` override for canary/snapshot) +2. For each target, generates a platform package in `dist/platform-packages/`: + - Creates `package.json` with `os`/`cpu` fields for npm platform selection + - Copies the compiled binary from the build artifacts +3. Publishes each platform package with `--access public` (authentication and provenance use npm OIDC trusted publishing -- no `NPM_TOKEN` secret needed, just `id-token: write` permission on a GitHub-hosted runner) +4. Temporarily mutates the wrapper `package.json` to add `optionalDependencies` and remove `private: true`, publishes it, then restores the original file +5. For stable releases, creates a Git tag and GitHub Release via the GitHub API + +The releaser accepts these flags: + +- `--dry-run` -- simulate publishing without actually uploading to npm +- `--tag ` -- publish with a specific npm dist-tag (e.g., `canary`, `snapshot`); defaults to `latest` +- `--version ` -- override the version read from `package.json` + +All publishes are idempotent -- the script checks `npm view` before publishing and skips already-published versions. + +#### Environment Variables + +The releaser script and publish workflow steps use these environment variables: + +- `ARTIFACTS_DIR` -- path to directory containing compiled binaries from the build job (defaults to `./dist/artifacts`) +- `GH_TOKEN` -- GitHub token used by the releaser to create tags and releases (stable only) + +#### npm Authentication + +Publishing uses [npm OIDC trusted publishing](https://docs.npmjs.com/trusted-publishers/) instead of stored secrets. The publish jobs run on GitHub-hosted runners with `id-token: write` permission, and npm >= 11.5.1 automatically authenticates via GitHub's OIDC provider. Each package must have a trusted publisher configured on npmjs.com pointing to the correct workflow file. + +> **First publish**: New packages cannot use trusted publishing until they exist on npm. The very first stable release requires a one-time `NODE_AUTH_TOKEN` with a granular access token. After that, configure trusted publishers for all packages and remove the token. + +### 4. Upload GitHub Assets Job + +Attaches the compiled binaries to the GitHub Release for direct download. Binaries are uploaded with display names following the `clerk-` convention (e.g., `clerk-darwin-arm64`, `clerk-win32-x64.exe`). + +## Key Files + +| File | Purpose | +| -------------------------------------- | ------------------------------------------------------------------------------ | +| `packages/cli/bin/clerk` | CJS shim that resolves and spawns the platform binary | +| `packages/cli/package.json` | Wrapper package (has `prepublishOnly` guard against accidental direct publish) | +| `packages/cli-core/src/cli.ts` | CLI entrypoint (reads `CLI_VERSION` global at runtime) | +| `packages/cli-core/src/globals.d.ts` | TypeScript declaration for the `CLI_VERSION` compile-time define | +| `scripts/releaser/index.ts` | Generates platform packages and publishes everything to npm | +| `scripts/releaser/targets.ts` | Target definitions (used by both releaser and build.ts) | +| `scripts/build.ts` | Cross-compiles CLI binaries for all 8 platform targets | +| `scripts/canary.ts` | Versions packages for canary channel using Changesets snapshots | +| `scripts/snapshot.ts` | Versions packages for snapshot channel using Changesets snapshots | +| `scripts/check-release.ts` | Detects if a stable release is needed (compares version to npm registry) | +| `.changeset/config.json` | Changesets configuration | +| `.github/workflows/build-binaries.yml` | Reusable workflow for cross-compiling binaries (called by release + snapshot) | +| `.github/workflows/smoke-test.yml` | Reusable workflow for smoke-testing binaries (called by release + snapshot) | +| `.github/workflows/release.yml` | GitHub Actions release + canary workflow | +| `.github/workflows/snapshot.yml` | GitHub Actions snapshot workflow (triggered by PR comments) | + +## Keeping Targets in Sync + +The target list exists in these places that must stay in sync: + +1. `scripts/releaser/targets.ts` -- used by the releaser to generate platform packages and by `scripts/build.ts` to cross-compile binaries +2. `.github/workflows/smoke-test.yml` preset definitions -- defines the target matrix for each preset (`stable`, `canary`, `snapshot`) + +If you add or remove a target, update both of these. Note that the smoke-test presets may not cover every target if a native runner isn't available (e.g., `win32-arm64`). + +## Local Development + +```sh +# Run CLI source directly (fastest, no compilation) +bun run dev + +# Compile a native binary and run it +bun run start -- --help + +# Cross-compile all 8 targets to dist/artifacts/ +bun run build:compile:all + +# Cross-compile a single target +bun run scripts/build.ts --target=bun-darwin-arm64 +``` + +The `dev` and `start` commands do not inject a version (falls back to `0.0.0-dev`). The release workflow handles version injection. + +To test the releaser without publishing: + +```sh +bun run scripts/releaser/index.ts --dry-run +``` + +## Contributing + +When submitting a PR that changes user-facing behavior, add a changeset: + +```sh +bunx changeset +``` + +Follow the interactive prompts to select the affected package (`clerk`) and the bump type (patch, minor, or major). Commit the generated `.changeset/*.md` file with your PR. The `changesets/action` bot will incorporate it into the next "Version Packages" PR automatically. + +If your change is internal-only (CI, tests, docs, refactoring), you can skip the changeset -- the bot will note that no packages need version bumps. + +## Safeguards + +- **`prepublishOnly` guard**: The wrapper `package.json` has a `prepublishOnly` script that exits with an error, preventing accidental `npm publish` from the package directory. The releaser bypasses this with `--ignore-scripts`. +- **`private: true`**: Both `packages/cli` and `packages/cli-core` are marked private. The releaser removes this flag from the wrapper before publishing and restores it afterward. +- **Idempotent publishing**: The releaser checks npm before publishing and skips already-published versions, making it safe to re-run. +- **Release detection**: `scripts/check-release.ts` compares the version in `package.json` against the npm registry, ensuring stable releases only trigger when there is genuinely a new version to publish. +- **Binary format verification**: The build script verifies each compiled binary matches its expected architecture before uploading. +- **Native smoke tests**: Each binary is executed on a native runner for its platform before publishing. This catches cross-compilation issues that format checks alone would miss. +- **Org membership check**: Snapshot releases require the commenter to be a `MEMBER` or `OWNER` of the repository's organization, verified via `author_association`. +- **OIDC trusted publishing**: Publish jobs authenticate via GitHub's OIDC provider instead of stored npm tokens. This eliminates secret rotation, prevents token exfiltration, and scopes publish permissions to specific workflow files. +- **CI build check**: Every PR to `main` runs a JS bundle build to catch bundler-specific failures before merge. diff --git a/package.json b/package.json index 3cff281b4..7e8603fdc 100644 --- a/package.json +++ b/package.json @@ -1,35 +1,33 @@ { - "name": "@clerk/cli", - "version": "0.0.1", - "description": "Clerk CLI", - "bin": { - "clerk": "./dist/cli.js" - }, - "type": "module", + "name": "@clerk/cli-workspace", + "private": true, + "workspaces": [ + "packages/*" + ], "scripts": { - "build": "bun build ./src/cli.ts --outfile ./dist/cli.js --target node --external @napi-rs/keyring", - "dev": "bun run ./src/cli.ts", - "test": "bun test", - "lint": "oxlint src/", - "format": "oxfmt --write src/", - "format:check": "oxfmt --check src/", + "build": "bun run --filter @clerk/cli-core build", + "dev": "bun run --filter @clerk/cli-core dev", + "test": "bun run --filter @clerk/cli-core test", + "lint": "bun run --filter @clerk/cli-core lint && oxlint scripts/", + "format": "bun run --filter @clerk/cli-core format && oxfmt --write scripts/", + "format:check": "bun run --filter @clerk/cli-core format:check && oxfmt --check scripts/", + "build:compile": "bun run --filter @clerk/cli-core build:compile", + "version-packages": "bunx changeset version", + "version-packages:canary": "bun run scripts/snapshot.ts -- canary", + "version-packages:snapshot": "bun run scripts/snapshot.ts", + "release": "bun run scripts/releaser/index.ts", + "release:canary": "bun run scripts/releaser/index.ts --tag canary", + "release:snapshot": "bun run scripts/releaser/index.ts --tag snapshot", + "build:compile:all": "bun run scripts/build.ts", + "start": "bun run build:compile && ./packages/cli-core/dist/clerk", "prepare": "git config core.hooksPath .hooks" }, - "dependencies": { - "@commander-js/extra-typings": "^14.0.0", - "@inquirer/prompts": "^8.2.0", - "@napi-rs/keyring": "^1.2.0", - "commander": "^14.0.3", - "env-paths": "^4.0.0", - "yaml": "^2.8.2" - }, "devDependencies": { - "@types/bun": "latest", + "@changesets/cli": "^2.29.4", + "@types/bun": "^1.3.9", "nano-staged": "^0.9.0", "oxfmt": "^0.36.0", - "oxlint": "^1.51.0" - }, - "peerDependencies": { + "oxlint": "^1.51.0", "typescript": "^5" }, "nano-staged": { diff --git a/mocks/bun.lock b/packages/cli-core/mocks/bun.lock similarity index 100% rename from mocks/bun.lock rename to packages/cli-core/mocks/bun.lock diff --git a/mocks/index.html b/packages/cli-core/mocks/index.html similarity index 100% rename from mocks/index.html rename to packages/cli-core/mocks/index.html diff --git a/mocks/package.json b/packages/cli-core/mocks/package.json similarity index 75% rename from mocks/package.json rename to packages/cli-core/mocks/package.json index 15c083e69..d7f08b022 100644 --- a/mocks/package.json +++ b/packages/cli-core/mocks/package.json @@ -6,7 +6,7 @@ "dev": "vite" }, "devDependencies": { - "vite": "^6.3.5", - "typescript": "^5" + "typescript": "^5", + "vite": "^6.3.5" } } diff --git a/mocks/src/main.ts b/packages/cli-core/mocks/src/main.ts similarity index 96% rename from mocks/src/main.ts rename to packages/cli-core/mocks/src/main.ts index 0a67e7135..0df3200aa 100644 --- a/mocks/src/main.ts +++ b/packages/cli-core/mocks/src/main.ts @@ -4,12 +4,7 @@ const app = document.getElementById("app")!; const params = new URLSearchParams(window.location.search); const callbackPort = params.get("callback_port"); -type Screen = - | "sign-in" - | "sign-up" - | "select-app" - | "success-new" - | "success-existing"; +type Screen = "sign-in" | "sign-up" | "select-app" | "success-new" | "success-existing"; function render(screen: Screen) { switch (screen) { @@ -135,9 +130,7 @@ function renderSelectApp() { document.getElementById("app-list")!.addEventListener("click", (e) => { const li = (e.target as HTMLElement).closest("li"); if (!li) return; - document - .querySelectorAll(".app-list li") - .forEach((el) => el.classList.remove("selected")); + document.querySelectorAll(".app-list li").forEach((el) => el.classList.remove("selected")); li.classList.add("selected"); selected = li.dataset.id!; linkBtn.disabled = false; diff --git a/mocks/src/style.css b/packages/cli-core/mocks/src/style.css similarity index 97% rename from mocks/src/style.css rename to packages/cli-core/mocks/src/style.css index 95eef2241..afa90783e 100644 --- a/mocks/src/style.css +++ b/packages/cli-core/mocks/src/style.css @@ -119,7 +119,9 @@ body { margin-bottom: 8px; cursor: pointer; font-size: 14px; - transition: border-color 0.15s, background 0.15s; + transition: + border-color 0.15s, + background 0.15s; } .app-list li:hover, diff --git a/mocks/tsconfig.json b/packages/cli-core/mocks/tsconfig.json similarity index 100% rename from mocks/tsconfig.json rename to packages/cli-core/mocks/tsconfig.json diff --git a/packages/cli-core/package.json b/packages/cli-core/package.json new file mode 100644 index 000000000..06bf5b445 --- /dev/null +++ b/packages/cli-core/package.json @@ -0,0 +1,23 @@ +{ + "name": "@clerk/cli-core", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "bun build ./src/cli.ts --outfile ./dist/cli.js --target node --external @napi-rs/keyring", + "build:compile": "bun build --compile --no-compile-autoload-dotenv ./src/cli.ts --outfile ./dist/clerk", + "dev": "bun run ./src/cli.ts", + "test": "for f in $(find src -name '*.test.ts' | sort); do bun test \"$f\" || exit 1; done; # Runs each test file individually to work around Bun mock.module state leaking between files (see 1e85a13)", + "lint": "oxlint src/", + "format": "oxfmt --write src/", + "format:check": "oxfmt --check src/" + }, + "dependencies": { + "@commander-js/extra-typings": "^14.0.0", + "@inquirer/prompts": "^8.2.0", + "@napi-rs/keyring": "^1.2.0", + "commander": "^14.0.3", + "env-paths": "^4.0.0", + "yaml": "^2.8.2" + } +} diff --git a/src/cli-program.ts b/packages/cli-core/src/cli-program.ts similarity index 89% rename from src/cli-program.ts rename to packages/cli-core/src/cli-program.ts index 79570f263..3af2487bc 100644 --- a/src/cli-program.ts +++ b/packages/cli-core/src/cli-program.ts @@ -1,26 +1,26 @@ import { Command } from "@commander-js/extra-typings"; -import { setMode, type Mode } from "./mode.js"; -import { init } from "./commands/init/index.js"; -import { login } from "./commands/auth/login.js"; -import { logout } from "./commands/auth/logout.js"; -import { whoami } from "./commands/whoami/index.js"; -import { pull } from "./commands/env/pull.js"; -import { deploy } from "./commands/deploy/index.js"; -import { configPull } from "./commands/config/pull.js"; -import { configPatch, configPut } from "./commands/config/push.js"; -import { configSchema } from "./commands/config/schema.js"; -import { api } from "./commands/api/index.js"; -import { link } from "./commands/link/index.js"; -import { unlink } from "./commands/unlink/index.js"; -import { doctor } from "./commands/doctor/index.js"; -import { CliError, UserAbortError, ApiError, EXIT_CODE, throwUsageError } from "./lib/errors.js"; -import { red } from "./lib/color.js"; +import { setMode, type Mode } from "./mode.ts"; +import { init } from "./commands/init/index.ts"; +import { login } from "./commands/auth/login.ts"; +import { logout } from "./commands/auth/logout.ts"; +import { whoami } from "./commands/whoami/index.ts"; +import { pull } from "./commands/env/pull.ts"; +import { deploy } from "./commands/deploy/index.ts"; +import { configPull } from "./commands/config/pull.ts"; +import { configPatch, configPut } from "./commands/config/push.ts"; +import { configSchema } from "./commands/config/schema.ts"; +import { api } from "./commands/api/index.ts"; +import { link } from "./commands/link/index.ts"; +import { unlink } from "./commands/unlink/index.ts"; +import { doctor } from "./commands/doctor/index.ts"; +import { CliError, UserAbortError, ApiError, EXIT_CODE, throwUsageError } from "./lib/errors.ts"; +import { red } from "./lib/color.ts"; export function createProgram() { const program = new Command() .name("clerk") .description("Clerk CLI") - .version(require("../package.json").version) + .version(typeof CLI_VERSION !== "undefined" ? CLI_VERSION : "0.0.0-dev") .option( "--mode ", "Force interaction mode (human or agent). Defaults to auto-detect based on TTY.", diff --git a/src/cli.ts b/packages/cli-core/src/cli.ts similarity index 50% rename from src/cli.ts rename to packages/cli-core/src/cli.ts index 5c730fe28..7fe65d77c 100644 --- a/src/cli.ts +++ b/packages/cli-core/src/cli.ts @@ -1,5 +1,5 @@ #!/usr/bin/env node -import { createProgram, runProgram } from "./cli-program.js"; -import { EXIT_CODE } from "./lib/errors.js"; +import { createProgram, runProgram } from "./cli-program.ts"; +import { EXIT_CODE } from "./lib/errors.ts"; process.on("SIGINT", () => process.exit(EXIT_CODE.SIGINT)); runProgram(createProgram()); diff --git a/src/commands/api/README.md b/packages/cli-core/src/commands/api/README.md similarity index 100% rename from src/commands/api/README.md rename to packages/cli-core/src/commands/api/README.md diff --git a/src/commands/api/bapi.test.ts b/packages/cli-core/src/commands/api/bapi.test.ts similarity index 98% rename from src/commands/api/bapi.test.ts rename to packages/cli-core/src/commands/api/bapi.test.ts index 9cdffaa56..ae6082d6b 100644 --- a/src/commands/api/bapi.test.ts +++ b/packages/cli-core/src/commands/api/bapi.test.ts @@ -1,7 +1,7 @@ import { test, expect, describe, afterEach } from "bun:test"; import { stubFetch } from "../../test/stubs.ts"; -import { bapiRequest } from "./bapi"; -import { BapiError } from "../../lib/errors"; +import { bapiRequest } from "./bapi.ts"; +import { BapiError } from "../../lib/errors.ts"; describe("bapi", () => { const originalFetch = globalThis.fetch; diff --git a/src/commands/api/bapi.ts b/packages/cli-core/src/commands/api/bapi.ts similarity index 100% rename from src/commands/api/bapi.ts rename to packages/cli-core/src/commands/api/bapi.ts diff --git a/src/commands/api/catalog.test.ts b/packages/cli-core/src/commands/api/catalog.test.ts similarity index 98% rename from src/commands/api/catalog.test.ts rename to packages/cli-core/src/commands/api/catalog.test.ts index 4997a837f..3ee74def3 100644 --- a/src/commands/api/catalog.test.ts +++ b/packages/cli-core/src/commands/api/catalog.test.ts @@ -3,7 +3,13 @@ import { stubFetch } from "../../test/stubs.ts"; import { mkdtemp, rm } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { parseSpec, loadCatalog, filterEndpoints, endpointsByTag, _setCacheDir } from "./catalog"; +import { + parseSpec, + loadCatalog, + filterEndpoints, + endpointsByTag, + _setCacheDir, +} from "./catalog.ts"; const MINIMAL_SPEC = ` openapi: "3.0.0" diff --git a/src/commands/api/catalog.ts b/packages/cli-core/src/commands/api/catalog.ts similarity index 100% rename from src/commands/api/catalog.ts rename to packages/cli-core/src/commands/api/catalog.ts diff --git a/src/commands/api/index.test.ts b/packages/cli-core/src/commands/api/index.test.ts similarity index 98% rename from src/commands/api/index.test.ts rename to packages/cli-core/src/commands/api/index.test.ts index 6dbd17f37..ec0d3e655 100644 --- a/src/commands/api/index.test.ts +++ b/packages/cli-core/src/commands/api/index.test.ts @@ -53,8 +53,8 @@ mock.module("../../lib/config.ts", () => ({ mock.module("@inquirer/prompts", () => promptsStubs); -const { _setConfigDir } = (await import("../../lib/config")) as any; -const { setMode } = (await import("../../mode")) as any; +const { _setConfigDir } = (await import("../../lib/config.ts")) as any; +const { setMode } = (await import("../../mode.ts")) as any; describe("api command", () => { const originalEnv = { ...process.env }; @@ -107,7 +107,7 @@ describe("api command", () => { }); async function runApi(endpoint: string, options: Record = {}) { - const { api } = await import("./index"); + const { api } = await import("./index.ts"); return api(endpoint, undefined, options); } diff --git a/src/commands/api/index.ts b/packages/cli-core/src/commands/api/index.ts similarity index 100% rename from src/commands/api/index.ts rename to packages/cli-core/src/commands/api/index.ts diff --git a/src/commands/api/interactive.test.ts b/packages/cli-core/src/commands/api/interactive.test.ts similarity index 93% rename from src/commands/api/interactive.test.ts rename to packages/cli-core/src/commands/api/interactive.test.ts index 814096d78..ecb813050 100644 --- a/src/commands/api/interactive.test.ts +++ b/packages/cli-core/src/commands/api/interactive.test.ts @@ -14,8 +14,8 @@ mock.module("../../mode.ts", () => ({ isHuman: () => _mode !== "agent", })); -const { parseSpec, _setCacheDir } = (await import("./catalog")) as any; -const { setMode } = (await import("../../mode")) as any; +const { parseSpec, _setCacheDir } = (await import("./catalog.ts")) as any; +const { setMode } = (await import("../../mode.ts")) as any; const MINIMAL_SPEC = ` openapi: "3.0.0" @@ -125,7 +125,7 @@ describe("apiInteractive", () => { test("shows help and returns in agent mode", async () => { setMode("agent"); - const { apiInteractive } = await import("./interactive"); + const { apiInteractive } = await import("./interactive.ts"); await apiInteractive({}); expect(errorSpy).toHaveBeenCalledWith( @@ -136,7 +136,7 @@ describe("apiInteractive", () => { test("completes full flow for GET endpoint (no body, no params)", async () => { setMode("human"); - const { apiInteractive } = await import("./interactive"); + const { apiInteractive } = await import("./interactive.ts"); // Step 1: select tag "Users" selectResponses.push("Users"); @@ -162,7 +162,7 @@ describe("apiInteractive", () => { test("prompts for path parameters", async () => { setMode("human"); - const { apiInteractive } = await import("./interactive"); + const { apiInteractive } = await import("./interactive.ts"); selectResponses.push("Users"); selectResponses.push({ @@ -185,7 +185,7 @@ describe("apiInteractive", () => { test("aborts when user declines confirmation", async () => { setMode("human"); - const { apiInteractive } = await import("./interactive"); + const { apiInteractive } = await import("./interactive.ts"); selectResponses.push("Users"); selectResponses.push({ diff --git a/src/commands/api/interactive.ts b/packages/cli-core/src/commands/api/interactive.ts similarity index 100% rename from src/commands/api/interactive.ts rename to packages/cli-core/src/commands/api/interactive.ts diff --git a/src/commands/api/ls.test.ts b/packages/cli-core/src/commands/api/ls.test.ts similarity index 97% rename from src/commands/api/ls.test.ts rename to packages/cli-core/src/commands/api/ls.test.ts index 72dd13e76..d2da3ef33 100644 --- a/src/commands/api/ls.test.ts +++ b/packages/cli-core/src/commands/api/ls.test.ts @@ -2,9 +2,9 @@ import { test, expect, describe, beforeEach, afterEach, spyOn } from "bun:test"; import { mkdtemp, rm } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { parseSpec, _setCacheDir } from "./catalog"; +import { parseSpec, _setCacheDir } from "./catalog.ts"; import { stubFetch } from "../../test/stubs.ts"; -import { apiLs } from "./ls"; +import { apiLs } from "./ls.ts"; const MINIMAL_SPEC = ` openapi: "3.0.0" diff --git a/src/commands/api/ls.ts b/packages/cli-core/src/commands/api/ls.ts similarity index 100% rename from src/commands/api/ls.ts rename to packages/cli-core/src/commands/api/ls.ts diff --git a/src/commands/auth/README.md b/packages/cli-core/src/commands/auth/README.md similarity index 100% rename from src/commands/auth/README.md rename to packages/cli-core/src/commands/auth/README.md diff --git a/src/commands/auth/login.test.ts b/packages/cli-core/src/commands/auth/login.test.ts similarity index 100% rename from src/commands/auth/login.test.ts rename to packages/cli-core/src/commands/auth/login.test.ts diff --git a/src/commands/auth/login.ts b/packages/cli-core/src/commands/auth/login.ts similarity index 100% rename from src/commands/auth/login.ts rename to packages/cli-core/src/commands/auth/login.ts diff --git a/src/commands/auth/logout.test.ts b/packages/cli-core/src/commands/auth/logout.test.ts similarity index 100% rename from src/commands/auth/logout.test.ts rename to packages/cli-core/src/commands/auth/logout.test.ts diff --git a/src/commands/auth/logout.ts b/packages/cli-core/src/commands/auth/logout.ts similarity index 100% rename from src/commands/auth/logout.ts rename to packages/cli-core/src/commands/auth/logout.ts diff --git a/src/commands/config/README.md b/packages/cli-core/src/commands/config/README.md similarity index 100% rename from src/commands/config/README.md rename to packages/cli-core/src/commands/config/README.md diff --git a/src/commands/config/pull.test.ts b/packages/cli-core/src/commands/config/pull.test.ts similarity index 98% rename from src/commands/config/pull.test.ts rename to packages/cli-core/src/commands/config/pull.test.ts index d90a9f38e..addf26443 100644 --- a/src/commands/config/pull.test.ts +++ b/packages/cli-core/src/commands/config/pull.test.ts @@ -2,7 +2,7 @@ import { test, expect, describe, beforeEach, afterEach, spyOn, mock } from "bun: import { mkdtemp, rm } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { _setConfigDir, setProfile } from "../../lib/config"; +import { _setConfigDir, setProfile } from "../../lib/config.ts"; import { credentialStoreStubs, gitStubs, stubFetch } from "../../test/stubs.ts"; mock.module("../../lib/credential-store.ts", () => credentialStoreStubs); @@ -48,7 +48,7 @@ describe("config pull", () => { // Dynamically import to get fresh module state async function runConfigPull(options: { instance?: string; output?: string } = {}) { - const { configPull } = await import("./pull"); + const { configPull } = await import("./pull.ts"); return configPull(options); } diff --git a/src/commands/config/pull.ts b/packages/cli-core/src/commands/config/pull.ts similarity index 100% rename from src/commands/config/pull.ts rename to packages/cli-core/src/commands/config/pull.ts diff --git a/src/commands/config/push.test.ts b/packages/cli-core/src/commands/config/push.test.ts similarity index 98% rename from src/commands/config/push.test.ts rename to packages/cli-core/src/commands/config/push.test.ts index 03931c84c..cfe210e28 100644 --- a/src/commands/config/push.test.ts +++ b/packages/cli-core/src/commands/config/push.test.ts @@ -2,7 +2,7 @@ import { test, expect, describe, beforeEach, afterEach, spyOn, mock } from "bun: import { mkdtemp, rm } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { _setConfigDir, setProfile } from "../../lib/config"; +import { _setConfigDir, setProfile } from "../../lib/config.ts"; import { credentialStoreStubs, gitStubs, promptsStubs, stubFetch } from "../../test/stubs.ts"; mock.module("../../lib/credential-store.ts", () => credentialStoreStubs); @@ -56,7 +56,7 @@ describe("config push", () => { yes?: boolean; } = {}, ) { - const { configPatch } = await import("./push"); + const { configPatch } = await import("./push.ts"); return configPatch(options); } @@ -69,7 +69,7 @@ describe("config push", () => { yes?: boolean; } = {}, ) { - const { configPut } = await import("./push"); + const { configPut } = await import("./push.ts"); return configPut(options); } diff --git a/src/commands/config/push.ts b/packages/cli-core/src/commands/config/push.ts similarity index 100% rename from src/commands/config/push.ts rename to packages/cli-core/src/commands/config/push.ts diff --git a/src/commands/config/schema.test.ts b/packages/cli-core/src/commands/config/schema.test.ts similarity index 98% rename from src/commands/config/schema.test.ts rename to packages/cli-core/src/commands/config/schema.test.ts index 0d4658ee3..2341edee9 100644 --- a/src/commands/config/schema.test.ts +++ b/packages/cli-core/src/commands/config/schema.test.ts @@ -2,7 +2,7 @@ import { test, expect, describe, beforeEach, afterEach, spyOn, mock } from "bun: import { mkdtemp, rm } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { _setConfigDir, setProfile } from "../../lib/config"; +import { _setConfigDir, setProfile } from "../../lib/config.ts"; import { credentialStoreStubs, gitStubs, stubFetch } from "../../test/stubs.ts"; mock.module("../../lib/credential-store.ts", () => credentialStoreStubs); @@ -54,7 +54,7 @@ describe("config schema", () => { async function runConfigSchema( options: { instance?: string; output?: string; keys?: string[] } = {}, ) { - const { configSchema } = await import("./schema"); + const { configSchema } = await import("./schema.ts"); return configSchema(options); } diff --git a/src/commands/config/schema.ts b/packages/cli-core/src/commands/config/schema.ts similarity index 100% rename from src/commands/config/schema.ts rename to packages/cli-core/src/commands/config/schema.ts diff --git a/src/commands/deploy/README.md b/packages/cli-core/src/commands/deploy/README.md similarity index 100% rename from src/commands/deploy/README.md rename to packages/cli-core/src/commands/deploy/README.md diff --git a/src/commands/deploy/index.test.ts b/packages/cli-core/src/commands/deploy/index.test.ts similarity index 100% rename from src/commands/deploy/index.test.ts rename to packages/cli-core/src/commands/deploy/index.test.ts diff --git a/src/commands/deploy/index.ts b/packages/cli-core/src/commands/deploy/index.ts similarity index 99% rename from src/commands/deploy/index.ts rename to packages/cli-core/src/commands/deploy/index.ts index a8fc4319a..043cf095e 100644 --- a/src/commands/deploy/index.ts +++ b/packages/cli-core/src/commands/deploy/index.ts @@ -1,6 +1,6 @@ import { select, input, confirm, password } from "@inquirer/prompts"; -import { isAgent } from "../../mode.js"; -import { dim, bold, cyan, green, blue, yellow } from "../../lib/color.js"; +import { isAgent } from "../../mode.ts"; +import { dim, bold, cyan, green, blue, yellow } from "../../lib/color.ts"; const DEPLOY_PROMPT = `You are deploying a Clerk application to production. Follow these steps: diff --git a/src/commands/doctor/README.md b/packages/cli-core/src/commands/doctor/README.md similarity index 100% rename from src/commands/doctor/README.md rename to packages/cli-core/src/commands/doctor/README.md diff --git a/src/commands/doctor/checks.ts b/packages/cli-core/src/commands/doctor/checks.ts similarity index 100% rename from src/commands/doctor/checks.ts rename to packages/cli-core/src/commands/doctor/checks.ts diff --git a/src/commands/doctor/context.test.ts b/packages/cli-core/src/commands/doctor/context.test.ts similarity index 100% rename from src/commands/doctor/context.test.ts rename to packages/cli-core/src/commands/doctor/context.test.ts diff --git a/src/commands/doctor/context.ts b/packages/cli-core/src/commands/doctor/context.ts similarity index 100% rename from src/commands/doctor/context.ts rename to packages/cli-core/src/commands/doctor/context.ts diff --git a/src/commands/doctor/doctor.test.ts b/packages/cli-core/src/commands/doctor/doctor.test.ts similarity index 100% rename from src/commands/doctor/doctor.test.ts rename to packages/cli-core/src/commands/doctor/doctor.test.ts diff --git a/src/commands/doctor/format.ts b/packages/cli-core/src/commands/doctor/format.ts similarity index 100% rename from src/commands/doctor/format.ts rename to packages/cli-core/src/commands/doctor/format.ts diff --git a/src/commands/doctor/index.ts b/packages/cli-core/src/commands/doctor/index.ts similarity index 100% rename from src/commands/doctor/index.ts rename to packages/cli-core/src/commands/doctor/index.ts diff --git a/src/commands/doctor/types.ts b/packages/cli-core/src/commands/doctor/types.ts similarity index 100% rename from src/commands/doctor/types.ts rename to packages/cli-core/src/commands/doctor/types.ts diff --git a/src/commands/env/README.md b/packages/cli-core/src/commands/env/README.md similarity index 100% rename from src/commands/env/README.md rename to packages/cli-core/src/commands/env/README.md diff --git a/src/commands/env/pull.test.ts b/packages/cli-core/src/commands/env/pull.test.ts similarity index 99% rename from src/commands/env/pull.test.ts rename to packages/cli-core/src/commands/env/pull.test.ts index d14b2024b..3b2de69b9 100644 --- a/src/commands/env/pull.test.ts +++ b/packages/cli-core/src/commands/env/pull.test.ts @@ -36,7 +36,7 @@ mock.module("../../lib/config.ts", () => ({ }, })); -const { _setConfigDir, setProfile } = (await import("../../lib/config")) as any; +const { _setConfigDir, setProfile } = (await import("../../lib/config.ts")) as any; describe("env pull", () => { const originalEnv = { ...process.env }; @@ -99,7 +99,7 @@ describe("env pull", () => { }); async function runEnvPull(options: { instance?: string; file?: string } = {}) { - const { pull } = await import("./pull"); + const { pull } = await import("./pull.ts"); return pull(options); } diff --git a/src/commands/env/pull.ts b/packages/cli-core/src/commands/env/pull.ts similarity index 100% rename from src/commands/env/pull.ts rename to packages/cli-core/src/commands/env/pull.ts diff --git a/src/commands/init/README.md b/packages/cli-core/src/commands/init/README.md similarity index 100% rename from src/commands/init/README.md rename to packages/cli-core/src/commands/init/README.md diff --git a/src/commands/init/index.ts b/packages/cli-core/src/commands/init/index.ts similarity index 93% rename from src/commands/init/index.ts rename to packages/cli-core/src/commands/init/index.ts index 9a151249f..5e8cb7ef1 100644 --- a/src/commands/init/index.ts +++ b/packages/cli-core/src/commands/init/index.ts @@ -1,10 +1,10 @@ import { join } from "node:path"; -import { login } from "../auth/login.js"; -import { link } from "../link/index.js"; -import { pull } from "../env/pull.js"; -import { detectFramework } from "../../lib/framework.js"; -import { isAgent } from "../../mode.js"; -import { dim, cyan } from "../../lib/color.js"; +import { login } from "../auth/login.ts"; +import { link } from "../link/index.ts"; +import { pull } from "../env/pull.ts"; +import { detectFramework } from "../../lib/framework.ts"; +import { isAgent } from "../../mode.ts"; +import { dim, cyan } from "../../lib/color.ts"; const AGENT_PROMPT = `You are integrating Clerk authentication into an existing project. Follow these steps: diff --git a/src/commands/link/README.md b/packages/cli-core/src/commands/link/README.md similarity index 100% rename from src/commands/link/README.md rename to packages/cli-core/src/commands/link/README.md diff --git a/src/commands/link/index.test.ts b/packages/cli-core/src/commands/link/index.test.ts similarity index 99% rename from src/commands/link/index.test.ts rename to packages/cli-core/src/commands/link/index.test.ts index c985e583c..343acf45c 100644 --- a/src/commands/link/index.test.ts +++ b/packages/cli-core/src/commands/link/index.test.ts @@ -103,7 +103,6 @@ const mockApp = { describe("link", () => { let consoleSpy: ReturnType; - let errorSpy: ReturnType; afterEach(() => { _modeOverride = undefined; @@ -131,7 +130,6 @@ describe("link", () => { mockSearch.mockReset(); mockConfirm.mockReset(); consoleSpy?.mockRestore(); - errorSpy?.mockRestore(); }); describe("agent mode", () => { @@ -700,7 +698,7 @@ describe("link", () => { profile: { workspaceId: "", appId: "app_detected", instances: { development: "ins_1" } }, }); consoleSpy = spyOn(console, "log").mockImplementation(() => {}); - errorSpy = spyOn(console, "error").mockImplementation(() => {}); + spyOn(console, "error").mockImplementation(() => {}); await link({ skipIfLinked: true }); diff --git a/src/commands/link/index.ts b/packages/cli-core/src/commands/link/index.ts similarity index 94% rename from src/commands/link/index.ts rename to packages/cli-core/src/commands/link/index.ts index 3203cc242..06424874c 100644 --- a/src/commands/link/index.ts +++ b/packages/cli-core/src/commands/link/index.ts @@ -1,14 +1,14 @@ import { basename } from "node:path"; import { search, confirm } from "@inquirer/prompts"; -import { isAgent } from "../../mode.js"; -import { getToken } from "../../lib/credential-store.js"; -import { login } from "../auth/login.js"; -import { listApplications, fetchApplication, type Application } from "../../lib/plapi.js"; -import { setProfile, resolveProfile, moveProfile } from "../../lib/config.js"; -import { autolink, findClerkKeys, matchKeyToApp } from "../../lib/autolink.js"; -import { getGitRepoIdentifier, getGitRepoRoot, getGitNormalizedRemote } from "../../lib/git.js"; -import { dim, cyan } from "../../lib/color.js"; -import { CliError } from "../../lib/errors.js"; +import { isAgent } from "../../mode.ts"; +import { getToken } from "../../lib/credential-store.ts"; +import { login } from "../auth/login.ts"; +import { listApplications, fetchApplication, type Application } from "../../lib/plapi.ts"; +import { setProfile, resolveProfile, moveProfile } from "../../lib/config.ts"; +import { autolink, findClerkKeys, matchKeyToApp } from "../../lib/autolink.ts"; +import { getGitRepoIdentifier, getGitRepoRoot, getGitNormalizedRemote } from "../../lib/git.ts"; +import { dim, cyan } from "../../lib/color.ts"; +import { CliError } from "../../lib/errors.ts"; const AGENT_PROMPT = `You are linking a Clerk application to the current project directory. diff --git a/src/commands/unlink/README.md b/packages/cli-core/src/commands/unlink/README.md similarity index 100% rename from src/commands/unlink/README.md rename to packages/cli-core/src/commands/unlink/README.md diff --git a/src/commands/unlink/index.test.ts b/packages/cli-core/src/commands/unlink/index.test.ts similarity index 100% rename from src/commands/unlink/index.test.ts rename to packages/cli-core/src/commands/unlink/index.test.ts diff --git a/src/commands/unlink/index.ts b/packages/cli-core/src/commands/unlink/index.ts similarity index 86% rename from src/commands/unlink/index.ts rename to packages/cli-core/src/commands/unlink/index.ts index 50e6c0608..012cf57a7 100644 --- a/src/commands/unlink/index.ts +++ b/packages/cli-core/src/commands/unlink/index.ts @@ -1,9 +1,9 @@ import { confirm } from "@inquirer/prompts"; -import { isAgent, isHuman } from "../../mode.js"; -import { resolveProfile, removeProfile } from "../../lib/config.js"; -import { getGitRepoRoot } from "../../lib/git.js"; -import { dim, cyan } from "../../lib/color.js"; -import { CliError, throwUserAbort } from "../../lib/errors.js"; +import { isAgent, isHuman } from "../../mode.ts"; +import { resolveProfile, removeProfile } from "../../lib/config.ts"; +import { getGitRepoRoot } from "../../lib/git.ts"; +import { dim, cyan } from "../../lib/color.ts"; +import { CliError, throwUserAbort } from "../../lib/errors.ts"; const AGENT_PROMPT = `You are unlinking a Clerk application from the current project directory. diff --git a/src/commands/whoami/README.md b/packages/cli-core/src/commands/whoami/README.md similarity index 100% rename from src/commands/whoami/README.md rename to packages/cli-core/src/commands/whoami/README.md diff --git a/src/commands/whoami/index.test.ts b/packages/cli-core/src/commands/whoami/index.test.ts similarity index 100% rename from src/commands/whoami/index.test.ts rename to packages/cli-core/src/commands/whoami/index.test.ts diff --git a/src/commands/whoami/index.ts b/packages/cli-core/src/commands/whoami/index.ts similarity index 100% rename from src/commands/whoami/index.ts rename to packages/cli-core/src/commands/whoami/index.ts diff --git a/packages/cli-core/src/globals.d.ts b/packages/cli-core/src/globals.d.ts new file mode 100644 index 000000000..f6a3a72c5 --- /dev/null +++ b/packages/cli-core/src/globals.d.ts @@ -0,0 +1 @@ +declare const CLI_VERSION: string | undefined; diff --git a/src/lib/auth-server.test.ts b/packages/cli-core/src/lib/auth-server.test.ts similarity index 98% rename from src/lib/auth-server.test.ts rename to packages/cli-core/src/lib/auth-server.test.ts index cd9cf22a7..17e1ffaf4 100644 --- a/src/lib/auth-server.test.ts +++ b/packages/cli-core/src/lib/auth-server.test.ts @@ -1,5 +1,5 @@ import { test, expect, describe } from "bun:test"; -import { startAuthServer } from "./auth-server"; +import { startAuthServer } from "./auth-server.ts"; describe("auth-server", () => { test("starts on a random port", () => { diff --git a/src/lib/auth-server.ts b/packages/cli-core/src/lib/auth-server.ts similarity index 100% rename from src/lib/auth-server.ts rename to packages/cli-core/src/lib/auth-server.ts diff --git a/src/lib/autolink.test.ts b/packages/cli-core/src/lib/autolink.test.ts similarity index 100% rename from src/lib/autolink.test.ts rename to packages/cli-core/src/lib/autolink.test.ts diff --git a/src/lib/autolink.ts b/packages/cli-core/src/lib/autolink.ts similarity index 100% rename from src/lib/autolink.ts rename to packages/cli-core/src/lib/autolink.ts diff --git a/src/lib/color.ts b/packages/cli-core/src/lib/color.ts similarity index 100% rename from src/lib/color.ts rename to packages/cli-core/src/lib/color.ts diff --git a/src/lib/config.test.ts b/packages/cli-core/src/lib/config.test.ts similarity index 99% rename from src/lib/config.test.ts rename to packages/cli-core/src/lib/config.test.ts index 32d6815d0..ae4b77503 100644 --- a/src/lib/config.test.ts +++ b/packages/cli-core/src/lib/config.test.ts @@ -12,7 +12,7 @@ import { resolveInstanceId, _setConfigDir, type Profile, -} from "./config"; +} from "./config.ts"; import { join } from "node:path"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; diff --git a/src/lib/config.ts b/packages/cli-core/src/lib/config.ts similarity index 100% rename from src/lib/config.ts rename to packages/cli-core/src/lib/config.ts diff --git a/src/lib/constants.ts b/packages/cli-core/src/lib/constants.ts similarity index 100% rename from src/lib/constants.ts rename to packages/cli-core/src/lib/constants.ts diff --git a/src/lib/credential-store.test.ts b/packages/cli-core/src/lib/credential-store.test.ts similarity index 100% rename from src/lib/credential-store.test.ts rename to packages/cli-core/src/lib/credential-store.test.ts diff --git a/src/lib/credential-store.ts b/packages/cli-core/src/lib/credential-store.ts similarity index 100% rename from src/lib/credential-store.ts rename to packages/cli-core/src/lib/credential-store.ts diff --git a/src/lib/dotenv.test.ts b/packages/cli-core/src/lib/dotenv.test.ts similarity index 99% rename from src/lib/dotenv.test.ts rename to packages/cli-core/src/lib/dotenv.test.ts index 53f887419..cffd30cff 100644 --- a/src/lib/dotenv.test.ts +++ b/packages/cli-core/src/lib/dotenv.test.ts @@ -1,5 +1,5 @@ import { test, expect, describe } from "bun:test"; -import { parseEnvFile, mergeEnvVars, serializeEnvFile } from "./dotenv"; +import { parseEnvFile, mergeEnvVars, serializeEnvFile } from "./dotenv.ts"; describe("parseEnvFile", () => { test("returns empty array for empty string", () => { diff --git a/src/lib/dotenv.ts b/packages/cli-core/src/lib/dotenv.ts similarity index 100% rename from src/lib/dotenv.ts rename to packages/cli-core/src/lib/dotenv.ts diff --git a/src/lib/errors.ts b/packages/cli-core/src/lib/errors.ts similarity index 98% rename from src/lib/errors.ts rename to packages/cli-core/src/lib/errors.ts index 0992b2b58..138513c88 100644 --- a/src/lib/errors.ts +++ b/packages/cli-core/src/lib/errors.ts @@ -1,4 +1,4 @@ -import { isAgent } from "../mode"; +import { isAgent } from "../mode.ts"; /** Standard process exit codes used by the CLI. */ export const EXIT_CODE = { @@ -54,7 +54,7 @@ export class CliError extends Error { // without a .md extension, add .md to get the raw markdown URL. if ( isAgent() && - this.docsUrl.startsWith("https://docs.clerk.com/") && + this.docsUrl.startsWith("https://clerk.com/docs/") && !this.docsUrl.endsWith(".md") ) { this.docsUrl += ".md"; diff --git a/src/lib/framework.test.ts b/packages/cli-core/src/lib/framework.test.ts similarity index 99% rename from src/lib/framework.test.ts rename to packages/cli-core/src/lib/framework.test.ts index c214bd82a..45c2eb6ce 100644 --- a/src/lib/framework.test.ts +++ b/packages/cli-core/src/lib/framework.test.ts @@ -2,7 +2,7 @@ import { test, expect, describe, beforeEach, afterEach } from "bun:test"; import { mkdtemp, rm } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { detectPublishableKeyName, detectFramework } from "./framework"; +import { detectPublishableKeyName, detectFramework } from "./framework.ts"; function writePkg(dir: string, deps: Record, devDeps?: Record) { return Bun.write( diff --git a/src/lib/framework.ts b/packages/cli-core/src/lib/framework.ts similarity index 100% rename from src/lib/framework.ts rename to packages/cli-core/src/lib/framework.ts diff --git a/src/lib/git.test.ts b/packages/cli-core/src/lib/git.test.ts similarity index 100% rename from src/lib/git.test.ts rename to packages/cli-core/src/lib/git.test.ts diff --git a/src/lib/git.ts b/packages/cli-core/src/lib/git.ts similarity index 100% rename from src/lib/git.ts rename to packages/cli-core/src/lib/git.ts diff --git a/src/lib/pkce.test.ts b/packages/cli-core/src/lib/pkce.test.ts similarity index 98% rename from src/lib/pkce.test.ts rename to packages/cli-core/src/lib/pkce.test.ts index b1351c126..bc61b42cb 100644 --- a/src/lib/pkce.test.ts +++ b/packages/cli-core/src/lib/pkce.test.ts @@ -1,5 +1,5 @@ import { test, expect, describe } from "bun:test"; -import { generateCodeVerifier, generateCodeChallenge, generateState } from "./pkce"; +import { generateCodeVerifier, generateCodeChallenge, generateState } from "./pkce.ts"; describe("PKCE", () => { test("generateCodeVerifier returns a 43-char string", () => { diff --git a/src/lib/pkce.ts b/packages/cli-core/src/lib/pkce.ts similarity index 100% rename from src/lib/pkce.ts rename to packages/cli-core/src/lib/pkce.ts diff --git a/src/lib/plapi.test.ts b/packages/cli-core/src/lib/plapi.test.ts similarity index 100% rename from src/lib/plapi.test.ts rename to packages/cli-core/src/lib/plapi.test.ts diff --git a/src/lib/plapi.ts b/packages/cli-core/src/lib/plapi.ts similarity index 100% rename from src/lib/plapi.ts rename to packages/cli-core/src/lib/plapi.ts diff --git a/src/lib/token-exchange.test.ts b/packages/cli-core/src/lib/token-exchange.test.ts similarity index 99% rename from src/lib/token-exchange.test.ts rename to packages/cli-core/src/lib/token-exchange.test.ts index ab1191053..5c0609b21 100644 --- a/src/lib/token-exchange.test.ts +++ b/packages/cli-core/src/lib/token-exchange.test.ts @@ -1,5 +1,5 @@ import { test, expect, describe, afterEach, mock } from "bun:test"; -import { exchangeCodeForToken, fetchUserInfo } from "./token-exchange"; +import { exchangeCodeForToken, fetchUserInfo } from "./token-exchange.ts"; const originalFetch = globalThis.fetch; diff --git a/src/lib/token-exchange.ts b/packages/cli-core/src/lib/token-exchange.ts similarity index 100% rename from src/lib/token-exchange.ts rename to packages/cli-core/src/lib/token-exchange.ts diff --git a/src/mode.test.ts b/packages/cli-core/src/mode.test.ts similarity index 95% rename from src/mode.test.ts rename to packages/cli-core/src/mode.test.ts index 0a6d4fd6d..c943b8272 100644 --- a/src/mode.test.ts +++ b/packages/cli-core/src/mode.test.ts @@ -2,7 +2,7 @@ import { test, expect, afterEach, describe } from "bun:test"; // Re-import fresh module per test by using dynamic import // But since bun caches modules, we test the exported functions directly -import { getMode, setMode, isHuman, isAgent } from "./mode"; +import { getMode, setMode, isHuman, isAgent } from "./mode.ts"; describe("mode detection", () => { const originalEnv = process.env.CLERK_MODE; diff --git a/src/mode.ts b/packages/cli-core/src/mode.ts similarity index 100% rename from src/mode.ts rename to packages/cli-core/src/mode.ts diff --git a/src/test/integration/agent-mode.test.ts b/packages/cli-core/src/test/integration/agent-mode.test.ts similarity index 100% rename from src/test/integration/agent-mode.test.ts rename to packages/cli-core/src/test/integration/agent-mode.test.ts diff --git a/src/test/integration/api-queries.test.ts b/packages/cli-core/src/test/integration/api-queries.test.ts similarity index 100% rename from src/test/integration/api-queries.test.ts rename to packages/cli-core/src/test/integration/api-queries.test.ts diff --git a/src/test/integration/auth-lifecycle.test.ts b/packages/cli-core/src/test/integration/auth-lifecycle.test.ts similarity index 100% rename from src/test/integration/auth-lifecycle.test.ts rename to packages/cli-core/src/test/integration/auth-lifecycle.test.ts diff --git a/src/test/integration/config-management.test.ts b/packages/cli-core/src/test/integration/config-management.test.ts similarity index 100% rename from src/test/integration/config-management.test.ts rename to packages/cli-core/src/test/integration/config-management.test.ts diff --git a/src/test/integration/config-put.test.ts b/packages/cli-core/src/test/integration/config-put.test.ts similarity index 100% rename from src/test/integration/config-put.test.ts rename to packages/cli-core/src/test/integration/config-put.test.ts diff --git a/src/test/integration/deploy-to-prod.test.ts b/packages/cli-core/src/test/integration/deploy-to-prod.test.ts similarity index 100% rename from src/test/integration/deploy-to-prod.test.ts rename to packages/cli-core/src/test/integration/deploy-to-prod.test.ts diff --git a/src/test/integration/dry-run.test.ts b/packages/cli-core/src/test/integration/dry-run.test.ts similarity index 100% rename from src/test/integration/dry-run.test.ts rename to packages/cli-core/src/test/integration/dry-run.test.ts diff --git a/src/test/integration/env-merge.test.ts b/packages/cli-core/src/test/integration/env-merge.test.ts similarity index 100% rename from src/test/integration/env-merge.test.ts rename to packages/cli-core/src/test/integration/env-merge.test.ts diff --git a/src/test/integration/error-recovery.test.ts b/packages/cli-core/src/test/integration/error-recovery.test.ts similarity index 100% rename from src/test/integration/error-recovery.test.ts rename to packages/cli-core/src/test/integration/error-recovery.test.ts diff --git a/src/test/integration/onboard.test.ts b/packages/cli-core/src/test/integration/onboard.test.ts similarity index 100% rename from src/test/integration/onboard.test.ts rename to packages/cli-core/src/test/integration/onboard.test.ts diff --git a/src/test/integration/switch-apps.test.ts b/packages/cli-core/src/test/integration/switch-apps.test.ts similarity index 100% rename from src/test/integration/switch-apps.test.ts rename to packages/cli-core/src/test/integration/switch-apps.test.ts diff --git a/src/test/lib/http.ts b/packages/cli-core/src/test/lib/http.ts similarity index 100% rename from src/test/lib/http.ts rename to packages/cli-core/src/test/lib/http.ts diff --git a/src/test/lib/setup.ts b/packages/cli-core/src/test/lib/setup.ts similarity index 100% rename from src/test/lib/setup.ts rename to packages/cli-core/src/test/lib/setup.ts diff --git a/src/test/stubs.ts b/packages/cli-core/src/test/stubs.ts similarity index 100% rename from src/test/stubs.ts rename to packages/cli-core/src/test/stubs.ts diff --git a/packages/cli-core/tsconfig.json b/packages/cli-core/tsconfig.json new file mode 100644 index 000000000..190ca9268 --- /dev/null +++ b/packages/cli-core/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + // Environment setup & latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "Preserve", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + }, + "include": ["src"] +} diff --git a/packages/cli/LICENSE b/packages/cli/LICENSE new file mode 100644 index 000000000..daceccfbc --- /dev/null +++ b/packages/cli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Clerk, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/cli/bin/clerk b/packages/cli/bin/clerk new file mode 100755 index 000000000..1b2d1d50d --- /dev/null +++ b/packages/cli/bin/clerk @@ -0,0 +1,62 @@ +#!/usr/bin/env node + +"use strict"; + +const { spawnSync } = require("node:child_process"); +const { constants } = require("node:os"); +const { platform, arch, argv, exit } = require("node:process"); + +// Detect musl vs glibc to select the correct prebuilt binary. +// Approach based on detect-libc: https://github.com/lovell/detect-libc +function isMusl() { + if (platform !== "linux") return false; + // On musl systems, ldd is a shell script (or the dynamic linker itself) + // containing "musl". Similar to rustup's `ldd --version | grep musl`. + // Check common ldd locations; on musl systems the file contains "musl". + for (const p of ["/usr/bin/ldd", "/bin/ldd"]) { + try { + if (require("fs").readFileSync(p, "utf-8").includes("musl")) return true; + } catch {} + } + // Fallback: Node's diagnostic report exposes libc info without child processes. + // https://nodejs.org/api/report.html#reportgetreport + try { + const report = process.report.getReport(); + if (report.header && report.header.glibcVersionRuntime) return false; + if (Array.isArray(report.sharedObjects)) { + return report.sharedObjects.some((f) => f.includes("libc.musl-") || f.includes("ld-musl-")); + } + } catch {} + console.warn( + "Warning: Could not detect libc variant on Linux. Defaulting to glibc. " + + "If the binary fails to start, try installing the musl variant manually.", + ); + return false; +} + +const suffix = isMusl() ? "-musl" : ""; +const key = `${platform}-${arch}${suffix}`; +const pkg = `@clerk/cli-${key}`; +const ext = platform === "win32" ? ".exe" : ""; + +let binPath; +try { + binPath = require.resolve(`${pkg}/bin/clerk${ext}`); +} catch { + console.error( + `Unsupported or missing platform: ${key}\n` + + `Expected package: ${pkg}\n` + + `Try reinstalling with: npm install -g clerk`, + ); + exit(1); +} + +const { status, signal, error } = spawnSync(binPath, argv.slice(2), { stdio: "inherit" }); +if (error) { + console.error(`Failed to start Clerk CLI: ${error.message}`); + exit(1); +} +if (signal) { + exit(128 + (constants.signals[signal] ?? 1)); +} +exit(status ?? 1); diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 000000000..02055657b --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,33 @@ +{ + "name": "clerk", + "version": "0.0.1", + "private": true, + "description": "Clerk CLI", + "keywords": [ + "authentication", + "clerk", + "cli" + ], + "homepage": "https://clerk.com/docs", + "bugs": { + "url": "https://github.com/clerk/cli/issues" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/clerk/cli.git" + }, + "bin": { + "clerk": "./bin/clerk" + }, + "files": [ + "bin", + "LICENSE" + ], + "scripts": { + "prepublishOnly": "echo 'Use scripts/releaser instead' && exit 1" + }, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 000000000..871efbfa6 --- /dev/null +++ b/scripts/build.ts @@ -0,0 +1,76 @@ +import { mkdir } from "node:fs/promises"; +import { join } from "node:path"; +import { parseArgs } from "node:util"; +import { targets } from "./releaser/targets.ts"; + +const { values } = parseArgs({ + args: Bun.argv.slice(2), + options: { + target: { type: "string" }, + version: { type: "string", default: "0.0.0-dev" }, + }, +}); + +const targetFilter = values.target; +const version = values.version!; + +const selectedTargets = targetFilter + ? targets.filter((t) => t.bunTarget === targetFilter || t.name === targetFilter) + : targets; + +if (selectedTargets.length === 0) { + throw new Error( + `Unknown target: ${targetFilter}\nAvailable targets: ${targets.map((t) => t.bunTarget).join(", ")}`, + ); +} + +console.log(`Building ${selectedTargets.length} target(s) with version ${version}\n`); + +let failed = false; +for (const target of selectedTargets) { + const outDir = join("dist", "artifacts", target.name); + const outFile = join(outDir, `clerk${target.ext}`); + + await mkdir(outDir, { recursive: true }); + + console.log(`Building ${target.name} (${target.bunTarget})...`); + const buildResult = Bun.spawnSync( + [ + "bun", + "build", + "--compile", + "--no-compile-autoload-dotenv", + `--target=${target.bunTarget}`, + `--define`, + `CLI_VERSION="${version}"`, + "./packages/cli-core/src/cli.ts", + "--outfile", + outFile, + ], + { stdio: ["ignore", "pipe", "pipe"] }, + ); + + if (buildResult.exitCode !== 0) { + console.error(` FAIL: ${buildResult.stderr.toString().trim()}`); + failed = true; + continue; + } + + // Verify binary format + const fileResult = Bun.spawnSync(["file", outFile], { stdio: ["ignore", "pipe", "pipe"] }); + const fileOutput = fileResult.stdout.toString(); + if (!target.verifyPattern.test(fileOutput)) { + console.error(` FAIL: binary format mismatch for ${target.name}`); + console.error(` file output: ${fileOutput.trim()}`); + failed = true; + continue; + } + + console.log(` OK: ${outFile}`); +} + +if (failed) { + throw new Error("Some targets failed to build."); +} + +console.log("\nAll targets built successfully."); diff --git a/scripts/check-release.ts b/scripts/check-release.ts new file mode 100644 index 000000000..aeb3597d9 --- /dev/null +++ b/scripts/check-release.ts @@ -0,0 +1,22 @@ +import { join } from "node:path"; +import { appendFile } from "node:fs/promises"; +import { isPublished } from "./lib/npm.ts"; + +const WRAPPER_PKG_PATH = join(import.meta.dir, "../packages/cli/package.json"); +const GITHUB_OUTPUT = process.env.GITHUB_OUTPUT; + +const pkg = await Bun.file(WRAPPER_PKG_PATH).json(); +const version: string = pkg.version; +const published = isPublished("clerk", version); + +if (published) { + console.log(`Version ${version} is already published — skipping stable release.`); +} else { + console.log(`Version ${version} is not published — triggering stable release.`); +} + +if (GITHUB_OUTPUT) { + const lines = [`release_created=${!published}`]; + if (!published) lines.push(`version=${version}`); + await appendFile(GITHUB_OUTPUT, lines.join("\n") + "\n"); +} diff --git a/scripts/lib/npm.ts b/scripts/lib/npm.ts new file mode 100644 index 000000000..5363266e6 --- /dev/null +++ b/scripts/lib/npm.ts @@ -0,0 +1,18 @@ +/** + * Check if a package version is published on npm. + * Distinguishes "not found" (E404) from real errors (network, auth). + */ +export function isPublished(name: string, version: string): boolean { + const result = Bun.spawnSync(["npm", "view", `${name}@${version}`, "version"], { + stdio: ["ignore", "pipe", "pipe"], + }); + + if (result.exitCode === 0) return true; + + const stderr = result.stderr.toString(); + if (stderr.includes("E404") || stderr.includes("is not in this registry")) { + return false; + } + + throw new Error(`npm view ${name}@${version} failed (exit ${result.exitCode}): ${stderr.trim()}`); +} diff --git a/scripts/releaser/index.ts b/scripts/releaser/index.ts new file mode 100644 index 000000000..71b0f648f --- /dev/null +++ b/scripts/releaser/index.ts @@ -0,0 +1,148 @@ +import { mkdir, cp, rm, chmod, copyFile } from "node:fs/promises"; +import { join } from "node:path"; +import { parseArgs } from "node:util"; +import { type Target, targets, SCOPE, PKG_PREFIX } from "./targets.ts"; +import { isPublished } from "../lib/npm.ts"; + +const DIST_DIR = join(import.meta.dir, "../../dist/platform-packages"); +const ARTIFACTS_DIR = process.env.ARTIFACTS_DIR ?? join(import.meta.dir, "../../dist/artifacts"); +const WRAPPER_PKG_PATH = join(import.meta.dir, "../../packages/cli/package.json"); + +function run(cmd: string[], opts?: { cwd?: string }): void { + const result = Bun.spawnSync(cmd, { + cwd: opts?.cwd, + stdio: ["ignore", "pipe", "pipe"], + }); + if (result.exitCode !== 0) { + const stderr = result.stderr.toString().trim(); + throw new Error( + `${cmd.join(" ")} failed (exit ${result.exitCode})${stderr ? `: ${stderr}` : ""}`, + ); + } +} + +function parseCliArgs(): { dryRun: boolean; tag?: string; versionOverride?: string } { + const { values } = parseArgs({ + args: Bun.argv.slice(2), + options: { + "dry-run": { type: "boolean", default: false }, + tag: { type: "string" }, + version: { type: "string" }, + }, + }); + return { dryRun: values["dry-run"]!, tag: values.tag, versionOverride: values.version }; +} + +function packageName(targetName: string): string { + return `${SCOPE}/${PKG_PREFIX}-${targetName}`; +} + +async function generatePlatformPackage(target: Target, version: string): Promise { + const dir = join(DIST_DIR, target.name); + const binDir = join(dir, "bin"); + + await mkdir(binDir, { recursive: true }); + + const binaryName = `clerk${target.ext}`; + const artifactPath = join(ARTIFACTS_DIR, `clerk-${target.name}`, binaryName); + const destPath = join(binDir, binaryName); + await cp(artifactPath, destPath); + await chmod(destPath, 0o755); + + const pkg: Record = { + name: packageName(target.name), + version, + description: `Clerk CLI binary for ${target.name}`, + license: "MIT", + repository: { type: "git", url: "https://github.com/clerk/cli.git" }, + homepage: "https://clerk.com/docs", + os: [target.os], + cpu: [target.cpu], + preferUnplugged: true, + files: ["bin"], + }; + if (target.libc) { + pkg.libc = [target.libc]; + } + await Bun.write(join(dir, "package.json"), JSON.stringify(pkg, null, 2) + "\n"); + + // Include the LICENSE file in each platform package. + const licensePath = join(import.meta.dir, "../../LICENSE"); + await copyFile(licensePath, join(dir, "LICENSE")); + + return dir; +} + +function publish(dir: string, dryRun: boolean, tag?: string): void { + const flags = ["npm", "publish", "--access", "public", "--provenance", "--ignore-scripts"]; + if (tag) flags.push("--tag", tag); + if (dryRun) flags.push("--dry-run"); + run(flags, { cwd: dir }); +} + +const { dryRun, tag, versionOverride } = parseCliArgs(); +const version = versionOverride ?? (await Bun.file(WRAPPER_PKG_PATH).json()).version; +console.log( + `Publishing version ${version}${tag ? ` (tag: ${tag})` : ""}${dryRun ? " (dry run)" : ""}`, +); + +await rm(DIST_DIR, { recursive: true, force: true }); + +for (const target of targets) { + const name = packageName(target.name); + if (isPublished(name, version)) { + console.log(`Skipping ${name}@${version} (already published)`); + continue; + } + console.log(`Publishing ${name}@${version}...`); + const dir = await generatePlatformPackage(target, version); + publish(dir, dryRun, tag); +} + +// Build wrapper package.json for publishing: add optionalDependencies from targets and remove private flag. +// This mutation is intentional — the repo omits optionalDependencies while the published package includes them. +// We restore the original file after publishing (or on failure) so the working tree stays clean. +const wrapperRaw = await Bun.file(WRAPPER_PKG_PATH).text(); +try { + const wrapperPkg = JSON.parse(wrapperRaw); + wrapperPkg.version = version; + wrapperPkg.optionalDependencies = Object.fromEntries( + targets.map((t) => [packageName(t.name), version]), + ); + delete wrapperPkg.private; + await Bun.write(WRAPPER_PKG_PATH, JSON.stringify(wrapperPkg, null, 2) + "\n"); + + const wrapperName = "clerk"; + if (isPublished(wrapperName, version)) { + console.log(`Skipping ${wrapperName}@${version} (already published)`); + } else { + console.log(`Publishing ${wrapperName}@${version}...`); + publish(join(import.meta.dir, "../../packages/cli"), dryRun, tag); + } +} finally { + await Bun.write(WRAPPER_PKG_PATH, wrapperRaw); +} + +// Create git tag and GitHub Release for stable releases (no --tag flag). +// Canary (--tag canary) and snapshot (--tag snapshot) skip this. +if (!tag && !dryRun) { + const tagName = `v${version}`; + + // Check if tag already exists (idempotency) + const tagCheck = Bun.spawnSync(["git", "rev-parse", tagName], { + stdio: ["ignore", "pipe", "pipe"], + }); + + if (tagCheck.exitCode === 0) { + console.log(`Tag ${tagName} already exists, skipping.`); + } else { + console.log(`Creating tag ${tagName}...`); + run(["git", "tag", tagName]); + run(["git", "push", "origin", tagName]); + + console.log(`Creating GitHub Release for ${tagName}...`); + run(["gh", "release", "create", tagName, "--generate-notes"]); + } +} + +console.log("Done!"); diff --git a/scripts/releaser/targets.ts b/scripts/releaser/targets.ts new file mode 100644 index 000000000..4c24fc0dd --- /dev/null +++ b/scripts/releaser/targets.ts @@ -0,0 +1,87 @@ +export interface Target { + name: string; + bunTarget: string; + os: string; + cpu: string; + libc?: string; + ext: string; + /** Regex to verify the compiled binary format via `file` output. */ + verifyPattern: RegExp; +} + +// Target names use Node.js ${process.platform}-${process.arch} convention so the wrapper +// shim (packages/cli/bin/clerk) can derive package names without a lookup table. +// Used by scripts/build.ts and scripts/releaser/index.ts. +export const targets: Target[] = [ + { + name: "darwin-arm64", + bunTarget: "bun-darwin-arm64", + os: "darwin", + cpu: "arm64", + ext: "", + verifyPattern: /Mach-O.*arm64/, + }, + { + name: "darwin-x64", + bunTarget: "bun-darwin-x64", + os: "darwin", + cpu: "x64", + ext: "", + verifyPattern: /Mach-O.*x86_64/, + }, + { + name: "linux-arm64", + bunTarget: "bun-linux-arm64", + os: "linux", + cpu: "arm64", + libc: "glibc", + ext: "", + verifyPattern: /ELF.*ARM aarch64/, + }, + { + name: "linux-arm64-musl", + bunTarget: "bun-linux-arm64-musl", + os: "linux", + cpu: "arm64", + libc: "musl", + ext: "", + verifyPattern: /ELF.*ARM aarch64/, + }, + { + name: "linux-x64", + bunTarget: "bun-linux-x64", + os: "linux", + cpu: "x64", + libc: "glibc", + ext: "", + verifyPattern: /ELF.*x86-64/, + }, + { + name: "linux-x64-musl", + bunTarget: "bun-linux-x64-musl", + os: "linux", + cpu: "x64", + libc: "musl", + ext: "", + verifyPattern: /ELF.*x86-64/, + }, + { + name: "win32-arm64", + bunTarget: "bun-windows-arm64", + os: "win32", + cpu: "arm64", + ext: ".exe", + verifyPattern: /PE32\+.*Aarch64/, + }, + { + name: "win32-x64", + bunTarget: "bun-windows-x64", + os: "win32", + cpu: "x64", + ext: ".exe", + verifyPattern: /PE32\+.*x86-64/, + }, +]; + +export const SCOPE = "@clerk"; +export const PKG_PREFIX = "cli"; diff --git a/scripts/snapshot.ts b/scripts/snapshot.ts new file mode 100644 index 000000000..11832cad9 --- /dev/null +++ b/scripts/snapshot.ts @@ -0,0 +1,59 @@ +import { join } from "node:path"; +import { parseArgs } from "node:util"; + +const CHANGESET_CONFIG = join(import.meta.dir, "../.changeset/config.json"); +const WRAPPER_PKG = join(import.meta.dir, "../packages/cli/package.json"); + +const { values, positionals } = parseArgs({ + args: Bun.argv.slice(2), + options: { + name: { type: "string" }, + }, + allowPositionals: true, +}); + +const name = values.name || positionals[0] || "snapshot"; + +// Validate kebab-case +if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(name)) { + throw new Error(`Invalid snapshot name: ${name} (must be kebab-case)`); +} + +// Temporarily disable changelog generation +const configRaw = await Bun.file(CHANGESET_CONFIG).text(); +const config = JSON.parse(configRaw); +config.changelog = false; +await Bun.write(CHANGESET_CONFIG, JSON.stringify(config, null, 2) + "\n"); + +try { + // Exit prerelease mode if active + Bun.spawnSync(["bunx", "changeset", "pre", "exit"], { + stdio: ["ignore", "pipe", "pipe"], + }); + + // Bump versions to clear pre state + Bun.spawnSync(["bunx", "changeset", "version"], { + stdio: ["ignore", "pipe", "pipe"], + }); + + // Create temp changeset + const snapshot = `---\n"clerk": patch\n---\n\nSnapshot release\n`; + await Bun.write(join(import.meta.dir, "../.changeset/snapshot-temp.md"), snapshot); + + // Run changeset version --snapshot + const result = Bun.spawnSync(["bunx", "changeset", "version", "--snapshot", name], { + stdio: ["ignore", "pipe", "pipe"], + }); + + if (result.exitCode !== 0) { + throw new Error(`changeset version failed: ${result.stderr.toString().trim()}`); + } + + const pkg = await Bun.file(WRAPPER_PKG).json(); + console.log(`Snapshot version: ${pkg.version}`); +} finally { + // Restore config + Bun.spawnSync(["git", "checkout", "HEAD", "--", CHANGESET_CONFIG], { + stdio: ["ignore", "pipe", "pipe"], + }); +}