Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cli-missing-wrangler.md
Original file line number Diff line number Diff line change
@@ -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` prints the mieweb version and exits 0 on every target (it no longer errors on non-cloudflare targets).
7 changes: 7 additions & 0 deletions .changeset/cli-os-cloud-provider.md
Original file line number Diff line number Diff line change
@@ -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.
6 changes: 6 additions & 0 deletions .changeset/init-start-script.md
Original file line number Diff line number Diff line change
@@ -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.
140 changes: 0 additions & 140 deletions .github/workflows/pr-preview.yml

This file was deleted.

10 changes: 7 additions & 3 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/mieweb-config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"serverless"
],
"engines": {
"node": ">=22.16.0"
"node": ">=22.18.0"
},
"bin": {
"mieweb": "./src/index.mjs"
Expand All @@ -33,6 +33,7 @@
"@mieweb/cloud-adapters": "workspace:*",
"@mieweb/deploy-contract": "workspace:*",
"@mieweb/deploy-wrangler": "workspace:*",
"@mieweb/os-cloud-provider": "^2026.10.3",
Comment thread
runleveldev marked this conversation as resolved.
Resolved
"import-meta-resolve": "^4.2.0",
"jsonc-parser": "^3.3.1"
},
Expand Down
14 changes: 11 additions & 3 deletions packages/cli/src/cloudflare.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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<number>} wrangler's exit code (1 if killed by a signal)
* @returns {Promise<number>} 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;
}
}
22 changes: 16 additions & 6 deletions packages/cli/src/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ 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';
import { PROVIDER_VERBS, providerImplements, resolveProvider, runProviderVerb } from './provider.mjs';

/** Verbs handled by the deploy-contract provider layer. */
/** 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() {
Expand Down Expand Up @@ -69,7 +70,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`);
}

Expand All @@ -81,6 +83,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.
Expand All @@ -105,7 +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 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 {
Expand All @@ -114,7 +122,7 @@ async function main(argv) {
console.error(`mieweb: ${err?.message ?? err}`);
return 1;
}
if (provider) {
if (provider && (providerImplements(provider, args[0]) || !LEGACY_FALLBACK_VERBS.has(args[0]))) {
return runProviderVerb(
/** @type {import('./provider.mjs').ProviderVerb} */ (args[0]),
provider,
Expand All @@ -126,7 +134,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`
Expand Down
23 changes: 18 additions & 5 deletions packages/cli/src/init.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
* 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
* 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';

/**
Expand Down Expand Up @@ -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(
Expand All @@ -100,10 +103,20 @@ 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',
'@mieweb/cloud': '^0.2.0',
// 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',
'@mieweb/cli': `^${CLI_VERSION}`,
'@mieweb/cloud': `^${CLI_VERSION}`,
ioredis: '^5.4.1',
},
},
null,
Expand Down
Loading
Loading