diff --git a/.changepacks/changepack_log_MKrnQd_SF1OEJcvW1GeJa.json b/.changepacks/changepack_log_MKrnQd_SF1OEJcvW1GeJa.json new file mode 100644 index 00000000..959ded21 --- /dev/null +++ b/.changepacks/changepack_log_MKrnQd_SF1OEJcvW1GeJa.json @@ -0,0 +1 @@ +{"changes": {"crates/vespertide-cli/Cargo.toml": "Minor", "bridge/node/package.json": "Minor"}, "note": "vespertide-cli를 lib + bin으로 나누고(run / main / run_cli 진입점) 그 위에 N-API 브리지 bridge/node를 추가해 CLI를 npm 패키지 vespertide(플랫폼 패키지 @vespertide/cli-*)로 배포한다. cli는 다른 크레이트가 핀하지 않는 leaf라 단독 Minor로 올리고, npm 매니페스트는 updateOn으로 매 크레이트 웨이브에 Patch로 따라오며, 이번처럼 같은 단계로 올릴 때는 명시한다.", "date": "2026-09-08T09:00:00.0000000Z"} \ No newline at end of file diff --git a/.changepacks/config.json b/.changepacks/config.json index ef8c28cd..9f0840ea 100644 --- a/.changepacks/config.json +++ b/.changepacks/config.json @@ -3,11 +3,18 @@ "**", "!crates/**", "crates/vespertide-schema-gen/Cargo.toml", - "!apps/vscode-extension/package.json" + "!apps/vscode-extension/package.json", + "!bridge/node/package.json" ], "baseBranch": "main", "latestPackage": "crates/vespertide/Cargo.toml", "publish": { - "apps/vscode-extension/package.json": "echo skip npm publish for vscode-extension - shipped via vsce in the vscode-release job" + "apps/vscode-extension/package.json": "echo skip npm publish for vscode-extension - shipped via vsce in the vscode-release job", + "bridge/node/package.json": "echo skip npm publish here - published by the node-publish job" + }, + "updateOn": { + "crates/*": [ + "bridge/node/package.json" + ] } } diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index f4419a3d..412c72f9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -157,7 +157,8 @@ jobs: echo "Computed cargo-semver-checks release-type: '${RT:-}'" - uses: obi1kenobi/cargo-semver-checks-action@v2 with: - # Only check published crates; skip cli (binary) and schema-gen (publish=false) + # Only check published crates; skip schema-gen (publish=false) and cli, whose + # lib target is new — add it once a release with the lib is on crates.io package: vespertide,vespertide-core,vespertide-config,vespertide-loader,vespertide-naming,vespertide-planner,vespertide-query,vespertide-exporter,vespertide-macro feature-group: default-features # Empty => action derives strictly from the Cargo.toml version. @@ -564,6 +565,144 @@ jobs: - name: Check schema drift run: git diff --no-index --exit-code -- schemas _tmp_schemas + # npm: the CLI as a Node addon (bridge/node, napi-rs). Fires only when the + # npm manifest is in the changepacks wave. Mirrors lsp-release's target + # table; `cross` is used for the same single aarch64-linux cell. + node-build: + name: npm Build (${{ matrix.target }}) + needs: changepacks + if: ${{ contains(needs.changepacks.outputs.changepacks, 'bridge/node/package.json') }} + runs-on: ${{ matrix.runs-on }} + strategy: + fail-fast: false + matrix: + include: + - target: x86_64-unknown-linux-gnu + runs-on: ubuntu-latest + use_cross: false + - target: aarch64-unknown-linux-gnu + runs-on: ubuntu-latest + use_cross: true + - target: x86_64-apple-darwin + # Cross-arch on the ARM runner, as in lsp-release. + runs-on: macos-latest + use_cross: false + - target: aarch64-apple-darwin + runs-on: macos-latest + use_cross: false + - target: x86_64-pc-windows-msvc + runs-on: windows-latest + use_cross: false + steps: + - uses: actions/checkout@v7 + + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + target: ${{ matrix.target }} + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install cross (aarch64-unknown-linux-gnu only) + if: matrix.use_cross + uses: taiki-e/install-action@v2.86.3 + with: + tool: cross + + - name: Install + working-directory: bridge/node + run: bun install --frozen-lockfile + + - name: Build addon (native) + if: ${{ !matrix.use_cross }} + working-directory: bridge/node + run: bun run build --target ${{ matrix.target }} + + - name: Build addon (cross) + if: matrix.use_cross + working-directory: bridge/node + run: bun run build --target ${{ matrix.target }} --use-cross + + # The addon only loads where it was built for; the two cross-arch + # cells skip the smoke test. + - name: Smoke test + if: ${{ matrix.target != 'aarch64-unknown-linux-gnu' && matrix.target != 'x86_64-apple-darwin' }} + working-directory: bridge/node + run: bun test + + - uses: actions/upload-artifact@v7 + with: + name: bindings-${{ matrix.target }} + path: bridge/node/vespertide.*.node + if-no-files-found: error + retention-days: 1 + + node-publish: + name: npm Publish + needs: [changepacks, node-build] + if: ${{ contains(needs.changepacks.outputs.changepacks, 'bridge/node/package.json') }} + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v7 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + # npm (not bun) performs the publish so the registry auth from + # setup-node applies; `npm publish` runs `prepublishOnly`, which pushes + # the five platform packages before the root one. + - uses: actions/setup-node@v7 + with: + node-version: '24' + registry-url: 'https://registry.npmjs.org' + + - uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Install + working-directory: bridge/node + run: bun install --frozen-lockfile + + # `index.js` / `index.d.ts` are build outputs (gitignored); only + # `napi build` writes them, so the publish host builds once for + # its own platform before assembling the packages. + - name: Build addon (regenerates the JS loader) + working-directory: bridge/node + run: bun run build + + - name: Create platform package dirs + working-directory: bridge/node + run: bunx napi create-npm-dirs + + - uses: actions/download-artifact@v8 + with: + path: bridge/node/artifacts + + - name: Move addons into platform packages + working-directory: bridge/node + run: bun run artifacts + + - name: Upload addons to changepacks release + uses: owjs3901/upload-github-release-asset@main + with: + upload_url: ${{ fromJson(needs.changepacks.outputs.release_assets_urls)['bridge/node/package.json'] }} + asset_path: bridge/node/npm/*/vespertide.*.node + + - name: Publish to npm + working-directory: bridge/node + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + if [ -z "$NODE_AUTH_TOKEN" ]; then + echo "::warning::NPM_TOKEN not set — skipping npm publish" + else + npm publish --access public + fi + shell: bash + insta-pending: name: insta-pending runs-on: ubuntu-latest diff --git a/AGENTS.md b/AGENTS.md index 7d002fae..4e47698a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,6 +26,7 @@ vespertide/ │ ├── vespertide-naming/ # Naming convention utilities │ ├── vespertide-schema-gen/# JSON Schema generation │ └── vespertide/ # Re-export crate (user-facing API) +├── bridge/node/ # N-API bridge: the CLI as the `vespertide` npm package (+ @vespertide/cli-* platform packages) ├── examples/app/ # Example project with models/migrations (out-of-workspace) ├── tools/lsp-profile/ # LSP synthetic / realistic workload + latency profiler (out-of-workspace) ├── fuzz/ # cargo-fuzz targets (4 targets, see FUZZING section) @@ -522,6 +523,13 @@ There is no separate `lsp-release.yml` or `vscode-release.yml`. `crates/vespertide-lsp/Cargo.toml` is in the wave. Builds the `vespertide-lsp` binary natively + cross + windows, packages `tar.gz`/`zip` with `sha256`, uploads to the changepacks release. + - **`node-build`** (matrix × 5 targets) + **`node-publish`** — fire only when + `bridge/node/package.json` is in the wave. Build the napi addon per + target, rebuild once on the publish host (only `napi build` writes the + gitignored `index.js` loader), assemble the `@vespertide/cli-*` platform packages with + `napi create-npm-dirs` / `napi artifacts`, upload the `.node` files to + the changepacks release, then `npm publish` (skipped with a warning + when `NPM_TOKEN` is unset). - **`vscode-release`** (matrix × 5 vsce targets) — fires only when `apps/vscode-extension/package.json` is in the wave. Pulls the matching LSP binary (just-released if LSP is also in the wave, otherwise the latest @@ -530,10 +538,13 @@ There is no separate `lsp-release.yml` or `vscode-release.yml`. ### Configuration - `.changepacks/config.json` — tracks `crates/**/Cargo.toml` (except - `vespertide-schema-gen` which is `publish=false`) and - `apps/vscode-extension/package.json`. `apps/landing`, `apps/zed-extension`, + `vespertide-schema-gen` which is `publish=false`), + `apps/vscode-extension/package.json` and `bridge/node/package.json`. + `updateOn` adds the npm manifest to every crate wave as a **Patch** bump so + `node-publish` always fires; list it explicitly in the descriptor when the + npm version should move at the same level as the crates. `apps/landing`, `apps/zed-extension`, `tools/`, and `tests/` are intentionally not tracked. -- Required secrets: `CARGO_REGISTRY_TOKEN`, `VSCE_PAT`, `OVSX_PAT`. +- Required secrets: `CARGO_REGISTRY_TOKEN`, `VSCE_PAT`, `OVSX_PAT`, `NPM_TOKEN`. - `.changepacks/changepack_log_*.json` files are the **committed bump descriptors** (written by `bunx @changepacks/cli`, consumed by `changepacks/action` on merge — analogous to changesets' `.changeset/*.md`), diff --git a/Cargo.lock b/Cargo.lock index dd11e4cd..ef6cc004 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,7 +333,7 @@ checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.8.9", ] [[package]] @@ -403,6 +403,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -475,6 +484,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" +[[package]] +name = "ctor" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" + [[package]] name = "darling" version = "0.20.11" @@ -1177,6 +1192,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libsqlite3-sys" version = "0.38.2" @@ -1284,6 +1309,71 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" +[[package]] +name = "napi" +version = "3.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c5f4d5375213fdb7be2655e152386e82f026f9a5ba36a75556e11359aafe09" +dependencies = [ + "bitflags 2.13.1", + "ctor", + "futures", + "libc", + "napi-build", + "napi-sys", + "nohash-hasher", + "rustc-hash 2.1.3", + "tokio", +] + +[[package]] +name = "napi-build" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60fdf9b392c50e7c4170fa633bd909490ed7835cea4c046776d1a4dd8d2ae0ab" + +[[package]] +name = "napi-derive" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa55ea69990c90b888e9e77044410e304ce7f35de599dc6d0b5c1923d2e59af" +dependencies = [ + "convert_case", + "ctor", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "napi-derive-backend" +version = "6.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df4056ac7c18e4438ccf0edaed4340ca0d269278c8ec19284f7b23cb039fd0ae" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "semver", + "syn 2.0.119", +] + +[[package]] +name = "napi-sys" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fbf1fa9f1babfe396d74bbbf52b3643770243e8f5b0b46715d4caf7f0dfc9a" +dependencies = [ + "libloading 0.9.0", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "nom" version = "7.1.3" @@ -2670,6 +2760,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + [[package]] name = "unicode-width" version = "0.2.2" @@ -2910,6 +3006,16 @@ dependencies = [ "rstest", ] +[[package]] +name = "vespertide-napi" +version = "0.1.0" +dependencies = [ + "napi", + "napi-build", + "napi-derive", + "vespertide-cli", +] + [[package]] name = "vespertide-planner" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index f75c6a8a..a053f279 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ # cargo test --manifest-path tests/runtime-sqlite/Cargo.toml # cargo build --manifest-path examples/app/Cargo.toml # cargo run --release --manifest-path tools/lsp-profile/Cargo.toml -- --workload synthetic -members = ["crates/*", "fuzz"] +members = ["crates/*", "fuzz", "bridge/*"] exclude = ["apps/zed-extension", "examples/app", "tests/runtime-sqlite", "tools/lsp-profile"] default-members = [ "crates/vespertide", @@ -95,6 +95,7 @@ vespertide-planner = { path = "crates/vespertide-planner", version = "=0.3.0" } vespertide-query = { path = "crates/vespertide-query", version = "=0.3.0" } vespertide-exporter = { path = "crates/vespertide-exporter", version = "=0.3.0" } vespertide-lsp = { path = "crates/vespertide-lsp", version = "=0.3.0" } +vespertide-cli = { path = "crates/vespertide-cli", version = "=0.3.0" } [profile.dev] debug = 1 # Line tables only — faster DWARF generation for large codegen output diff --git a/README.md b/README.md index 6fbfb2d9..0b1ac708 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,12 @@ The `vespertide-lsp` binary ships with VSCode and Zed extensions (`apps/vscode-e ## Installation ```bash +# npm (no Rust toolchain needed) +npm install -g vespertide +# or try it without installing +npx vespertide init + +# crates.io cargo install vespertide-cli ``` diff --git a/bridge/node/.gitignore b/bridge/node/.gitignore new file mode 100644 index 00000000..88c6d343 --- /dev/null +++ b/bridge/node/.gitignore @@ -0,0 +1,5 @@ +# napi-rs build outputs +*.node +/npm +index.js +index.d.ts diff --git a/bridge/node/Cargo.toml b/bridge/node/Cargo.toml new file mode 100644 index 00000000..b4312708 --- /dev/null +++ b/bridge/node/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "vespertide-napi" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "N-API bridge that ships the vespertide CLI to npm" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +napi = { version = "3.12", features = ["tokio_rt"] } +napi-derive = "3.6" +vespertide-cli = { workspace = true } + +[build-dependencies] +napi-build = "2.4" + +[lints] +workspace = true diff --git a/bridge/node/README.md b/bridge/node/README.md new file mode 100644 index 00000000..864c4738 --- /dev/null +++ b/bridge/node/README.md @@ -0,0 +1,19 @@ +# vespertide + +Declarative database schema management: define tables in JSON or YAML, diff +them against the migration history, and generate SQL for PostgreSQL, MySQL +and SQLite plus ORM code (SeaORM, SQLAlchemy, SQLModel, JPA, Prisma, Drizzle). + +This package is the `vespertide` command-line tool as a native Node addon, +so no Rust toolchain is needed. + +```bash +npm install -g vespertide +vespertide init + +# or without installing +npx vespertide init +``` + +Every command, flag and exit code is the one the native binary has; see the +[project README](https://github.com/dev-five-git/vespertide#readme). diff --git a/bridge/node/build.rs b/bridge/node/build.rs new file mode 100644 index 00000000..0f1b0100 --- /dev/null +++ b/bridge/node/build.rs @@ -0,0 +1,3 @@ +fn main() { + napi_build::setup(); +} diff --git a/bridge/node/bun.lock b/bridge/node/bun.lock new file mode 100644 index 00000000..09f2e050 --- /dev/null +++ b/bridge/node/bun.lock @@ -0,0 +1,251 @@ +{ + "lockfileVersion": 2, + "configVersion": 1, + "workspaces": { + "": { + "name": "vespertide", + "devDependencies": { + "@napi-rs/cli": "^3.9", + }, + }, + }, + "packages": { + "@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@inquirer/ansi": ["@inquirer/ansi@2.0.8", "", {}, "sha512-WpQM+Ti6Z40EFwwt+uL2p4UabT+W179zHp6HhLVOzfbwnVn05IPO/eXIZXGNqcT1jbQ15SujNLzQ39k4QPPxBQ=="], + + "@inquirer/checkbox": ["@inquirer/checkbox@5.2.5", "", { "dependencies": { "@inquirer/ansi": "^2.0.8", "@inquirer/core": "^12.0.3", "@inquirer/figures": "^2.0.9", "@inquirer/type": "4.1.1" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-bRt8J8m+Fot9CXv+zNQGXUq2ET0MggR1fPz7v6edN6MFYmsbfGnMmkmWZJEegMKqrAC8ej/o1sqisHZXZJMAfQ=="], + + "@inquirer/confirm": ["@inquirer/confirm@6.3.2", "", { "dependencies": { "@inquirer/core": "^12.0.3", "@inquirer/type": "4.1.1" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Xvr/0HggjddPtGppuqVmxhTw+Hr8PvsZ/k0HmOEaAqQEt80OITNkFWnsdNmyT0/eM4Ab+iJLx2R8rctlEyfSVg=="], + + "@inquirer/core": ["@inquirer/core@12.0.3", "", { "dependencies": { "@inquirer/ansi": "^2.0.8", "@inquirer/figures": "^2.0.9", "@inquirer/type": "4.1.1", "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-wsSy0sznmXwkty+2PzZwx00Cazc/E0r0B7mAzdGROz2Ct+DFZXaK7WDjGZvgjRldxH5ZhFVfF2lgkYrqgOw2KA=="], + + "@inquirer/editor": ["@inquirer/editor@5.3.3", "", { "dependencies": { "@inquirer/core": "^12.0.3", "@inquirer/external-editor": "^3.0.5", "@inquirer/type": "4.1.1" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-YsKkS2q63IiLtaDK/9nqzdComN97SDQrmKiyNggN+ceP4ty+Z6VwyTz3FpjeUWeW1Efss2xHFKCC9sx7hnrsxg=="], + + "@inquirer/expand": ["@inquirer/expand@5.1.5", "", { "dependencies": { "@inquirer/core": "^12.0.3", "@inquirer/type": "4.1.1" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-uHuXLmXW+TtIfT/9vSBotypAkqn1n34Ul+CLGPos/xANyO4Ff5xZzkYhbKR4NEcfVK4a9mHQOpwVZzluSHFRGw=="], + + "@inquirer/external-editor": ["@inquirer/external-editor@3.0.5", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-f3QQJRIX5ZEneBHNUIuPjmbdzHnmRFJA8r2dkcb8q+OM5Uv5KtnuAttQumnrjcBVBM3mcTX1CkmtAkU58VRZxg=="], + + "@inquirer/figures": ["@inquirer/figures@2.0.9", "", {}, "sha512-EAWgUTGQ/Umgga51dE3B2PUHbufuXarDfg86uVgoSgNHNNQnyFKcOrQLWVqYMghuSyHh8+2HUH0Js9cTC1WAdg=="], + + "@inquirer/input": ["@inquirer/input@5.1.6", "", { "dependencies": { "@inquirer/core": "^12.0.3", "@inquirer/type": "4.1.1" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-HtcJhB2QFVXbLuJ5S3syhNbTUVxYvwqV4VRBDkQceBloC9bmTViUoRFP5PbSaDZb3HzfPmpuU/gG4ybVBz4FHA=="], + + "@inquirer/number": ["@inquirer/number@4.2.3", "", { "dependencies": { "@inquirer/core": "^12.0.3", "@inquirer/type": "4.1.1" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-6Yuwh1NGSbu1Lo4N1EWjXs1jKRntLg/ZCwhmeorEHde90v1XxAozdbd4Iu30eOQLW+6h1hp2O9ujNfLSbTPJnA=="], + + "@inquirer/password": ["@inquirer/password@5.2.2", "", { "dependencies": { "@inquirer/ansi": "^2.0.8", "@inquirer/core": "^12.0.3", "@inquirer/type": "4.1.1" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-W9zYdyzogK+6110mqwaSJWCBu2yA5Q/OfnGSjjZB1bNpHlmUozXxTl0+QOZBNeVd6Qo81/qT75gW05gLAtITxw=="], + + "@inquirer/prompts": ["@inquirer/prompts@8.7.2", "", { "dependencies": { "@inquirer/checkbox": "^5.2.5", "@inquirer/confirm": "^6.3.2", "@inquirer/editor": "^5.3.3", "@inquirer/expand": "^5.1.5", "@inquirer/input": "^5.1.6", "@inquirer/number": "^4.2.3", "@inquirer/password": "^5.2.2", "@inquirer/rawlist": "^5.3.5", "@inquirer/search": "^4.3.3", "@inquirer/select": "^5.2.5" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QoRB4wFIjgH5iOhSjoIKMkTvSHDuV+O3OITlIqAYO0oK5x364GJILXiMBvlPiE+klg7Xx9tq5XVqQHcGUDYYPA=="], + + "@inquirer/rawlist": ["@inquirer/rawlist@5.3.5", "", { "dependencies": { "@inquirer/core": "^12.0.3", "@inquirer/type": "4.1.1" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-1oHky1ONfCOwNrnkQGDE1oaSij/3fI6HFMSf2H/WsGO2lEyDX9My82iggITSy9ddSZ8yk8j9v41OI0fVoSIoaA=="], + + "@inquirer/search": ["@inquirer/search@4.3.3", "", { "dependencies": { "@inquirer/core": "^12.0.3", "@inquirer/figures": "^2.0.9", "@inquirer/type": "4.1.1" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-fyuIU1Nbpvwlikjg3gXwJFDI11+EFjqQ7P+iByfmivIKQ1vmaykNrD/vy5unHuUqUpsOsnvJ25//tPF7E/RBRA=="], + + "@inquirer/select": ["@inquirer/select@5.2.5", "", { "dependencies": { "@inquirer/ansi": "^2.0.8", "@inquirer/core": "^12.0.3", "@inquirer/figures": "^2.0.9", "@inquirer/type": "4.1.1" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-9kc15hr8r/kI+3DO/xLog5nOzTz1jqsHXa6JBFzmQKhkoJ8Slda1I1L/uD8ZSZ9tF1yp79wwXe7mclvX1rqR2Q=="], + + "@inquirer/type": ["@inquirer/type@4.1.1", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-yJoHYrMnxIsJZCY+0Vb66Dy3he3kL3e2wOBKhoSwWWAzZAY82emlxwgprCtp6yRixvNRNq9ztfRWQYPNr3Go7A=="], + + "@napi-rs/cli": ["@napi-rs/cli@3.9.0", "", { "dependencies": { "@inquirer/prompts": "^8.5.2", "@napi-rs/cross-toolchain": "^1.0.3", "@napi-rs/wasm-tools": "^1.1.0", "@octokit/rest": "^22.0.1", "clipanion": "^4.0.0-rc.4", "colorette": "^2.0.20", "es-toolkit": "^1.47.0", "js-yaml": "^4.2.0", "obug": "^2.1.2", "semver": "^7.8.2", "typanion": "^3.14.0", "typescript": "^6.0.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4", "emnapi": "^1.7.1 || ^2.0.0-alpha.4" }, "optionalPeers": ["@emnapi/core", "@emnapi/runtime", "emnapi"], "bin": { "napi": "dist/cli.js", "napi-raw": "cli.mjs" } }, "sha512-ZlerYOCLgVdaqbVDt8+uQxmU9j8bKMVFcYo6zRHJJHTO9jN5060+y953aIVc/0zkVSKq+fHjDAtTGOrbxpeZCw=="], + + "@napi-rs/cross-toolchain": ["@napi-rs/cross-toolchain@1.0.3", "", { "dependencies": { "@napi-rs/lzma": "^1.4.5", "@napi-rs/tar": "^1.1.0", "debug": "^4.4.1" }, "peerDependencies": { "@napi-rs/cross-toolchain-arm64-target-aarch64": "^1.0.3", "@napi-rs/cross-toolchain-arm64-target-armv7": "^1.0.3", "@napi-rs/cross-toolchain-arm64-target-ppc64le": "^1.0.3", "@napi-rs/cross-toolchain-arm64-target-s390x": "^1.0.3", "@napi-rs/cross-toolchain-arm64-target-x86_64": "^1.0.3", "@napi-rs/cross-toolchain-x64-target-aarch64": "^1.0.3", "@napi-rs/cross-toolchain-x64-target-armv7": "^1.0.3", "@napi-rs/cross-toolchain-x64-target-ppc64le": "^1.0.3", "@napi-rs/cross-toolchain-x64-target-s390x": "^1.0.3", "@napi-rs/cross-toolchain-x64-target-x86_64": "^1.0.3" }, "optionalPeers": ["@napi-rs/cross-toolchain-arm64-target-aarch64", "@napi-rs/cross-toolchain-arm64-target-armv7", "@napi-rs/cross-toolchain-arm64-target-ppc64le", "@napi-rs/cross-toolchain-arm64-target-s390x", "@napi-rs/cross-toolchain-arm64-target-x86_64", "@napi-rs/cross-toolchain-x64-target-aarch64", "@napi-rs/cross-toolchain-x64-target-armv7", "@napi-rs/cross-toolchain-x64-target-ppc64le", "@napi-rs/cross-toolchain-x64-target-s390x", "@napi-rs/cross-toolchain-x64-target-x86_64"] }, "sha512-ENPfLe4937bsKVTDA6zdABx4pq9w0tHqRrJHyaGxgaPq03a2Bd1unD5XSKjXJjebsABJ+MjAv1A2OvCgK9yehg=="], + + "@napi-rs/lzma": ["@napi-rs/lzma@1.5.1", "", { "optionalDependencies": { "@napi-rs/lzma-android-arm-eabi": "1.5.1", "@napi-rs/lzma-android-arm64": "1.5.1", "@napi-rs/lzma-darwin-arm64": "1.5.1", "@napi-rs/lzma-darwin-x64": "1.5.1", "@napi-rs/lzma-freebsd-x64": "1.5.1", "@napi-rs/lzma-linux-arm-gnueabihf": "1.5.1", "@napi-rs/lzma-linux-arm64-gnu": "1.5.1", "@napi-rs/lzma-linux-arm64-musl": "1.5.1", "@napi-rs/lzma-linux-ppc64-gnu": "1.5.1", "@napi-rs/lzma-linux-riscv64-gnu": "1.5.1", "@napi-rs/lzma-linux-s390x-gnu": "1.5.1", "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@napi-rs/lzma-linux-x64-musl": "1.5.1", "@napi-rs/lzma-wasm32-wasi": "1.5.1", "@napi-rs/lzma-win32-arm64-msvc": "1.5.1", "@napi-rs/lzma-win32-ia32-msvc": "1.5.1", "@napi-rs/lzma-win32-x64-msvc": "1.5.1" } }, "sha512-sgOZ89+y8cDbY+3WbzR8CtIhCuFRWotZ9/2PjPVDJHz6np5KFTAev0DrwiyTJTgFsCRDhfGlbmhMgyhHbWdZ6g=="], + + "@napi-rs/lzma-android-arm-eabi": ["@napi-rs/lzma-android-arm-eabi@1.5.1", "", { "os": "android", "cpu": "arm" }, "sha512-sahBe4ko2Z69NPTddaX6ZgbQZu9SDoITxw1S3dWl1gAGynZG34qHHCT8UaUMFxf3h3zMhCJjEzz4basaBxiTuQ=="], + + "@napi-rs/lzma-android-arm64": ["@napi-rs/lzma-android-arm64@1.5.1", "", { "os": "android", "cpu": "arm64" }, "sha512-7tkQAJJuBHxAxiEBNFgSTpvrtGpbwZYYJUSOmGEK3OfbdbNeoT2rdBxpM/gY1s+itEVbtOSlpaRPPG19MnwOzA=="], + + "@napi-rs/lzma-darwin-arm64": ["@napi-rs/lzma-darwin-arm64@1.5.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XWX8gtF+GHGk3nH3Wm3QUZNcxw9QHsFVZz3MzVLhWWHhceede1J4/vD+3dj3E1iKB9G6mualaZxOoD08R3E+7g=="], + + "@napi-rs/lzma-darwin-x64": ["@napi-rs/lzma-darwin-x64@1.5.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-CfsqUpMTI1z8enrA/b+GcHM6YDI8D0kqCiqPYEnst4rbOABQ9KZ92ybTTNnlnZ7A017WoMZKUEWc36KXDwi0xg=="], + + "@napi-rs/lzma-freebsd-x64": ["@napi-rs/lzma-freebsd-x64@1.5.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-bTyNfg90FXIgE61U7l14aMmVOqRQ6AyP5JMT3jmCStaZI18apLNPdzZ8i7yqxZfKvRMVfPjE2brXIw27c+RRgA=="], + + "@napi-rs/lzma-linux-arm-gnueabihf": ["@napi-rs/lzma-linux-arm-gnueabihf@1.5.1", "", { "os": "linux", "cpu": "arm" }, "sha512-vNE+D8nrw+eOkBsdKCsmDhowDV3pIMKXEhedvXfbgrWbrO7GlZJH+RXL+X+RYLxGwi8Ym61ZMt15sIOnNmh9Sw=="], + + "@napi-rs/lzma-linux-arm64-gnu": ["@napi-rs/lzma-linux-arm64-gnu@1.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-csUem4WgoKGTprv/pOPm9UIWbb+hrfUwYXefpTHPAEGVFLl5behEFabisJ7FtihCa3yG2Efcl+yw25rlhhrIYw=="], + + "@napi-rs/lzma-linux-arm64-musl": ["@napi-rs/lzma-linux-arm64-musl@1.5.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-kB/xhlVN1eLvVmDJSKZEjp5Gg2xDYexNrB5jwpSMbOkeGS6N9AasByPBg5VqCpMYC+zZi7DM458DRhtWYhqXTQ=="], + + "@napi-rs/lzma-linux-ppc64-gnu": ["@napi-rs/lzma-linux-ppc64-gnu@1.5.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-s28RW0W1yBWQc1nbPdF7tp14koqslY3ZWLVI8uaanX292Dc6ezd4NPVwxEoCNBVON/oD7BmUbWGtyFvmm7dQ5A=="], + + "@napi-rs/lzma-linux-riscv64-gnu": ["@napi-rs/lzma-linux-riscv64-gnu@1.5.1", "", { "os": "linux", "cpu": "none" }, "sha512-+lGNwYlIN14YPMTNvYtIJJqHFevDTd6Juw/1NmXbWx/iRd/LLrjhlM/yluMX6pxs6NkOGsuuEXJJrbbEUS59OQ=="], + + "@napi-rs/lzma-linux-s390x-gnu": ["@napi-rs/lzma-linux-s390x-gnu@1.5.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-PB44FFWWFrLeQowhcep1hPD1YcLqKlnnY60RMU74qrxTlr4YGEyzeMItJqh2uivBfv9kQScOF/B0J9+Vab/oyw=="], + + "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="], + + "@napi-rs/lzma-linux-x64-musl": ["@napi-rs/lzma-linux-x64-musl@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-I3nsYrWtrW9JpeCr+mkJIVDt0HY3m6qVUBs5vTtoIvJQxwqf1PBXSy5IS7T53ksQFH2kd2UX8rLxJ7B4WISpZg=="], + + "@napi-rs/lzma-wasm32-wasi": ["@napi-rs/lzma-wasm32-wasi@1.5.1", "", { "dependencies": { "@emnapi/core": "1.11.2", "@emnapi/runtime": "1.11.2", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-gy3wwPBa6+XEyA4fUzq6CClrXA1ajXjuVf5zbnHytJRgoHznj+mvpU3+co2fxXwqTCmIpn6KrzqH5bRDztBPhA=="], + + "@napi-rs/lzma-win32-arm64-msvc": ["@napi-rs/lzma-win32-arm64-msvc@1.5.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-dK+huOsHiyH6oJjij+cnjqFCakk2HgWmpI12Xm4pLUyPphe4ebYoJBgehaNAxprmjFqBQ7nL95YPVz9BHyqmPg=="], + + "@napi-rs/lzma-win32-ia32-msvc": ["@napi-rs/lzma-win32-ia32-msvc@1.5.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-dGE8L+0EQ+GyU9ap9InqB/t/PmPG/bLj918q7OsJ29FuTdn8fK4OX3U4IQZhylHIA+/dQ/SXJk5n4yfah2XVvA=="], + + "@napi-rs/lzma-win32-x64-msvc": ["@napi-rs/lzma-win32-x64-msvc@1.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-EKW4t/iqdCT/xnd5t9oXLvVER/PMNAWXKqUAl3fgvUcOILeZIIht77/dVnfFcc9htA/DCBXC/6YQWdW+LusjFA=="], + + "@napi-rs/tar": ["@napi-rs/tar@1.1.1", "", { "optionalDependencies": { "@napi-rs/tar-android-arm-eabi": "1.1.1", "@napi-rs/tar-android-arm64": "1.1.1", "@napi-rs/tar-darwin-arm64": "1.1.1", "@napi-rs/tar-darwin-x64": "1.1.1", "@napi-rs/tar-freebsd-x64": "1.1.1", "@napi-rs/tar-linux-arm-gnueabihf": "1.1.1", "@napi-rs/tar-linux-arm64-gnu": "1.1.1", "@napi-rs/tar-linux-arm64-musl": "1.1.1", "@napi-rs/tar-linux-ppc64-gnu": "1.1.1", "@napi-rs/tar-linux-s390x-gnu": "1.1.1", "@napi-rs/tar-linux-x64-gnu": "1.1.1", "@napi-rs/tar-linux-x64-musl": "1.1.1", "@napi-rs/tar-wasm32-wasi": "1.1.1", "@napi-rs/tar-win32-arm64-msvc": "1.1.1", "@napi-rs/tar-win32-ia32-msvc": "1.1.1", "@napi-rs/tar-win32-x64-msvc": "1.1.1" } }, "sha512-p6q2HhUc5vwH1CNwfOcrhLoxfgn8ust8Sqlfx+sA4VzAcp1cMbvbkl99tZZlDqOjCHgQNSiTfk/yWPjl/D42qA=="], + + "@napi-rs/tar-android-arm-eabi": ["@napi-rs/tar-android-arm-eabi@1.1.1", "", { "os": "android", "cpu": "arm" }, "sha512-cAhnA10cSusAUbcE9HtjQY/tZ9BH/0w2sKtRcQc94TzIlnm7QSr1htJSd/PPrbWNPtrv1orXb2CkrHlVlbnlHA=="], + + "@napi-rs/tar-android-arm64": ["@napi-rs/tar-android-arm64@1.1.1", "", { "os": "android", "cpu": "arm64" }, "sha512-EslUWHCDBY/g5abTPBiHLsMaML4GagV0TXLm5WL9hAjx/DDtlxz9fegMb77RJ+f7nFLOIsUxF/3QWFvgOT0sMQ=="], + + "@napi-rs/tar-darwin-arm64": ["@napi-rs/tar-darwin-arm64@1.1.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+A42/6ES5G9CQ35BOwzwA+WBjLID28r2jNPgc0dteD2hhClIhng0mva7D2ujUlXBNmgNOsr1LHn3stA4uTf4NQ=="], + + "@napi-rs/tar-darwin-x64": ["@napi-rs/tar-darwin-x64@1.1.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-RYtE8w1dkEvj8hSJCDV5Jw0Rz2i13fsM7u893zv5O9n/4Ad5GNsw/f4RQ7/0YGSFaenkVxqPFrjmEvUHlKzsrg=="], + + "@napi-rs/tar-freebsd-x64": ["@napi-rs/tar-freebsd-x64@1.1.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-rEepBvCJUwcuvUYkY83e8aot8RsR5Jcnal4PsG3tbWGKW1yAvcXhyMXf0fN6ZGpVRZFnB+FJqDyBxvsCPEXKhw=="], + + "@napi-rs/tar-linux-arm-gnueabihf": ["@napi-rs/tar-linux-arm-gnueabihf@1.1.1", "", { "os": "linux", "cpu": "arm" }, "sha512-an1bJdfyhI5FpZYyTQ20mrqwR+a676i8GkaYc4Uy12dH/a7TJIfrK6Qa2Gm46arZvxUvx56qxoRKXbpOjUPvwA=="], + + "@napi-rs/tar-linux-arm64-gnu": ["@napi-rs/tar-linux-arm64-gnu@1.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-w++Vtx36T2yHTKws7GVnmHHcUT1ybB59xLWSh9A8bwEpJVG4dG7Qub9mFe5cpcbfrJ+XP2mKKxC3oUJSunK3iQ=="], + + "@napi-rs/tar-linux-arm64-musl": ["@napi-rs/tar-linux-arm64-musl@1.1.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Rh6UFhNtj3i4deJHOBINFIeRL0072mgbeyuK5rl1HokKnNoMKx8qKIZNEzBTTqpogMfDHWGvzyTQdnVxes5dpA=="], + + "@napi-rs/tar-linux-ppc64-gnu": ["@napi-rs/tar-linux-ppc64-gnu@1.1.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Cp+AxFbv9zcyAXtnzQi0OzmgDnQgy2w9D4Ubr+iwzMtVgJcztzcEoCcCrN1k2ATdEB01LX2Vb49IaocGOZhC9Q=="], + + "@napi-rs/tar-linux-s390x-gnu": ["@napi-rs/tar-linux-s390x-gnu@1.1.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-ZyscC3SYKTBWyDRYjLOKAd5TyJ7q0KACRdQ8bWrb3rgrra1CCIJD66CsGTH6Dh0AVSdfLwZ8MfIIXU6+14BMjQ=="], + + "@napi-rs/tar-linux-x64-gnu": ["@napi-rs/tar-linux-x64-gnu@1.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-LlIv+zg4fiOQge9LQX/ieBdRWE2fhVDjCTHxnunZkbugNmdhdelxWf1RpZb/6ZujWpNF4LPu4N/MW7ygg2oYAQ=="], + + "@napi-rs/tar-linux-x64-musl": ["@napi-rs/tar-linux-x64-musl@1.1.1", "", { "os": "linux", "cpu": "x64" }, "sha512-gZBeoKLjanOVj55qk4EMu13P2i9M0SuINmlGQkOxm1niIJofexzddHUYtqO5o/5QqtyL8lADmAcZplLILMLhHA=="], + + "@napi-rs/tar-wasm32-wasi": ["@napi-rs/tar-wasm32-wasi@1.1.1", "", { "dependencies": { "@emnapi/core": "1.11.2", "@emnapi/runtime": "1.11.2", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-rwtQ1Mdt/ft6g6I54fJzbUeLspl4yTwj6I3UJ6mitKnrN42soJkcDrdh3Y/FGvlpqZTad2YMQ96fGJl3EtAm2Q=="], + + "@napi-rs/tar-win32-arm64-msvc": ["@napi-rs/tar-win32-arm64-msvc@1.1.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-30PVp1AehRpfwxmv5wI4cg0yj3WmWBsZ+1QnLGnvEELu7Eu/+dhNU0nrmhI7VfPgLwSRK2eg9DQTB3tP7Wv9bA=="], + + "@napi-rs/tar-win32-ia32-msvc": ["@napi-rs/tar-win32-ia32-msvc@1.1.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-aI3/rmz+izUChiSeaPxcasAOxhf3FpJNuIHMXlxS/vpW+HIxUsSDR5+XV61PEG5DL4L/75iENVUxmSGM5l2yaw=="], + + "@napi-rs/tar-win32-x64-msvc": ["@napi-rs/tar-win32-x64-msvc@1.1.1", "", { "os": "win32", "cpu": "x64" }, "sha512-yJsB2IsrODQVLKbm2Fg1nHiVRbEj49mSPbj4x7JPZWJI0jGVPjohE2Sif0FBbx8OxsVoUODvS0BwksZZ8jl/OA=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.3", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q=="], + + "@napi-rs/wasm-tools": ["@napi-rs/wasm-tools@1.1.0", "", { "optionalDependencies": { "@napi-rs/wasm-tools-android-arm-eabi": "1.1.0", "@napi-rs/wasm-tools-android-arm64": "1.1.0", "@napi-rs/wasm-tools-darwin-arm64": "1.1.0", "@napi-rs/wasm-tools-darwin-x64": "1.1.0", "@napi-rs/wasm-tools-freebsd-x64": "1.1.0", "@napi-rs/wasm-tools-linux-arm64-gnu": "1.1.0", "@napi-rs/wasm-tools-linux-arm64-musl": "1.1.0", "@napi-rs/wasm-tools-linux-x64-gnu": "1.1.0", "@napi-rs/wasm-tools-linux-x64-musl": "1.1.0", "@napi-rs/wasm-tools-wasm32-wasi": "1.1.0", "@napi-rs/wasm-tools-win32-arm64-msvc": "1.1.0", "@napi-rs/wasm-tools-win32-ia32-msvc": "1.1.0", "@napi-rs/wasm-tools-win32-x64-msvc": "1.1.0" } }, "sha512-VjHyKEqXAwYZK+HY7iJctYvRm3TFEbaQxeZwvAG1QRkoo1a39phMY8J6x9tUEqJI03W6MysB8F2jacI6wvcx+w=="], + + "@napi-rs/wasm-tools-android-arm-eabi": ["@napi-rs/wasm-tools-android-arm-eabi@1.1.0", "", { "os": "android", "cpu": "arm" }, "sha512-p6J8PB59I8d/XItXB/go5JH6nKW+xIbpzaL43EBTV0hi7mrS/Z4gs+MsB04ZrlqZN29BdZV8fChRyasuXLhRaA=="], + + "@napi-rs/wasm-tools-android-arm64": ["@napi-rs/wasm-tools-android-arm64@1.1.0", "", { "os": "android", "cpu": "arm64" }, "sha512-lWoKN3suypeBSCIRPIw+++sH9V2K6nQkhtdt1opu7XY3v9JwLs6Gw063HWRqkNjphlYpkd/Qy8XcfSPGbJj7nQ=="], + + "@napi-rs/wasm-tools-darwin-arm64": ["@napi-rs/wasm-tools-darwin-arm64@1.1.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jfw5vyNDUf6oe0kP8lMveFN9U7cLk1cUosS7uMIfw/xmqmopYfKQ198DAx2g/6aEF7Tm+CqER2gpMpYKui30LA=="], + + "@napi-rs/wasm-tools-darwin-x64": ["@napi-rs/wasm-tools-darwin-x64@1.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-R+pjeudAB7BYdH1vKkOJM61Tfv5jB6uXkxmFscYd+KKpdUpWBlNG+s4hr0w4i1rMBM91VhIAETZn2pz+MDHK9A=="], + + "@napi-rs/wasm-tools-freebsd-x64": ["@napi-rs/wasm-tools-freebsd-x64@1.1.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hQJTe+aazrT++Vgm6I4lUd9099ItUCFYdd+aKg6Ys6nax6d/cZ1barDLTwA2lwOoVDsXMekJI/FOL6ZvVlIYBg=="], + + "@napi-rs/wasm-tools-linux-arm64-gnu": ["@napi-rs/wasm-tools-linux-arm64-gnu@1.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-1TAXJxUHsWGar90k3W/MknavvBMwOWzjh7Q6Spxo8twRcWJbBD5Kow/Q2KhhDq5hxh2sKGDXn3uLc1tdtz4WUg=="], + + "@napi-rs/wasm-tools-linux-arm64-musl": ["@napi-rs/wasm-tools-linux-arm64-musl@1.1.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-7rw3nlubTjNAVRH2LwphCxHy1b/N2/TerXocQ6XRn4Q+buaY1Z7P/hbdALy1i1ex2yfOU2Xcij7ib7ZLi/lKfw=="], + + "@napi-rs/wasm-tools-linux-x64-gnu": ["@napi-rs/wasm-tools-linux-x64-gnu@1.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-1sel0t9MRjI/tdT89M8Dd6gPfANeeFP24Xa46R11WeHNwhjsXXZh+xUk50uWCRTSGcaCy3ugm3AMK/lmHYQJkg=="], + + "@napi-rs/wasm-tools-linux-x64-musl": ["@napi-rs/wasm-tools-linux-x64-musl@1.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-o2jH5AMfor4EKF2HII1LBnMQxoWu7+usPifTEY8Zk6e9OiSi4EJkAXf9v3ANlX7TI2V/cUEV34OEW7r10GiVIA=="], + + "@napi-rs/wasm-tools-wasm32-wasi": ["@napi-rs/wasm-tools-wasm32-wasi@1.1.0", "", { "dependencies": { "@emnapi/core": "1.9.2", "@emnapi/runtime": "1.9.2", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-s6YDtDR1UWrsqJPtaxf+JLYLceWVyn3l8OpQYElHkDhf3Qfz9R6Ba3S0OgznTBv38L5/TIHysQ9Q4yO73Z0csg=="], + + "@napi-rs/wasm-tools-win32-arm64-msvc": ["@napi-rs/wasm-tools-win32-arm64-msvc@1.1.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-x+NuxbG84VxU68tU8w7Rf5lSyq0l584M6dVlke5DTweHYFZoMyeqkpbwEq+qsyAX6ivfipK8xRsmFwamb5uDnA=="], + + "@napi-rs/wasm-tools-win32-ia32-msvc": ["@napi-rs/wasm-tools-win32-ia32-msvc@1.1.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-mdD96QDEp70SX67rXFTY6c725nVYeqEEjyDqzzbNh6u1APj7CI7IMNpMmvE75XbCRl4C2MHZVU4U6AWdAzvyQQ=="], + + "@napi-rs/wasm-tools-win32-x64-msvc": ["@napi-rs/wasm-tools-win32-x64-msvc@1.1.0", "", { "os": "win32", "cpu": "x64" }, "sha512-bVVjuvhlyVX++3eJXfDR63cXdw1ay5QYac6iq0MKQw8wZARInTM+bXCtByDT4fzVFI3+7ZthYb/ERWRdBNIqgQ=="], + + "@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="], + + "@octokit/core": ["@octokit/core@7.0.8", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.5", "@octokit/request": "^10.0.16", "@octokit/request-error": "^7.1.2", "@octokit/types": "^18.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-L7y8eYc+AwxGr2PWI4WFt1VG4TiJ66c26BD16mXpYIlXxG0SMigM1+m4aTSlYyBr5BlQsGAlz8uDCoZN4SEMcg=="], + + "@octokit/endpoint": ["@octokit/endpoint@11.0.5", "", { "dependencies": { "@octokit/types": "^18.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-iXa654H3yFafF/ieHkukfbgWo2rmXD2ceD0ZOtrPhw1bc3FDch1d9N/TNs0FQ1/cIbwb7kspUX8jzIs8nzb9DQ=="], + + "@octokit/graphql": ["@octokit/graphql@9.0.5", "", { "dependencies": { "@octokit/request": "^10.0.16", "@octokit/types": "^18.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-bt/hm03LeU6Vy7FwTrkkC9p3XGT/lBwClglMqxBSe5/q0E5CdJTXeAqEI0vlw89/LF/G6tryTIH8HirZ3prMVg=="], + + "@octokit/openapi-types": ["@octokit/openapi-types@29.0.1", "", {}, "sha512-9qWOMFNxxLokERcms42rU0PTLqQmVs7g5E41TI4mCOxmpFayD1rfC7XxOL55cG9MBZLFlC31BrR37myMKardwg=="], + + "@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@14.0.0", "", { "dependencies": { "@octokit/types": "^16.0.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw=="], + + "@octokit/plugin-request-log": ["@octokit/plugin-request-log@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="], + + "@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@17.0.0", "", { "dependencies": { "@octokit/types": "^16.0.0" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw=="], + + "@octokit/request": ["@octokit/request@10.0.16", "", { "dependencies": { "@octokit/endpoint": "^11.0.5", "@octokit/request-error": "^7.1.2", "@octokit/types": "^18.0.0", "content-type": "^3.0.0", "json-with-bigint": "^3.5.12", "universal-user-agent": "^7.0.2" } }, "sha512-A0zWGjHzISIb+9ccG8s0dq7LKO5zVpJLRICjgUb+sJxEWqn8RUHB1rD3AE51+PECvXHIxqZ1VVvs4fHTSD9nUQ=="], + + "@octokit/request-error": ["@octokit/request-error@7.1.2", "", { "dependencies": { "@octokit/types": "^18.0.0" } }, "sha512-XZRuT3xZ84D3gYErI1DZvhJ33dCWVV6uzBtWkaBB4TvA/L6eOeTZodxLFVB44bBEEo3vEx7y00UfX1tBLrtLRg=="], + + "@octokit/rest": ["@octokit/rest@22.0.1", "", { "dependencies": { "@octokit/core": "^7.0.6", "@octokit/plugin-paginate-rest": "^14.0.0", "@octokit/plugin-request-log": "^6.0.0", "@octokit/plugin-rest-endpoint-methods": "^17.0.0" } }, "sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw=="], + + "@octokit/types": ["@octokit/types@18.0.0", "", { "dependencies": { "@octokit/openapi-types": "^29.0.1" } }, "sha512-l6bAF43PNxkJp6g+W4PjoUSSkxHomXw2nOum5CTftJz1NlV3vu93NImgOYtLf6CbBUb5j+fiuzW0PPQ5JTSvZA=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], + + "chardet": ["chardet@2.2.0", "", {}, "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA=="], + + "cli-width": ["cli-width@4.1.0", "", {}, "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ=="], + + "clipanion": ["clipanion@4.0.0-rc.4", "", { "dependencies": { "typanion": "^3.8.0" } }, "sha512-CXkMQxU6s9GklO/1f714dkKBMu1lopS1WFF0B8o4AxPykR1hpozxSiUZ5ZUeBjfPgCWqbcNOtZVFhB8Lkfp1+Q=="], + + "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + + "content-type": ["content-type@3.0.0", "", {}, "sha512-AIi5H6p0xk5uknXcN3/rmhP8jgp69OfSe/JuKiQAFprJ7UGw7mwj7m4XcmDzlrnJDG+cGpphAINGdU3g3g7kDw=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" }, "peerDependencies": { "supports-color": "*" }, "optionalPeers": ["supports-color"] }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "es-toolkit": ["es-toolkit@1.52.0", "", {}, "sha512-XTNEJQh1tY1ZJVcf6ayP/2n4ZPyaHlW2FWs7xvw5ddPuhUVjLD3olQVQS7kf58JbAB48iL0uL/jerTrjtV3lDA=="], + + "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.2", "", { "dependencies": { "fast-string-width": "^3.0.2" } }, "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q=="], + + "iconv-lite": ["iconv-lite@0.7.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ=="], + + "js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], + + "json-with-bigint": ["json-with-bigint@3.5.12", "", {}, "sha512-uwbF/wSSuOgC7qqlq27Xp5B6a2MHVug3t0idZdTqu0JnlFvgJuH7ju+KAk/J06C7GfhoYy2gnb9wz2INqcne7w=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="], + + "obug": ["obug@2.1.4", "", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typanion": ["typanion@3.14.0", "", {}, "sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug=="], + + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + + "universal-user-agent": ["universal-user-agent@7.0.3", "", {}, "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A=="], + + "@napi-rs/lzma-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="], + + "@napi-rs/lzma-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], + + "@napi-rs/tar-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="], + + "@napi-rs/tar-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], + + "@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + + "@napi-rs/lzma-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + + "@napi-rs/tar-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], + + "@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + } +} diff --git a/bridge/node/main.js b/bridge/node/main.js new file mode 100644 index 00000000..2287c248 --- /dev/null +++ b/bridge/node/main.js @@ -0,0 +1,12 @@ +#!/usr/bin/env node +const { main } = require('./index.js') + +main(process.argv.slice(2)).then( + (code) => { + process.exitCode = code + }, + (err) => { + console.error(err) + process.exitCode = 1 + }, +) diff --git a/bridge/node/package.json b/bridge/node/package.json new file mode 100644 index 00000000..bda21fed --- /dev/null +++ b/bridge/node/package.json @@ -0,0 +1,49 @@ +{ + "name": "vespertide", + "version": "0.3.0", + "description": "Declarative database schema management: models in JSON/YAML, migration planning, SQL and ORM code generation", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/dev-five-git/vespertide.git", + "directory": "bridge/node" + }, + "main": "index.js", + "types": "index.d.ts", + "bin": { + "vespertide": "./main.js" + }, + "engines": { + "node": ">=18" + }, + "files": [ + "main.js", + "index.js", + "index.d.ts", + "README.md" + ], + "napi": { + "binaryName": "vespertide", + "packageName": "@vespertide/cli", + "targets": [ + "x86_64-pc-windows-msvc", + "x86_64-apple-darwin", + "aarch64-apple-darwin", + "x86_64-unknown-linux-gnu", + "aarch64-unknown-linux-gnu" + ] + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "napi build --platform --release", + "build:debug": "napi build --platform", + "artifacts": "napi artifacts", + "prepublishOnly": "napi prepublish -t npm --no-gh-release", + "test": "bun test" + }, + "devDependencies": { + "@napi-rs/cli": "^3.9" + } +} diff --git a/bridge/node/src/lib.rs b/bridge/node/src/lib.rs new file mode 100644 index 00000000..b3522fee --- /dev/null +++ b/bridge/node/src/lib.rs @@ -0,0 +1,17 @@ +//! N-API bridge: the `vespertide` CLI as a Node addon. +//! +//! One exported function, `main(args)`, which is [`vespertide_cli::run_cli`] +//! fed the arguments Node received. Output, prompts and exit codes are the +//! binary's own; `main.js` only hands the code to the process. + +use napi_derive::napi; + +/// Run the CLI with `args` (everything after the program name) and return +/// the exit code the binary would have exited with. +#[napi] +#[cfg(not(tarpaulin_include))] +pub async fn main(args: Vec) -> i32 { + // clap reads the usage line's binary name from argv[0]. + let argv = std::iter::once("vespertide".to_string()).chain(args); + i32::from(vespertide_cli::run_cli(argv).await) +} diff --git a/bridge/node/test/main.test.js b/bridge/node/test/main.test.js new file mode 100644 index 00000000..b20dd27c --- /dev/null +++ b/bridge/node/test/main.test.js @@ -0,0 +1,38 @@ +import { describe, expect, test } from 'bun:test' +import { spawnSync } from 'node:child_process' +import { existsSync, mkdtempSync, rmSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { join } from 'node:path' + +const root = join(import.meta.dir, '..') +// The addon only exists after `napi build`; without it there is nothing to test. +const built = existsSync(join(root, 'index.js')) +// Spawn `node` by name: under `bun test` `process.execPath` is Bun, and the +// package promises Node's N-API (`engines.node`). +const run = (args, cwd = root) => + spawnSync('node', [join(root, 'main.js'), ...args], { cwd, encoding: 'utf8' }) + +describe('main.js', () => { + test.skipIf(!built)('--version exits 0 with the version line', () => { + const result = run(['--version']) + expect(result.status).toBe(0) + expect(result.stdout).toMatch(/^vespertide \d/) + }) + + test.skipIf(!built)("an unknown subcommand exits 2 with clap's message, printed once", () => { + const result = run(['bogus']) + expect(result.status).toBe(2) + expect(result.stderr.match(/unrecognized subcommand/g)).toHaveLength(1) + }) + + test.skipIf(!built)('a failed command exits 1 with the Error: prefix', () => { + const dir = mkdtempSync(join(tmpdir(), 'vespertide-')) + try { + const result = run(['status'], dir) + expect(result.status).toBe(1) + expect(result.stderr).toStartWith('Error: ') + } finally { + rmSync(dir, { recursive: true, force: true }) + } + }) +}) diff --git a/crates/vespertide-cli/AGENTS.md b/crates/vespertide-cli/AGENTS.md index f22c62c1..118ce130 100644 --- a/crates/vespertide-cli/AGENTS.md +++ b/crates/vespertide-cli/AGENTS.md @@ -6,7 +6,9 @@ CLI for declarative database schema management. Uses clap for argument parsing, ``` src/ -├── main.rs # Clap CLI definition, command dispatch +├── main.rs # Binary shell: `ExitCode::from(run_cli(std::env::args_os()))` +├── lib.rs # Entry points: run (dispatch) / main (parse, embedder-safe) / run_cli (reports failure, returns the exit code) +├── cli.rs # Clap CLI definition (Cli, Commands, BackendArg) ├── utils.rs # Re-exports loader functions, migration filename generation └── commands/ ├── mod.rs # Public exports: cmd_{init,new,diff,sql,revision,status,log,export} + cmd_erd_with_filters @@ -43,7 +45,7 @@ src/ | Task | File | Key Functions | |------|------|---------------| -| Add new CLI command | `main.rs` | Add to `Commands` enum, match in `main()` | +| Add new CLI command | `cli.rs` + `lib.rs` | Add to `Commands` enum, match in `run()` | | Modify action display | `diff/mod.rs` | `format_action()`, `format_constraint_type()` | | Change fill-with flow | `revision/prompts/fill_with.rs` | fill-with prompt + collection helpers | | Export logic | `export/mod.rs` | `walk_models()`, `ensure_mod_chain()`, `build_output_path()` | diff --git a/crates/vespertide-cli/src/cli.rs b/crates/vespertide-cli/src/cli.rs new file mode 100644 index 00000000..9212198a --- /dev/null +++ b/crates/vespertide-cli/src/cli.rs @@ -0,0 +1,133 @@ +//! The clap surface: every subcommand and flag the `vespertide` binary +//! accepts. Parsing lives in [`crate::main`], so the N-API bridge drives +//! exactly this definition and never a copy of it. + +use clap::{Parser, Subcommand, ValueEnum}; +use vespertide_config::FileFormat; +use vespertide_exporter::Orm; +use vespertide_query::DatabaseBackend; + +use crate::commands::erd::ErdFormat; + +#[derive(Copy, Clone, Debug, ValueEnum)] +pub enum BackendArg { + Postgres, + Mysql, + Sqlite, +} + +impl From for DatabaseBackend { + fn from(value: BackendArg) -> Self { + match value { + BackendArg::Postgres => DatabaseBackend::Postgres, + BackendArg::Mysql => DatabaseBackend::MySql, + BackendArg::Sqlite => DatabaseBackend::Sqlite, + } + } +} + +/// vespertide command-line interface. +#[derive(Parser, Debug)] +#[command(name = "vespertide", author, version, about)] +#[non_exhaustive] +pub struct Cli { + #[command(subcommand)] + pub command: Option, +} + +#[derive(Subcommand, Debug)] +#[non_exhaustive] +pub enum Commands { + /// Show diff between applied migrations and current models. + Diff, + /// Show SQL statements for the pending migration plan. + Sql { + /// Database backend for SQL generation. + #[arg(short = 'b', long = "backend", value_enum, default_value = "postgres")] + backend: BackendArg, + /// Wrap the emitted statements in a plan-level `BEGIN;` / `COMMIT;` + /// transaction (opt-in; for pasting into a manual SQL runner). + /// Note: `MySQL` DDL implicitly commits, so this is advisory there. + #[arg(long = "transaction")] + transaction: bool, + }, + /// Show SQL per applied migration (chronological log). + Log { + /// Database backend for SQL generation. + #[arg(short = 'b', long = "backend", value_enum, default_value = "postgres")] + backend: BackendArg, + /// Wrap each migration's statements in a plan-level `BEGIN;` / + /// `COMMIT;` transaction (opt-in; mirrors how each migration is + /// applied at runtime). Note: `MySQL` DDL implicitly commits, so this + /// is advisory there. + #[arg(long = "transaction")] + transaction: bool, + }, + /// Create a new model file from template. + New { + /// Model name (table name). + name: String, + /// Output format: json|yaml|yml (default: config modelFormat or json). + #[arg(short = 'f', long = "format", value_enum)] + format: Option, + }, + /// Show current status. + Status, + /// Create a new revision with a message. + Revision { + #[arg(short = 'm', long = "message")] + message: String, + /// Fill values for NOT NULL columns without defaults. + /// Format: table.column=value (can be specified multiple times) + #[arg(long = "fill-with")] + fill_with: Vec, + /// Delete rows with NULL values instead of filling. + /// Format: table.column (can be specified multiple times) + #[arg(long = "delete-null-rows")] + delete_null_rows: Vec, + }, + /// Initialize vespertide.json with defaults. + Init, + /// Export models into ORM-specific code. + Export { + /// Target ORM for export. + #[arg(short = 'o', long = "orm", value_enum, default_value = "seaorm")] + orm: Orm, + /// Output directory (defaults to config modelsDir or src/models). + #[arg(short = 'd', long = "export-dir")] + export_dir: Option, + }, + /// Export schema as ERD diagrams (SVG, Mermaid, Graphviz DOT). + Erd { + /// Output format: svg|mermaid|dot. + #[arg(short = 'f', long = "format", value_enum, default_value = "svg")] + format: ErdFormat, + /// Output file path (defaults to stdout if not specified). + #[arg(short = 'o', long = "output")] + output: Option, + /// Include only these tables, plus FK-graph neighbors from --depth. + #[arg(long, value_delimiter = ',')] + include: Vec, + /// Exclude these tables after applying --include and --depth. + #[arg(long, value_delimiter = ',')] + exclude: Vec, + /// FK-graph hop distance from --include tables. 0 = include set only. + #[arg(long, default_value = "0")] + depth: usize, + }, +} + +#[cfg(test)] +mod tests { + use rstest::rstest; + + use super::*; + + #[rstest] + #[case::postgres(BackendArg::Postgres, DatabaseBackend::Postgres)] + #[case::mysql(BackendArg::Mysql, DatabaseBackend::MySql)] + #[case::sqlite(BackendArg::Sqlite, DatabaseBackend::Sqlite)] + fn backend_arg_maps_to_backend(#[case] arg: BackendArg, #[case] expected: DatabaseBackend) { + assert_eq!(DatabaseBackend::from(arg), expected); + } +} diff --git a/crates/vespertide-cli/src/commands/erd/tests/snapshots/vespertide__commands__erd__tests__svg_coverage__render_svg_full_deterministic_fixture_snapshot.snap b/crates/vespertide-cli/src/commands/erd/tests/snapshots/vespertide_cli__commands__erd__tests__svg_coverage__render_svg_full_deterministic_fixture_snapshot.snap similarity index 100% rename from crates/vespertide-cli/src/commands/erd/tests/snapshots/vespertide__commands__erd__tests__svg_coverage__render_svg_full_deterministic_fixture_snapshot.snap rename to crates/vespertide-cli/src/commands/erd/tests/snapshots/vespertide_cli__commands__erd__tests__svg_coverage__render_svg_full_deterministic_fixture_snapshot.snap diff --git a/crates/vespertide-cli/src/commands/export/tests/snapshots/vespertide__commands__export__tests__drizzle__export_drizzle_writes_one_file_per_dialect@mysql.snap b/crates/vespertide-cli/src/commands/export/tests/snapshots/vespertide_cli__commands__export__tests__drizzle__export_drizzle_writes_one_file_per_dialect@mysql.snap similarity index 100% rename from crates/vespertide-cli/src/commands/export/tests/snapshots/vespertide__commands__export__tests__drizzle__export_drizzle_writes_one_file_per_dialect@mysql.snap rename to crates/vespertide-cli/src/commands/export/tests/snapshots/vespertide_cli__commands__export__tests__drizzle__export_drizzle_writes_one_file_per_dialect@mysql.snap diff --git a/crates/vespertide-cli/src/commands/export/tests/snapshots/vespertide__commands__export__tests__drizzle__export_drizzle_writes_one_file_per_dialect@pg.snap b/crates/vespertide-cli/src/commands/export/tests/snapshots/vespertide_cli__commands__export__tests__drizzle__export_drizzle_writes_one_file_per_dialect@pg.snap similarity index 100% rename from crates/vespertide-cli/src/commands/export/tests/snapshots/vespertide__commands__export__tests__drizzle__export_drizzle_writes_one_file_per_dialect@pg.snap rename to crates/vespertide-cli/src/commands/export/tests/snapshots/vespertide_cli__commands__export__tests__drizzle__export_drizzle_writes_one_file_per_dialect@pg.snap diff --git a/crates/vespertide-cli/src/commands/export/tests/snapshots/vespertide__commands__export__tests__drizzle__export_drizzle_writes_one_file_per_dialect@sqlite.snap b/crates/vespertide-cli/src/commands/export/tests/snapshots/vespertide_cli__commands__export__tests__drizzle__export_drizzle_writes_one_file_per_dialect@sqlite.snap similarity index 100% rename from crates/vespertide-cli/src/commands/export/tests/snapshots/vespertide__commands__export__tests__drizzle__export_drizzle_writes_one_file_per_dialect@sqlite.snap rename to crates/vespertide-cli/src/commands/export/tests/snapshots/vespertide_cli__commands__export__tests__drizzle__export_drizzle_writes_one_file_per_dialect@sqlite.snap diff --git a/crates/vespertide-cli/src/lib.rs b/crates/vespertide-cli/src/lib.rs new file mode 100644 index 00000000..c750aa22 --- /dev/null +++ b/crates/vespertide-cli/src/lib.rs @@ -0,0 +1,258 @@ +//! Library face of the `vespertide` CLI. +//! +//! The binary in `main.rs` is a shell around [`run_cli`]; the N-API bridge +//! calls the same function with the arguments it received from Node. Both go +//! through one parse and one dispatch, so a flag added to [`Commands`] +//! reaches every host the same way. + +use std::ffi::OsString; +use std::fmt; +use std::io::Write; + +use clap::{CommandFactory, Parser}; + +mod cli; +mod commands; +mod parallel_config; +#[cfg(test)] +mod test_support; +mod utils; + +pub use cli::{BackendArg, Cli, Commands}; +pub use commands::erd::ErdFormat; +use commands::{ + cmd_diff, cmd_erd_with_filters, cmd_export, cmd_init, cmd_log, cmd_new, cmd_revision, cmd_sql, + cmd_status, +}; + +/// Why a CLI invocation did not succeed. +/// +/// The two arms carry different exit codes: clap decides the code for a +/// usage error (`2`, or `0` for `--help` / `--version`, which are "errors" +/// only in clap's model), while a command that ran and failed is always `1` +/// — the same code `main() -> Result` used to produce. +#[derive(Debug)] +#[non_exhaustive] +pub enum CliError { + /// The arguments did not parse. clap has already printed its message. + Usage(clap::Error), + /// The command ran and returned an error. + Failed(anyhow::Error), +} + +impl CliError { + /// The process exit code this error stands for. + pub fn exit_code(&self) -> u8 { + match self { + // clap reports 0 or 2; anything else is treated as a failure. + Self::Usage(err) => u8::try_from(err.exit_code()).unwrap_or(1), + Self::Failed(_) => 1, + } + } +} + +impl fmt::Display for CliError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Usage(err) => err.fmt(f), + // The alternate form carries the whole context chain, not just the + // outermost message — what a host that only sees `to_string()` needs. + Self::Failed(err) => write!(f, "{err:#}"), + } + } +} + +impl std::error::Error for CliError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::Usage(err) => Some(err), + Self::Failed(err) => Some(err.as_ref()), + } + } +} + +/// Dispatch a parsed command line. +/// +/// Pure routing — every arm is one `cmd_*` call — which is why it carries no +/// coverage of its own: the integration tests exercise it through the built +/// binary, exactly as they did when this `match` lived in `main`. +#[cfg(not(tarpaulin_include))] +pub async fn run(cli: Cli) -> anyhow::Result<()> { + match cli.command { + Some(Commands::Diff) => cmd_diff().await, + Some(Commands::Sql { + backend, + transaction, + }) => cmd_sql(backend.into(), transaction).await, + Some(Commands::Log { + backend, + transaction, + }) => cmd_log(backend.into(), transaction).await, + Some(Commands::New { name, format }) => cmd_new(name, format).await, + Some(Commands::Status) => cmd_status().await, + Some(Commands::Revision { + message, + fill_with, + delete_null_rows, + }) => cmd_revision(message, fill_with, delete_null_rows).await, + Some(Commands::Init) => cmd_init().await, + Some(Commands::Export { orm, export_dir }) => cmd_export(orm, export_dir).await, + Some(Commands::Erd { + format, + output, + include, + exclude, + depth, + }) => cmd_erd_with_filters(format, output, include, exclude, depth).await, + None => { + // No subcommand: show help and exit successfully. + Cli::command().print_help()?; + println!(); + Ok(()) + } + } +} + +/// Parse `args` — `argv[0]` first, since clap reads the usage line's binary +/// name from it — and run the command. +/// +/// This is the entry point for embedders. It never calls `process::exit`: +/// `--help` and `--version` print and return `Ok`, a usage error prints and +/// returns [`CliError::Usage`], so a host such as Node keeps its process. +/// Arguments are taken as `OsString`-convertible values so a path that is not +/// valid Unicode reaches clap intact, as it does from the shell. +pub async fn main(args: I) -> Result<(), CliError> +where + I: IntoIterator, + T: Into + Clone, +{ + let cli = match Cli::try_parse_from(args) { + Ok(cli) => cli, + Err(err) => { + // Swallow broken-pipe errors, as clap's own exit path does. + let _ = err.print(); + return if err.exit_code() == 0 { + Ok(()) + } else { + Err(CliError::Usage(err)) + }; + } + }; + run(cli).await.map_err(CliError::Failed) +} + +/// Run the command line and return the process exit code. +/// +/// A failed command is reported as `Error: {err:?}` on stderr — the text +/// std's `Termination` printed when `main` returned `Result` — and, like +/// that path, a stderr that cannot be written to is ignored rather than a +/// reason to panic. +pub async fn run_cli(args: I) -> u8 +where + I: IntoIterator, + T: Into + Clone, +{ + let code = match main(args).await { + Ok(()) => 0, + Err(err) => { + if let CliError::Failed(cause) = &err { + let _ = writeln!(std::io::stderr(), "Error: {cause:?}"); + } + err.exit_code() + } + }; + // A host that keeps the process alive (the N-API bridge) gets no exit-time + // flush, so drain line-buffered stdout before handing the code back. + let _ = std::io::stdout().flush(); + code +} + +#[cfg(test)] +mod tests { + use std::error::Error as _; + + use super::*; + use rstest::rstest; + use serial_test::serial; + + fn argv(rest: &[&str]) -> Vec { + std::iter::once("vespertide") + .chain(rest.iter().copied()) + .map(str::to_string) + .collect() + } + + /// An argument the platform cannot represent as UTF-8. + #[cfg(unix)] + fn non_unicode_arg() -> OsString { + use std::os::unix::ffi::OsStringExt; + OsString::from_vec(vec![0xff]) + } + + #[cfg(windows)] + fn non_unicode_arg() -> OsString { + use std::os::windows::ffi::OsStringExt; + OsString::from_wide(&[0xD800]) + } + + /// `--help`, `--version` and a bare invocation all print and succeed — + /// none of them may reach `process::exit`, or an embedding host dies. + #[rstest] + #[case::help(&["--help"])] + #[case::version(&["--version"])] + #[case::bare(&[])] + #[tokio::test] + async fn main_prints_and_succeeds(#[case] rest: &[&str]) { + assert!(main(argv(rest)).await.is_ok()); + } + + /// A usage error surfaces as `Usage` with clap's exit code and message, + /// after clap has printed that message itself. + #[rstest] + #[case::unknown_subcommand(&["bogus"], "unrecognized subcommand")] + #[case::bad_value(&["sql", "--backend", "nope"], "invalid value")] + #[tokio::test] + async fn main_reports_usage_errors(#[case] rest: &[&str], #[case] message: &str) { + let err = main(argv(rest)).await.unwrap_err(); + assert!(matches!(err, CliError::Usage(_))); + assert_eq!(err.exit_code(), 2); + assert!(err.to_string().contains(message)); + assert!(err.source().is_some()); + } + + /// A non-Unicode argument is clap's usage error, as it is from the shell + /// — not a panic on the way in. + #[tokio::test] + async fn main_keeps_non_unicode_arguments_a_usage_error() { + let args = vec![ + OsString::from("vespertide"), + OsString::from("new"), + non_unicode_arg(), + ]; + let err = main(args).await.unwrap_err(); + assert!(matches!(err, CliError::Usage(_))); + assert_eq!(err.exit_code(), 2); + } + + /// A command that ran and failed is `Failed` with its cause at the API + /// layer and exit code 1 at the process layer. + #[tokio::test] + #[serial] + async fn failed_command_is_exit_one_at_every_layer() { + let tmp = tempfile::tempdir().unwrap(); + let _guard = test_support::CwdGuard::new(tmp.path()); + let err = main(argv(&["status"])).await.unwrap_err(); + assert!(matches!(err, CliError::Failed(_))); + assert!(err.to_string().contains("vespertide.json")); + assert!(err.source().is_some()); + assert_eq!(run_cli(argv(&["status"])).await, 1); + } + + #[rstest] + #[case::success(&["--version"], 0)] + #[case::usage(&["bogus"], 2)] + #[tokio::test] + async fn run_cli_maps_exit_codes(#[case] rest: &[&str], #[case] expected: u8) { + assert_eq!(run_cli(argv(rest)).await, expected); + } +} diff --git a/crates/vespertide-cli/src/main.rs b/crates/vespertide-cli/src/main.rs index e9a8e264..c1c10ad7 100644 --- a/crates/vespertide-cli/src/main.rs +++ b/crates/vespertide-cli/src/main.rs @@ -1,184 +1,7 @@ -use anyhow::Result; -use clap::{CommandFactory, Parser, Subcommand, ValueEnum}; - -mod commands; -mod parallel_config; -#[cfg(test)] -mod test_support; -mod utils; -use crate::commands::erd::ErdFormat; -use commands::{ - cmd_diff, cmd_erd_with_filters, cmd_export, cmd_init, cmd_log, cmd_new, cmd_revision, cmd_sql, - cmd_status, -}; -use vespertide_config::FileFormat; -use vespertide_exporter::Orm; -use vespertide_query::DatabaseBackend; - -#[derive(Copy, Clone, Debug, ValueEnum)] -enum BackendArg { - Postgres, - Mysql, - Sqlite, -} - -impl From for DatabaseBackend { - fn from(value: BackendArg) -> Self { - match value { - BackendArg::Postgres => DatabaseBackend::Postgres, - BackendArg::Mysql => DatabaseBackend::MySql, - BackendArg::Sqlite => DatabaseBackend::Sqlite, - } - } -} - -/// vespertide command-line interface. -#[derive(Parser, Debug)] -#[command(name = "vespertide", author, version, about)] -struct Cli { - #[command(subcommand)] - command: Option, -} - -#[derive(Subcommand, Debug)] -enum Commands { - /// Show diff between applied migrations and current models. - Diff, - /// Show SQL statements for the pending migration plan. - Sql { - /// Database backend for SQL generation. - #[arg(short = 'b', long = "backend", value_enum, default_value = "postgres")] - backend: BackendArg, - /// Wrap the emitted statements in a plan-level `BEGIN;` / `COMMIT;` - /// transaction (opt-in; for pasting into a manual SQL runner). - /// Note: `MySQL` DDL implicitly commits, so this is advisory there. - #[arg(long = "transaction")] - transaction: bool, - }, - /// Show SQL per applied migration (chronological log). - Log { - /// Database backend for SQL generation. - #[arg(short = 'b', long = "backend", value_enum, default_value = "postgres")] - backend: BackendArg, - /// Wrap each migration's statements in a plan-level `BEGIN;` / - /// `COMMIT;` transaction (opt-in; mirrors how each migration is - /// applied at runtime). Note: `MySQL` DDL implicitly commits, so this - /// is advisory there. - #[arg(long = "transaction")] - transaction: bool, - }, - /// Create a new model file from template. - New { - /// Model name (table name). - name: String, - /// Output format: json|yaml|yml (default: config modelFormat or json). - #[arg(short = 'f', long = "format", value_enum)] - format: Option, - }, - /// Show current status. - Status, - /// Create a new revision with a message. - Revision { - #[arg(short = 'm', long = "message")] - message: String, - /// Fill values for NOT NULL columns without defaults. - /// Format: table.column=value (can be specified multiple times) - #[arg(long = "fill-with")] - fill_with: Vec, - /// Delete rows with NULL values instead of filling. - /// Format: table.column (can be specified multiple times) - #[arg(long = "delete-null-rows")] - delete_null_rows: Vec, - }, - /// Initialize vespertide.json with defaults. - Init, - /// Export models into ORM-specific code. - Export { - /// Target ORM for export. - #[arg(short = 'o', long = "orm", value_enum, default_value = "seaorm")] - orm: Orm, - /// Output directory (defaults to config modelsDir or src/models). - #[arg(short = 'd', long = "export-dir")] - export_dir: Option, - }, - /// Export schema as ERD diagrams (SVG, Mermaid, Graphviz DOT). - Erd { - /// Output format: svg|mermaid|dot. - #[arg(short = 'f', long = "format", value_enum, default_value = "svg")] - format: ErdFormat, - /// Output file path (defaults to stdout if not specified). - #[arg(short = 'o', long = "output")] - output: Option, - /// Include only these tables, plus FK-graph neighbors from --depth. - #[arg(long, value_delimiter = ',')] - include: Vec, - /// Exclude these tables after applying --include and --depth. - #[arg(long, value_delimiter = ',')] - exclude: Vec, - /// FK-graph hop distance from --include tables. 0 = include set only. - #[arg(long, default_value = "0")] - depth: usize, - }, -} +use std::process::ExitCode; #[cfg(not(tarpaulin_include))] #[tokio::main] -async fn main() -> Result<()> { - let cli = Cli::parse(); - match cli.command { - Some(Commands::Diff) => cmd_diff().await, - Some(Commands::Sql { - backend, - transaction, - }) => cmd_sql(backend.into(), transaction).await, - Some(Commands::Log { - backend, - transaction, - }) => cmd_log(backend.into(), transaction).await, - Some(Commands::New { name, format }) => cmd_new(name, format).await, - Some(Commands::Status) => cmd_status().await, - Some(Commands::Revision { - message, - fill_with, - delete_null_rows, - }) => cmd_revision(message, fill_with, delete_null_rows).await, - Some(Commands::Init) => cmd_init().await, - Some(Commands::Export { orm, export_dir }) => cmd_export(orm, export_dir).await, - Some(Commands::Erd { - format, - output, - include, - exclude, - depth, - }) => cmd_erd_with_filters(format, output, include, exclude, depth).await, - None => { - // No subcommand: show help and exit successfully. - Cli::command().print_help()?; - println!(); - Ok(()) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_backend_arg_from_postgres() { - let backend: DatabaseBackend = BackendArg::Postgres.into(); - assert!(matches!(backend, DatabaseBackend::Postgres)); - } - - #[test] - fn test_backend_arg_from_mysql() { - let backend: DatabaseBackend = BackendArg::Mysql.into(); - assert!(matches!(backend, DatabaseBackend::MySql)); - } - - #[test] - fn test_backend_arg_from_sqlite() { - let backend: DatabaseBackend = BackendArg::Sqlite.into(); - assert!(matches!(backend, DatabaseBackend::Sqlite)); - } +async fn main() -> ExitCode { + ExitCode::from(vespertide_cli::run_cli(std::env::args_os()).await) } diff --git a/crates/vespertide-cli/src/test_support.rs b/crates/vespertide-cli/src/test_support.rs index 0c8e22b5..4f4e8c45 100644 --- a/crates/vespertide-cli/src/test_support.rs +++ b/crates/vespertide-cli/src/test_support.rs @@ -1,7 +1,7 @@ //! Shared test-only helpers for the vespertide-cli crate. //! //! All items are gated under `#[cfg(test)]` via the parent module declaration -//! in `main.rs` and exposed `pub(crate)` so every inline `mod tests` and +//! in `lib.rs` and exposed `pub(crate)` so every inline `mod tests` and //! `commands//tests/mod.rs` entry can reuse the same implementation. use std::fs; diff --git a/crates/vespertide-cli/tests/integration_test.rs b/crates/vespertide-cli/tests/integration_test.rs index 562d3d42..a694292c 100644 --- a/crates/vespertide-cli/tests/integration_test.rs +++ b/crates/vespertide-cli/tests/integration_test.rs @@ -108,3 +108,23 @@ fn test_main_with_export_command() { cmd.args(["export", "--orm", "seaorm"]); let _ = cmd.assert(); } + +#[test] +fn test_unknown_subcommand_exits_two() { + vespertide() + .arg("bogus") + .assert() + .code(2) + .stderr(predicate::str::contains("unrecognized subcommand")); +} + +#[test] +fn test_failed_command_exits_one_with_error_prefix() { + let tmp = tempfile::tempdir().unwrap(); + vespertide() + .current_dir(tmp.path()) + .arg("status") + .assert() + .code(1) + .stderr(predicate::str::starts_with("Error: ")); +}