From 1986519d3978a8aef0b1b1df52bd8d138d5d52a7 Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Tue, 20 Oct 2026 10:52:13 -0400 Subject: [PATCH 1/5] feat(cli): mieweb target deploys via @mieweb/os-cloud-provider - built-in provider for --target mieweb: @mieweb/os-cloud-provider ^2026.10.3 (npmjs) - verbs a provider doesn't implement (dev) fall back to the Node host harness - DeployContext.persistTargetConfig: write resolved settings (e.g. the picked site) back into mieweb.jsonc, keeping comments (jsonc-parser) - mieweb init adds a start script for the cloud image; the host harness honors $PORT - remove the GitHub Packages preview workflow (cross-repo previews are no longer needed) --- .changeset/cli-os-cloud-provider.md | 7 + .changeset/init-start-script.md | 6 + .github/workflows/pr-preview.yml | 140 ---------- packages/cli/README.md | 10 +- packages/cli/mieweb-config.schema.json | 2 +- packages/cli/package.json | 1 + packages/cli/src/index.mjs | 9 +- packages/cli/src/init.mjs | 5 +- packages/cli/src/provider.mjs | 62 ++++- packages/cli/test/provider.test.mjs | 46 +++ packages/cloud-adapters/src/host.mjs | 3 +- pnpm-lock.yaml | 371 +++++++++++++++++++++++++ 12 files changed, 506 insertions(+), 156 deletions(-) create mode 100644 .changeset/cli-os-cloud-provider.md create mode 100644 .changeset/init-start-script.md delete mode 100644 .github/workflows/pr-preview.yml create mode 100644 packages/cli/test/provider.test.mjs diff --git a/.changeset/cli-os-cloud-provider.md b/.changeset/cli-os-cloud-provider.md new file mode 100644 index 0000000..f8b6d54 --- /dev/null +++ b/.changeset/cli-os-cloud-provider.md @@ -0,0 +1,7 @@ +--- +"@mieweb/cli": minor +--- + +`--target mieweb` now deploys through the opensource-server provider (`@mieweb/os-cloud-provider`, bundled as a dependency). Verbs a provider doesn't implement (`dev`) fall back to the Node host harness instead of erroring. + +Providers can now write back settings they resolved interactively (e.g. the os.mieweb.org site picked at the prompt) via `DeployContext.persistTargetConfig`; the CLI edits `mieweb.jsonc` in place, keeping comments. diff --git a/.changeset/init-start-script.md b/.changeset/init-start-script.md new file mode 100644 index 0000000..df25b45 --- /dev/null +++ b/.changeset/init-start-script.md @@ -0,0 +1,6 @@ +--- +"@mieweb/cli": patch +"@mieweb/cloud-adapters": patch +--- + +`mieweb init` adds a `start` script (`mieweb --target mieweb dev`), which the os.mieweb.org cloud image runs via `npm start`. The Node host harness now listens on `$PORT` when set. diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml deleted file mode 100644 index 8ef67d0..0000000 --- a/.github/workflows/pr-preview.yml +++ /dev/null @@ -1,140 +0,0 @@ -# PR preview packages. -# -# On every push to a same-repo pull request, publish every public @mieweb/* -# workspace package to the GitHub Packages npm registry (npm.pkg.github.com) -# as `-pr.` under the dist-tag `pr`. This lets -# cross-repo PRs depend on each other with ordinary semver references while -# they are in review, e.g. -# -# "@mieweb/deploy-contract": "0.2.1-pr14.3" -# -# plus, in the consumer, an .npmrc line: -# -# @mieweb:registry=https://npm.pkg.github.com -# -# Once the PRs merge, the real versions go to npmjs via release.yml and the -# consumers drop the .npmrc line and switch to the released version. -# -# Installing from GitHub Packages needs a token with `read:packages`, even for -# public packages. -name: PR preview packages - -on: - pull_request: - -concurrency: - group: pr-preview-${{ github.event.pull_request.number }} - cancel-in-progress: true - -# Two jobs so PR-controlled code never runs with a token: `pack` installs (read -# token, but no scripts run), then stamps and packs without any token -# (lifecycle scripts run here), and `publish` -# only uploads the resulting tarballs with --ignore-scripts. It never checks out -# or executes the PR's code. -permissions: - contents: read - -jobs: - pack: - # Same-repo branches only (fork PRs can't publish anyway). - if: github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest - permissions: - contents: read - packages: read - outputs: - packages: ${{ steps.stamp.outputs.packages }} - steps: - - uses: actions/checkout@v4 - with: - persist-credentials: false - - - uses: pnpm/action-setup@v4 - - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: pnpm - registry-url: https://npm.pkg.github.com - scope: '@mieweb' - - # The only step with a token (packages: read, for @mieweb deps on GitHub - # Packages). --ignore-scripts and --ignore-pnpmfile so none of the PR's - # code runs while it's in the environment; nothing here needs a build. - - run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile - env: - NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Stamp preview versions - id: stamp - env: - SUFFIX: pr${{ github.event.pull_request.number }}.${{ github.run_number }} - run: | - node -e ' - const fs = require("fs"), path = require("path"); - const out = []; - for (const dir of fs.readdirSync("packages")) { - const f = path.join("packages", dir, "package.json"); - if (!fs.existsSync(f)) continue; - const p = JSON.parse(fs.readFileSync(f, "utf8")); - if (p.private) continue; - p.version = `${p.version.replace(/-.*$/, "")}-${process.env.SUFFIX}`; - fs.writeFileSync(f, JSON.stringify(p, null, 2) + "\n"); - out.push(`${p.name}@${p.version}`); - } - fs.appendFileSync(process.env.GITHUB_OUTPUT, `packages=${out.join(" ")}\n`); - ' - - # pnpm pack rewrites workspace:* to the stamped versions and runs prepack. - - name: Pack - run: | - mkdir -p "$RUNNER_TEMP/tarballs" - for d in packages/*/; do - [ "$(node -p "require('./${d}package.json').private === true")" = true ] && continue - (cd "$d" && pnpm pack --pack-destination "$RUNNER_TEMP/tarballs") - done - ls "$RUNNER_TEMP/tarballs" - - - uses: actions/upload-artifact@v4 - with: - name: tarballs - path: ${{ runner.temp }}/tarballs/*.tgz - retention-days: 1 - - publish: - needs: pack - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - uses: actions/download-artifact@v4 - with: - name: tarballs - path: tarballs - - - uses: actions/setup-node@v4 - with: - node-version: 22 - registry-url: https://npm.pkg.github.com - scope: '@mieweb' - - - name: Publish to GitHub Packages - env: - NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - for t in ./tarballs/*.tgz; do - npm publish "$t" --ignore-scripts --tag pr${{ github.event.pull_request.number }} - done - - - name: Summary - env: - PACKAGES: ${{ needs.pack.outputs.packages }} - run: | - { - echo "### Preview packages (npm.pkg.github.com)" - echo - echo "Add \`@mieweb:registry=https://npm.pkg.github.com\` to .npmrc, then:" - echo - for p in $PACKAGES; do echo "- \`$p\`"; done - } >> "$GITHUB_STEP_SUMMARY" diff --git a/packages/cli/README.md b/packages/cli/README.md index eee5ab3..19435cc 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -6,9 +6,13 @@ most commands are forwarded verbatim to `wrangler`; the deploy lifecycle verbs through a pluggable **deploy provider** (`@mieweb/deploy-contract`) whose Cloudflare reference implementation still delegates to `wrangler` — adding structured logging, resource reporting, and auth-aware error handling around it. -On the built-in `local`/`mieweb` targets the CLI runs your unchanged worker on -the Node host harness backed by the matching adapters (a custom target with -`targets[t].provider` routes provider verbs through that provider instead). See +On the `mieweb` target (os.mieweb.org) `deploy`, `destroy`, `tail`, `login`, +`logout` and `whoami` go through the bundled opensource-server provider +(`@mieweb/os-cloud-provider`; configure `targets.mieweb.siteId` / +`instanceUrl`, auth via `MIEWEB_OS_TOKEN`). Verbs a provider doesn't implement +(`dev` on `mieweb`) fall back to the Node host harness, which also runs +everything on `local`. Any target can override its provider with +`targets[t].provider`. See the [root README](../../README.md) for the full model. ```sh diff --git a/packages/cli/mieweb-config.schema.json b/packages/cli/mieweb-config.schema.json index 2775308..12fd7c6 100644 --- a/packages/cli/mieweb-config.schema.json +++ b/packages/cli/mieweb-config.schema.json @@ -21,7 +21,7 @@ "type": "object", "properties": { "runtime": { "type": "string" }, - "provider": { "type": "string", "description": "Deploy provider for this target: a package name or path to a module exporting a DeployProvider (@mieweb/deploy-contract). Cloudflare defaults to the wrangler reference provider; a custom provider may serve a custom target name." }, + "provider": { "type": "string", "description": "Deploy provider for this target: a package name or path to a module exporting a DeployProvider (@mieweb/deploy-contract). Defaults: cloudflare → @mieweb/deploy-wrangler, mieweb → @mieweb/os-cloud-provider. Verbs the provider does not implement fall back to the CLI's legacy path; a custom provider may serve a custom target name." }, "port": { "type": "number" }, "registry": { "type": "object", diff --git a/packages/cli/package.json b/packages/cli/package.json index b3210d9..2c92668 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,6 +33,7 @@ "@mieweb/cloud-adapters": "workspace:*", "@mieweb/deploy-contract": "workspace:*", "@mieweb/deploy-wrangler": "workspace:*", + "@mieweb/os-cloud-provider": "^2026.10.3", "import-meta-resolve": "^4.2.0", "jsonc-parser": "^3.3.1" }, diff --git a/packages/cli/src/index.mjs b/packages/cli/src/index.mjs index 84e38bc..71de066 100755 --- a/packages/cli/src/index.mjs +++ b/packages/cli/src/index.mjs @@ -26,9 +26,7 @@ import { delegateToWrangler } from './cloudflare.mjs'; import { runHostTarget } from './local.mjs'; import { runInit } from './init.mjs'; import { runImagesCommand, runRegistryCommand } from './images.mjs'; -import { PROVIDER_VERBS, resolveProvider, runProviderVerb } from './provider.mjs'; - -/** Verbs handled by the deploy-contract provider layer. */ +import { PROVIDER_VERBS, providerImplements, resolveProvider, runProviderVerb } from './provider.mjs'; /** Read this CLI's version from its package.json. */ function miewebVersion() { @@ -105,7 +103,8 @@ async function main(argv) { // PROVIDER_VERBS). Cloudflare resolves to the wrangler reference provider; // other targets can name a provider package in mieweb.jsonc // (`targets[t].provider`). Everything else (d1 migrations, etc.) and any - // target without a provider falls through to the legacy paths below. + // target without a provider — or a verb the provider doesn't implement (e.g. + // `dev` on mieweb) — falls through to the legacy paths below. if (/** @type {readonly string[]} */ (PROVIDER_VERBS).includes(args[0])) { let provider = null; try { @@ -114,7 +113,7 @@ async function main(argv) { console.error(`mieweb: ${err?.message ?? err}`); return 1; } - if (provider) { + if (provider && providerImplements(provider, args[0])) { return runProviderVerb( /** @type {import('./provider.mjs').ProviderVerb} */ (args[0]), provider, diff --git a/packages/cli/src/init.mjs b/packages/cli/src/init.mjs index 14e0576..96bd3c5 100644 --- a/packages/cli/src/init.mjs +++ b/packages/cli/src/init.mjs @@ -7,7 +7,7 @@ * wrangler.jsonc source of truth for bindings (Cloudflare shapes) * mieweb.jsonc off-Cloudflare driver hints + default target * worker/index.mjs a normal `export default { fetch }` worker - * package.json wired to @mieweb/cli with dev/deploy scripts + * package.json wired to @mieweb/cli with dev/deploy/start scripts * .gitignore ignores .data/ + node_modules * * Mirrors `wrangler init` / `npm create cloudflare`: it only writes files, then @@ -100,6 +100,9 @@ function packageJson(name) { dev: 'mieweb --target local dev', 'dev:cf': 'mieweb dev', deploy: 'mieweb deploy', + // What os.mieweb.org's cloud image runs (app.service → `npm start`): + // the worker on the Node host harness with the networked adapters. + start: 'mieweb --target mieweb dev', }, devDependencies: { '@mieweb/cli': '^0.2.0', diff --git a/packages/cli/src/provider.mjs b/packages/cli/src/provider.mjs index 08b560d..abaf87f 100644 --- a/packages/cli/src/provider.mjs +++ b/packages/cli/src/provider.mjs @@ -3,7 +3,8 @@ import { pathToFileURL } from 'node:url'; import { resolve, join, isAbsolute } from 'node:path'; import { createRequire } from 'node:module'; import { resolve as resolveFrom } from 'import-meta-resolve'; -import { existsSync } from 'node:fs'; +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; +import { applyEdits, modify } from 'jsonc-parser'; /** * Provider selection + context construction for the mieweb CLI. @@ -34,15 +35,15 @@ export const PROVIDER_VERBS = /** @type {const} */ (['deploy', 'dev', 'tail', 'l /** @typedef {typeof PROVIDER_VERBS[number]} ProviderVerb */ /** - * Built-in provider mapping. Kept tiny and explicit for the POC: only the - * reference (cloudflare → wrangler) is wired in-repo. Other targets resolve - * their provider dynamically from config (below), so opensource-server can ship - * its provider as a separate package without a change here. + * Built-in provider mapping: cloudflare → the wrangler reference provider, + * mieweb (os.mieweb.org) → the opensource-server provider. Both are CLI + * dependencies. `targets[t].provider` in mieweb.jsonc overrides these. * * @type {Record} */ const BUILTIN_PROVIDERS = { cloudflare: '@mieweb/deploy-wrangler', + mieweb: '@mieweb/os-cloud-provider', }; /** @@ -191,6 +192,7 @@ export function buildContext(config, argv) { error: (m) => process.stderr.write(`[mieweb] ERROR ${m}\n`), }, signal: controller.signal, + persistTargetConfig: (patch) => persistTargetConfig(config, patch, context.logger), }; const dispose = () => { @@ -360,3 +362,53 @@ function unsupported(context, provider, verb) { context.logger.error(`provider "${provider.name}" does not implement "${verb}".`); return 1; } + +/** + * Whether `provider` implements the CLI verb. `deploy` is mandatory in the + * contract; the rest are optional methods. Verbs a provider lacks fall back to + * the CLI's legacy path for the target (e.g. `dev`/`tail` on mieweb run the + * Node host harness). + * + * @param {DeployProvider} provider + * @param {string} verb + * @returns {boolean} + */ +export function providerImplements(provider, verb) { + return typeof (/** @type {Record} */ (/** @type {unknown} */ (provider)))[verb] === 'function'; +} + +/** + * `DeployContext.persistTargetConfig`: write non-secret settings a provider + * resolved interactively into `mieweb.jsonc` → `targets[target]`, keeping + * comments and formatting. Creates mieweb.jsonc in the project root if absent. + * + * @param {MiewebConfig} config + * @param {Readonly>} patch + * @param {DeployContext['logger']} logger + * @returns {Promise} whether the file changed + */ +export async function persistTargetConfig(config, patch, logger) { + const entries = Object.entries(patch ?? {}); + for (const [k] of entries) { + if (k === 'bindings' || /(secret|token|password|credential|apikey|privatekey)$/i.test(k)) { + throw new Error(`refusing to write "${k}" to mieweb.jsonc: secrets belong in the environment`); + } + } + if (entries.length === 0) return false; + + const file = config.configPath && config.configPath.endsWith('mieweb.jsonc') + ? config.configPath + : join(config.root, 'mieweb.jsonc'); + const before = existsSync(file) ? readFileSync(file, 'utf8') : '{}\n'; + const fmt = { formattingOptions: { insertSpaces: true, tabSize: 2, eol: '\n' } }; + let text = before; + for (const [k, v] of entries) { + text = applyEdits(text, modify(text, ['targets', config.target, k], v, fmt)); + } + if (text === before) return false; + writeFileSync(file, text); + logger.info( + `Saved ${entries.map(([k, v]) => `targets.${config.target}.${k} = ${JSON.stringify(v)}`).join(', ')} to ${file}`, + ); + return true; +} diff --git a/packages/cli/test/provider.test.mjs b/packages/cli/test/provider.test.mjs new file mode 100644 index 0000000..029919e --- /dev/null +++ b/packages/cli/test/provider.test.mjs @@ -0,0 +1,46 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { resolveProvider, providerImplements } from '../src/provider.mjs'; + +test('mieweb target resolves the built-in opensource-server provider', async () => { + // root outside the repo: the provider must resolve from the CLI's own deps. + const p = await resolveProvider({ target: 'mieweb', root: '/', targetConfig: {} }); + assert.equal(p.name, 'opensource-server'); + assert.ok(p.supports('mieweb')); +}); + +test('verbs the provider lacks fall back to the legacy path', async () => { + const p = await resolveProvider({ target: 'mieweb', root: '/', targetConfig: {} }); + for (const v of ['deploy', 'destroy', 'tail', 'whoami', 'login', 'logout']) assert.ok(providerImplements(p, v), v); + for (const v of ['dev']) assert.equal(providerImplements(p, v), false, v); +}); + +test('persistTargetConfig writes into mieweb.jsonc, keeping comments; refuses secrets', async () => { + const { mkdtempSync, writeFileSync, readFileSync, rmSync } = await import('node:fs'); + const { tmpdir } = await import('node:os'); + const { join } = await import('node:path'); + const { persistTargetConfig } = await import('../src/provider.mjs'); + const dir = mkdtempSync(join(tmpdir(), 'mieweb-persist-')); + const logs = []; + const logger = { info: (m) => logs.push(m), warn() {}, error() {} }; + try { + const file = join(dir, 'mieweb.jsonc'); + writeFileSync(file, '{\n // keep me\n "target": "mieweb",\n "targets": { "mieweb": { "port": 8787 } }\n}\n'); + const config = { root: dir, target: 'mieweb', configPath: file }; + assert.equal(await persistTargetConfig(config, { siteId: 2 }, logger), true); + const text = readFileSync(file, 'utf8'); + assert.match(text, /\/\/ keep me/); + assert.match(text, /"siteId": 2/); + assert.match(text, /"port": 8787/); + assert.equal(await persistTargetConfig(config, { siteId: 2 }, logger), false, 'no-op when unchanged'); + await assert.rejects(persistTargetConfig(config, { apiToken: 'x' }, logger), /secrets belong in the environment/); + assert.match(logs[0], /targets\.mieweb\.siteId = 2/); + + // No mieweb.jsonc yet (config came from wrangler.jsonc): create one in root. + rmSync(file); + await persistTargetConfig({ root: dir, target: 'mieweb', configPath: join(dir, 'wrangler.jsonc') }, { siteId: 3 }, logger); + assert.deepEqual(JSON.parse(readFileSync(file, 'utf8')), { targets: { mieweb: { siteId: 3 } } }); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); diff --git a/packages/cloud-adapters/src/host.mjs b/packages/cloud-adapters/src/host.mjs index da5e513..a6d8925 100644 --- a/packages/cloud-adapters/src/host.mjs +++ b/packages/cloud-adapters/src/host.mjs @@ -86,7 +86,8 @@ export async function startLocalHost({ config }) { } // 3. Serve fetch over HTTP. - const port = targetConfig.port ?? wrangler?.dev?.port ?? 8787; + // $PORT wins: hosts like the os.mieweb.org cloud image assign the port. + const port = Number(process.env.PORT) || (targetConfig.port ?? wrangler?.dev?.port ?? 8787); const { serve } = await import('@hono/node-server'); const ctx = makeExecutionContext(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5df3b2..14d7321 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: '@mieweb/deploy-wrangler': specifier: workspace:* version: link:../deploy-wrangler + '@mieweb/os-cloud-provider': + specifier: ^2026.10.3 + version: 2026.10.3 import-meta-resolve: specifier: ^4.2.0 version: 4.2.0 @@ -344,6 +347,13 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@mieweb/deploy-contract@0.3.1': + resolution: {integrity: sha512-0H5je3UrZwHFMksJPeoGHe8OtS5UENjDne2/hZxI+3ZhLmdgoZK7fI3Pqtf/ub5xf+0Kjrp1PyswAIqDPr3LEg==} + + '@mieweb/os-cloud-provider@2026.10.3': + resolution: {integrity: sha512-gqV4UwCgmruf81YU5mQNytWS7bsi9iZ/WB4aaKLJiyBbe+lYMlIWAuHf7KBNk82kjOjAbTF+cll88fQLjqZivA==} + engines: {node: '>=22.18'} + '@neon-rs/load@0.0.4': resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==} @@ -425,6 +435,57 @@ packages: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} + asn1@0.2.6: + resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} + + b4a@1.9.0: + resolution: {integrity: sha512-dpfcF9fDNR6++cthXR67iyhgqWy9CBouAvIWhIntzBG6cvK/cnIPiZQjBwi/ZqjjBEDGfoNDtmB0kTjroOJ3pQ==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + bare-events@2.9.2: + resolution: {integrity: sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.8.4: + resolution: {integrity: sha512-MPC2eyrpbk3A873sK4y6rk+Iv4o2enxTjHZ8zH3HO0mjrpm5XjnguY2XmfidIsDV6vnjpbMBKUAJpV0ISD2SsQ==} + engines: {bare: '>=1.28.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-path@3.1.2: + resolution: {integrity: sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==} + + bare-stream@2.13.4: + resolution: {integrity: sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==} + peerDependencies: + bare-abort-controller: '*' + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.5.5: + resolution: {integrity: sha512-iM9qJCruvpt2rPaLsrumVDru9pogI7PHziHIa9kTNRFt8C8HuSa+kI9kFezK+NvHEFnnz71rKGZ1EAilq/3V/Q==} + + bcrypt-pbkdf@1.0.2: + resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} + better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} @@ -440,6 +501,14 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + buildcheck@0.0.7: + resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==} + engines: {node: '>=10.0.0'} + + bundle-name@4.1.1: + resolution: {integrity: sha512-DdH81/zPLVS11EUgWq3tEu/xn+EzljlMYooDNdzWEnFha3R3NBMpMV1UqYIpjYHV/SgpFKMIX1Oh7o06SjM/oA==} + engines: {node: '>=18'} + chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} @@ -447,6 +516,10 @@ packages: resolution: {integrity: sha512-rwHwUfXL40Chm1r08yrhU3qpUvdVlgkKNeyeGPOxnW8/SyVDvgRaed/Uz54AqWNaTCAThlj6QAs3TZcKI0xDEw==} engines: {node: '>=0.10.0'} + cpu-features@0.0.10: + resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==} + engines: {node: '>=10.0.0'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -464,6 +537,18 @@ packages: supports-color: optional: true + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.1: + resolution: {integrity: sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -489,9 +574,15 @@ packages: engines: {node: '>=4'} hasBin: true + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -557,6 +648,10 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.13: + resolution: {integrity: sha512-2E8Q6rh+1RRSq/YdaGsNfXsEZy9FVqv6BSSrkmZJP7BnaoPnRfKL+YaofPvP+5rzdk144qSm6i4+hvPgUcbV4Q==} + engines: {node: '>= 4'} + import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} @@ -564,6 +659,11 @@ packages: resolution: {integrity: sha512-ehuGcf94bQXhfagULNXrJdfnWO38v070jxSx/qE87Kjzmu2fU7ro5EFAb+OPituLqgfyuQaym5DlrNydW2sJ9A==} engines: {node: '>=12.22.0'} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -572,6 +672,19 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-network-error@1.3.2: + resolution: {integrity: sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==} + engines: {node: '>=16'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -584,6 +697,10 @@ packages: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -631,6 +748,13 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + + nan@2.30.0: + resolution: {integrity: sha512-P5mokDkSvCGBz6mZDD/2l+w9QoICRXdCyZ9ONGmTePr40oihJg3Kpn0YU3twKd6PJnwSlLBWNQeOT6yWEV2c4A==} + node-addon-api@8.9.2: resolution: {integrity: sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg==} engines: {node: ^18 || ^20 || >= 21} @@ -644,6 +768,16 @@ packages: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + open@11.0.4: + resolution: {integrity: sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==} + engines: {node: '>=20'} + + openapi-fetch@0.17.0: + resolution: {integrity: sha512-PsbZR1wAPcG91eEthKhN+Zn92FMHxv+/faECIwjXdxfTODGSGegYv0sc1Olz+HYPvKOuoXfp+0pA2XVt2cI0Ig==} + + openapi-typescript-helpers@0.1.0: + resolution: {integrity: sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw==} + outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} @@ -663,6 +797,10 @@ packages: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} engines: {node: '>=6'} + p-retry@8.0.1: + resolution: {integrity: sha512-NAigyu8hfvJe7MsS18mnc4is0MZtau3QMdBklaJKK23oBQb6occO3UPM3vHmTckW8KhfyjMZaUOqdTFEYliHgg==} + engines: {node: '>=22'} + p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -697,6 +835,10 @@ packages: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} + powershell-utils@0.2.1: + resolution: {integrity: sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==} + engines: {node: '>=20'} + prettier@2.8.8: resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} engines: {node: '>=10.13.0'} @@ -715,6 +857,10 @@ packages: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} + read@5.0.1: + resolution: {integrity: sha512-+nsqpqYkkpet2UVPG8ZiuE8d113DK4vHYEoEhcrXBAlPiq6di7QRTuNiKQAbaRYegobuX2BpZ6QjanKOXnJdTA==} + engines: {node: ^20.17.0 || >=22.9.0} + redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} @@ -731,6 +877,10 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -750,6 +900,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.12.0: + resolution: {integrity: sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==} + engines: {node: '>= 0.4'} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -792,9 +946,16 @@ packages: sqlite-vec@0.1.9: resolution: {integrity: sha512-L7XJWRIBNvR9O5+vh1FQ+IGkh/3D2AzVksW5gdtk28m78Hy8skFD0pqReKH1Yp0/BUKRGcffgKvyO/EON5JXpA==} + ssh2@1.17.0: + resolution: {integrity: sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==} + engines: {node: '>=10.16.0'} + standard-as-callback@2.1.0: resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} + streamx@2.28.1: + resolution: {integrity: sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -806,10 +967,19 @@ packages: strnum@2.3.0: resolution: {integrity: sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==} + tar-stream@3.2.2: + resolution: {integrity: sha512-+8NeqHRjQWH9nYlwo2gamAMImZCVzI4UoEgDpWorBt9OEfppiZn+uSkskzQKPWIZyji/C8fpWO7u69G0DX0tbg==} + + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -817,6 +987,9 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tweetnacl@0.14.5: + resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -838,6 +1011,10 @@ packages: engines: {node: '>= 8'} hasBin: true + write-file-atomic@7.0.1: + resolution: {integrity: sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==} + engines: {node: ^20.17.0 || >=22.9.0} + ws@8.21.0: resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} @@ -850,6 +1027,10 @@ packages: utf-8-validate: optional: true + wsl-utils@1.0.1: + resolution: {integrity: sha512-c16PKLIRxBYpR3jSWsdS7SFp0COm5SOQdiFjY818dYFHJbMGuLBszpF07LBWmOS1uzrHa9VGdynsEgoeKy0oOQ==} + engines: {node: '>=20'} + xml-naming@0.1.0: resolution: {integrity: sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==} engines: {node: '>=16.0.0'} @@ -1318,6 +1499,25 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 + '@mieweb/deploy-contract@0.3.1': {} + + '@mieweb/os-cloud-provider@2026.10.3': + dependencies: + '@mieweb/deploy-contract': 0.3.1 + ignore: 7.0.13 + open: 11.0.4 + openapi-fetch: 0.17.0 + p-retry: 8.0.1 + read: 5.0.1 + shell-quote: 1.12.0 + ssh2: 1.17.0 + tar-stream: 3.2.2 + write-file-atomic: 7.0.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + '@neon-rs/load@0.0.4': {} '@nodable/entities@2.1.1': {} @@ -1404,6 +1604,45 @@ snapshots: array-union@2.1.0: {} + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + + b4a@1.9.0: {} + + bare-events@2.9.2: {} + + bare-fs@4.8.4: + dependencies: + bare-events: 2.9.2 + bare-path: 3.1.2 + bare-stream: 2.13.4(bare-events@2.9.2) + bare-url: 2.5.5 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + bare-path@3.1.2: {} + + bare-stream@2.13.4(bare-events@2.9.2): + dependencies: + b4a: 1.9.0 + streamx: 2.28.1 + teex: 1.0.1 + optionalDependencies: + bare-events: 2.9.2 + transitivePeerDependencies: + - react-native-b4a + + bare-url@2.5.5: + dependencies: + bare-path: 3.1.2 + + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 @@ -1418,10 +1657,23 @@ snapshots: dependencies: fill-range: 7.1.1 + buildcheck@0.0.7: + optional: true + + bundle-name@4.1.1: + dependencies: + run-applescript: 7.1.0 + chardet@2.1.1: {} cluster-key-slot@1.1.1: {} + cpu-features@0.0.10: + dependencies: + buildcheck: 0.0.7 + nan: 2.30.0 + optional: true + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -1434,6 +1686,15 @@ snapshots: dependencies: ms: 2.1.3 + default-browser-id@5.0.1: {} + + default-browser@5.5.1: + dependencies: + bundle-name: 4.1.1 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + denque@2.1.0: {} detect-indent@6.1.0: {} @@ -1451,8 +1712,16 @@ snapshots: esprima@4.0.1: {} + events-universal@1.0.1: + dependencies: + bare-events: 2.9.2 + transitivePeerDependencies: + - bare-abort-controller + extendable-error@0.1.7: {} + fast-fifo@1.3.2: {} + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -1532,6 +1801,8 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.13: {} + import-meta-resolve@4.2.0: {} ioredis@5.11.1: @@ -1546,12 +1817,22 @@ snapshots: transitivePeerDependencies: - supports-color + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-in-ssh@1.0.0: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-network-error@1.3.2: {} + is-number@7.0.0: {} is-subdir@1.2.0: @@ -1560,6 +1841,10 @@ snapshots: is-windows@1.0.2: {} + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + isexe@2.0.0: {} js-base64@3.7.8: {} @@ -1609,6 +1894,11 @@ snapshots: ms@2.1.3: {} + mute-stream@3.0.0: {} + + nan@2.30.0: + optional: true + node-addon-api@8.9.2: {} node-domexception@1.0.0: {} @@ -1619,6 +1909,21 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 + open@11.0.4: + dependencies: + default-browser: 5.5.1 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.2.1 + wsl-utils: 1.0.1 + + openapi-fetch@0.17.0: + dependencies: + openapi-typescript-helpers: 0.1.0 + + openapi-typescript-helpers@0.1.0: {} + outdent@0.5.0: {} p-filter@2.1.0: @@ -1635,6 +1940,10 @@ snapshots: p-map@2.1.0: {} + p-retry@8.0.1: + dependencies: + is-network-error: 1.3.2 + p-try@2.2.0: {} package-manager-detector@0.2.11: @@ -1655,6 +1964,8 @@ snapshots: pify@4.0.1: {} + powershell-utils@0.2.1: {} + prettier@2.8.8: {} promise-limit@2.7.0: {} @@ -1670,6 +1981,10 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 + read@5.0.1: + dependencies: + mute-stream: 3.0.0 + redis-errors@1.2.0: {} redis-parser@3.0.0: @@ -1680,6 +1995,8 @@ snapshots: reusify@1.1.0: {} + run-applescript@7.1.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -1694,6 +2011,8 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.12.0: {} + signal-exit@4.1.0: {} slash@3.0.0: {} @@ -1728,8 +2047,25 @@ snapshots: sqlite-vec-linux-x64: 0.1.9 sqlite-vec-windows-x64: 0.1.9 + ssh2@1.17.0: + dependencies: + asn1: 0.2.6 + bcrypt-pbkdf: 1.0.2 + optionalDependencies: + cpu-features: 0.0.10 + nan: 2.30.0 + standard-as-callback@2.1.0: {} + streamx@2.28.1: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -1738,14 +2074,40 @@ snapshots: strnum@2.3.0: {} + tar-stream@3.2.2: + dependencies: + b4a: 1.9.0 + bare-fs: 4.8.4 + fast-fifo: 1.3.2 + streamx: 2.28.1 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + teex@1.0.1: + dependencies: + streamx: 2.28.1 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + term-size@2.2.1: {} + text-decoder@1.2.7: + dependencies: + b4a: 1.9.0 + transitivePeerDependencies: + - react-native-b4a + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 tslib@2.8.1: {} + tweetnacl@0.14.5: {} + typescript@5.9.3: {} undici-types@6.21.0: {} @@ -1758,6 +2120,15 @@ snapshots: dependencies: isexe: 2.0.0 + write-file-atomic@7.0.1: + dependencies: + signal-exit: 4.1.0 + ws@8.21.0: {} + wsl-utils@1.0.1: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.2.1 + xml-naming@0.1.0: {} From d901095781ee59d2a4f04a9a6305e9dd475b9227 Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Tue, 20 Oct 2026 10:40:56 -0400 Subject: [PATCH 2/5] fix(cli): report a missing wrangler instead of crashing; --version works without it --- .changeset/cli-missing-wrangler.md | 5 ++++ packages/cli/src/cloudflare.mjs | 14 +++++++++--- packages/cli/src/index.mjs | 7 ++++-- packages/cli/test/provider-core.test.mjs | 29 ++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 .changeset/cli-missing-wrangler.md diff --git a/.changeset/cli-missing-wrangler.md b/.changeset/cli-missing-wrangler.md new file mode 100644 index 0000000..d93a803 --- /dev/null +++ b/.changeset/cli-missing-wrangler.md @@ -0,0 +1,5 @@ +--- +"@mieweb/cli": patch +--- + +Don't crash with a stack trace when wrangler isn't installed: wrangler-delegated commands on the cloudflare target now print "wrangler binary not found…" and exit 1, and `mieweb --version` still prints the mieweb version (exit 0). diff --git a/packages/cli/src/cloudflare.mjs b/packages/cli/src/cloudflare.mjs index ab64716..2d88f84 100644 --- a/packages/cli/src/cloudflare.mjs +++ b/packages/cli/src/cloudflare.mjs @@ -9,11 +9,19 @@ import { runWrangler } from '@mieweb/deploy-wrangler'; * reference provider does (MIEWEB_REAL_WRANGLER, else the project's wrangler * via the active package manager). * + * Never throws: a missing wrangler (or a failed spawn) is reported on stderr + * and returned as exit code 1. + * * @param {string[]} args arguments to forward to wrangler * @param {{ cwd?: string }} [opts] - * @returns {Promise} wrangler's exit code (1 if killed by a signal) + * @returns {Promise} wrangler's exit code (1 if it couldn't run or was killed) */ export async function delegateToWrangler(args, opts = {}) { - const { code } = await runWrangler(args, { cwd: opts.cwd ?? process.cwd() }); - return code ?? 1; + try { + const { code } = await runWrangler(args, { cwd: opts.cwd ?? process.cwd() }); + return code ?? 1; + } catch (err) { + console.error(`mieweb: ${err instanceof Error ? err.message : String(err)}`); + return 1; + } } diff --git a/packages/cli/src/index.mjs b/packages/cli/src/index.mjs index 71de066..05a0bcc 100755 --- a/packages/cli/src/index.mjs +++ b/packages/cli/src/index.mjs @@ -67,7 +67,8 @@ async function main(argv) { // Version flags: always show the mieweb version first. On cloudflare we then // let wrangler print its own version below; other targets show mieweb only. - if (args[0] === '--version' || args[0] === '-v' || args[0] === '-V') { + const versionFlag = args[0] === '--version' || args[0] === '-v' || args[0] === '-V'; + if (versionFlag) { process.stdout.write(`mieweb ${miewebVersion()}\n`); } @@ -125,7 +126,9 @@ async function main(argv) { if (config.target === 'cloudflare') { // Reference path for non-provider commands: hand to wrangler untouched. - return delegateToWrangler(args, { cwd: config.root }); + const code = await delegateToWrangler(args, { cwd: config.root }); + // `--version` already printed mieweb's version; wrangler's is a bonus. + return versionFlag ? 0 : code; } // Node "host" targets run the unchanged worker via the host harness. `local` diff --git a/packages/cli/test/provider-core.test.mjs b/packages/cli/test/provider-core.test.mjs index 47f3770..aa27ac7 100644 --- a/packages/cli/test/provider-core.test.mjs +++ b/packages/cli/test/provider-core.test.mjs @@ -65,3 +65,32 @@ test('delegateToWrangler returns wrangler’s exit code', async (t) => { t.after(() => delete process.env.MIEWEB_REAL_WRANGLER); assert.equal(await delegateToWrangler(['d1', 'migrations', 'apply'], { cwd: dir }), 3); }); + +test('delegateToWrangler reports a missing wrangler instead of throwing', async (t) => { + const dir = tmp(t); + writeFileSync(join(dir, 'package.json'), '{"name":"app"}'); + const saved = process.env.MIEWEB_REAL_WRANGLER; + delete process.env.MIEWEB_REAL_WRANGLER; + t.after(() => { + if (saved !== undefined) process.env.MIEWEB_REAL_WRANGLER = saved; + }); + const errors = []; + t.mock.method(console, 'error', (m) => errors.push(m)); + assert.equal(await delegateToWrangler(['d1', 'list'], { cwd: dir }), 1); + assert.match(errors.join('\n'), /wrangler binary not found/); +}); + +test('`mieweb --version` works without wrangler installed', async (t) => { + const { execFileSync } = await import('node:child_process'); + const dir = tmp(t); + writeFileSync(join(dir, 'package.json'), '{"name":"app"}'); + const env = { ...process.env }; + delete env.MIEWEB_REAL_WRANGLER; + const out = execFileSync(process.execPath, [new URL('../src/index.mjs', import.meta.url).pathname, '--version'], { + cwd: dir, + env, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + }); + assert.match(out, /^mieweb \d+\.\d+\.\d+/); +}); From 522247e09d0f0e537de95b46a7a399d684ea70c7 Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Tue, 20 Oct 2026 10:52:47 -0400 Subject: [PATCH 3/5] fix(cli): mieweb --version exits 0 with just the version on non-cloudflare targets --- .changeset/cli-missing-wrangler.md | 2 +- packages/cli/src/index.mjs | 3 +++ packages/cli/test/provider-core.test.mjs | 13 +++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.changeset/cli-missing-wrangler.md b/.changeset/cli-missing-wrangler.md index d93a803..f8b28eb 100644 --- a/.changeset/cli-missing-wrangler.md +++ b/.changeset/cli-missing-wrangler.md @@ -2,4 +2,4 @@ "@mieweb/cli": patch --- -Don't crash with a stack trace when wrangler isn't installed: wrangler-delegated commands on the cloudflare target now print "wrangler binary not found…" and exit 1, and `mieweb --version` still prints the mieweb version (exit 0). +Don't crash with a stack trace when wrangler isn't installed: wrangler-delegated commands on the cloudflare target now print "wrangler binary not found…" and exit 1, and `mieweb --version` prints the mieweb version and exits 0 on every target (it no longer errors on non-cloudflare targets). diff --git a/packages/cli/src/index.mjs b/packages/cli/src/index.mjs index 05a0bcc..f0c1aef 100755 --- a/packages/cli/src/index.mjs +++ b/packages/cli/src/index.mjs @@ -80,6 +80,9 @@ async function main(argv) { const config = loadConfig({ overrideTarget }); + // Only cloudflare has a second version to show (wrangler's, below). + if (versionFlag && config.target !== 'cloudflare') return 0; + // Container image plumbing (build once, skopeo copy — container-plan.md M2). // `images push` on the cloudflare target delegates to wrangler's managed // registry; every other target goes through skopeo to its own registry. diff --git a/packages/cli/test/provider-core.test.mjs b/packages/cli/test/provider-core.test.mjs index aa27ac7..8d7c37e 100644 --- a/packages/cli/test/provider-core.test.mjs +++ b/packages/cli/test/provider-core.test.mjs @@ -94,3 +94,16 @@ test('`mieweb --version` works without wrangler installed', async (t) => { }); assert.match(out, /^mieweb \d+\.\d+\.\d+/); }); + +test('`mieweb --version` on a non-cloudflare target prints only the version', async (t) => { + const { execFileSync } = await import('node:child_process'); + const dir = tmp(t); + writeFileSync(join(dir, 'package.json'), '{"name":"app"}'); + writeFileSync(join(dir, 'mieweb.jsonc'), '{"target":"mieweb"}'); + const out = execFileSync(process.execPath, [new URL('../src/index.mjs', import.meta.url).pathname, '--version'], { + cwd: dir, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + }); + assert.match(out, /^mieweb \d+\.\d+\.\d+\n$/); +}); From c129e54e56046e427ba4194e318737c5a8e43e7a Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Tue, 20 Oct 2026 11:24:02 -0400 Subject: [PATCH 4/5] fix(cli): address review findings - engines: node >=22.18 (required by @mieweb/os-cloud-provider) - only dev/tail fall back to the legacy path when a provider lacks them; login/logout/whoami/destroy keep runProviderVerb's explanations - mieweb init: depend on this CLI's release and on @libsql/client + ioredis, which the generated mieweb bindings (and npm start on the cloud image) need - persistTargetConfig: refuse secret-looking keys anywhere in the patch, including nested objects/arrays and access-key names --- packages/cli/package.json | 2 +- packages/cli/src/index.mjs | 11 ++++-- packages/cli/src/init.mjs | 17 ++++++++-- packages/cli/src/provider.mjs | 34 ++++++++++++++++--- packages/cli/test/provider-core.test.mjs | 43 ++++++++++++++++++++++++ 5 files changed, 96 insertions(+), 11 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 2c92668..f12bce3 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -24,7 +24,7 @@ "serverless" ], "engines": { - "node": ">=22.16.0" + "node": ">=22.18.0" }, "bin": { "mieweb": "./src/index.mjs" diff --git a/packages/cli/src/index.mjs b/packages/cli/src/index.mjs index f0c1aef..5576447 100755 --- a/packages/cli/src/index.mjs +++ b/packages/cli/src/index.mjs @@ -28,6 +28,9 @@ import { runInit } from './init.mjs'; import { runImagesCommand, runRegistryCommand } from './images.mjs'; import { PROVIDER_VERBS, providerImplements, resolveProvider, runProviderVerb } from './provider.mjs'; +/** Provider verbs with a legacy implementation to fall back to. */ +const LEGACY_FALLBACK_VERBS = new Set(['dev', 'tail']); + /** Read this CLI's version from its package.json. */ function miewebVersion() { try { @@ -107,8 +110,10 @@ async function main(argv) { // PROVIDER_VERBS). Cloudflare resolves to the wrangler reference provider; // other targets can name a provider package in mieweb.jsonc // (`targets[t].provider`). Everything else (d1 migrations, etc.) and any - // target without a provider — or a verb the provider doesn't implement (e.g. - // `dev` on mieweb) — falls through to the legacy paths below. + // target without a provider — or a `dev`/`tail` the provider doesn't + // implement (e.g. `dev` on mieweb runs the host harness) — falls through to the + // legacy paths below. Other omitted verbs (login/logout/whoami/destroy) stay + // with runProviderVerb, which explains what the provider doesn't do. if (/** @type {readonly string[]} */ (PROVIDER_VERBS).includes(args[0])) { let provider = null; try { @@ -117,7 +122,7 @@ async function main(argv) { console.error(`mieweb: ${err?.message ?? err}`); return 1; } - if (provider && providerImplements(provider, args[0])) { + if (provider && (providerImplements(provider, args[0]) || !LEGACY_FALLBACK_VERBS.has(args[0]))) { return runProviderVerb( /** @type {import('./provider.mjs').ProviderVerb} */ (args[0]), provider, diff --git a/packages/cli/src/init.mjs b/packages/cli/src/init.mjs index 96bd3c5..88c0e8c 100644 --- a/packages/cli/src/init.mjs +++ b/packages/cli/src/init.mjs @@ -14,7 +14,7 @@ * prints next steps. It never installs deps or touches anything outside the * target directory. */ -import { mkdirSync, writeFileSync, existsSync, readdirSync } from 'node:fs'; +import { mkdirSync, writeFileSync, existsSync, readdirSync, readFileSync } from 'node:fs'; import { resolve, join, basename } from 'node:path'; /** @@ -88,6 +88,9 @@ function sanitizeName(name) { return cleaned || 'mieweb-app'; } +/** This CLI's version; scaffolds depend on the matching @mieweb release. */ +const CLI_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version; + function packageJson(name) { return ( JSON.stringify( @@ -104,9 +107,17 @@ function packageJson(name) { // the worker on the Node host harness with the networked adapters. start: 'mieweb --target mieweb dev', }, + // Runtime deps of `npm start` on the cloud image: the SDKs behind the + // generated `mieweb` bindings (libSQL, Valkey). cloud-adapters only + // lists them as optional peers, so the app has to install them. + dependencies: { + '@libsql/client': '^0.14.0', + ioredis: '^5.4.1', + }, + // All @mieweb packages release together, so pin both to this CLI's version. devDependencies: { - '@mieweb/cli': '^0.2.0', - '@mieweb/cloud': '^0.2.0', + '@mieweb/cli': `^${CLI_VERSION}`, + '@mieweb/cloud': `^${CLI_VERSION}`, }, }, null, diff --git a/packages/cli/src/provider.mjs b/packages/cli/src/provider.mjs index abaf87f..c2da9d6 100644 --- a/packages/cli/src/provider.mjs +++ b/packages/cli/src/provider.mjs @@ -389,10 +389,9 @@ export function providerImplements(provider, verb) { */ export async function persistTargetConfig(config, patch, logger) { const entries = Object.entries(patch ?? {}); - for (const [k] of entries) { - if (k === 'bindings' || /(secret|token|password|credential|apikey|privatekey)$/i.test(k)) { - throw new Error(`refusing to write "${k}" to mieweb.jsonc: secrets belong in the environment`); - } + const secret = secretKeyIn(patch); + if (secret) { + throw new Error(`refusing to write "${secret}" to mieweb.jsonc: secrets belong in the environment`); } if (entries.length === 0) return false; @@ -412,3 +411,30 @@ export async function persistTargetConfig(config, patch, logger) { ); return true; } + +/** Key names that look like credentials (matched anywhere in the name). */ +const SECRET_KEY = /secret|token|passw|credential|api[-_]?key|access[-_]?key|private[-_]?key/i; + +/** + * The path of the first secret-looking key in `value` (searched recursively, + * including arrays), or null. `bindings` counts too: it holds driver config. + * @param {unknown} value + * @param {string} [path] + * @returns {string|null} + */ +function secretKeyIn(value, path = '') { + if (Array.isArray(value)) { + for (const [i, v] of value.entries()) { + const hit = secretKeyIn(v, `${path}[${i}]`); + if (hit) return hit; + } + } else if (value && typeof value === 'object') { + for (const [k, v] of Object.entries(value)) { + const p = path ? `${path}.${k}` : k; + if (k === 'bindings' || SECRET_KEY.test(k)) return p; + const hit = secretKeyIn(v, p); + if (hit) return hit; + } + } + return null; +} diff --git a/packages/cli/test/provider-core.test.mjs b/packages/cli/test/provider-core.test.mjs index 8d7c37e..292c7b9 100644 --- a/packages/cli/test/provider-core.test.mjs +++ b/packages/cli/test/provider-core.test.mjs @@ -107,3 +107,46 @@ test('`mieweb --version` on a non-cloudflare target prints only the version', as }); assert.match(out, /^mieweb \d+\.\d+\.\d+\n$/); }); + +test('a provider without login/logout/whoami gets the built-in explanation, not the legacy path', async (t) => { + const { spawnSync } = await import('node:child_process'); + const dir = tmp(t); + writeFileSync(join(dir, 'package.json'), '{"name":"app","type":"module"}'); + writeFileSync(join(dir, 'wrangler.jsonc'), '{"name":"app","main":"w.mjs"}'); + writeFileSync(join(dir, 'p.mjs'), "export default { name: 'envonly', supports: () => true, deploy: async () => ({ resources: [] }) };"); + writeFileSync(join(dir, 'mieweb.jsonc'), '{"target":"custom","targets":{"custom":{"provider":"./p.mjs"}}}'); + const cli = new URL('../src/index.mjs', import.meta.url).pathname; + for (const verb of ['login', 'logout', 'whoami']) { + const r = spawnSync(process.execPath, [cli, verb], { cwd: dir, encoding: 'utf8' }); + assert.equal(r.status, 0, `${verb}: ${r.stderr}`); + assert.match(r.stderr + r.stdout, /envonly/, verb); + } +}); + +test('persistTargetConfig refuses nested and access-key style secrets', async (t) => { + const { persistTargetConfig } = await import('../src/provider.mjs'); + const dir = tmp(t); + const config = { root: dir, target: 'mieweb', configPath: join(dir, 'mieweb.jsonc') }; + const logger = { info() {}, warn() {}, error() {} }; + for (const patch of [{ secretAccessKey: 'x' }, { manager: { apiToken: 'x' } }, { list: [{ 'x-api-key': 'x' }] }]) { + await assert.rejects(persistTargetConfig(config, patch, logger), /secrets belong in the environment/); + } + assert.equal(await persistTargetConfig(config, { authRequired: true }, logger), true); +}); + +test('mieweb init depends on this CLI release and the SDKs its mieweb bindings use', async (t) => { + const { readFileSync } = await import('node:fs'); + const { runInit } = await import('../src/init.mjs'); + const dir = join(tmp(t), 'app'); + const log = t.mock.method(console, 'log', () => {}); + await runInit([dir]); + log.mock.restore(); + const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')); + const cli = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')); + const adapters = JSON.parse(readFileSync(new URL('../../cloud-adapters/package.json', import.meta.url), 'utf8')); + assert.equal(pkg.devDependencies['@mieweb/cli'], `^${cli.version}`); + assert.equal(pkg.devDependencies['@mieweb/cloud'], `^${cli.version}`); + for (const sdk of ['@libsql/client', 'ioredis']) { + assert.equal(pkg.dependencies[sdk], adapters.peerDependencies[sdk], sdk); + } +}); From 1fae403ffb5f2a3c19a7850da16ef2f36c60649b Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Tue, 20 Oct 2026 11:34:51 -0400 Subject: [PATCH 5/5] fix(cli): mieweb init lists @mieweb/cli and @mieweb/cloud as runtime dependencies (npm start runs mieweb) --- packages/cli/src/init.mjs | 13 ++++++------- packages/cli/test/provider-core.test.mjs | 6 ++++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/cli/src/init.mjs b/packages/cli/src/init.mjs index 88c0e8c..fb77bf1 100644 --- a/packages/cli/src/init.mjs +++ b/packages/cli/src/init.mjs @@ -107,17 +107,16 @@ function packageJson(name) { // the worker on the Node host harness with the networked adapters. start: 'mieweb --target mieweb dev', }, - // Runtime deps of `npm start` on the cloud image: the SDKs behind the - // generated `mieweb` bindings (libSQL, Valkey). cloud-adapters only - // lists them as optional peers, so the app has to install them. + // Runtime dependencies: `npm start` (what the os.mieweb.org cloud image + // runs) needs the CLI, the layer, and the SDKs behind the generated + // `mieweb` bindings (libSQL, Valkey; cloud-adapters only lists them as + // optional peers). All @mieweb packages release together, so they + // track this CLI's version. dependencies: { '@libsql/client': '^0.14.0', - ioredis: '^5.4.1', - }, - // All @mieweb packages release together, so pin both to this CLI's version. - devDependencies: { '@mieweb/cli': `^${CLI_VERSION}`, '@mieweb/cloud': `^${CLI_VERSION}`, + ioredis: '^5.4.1', }, }, null, diff --git a/packages/cli/test/provider-core.test.mjs b/packages/cli/test/provider-core.test.mjs index 292c7b9..8002a66 100644 --- a/packages/cli/test/provider-core.test.mjs +++ b/packages/cli/test/provider-core.test.mjs @@ -144,8 +144,10 @@ test('mieweb init depends on this CLI release and the SDKs its mieweb bindings u const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')); const cli = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')); const adapters = JSON.parse(readFileSync(new URL('../../cloud-adapters/package.json', import.meta.url), 'utf8')); - assert.equal(pkg.devDependencies['@mieweb/cli'], `^${cli.version}`); - assert.equal(pkg.devDependencies['@mieweb/cloud'], `^${cli.version}`); + // Runtime dependencies, not devDependencies: `npm start` runs `mieweb`. + assert.equal(pkg.dependencies['@mieweb/cli'], `^${cli.version}`); + assert.equal(pkg.dependencies['@mieweb/cloud'], `^${cli.version}`); + assert.equal(pkg.devDependencies, undefined); for (const sdk of ['@libsql/client', 'ioredis']) { assert.equal(pkg.dependencies[sdk], adapters.peerDependencies[sdk], sdk); }