@moku-labs/worker 0.8.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{cli-DNW8_355.mjs → cli--EPl98vG.mjs} +389 -78
- package/dist/{cli-l-AOWzhR.cjs → cli-imQGo0tc.cjs} +389 -78
- package/dist/cli.cjs +1 -1
- package/dist/cli.d.cts +1 -1
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/{index-BDkgen4r.d.cts → index-CWxQr2Q3.d.cts} +91 -18
- package/dist/{index-BDkgen4r.d.mts → index-CWxQr2Q3.d.mts} +91 -18
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
|
@@ -1631,6 +1631,76 @@ const runWranglerInherit = (args) => {
|
|
|
1631
1631
|
});
|
|
1632
1632
|
};
|
|
1633
1633
|
//#endregion
|
|
1634
|
+
//#region src/plugins/deploy/seed.ts
|
|
1635
|
+
/**
|
|
1636
|
+
* @file deploy plugin — shared D1 seed helpers (resolve the target db, run a configured seed).
|
|
1637
|
+
*
|
|
1638
|
+
* Pure orchestration over an INJECTED wrangler runner, so the post-deploy REMOTE seed (api.ts) and
|
|
1639
|
+
* the dev-session LOCAL seed (dev/runner.ts) stay in lockstep — same file, same KV-reset semantics,
|
|
1640
|
+
* differing only in the `--remote` / `--local` scope. Migrations are NOT applied here: each caller
|
|
1641
|
+
* applies the schema first (the deploy's migration step / dev's local-migrate step), then seeds.
|
|
1642
|
+
* Node-only; never imported by the runtime Worker bundle.
|
|
1643
|
+
*/
|
|
1644
|
+
/**
|
|
1645
|
+
* Resolve the single configured d1 database (or the one bound to `binding` when several exist) from
|
|
1646
|
+
* the d1 plugin's manifest. The shared resolver behind `seed()`, the post-deploy seed, and the dev
|
|
1647
|
+
* seed; throws a branded error when the choice is ambiguous (none/several, no binding) or unknown.
|
|
1648
|
+
*
|
|
1649
|
+
* @param ctx - The deploy plugin context.
|
|
1650
|
+
* @param binding - The d1 binding to target when more than one is configured; the sole one otherwise.
|
|
1651
|
+
* @returns The resolved d1 resource descriptor (its binding + optional migrations dir).
|
|
1652
|
+
* @throws {Error} When no single database resolves (none/several without a binding, or unknown binding).
|
|
1653
|
+
* @example
|
|
1654
|
+
* ```ts
|
|
1655
|
+
* const db = resolveD1(ctx, "DB");
|
|
1656
|
+
* ```
|
|
1657
|
+
*/
|
|
1658
|
+
const resolveD1 = (ctx, binding) => {
|
|
1659
|
+
const databases = ctx.require(d1Plugin).deployManifest();
|
|
1660
|
+
const matched = binding === void 0 ? databases : databases.filter((db) => db.binding === binding);
|
|
1661
|
+
const target = matched.length === 1 ? matched[0] : void 0;
|
|
1662
|
+
if (target === void 0) throw new Error(binding === void 0 ? `[moku-worker] seed: ${String(databases.length)} d1 databases configured — pass { binding } to choose one.` : `[moku-worker] seed: no d1 database is bound to "${binding}".`);
|
|
1663
|
+
return target;
|
|
1664
|
+
};
|
|
1665
|
+
/**
|
|
1666
|
+
* Run a configured seed against one scope: execute the seed SQL against the d1 database, then delete
|
|
1667
|
+
* each configured cached KV key so the next read rebuilds it from the freshly-seeded rows. The
|
|
1668
|
+
* schema is assumed to exist (the caller applies migrations first), so this never migrates. The
|
|
1669
|
+
* wrangler runner is injected so the same orchestration serves the streamed deploy path and the
|
|
1670
|
+
* injectable dev path.
|
|
1671
|
+
*
|
|
1672
|
+
* @param ctx - The deploy plugin context.
|
|
1673
|
+
* @param run - The wrangler runner to execute each command through.
|
|
1674
|
+
* @param seed - The resolved seed config (SQL file, optional binding, KV keys to reset).
|
|
1675
|
+
* @param scope - The wrangler scope: `--remote` (deploy) or `--local` (dev).
|
|
1676
|
+
* @returns Resolves once the seed file has executed and every cached KV key is cleared.
|
|
1677
|
+
* @throws {Error} When no d1 database is configured, or the seed's binding cannot be resolved.
|
|
1678
|
+
* @example
|
|
1679
|
+
* ```ts
|
|
1680
|
+
* await runConfiguredSeed(ctx, runWranglerInherit, ctx.config.seed, "--remote");
|
|
1681
|
+
* ```
|
|
1682
|
+
*/
|
|
1683
|
+
const runConfiguredSeed = async (ctx, run, seed, scope) => {
|
|
1684
|
+
if (!ctx.has("d1")) throw new Error("[moku-worker] seed: no d1 database is configured.");
|
|
1685
|
+
await run([
|
|
1686
|
+
"d1",
|
|
1687
|
+
"execute",
|
|
1688
|
+
resolveD1(ctx, seed.binding).binding,
|
|
1689
|
+
scope,
|
|
1690
|
+
"--file",
|
|
1691
|
+
seed.file
|
|
1692
|
+
]);
|
|
1693
|
+
for (const entry of seed.resetKv ?? []) await run([
|
|
1694
|
+
"kv",
|
|
1695
|
+
"key",
|
|
1696
|
+
"delete",
|
|
1697
|
+
entry.key,
|
|
1698
|
+
"--binding",
|
|
1699
|
+
entry.binding,
|
|
1700
|
+
scope
|
|
1701
|
+
]);
|
|
1702
|
+
};
|
|
1703
|
+
//#endregion
|
|
1634
1704
|
//#region src/plugins/deploy/dev/build.ts
|
|
1635
1705
|
/**
|
|
1636
1706
|
* @file deploy plugin — dev site-rebuild resolution.
|
|
@@ -1953,8 +2023,32 @@ const rebuild = async (ctx, deps, changedPaths, hooks) => {
|
|
|
1953
2023
|
}
|
|
1954
2024
|
};
|
|
1955
2025
|
/**
|
|
1956
|
-
*
|
|
1957
|
-
*
|
|
2026
|
+
* Load the configured seed into the LOCAL D1 for a `dev --seed` session: execute the SQL file, then
|
|
2027
|
+
* clear the configured cached KV keys so the app rebuilds them from the freshly-seeded rows. The
|
|
2028
|
+
* schema already exists (the migrate step above runs first), so this never migrates — the local
|
|
2029
|
+
* analogue of the deploy's remote seed, over the same `pluginConfigs.deploy.seed` config.
|
|
2030
|
+
*
|
|
2031
|
+
* @param ctx - The deploy plugin context.
|
|
2032
|
+
* @param deps - The injected dev deps (for the wrangler runner).
|
|
2033
|
+
* @returns Resolves once the seed file has executed and every cached KV key is cleared.
|
|
2034
|
+
* @throws {Error} When `--seed` is set but no seed is configured under `pluginConfigs.deploy.seed`.
|
|
2035
|
+
* @example
|
|
2036
|
+
* ```ts
|
|
2037
|
+
* await seedLocal(ctx, realDevDeps());
|
|
2038
|
+
* ```
|
|
2039
|
+
*/
|
|
2040
|
+
const seedLocal = async (ctx, deps) => {
|
|
2041
|
+
const config = ctx.config.seed;
|
|
2042
|
+
if (config === void 0) throw new Error("[moku-worker] dev({ seed: true }) but no seed is configured — set pluginConfigs.deploy.seed.");
|
|
2043
|
+
ctx.emit("dev:phase", {
|
|
2044
|
+
phase: "seed",
|
|
2045
|
+
detail: config.file
|
|
2046
|
+
});
|
|
2047
|
+
await runConfiguredSeed(ctx, deps.runWrangler, config, "--local");
|
|
2048
|
+
};
|
|
2049
|
+
/**
|
|
2050
|
+
* Run a long-lived dev session: cold build → (local d1 migrate) → (local seed) → spawn `wrangler
|
|
2051
|
+
* dev` → watch + rebuild on change → teardown on signal.
|
|
1958
2052
|
*
|
|
1959
2053
|
* @param ctx - The deploy plugin context (config + emit + require/has).
|
|
1960
2054
|
* @param opts - Optional options.
|
|
@@ -1962,23 +2056,25 @@ const rebuild = async (ctx, deps, changedPaths, hooks) => {
|
|
|
1962
2056
|
* @param opts.webBuild - Cold-build hook (also the per-change rebuild when `onChange` is omitted).
|
|
1963
2057
|
* @param opts.onChange - Incremental per-change rebuild hook (e.g. `c => web.cli.update(c)`); when
|
|
1964
2058
|
* set, each debounced change rebuilds only the changed paths instead of a full `webBuild()`.
|
|
2059
|
+
* @param opts.seed - Load the configured seed into the LOCAL D1 (+ reset its KV keys) before serving.
|
|
1965
2060
|
* @param deps - Injected side effects (real ones from realDevDeps in production).
|
|
1966
2061
|
* @returns Resolves when the session ends (SIGINT).
|
|
1967
2062
|
* @example
|
|
1968
2063
|
* ```ts
|
|
1969
|
-
* await runDev(ctx, { port: 8787, webBuild: () => web.cli.build()
|
|
2064
|
+
* await runDev(ctx, { port: 8787, seed: true, webBuild: () => web.cli.build() }, realDevDeps());
|
|
1970
2065
|
* ```
|
|
1971
2066
|
*/
|
|
1972
2067
|
const runDev = async (ctx, opts, deps) => {
|
|
1973
2068
|
const port = opts?.port ?? 8787;
|
|
1974
2069
|
const webBuild = opts?.webBuild;
|
|
1975
2070
|
const onChange = opts?.onChange;
|
|
2071
|
+
const seed = opts?.seed === true;
|
|
1976
2072
|
ctx.emit("dev:phase", {
|
|
1977
2073
|
phase: "build",
|
|
1978
2074
|
detail: "site"
|
|
1979
2075
|
});
|
|
1980
2076
|
await deps.build(ctx, webBuild);
|
|
1981
|
-
const migrationBindings = ctx.config.migrateLocal ? d1MigrationBindings(ctx) : [];
|
|
2077
|
+
const migrationBindings = ctx.config.migrateLocal || seed ? d1MigrationBindings(ctx) : [];
|
|
1982
2078
|
if (migrationBindings.length > 0) {
|
|
1983
2079
|
ctx.emit("dev:phase", {
|
|
1984
2080
|
phase: "migrate",
|
|
@@ -1992,6 +2088,7 @@ const runDev = async (ctx, opts, deps) => {
|
|
|
1992
2088
|
"--local"
|
|
1993
2089
|
]);
|
|
1994
2090
|
}
|
|
2091
|
+
if (seed) await seedLocal(ctx, deps);
|
|
1995
2092
|
ctx.emit("dev:phase", {
|
|
1996
2093
|
phase: "serve",
|
|
1997
2094
|
detail: `http://localhost:${String(port)}`
|
|
@@ -2016,12 +2113,12 @@ const runDev = async (ctx, opts, deps) => {
|
|
|
2016
2113
|
//#endregion
|
|
2017
2114
|
//#region src/plugins/deploy/infra/plan.ts
|
|
2018
2115
|
/**
|
|
2019
|
-
* Decide whether a single
|
|
2020
|
-
* (kv/d1) when it does. Durable Objects are
|
|
2021
|
-
* deploy` + the auto-derived DO migration create the namespace), never provisioned via the API
|
|
2022
|
-
*
|
|
2116
|
+
* Decide whether a single API-provisioned resource already exists in the account, recovering its id
|
|
2117
|
+
* (kv/d1) when it does. Durable Objects are NOT handled here — they ship with the Worker (`wrangler
|
|
2118
|
+
* deploy` + the auto-derived DO migration create the namespace), are never provisioned via the API,
|
|
2119
|
+
* and are partitioned into the plan's `ships` bucket by {@link planInfra} before this is ever called.
|
|
2023
2120
|
*
|
|
2024
|
-
* @param resource - The declared resource descriptor.
|
|
2121
|
+
* @param resource - The declared (provisionable) resource descriptor.
|
|
2025
2122
|
* @param existing - The indexed set of resources already in the account.
|
|
2026
2123
|
* @returns Whether it exists, plus the captured id for kv/d1.
|
|
2027
2124
|
* @example
|
|
@@ -2047,7 +2144,6 @@ const checkExisting = (resource, existing) => {
|
|
|
2047
2144
|
}
|
|
2048
2145
|
case "r2": return { exists: existing.r2.has(resource.name) };
|
|
2049
2146
|
case "queue": return { exists: existing.queue.has(resource.name) };
|
|
2050
|
-
case "do": return { exists: true };
|
|
2051
2147
|
}
|
|
2052
2148
|
};
|
|
2053
2149
|
/**
|
|
@@ -2056,7 +2152,7 @@ const checkExisting = (resource, existing) => {
|
|
|
2056
2152
|
*
|
|
2057
2153
|
* @param ctx - The deploy plugin context (env + emit).
|
|
2058
2154
|
* @param manifest - The assembled (or caller-supplied) deploy manifest.
|
|
2059
|
-
* @returns The infra plan: existing (with ids) vs missing
|
|
2155
|
+
* @returns The infra plan: existing (with ids) vs missing vs ships-with-Worker (Durable Objects).
|
|
2060
2156
|
* @throws {Error} When the token is absent/invalid or a Cloudflare listing fails.
|
|
2061
2157
|
* @example
|
|
2062
2158
|
* ```ts
|
|
@@ -2075,7 +2171,12 @@ const planInfra = async (ctx, manifest) => {
|
|
|
2075
2171
|
const existing = await listExisting(token, account.id, kinds);
|
|
2076
2172
|
const exists = [];
|
|
2077
2173
|
const missing = [];
|
|
2174
|
+
const ships = [];
|
|
2078
2175
|
for (const resource of manifest.resources) {
|
|
2176
|
+
if (resource.kind === "do") {
|
|
2177
|
+
ships.push(resource);
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2079
2180
|
const check = checkExisting(resource, existing);
|
|
2080
2181
|
if (check.exists) exists.push(check.id === void 0 ? { resource } : {
|
|
2081
2182
|
resource,
|
|
@@ -2086,13 +2187,15 @@ const planInfra = async (ctx, manifest) => {
|
|
|
2086
2187
|
ctx.emit("provision:plan", {
|
|
2087
2188
|
exists: exists.length,
|
|
2088
2189
|
missing: missing.length,
|
|
2190
|
+
ships: ships.length,
|
|
2089
2191
|
account: account.name
|
|
2090
2192
|
});
|
|
2091
2193
|
return {
|
|
2092
2194
|
account: account.name,
|
|
2093
2195
|
accountId: account.id,
|
|
2094
2196
|
exists,
|
|
2095
|
-
missing
|
|
2197
|
+
missing,
|
|
2198
|
+
ships
|
|
2096
2199
|
};
|
|
2097
2200
|
};
|
|
2098
2201
|
//#endregion
|
|
@@ -2123,6 +2226,12 @@ const resourceName = (resource) => resource.kind === "do" ? resource.className :
|
|
|
2123
2226
|
*/
|
|
2124
2227
|
const cell = (kind, name) => `${kind.padEnd(6)}${name}`;
|
|
2125
2228
|
/**
|
|
2229
|
+
* Row tag for a Durable Object — it ships with the Worker (`wrangler deploy` creates the namespace),
|
|
2230
|
+
* so it is NEVER labelled `(exists)` (the planner never queried the account for it). Shared by the
|
|
2231
|
+
* plan and provision-result panels so the two always read the same.
|
|
2232
|
+
*/
|
|
2233
|
+
const SHIPS_WITH_WORKER = "(ships with worker)";
|
|
2234
|
+
/**
|
|
2126
2235
|
* ANSI SGR matcher — built from `String.fromCharCode(27)` (the ESC byte) so no control character
|
|
2127
2236
|
* appears in a regex literal (which both linters reject).
|
|
2128
2237
|
*/
|
|
@@ -2183,10 +2292,11 @@ const wrapText = (text, width) => {
|
|
|
2183
2292
|
/**
|
|
2184
2293
|
* Render the infra preflight plan as a branded panel: a dim summary line (counts + account) then one
|
|
2185
2294
|
* row per declared resource — a pink `+` for those to create, a dim `~ (exists)` for those already
|
|
2186
|
-
* present
|
|
2295
|
+
* present, and a dim `~ (ships with worker)` for Durable Objects (created by `wrangler deploy`, never
|
|
2296
|
+
* pre-provisioned). When nothing needs creating it still renders, so the user sees the full picture.
|
|
2187
2297
|
*
|
|
2188
2298
|
* @param ui - The branded console to render through.
|
|
2189
|
-
* @param plan - The infra plan (existing vs missing) from checkInfra()/planInfra().
|
|
2299
|
+
* @param plan - The infra plan (existing vs missing vs ships-with-Worker) from checkInfra()/planInfra().
|
|
2190
2300
|
* @example
|
|
2191
2301
|
* ```ts
|
|
2192
2302
|
* renderPlan(ui, await planInfra(ctx, manifest));
|
|
@@ -2194,22 +2304,27 @@ const wrapText = (text, width) => {
|
|
|
2194
2304
|
*/
|
|
2195
2305
|
const renderPlan = (ui, plan) => {
|
|
2196
2306
|
const { palette } = ui;
|
|
2197
|
-
const
|
|
2198
|
-
|
|
2307
|
+
const counts = [`${String(plan.missing.length)} to create`, `${String(plan.exists.length)} exist`];
|
|
2308
|
+
if (plan.ships.length > 0) counts.push(`${String(plan.ships.length)} with worker`);
|
|
2309
|
+
const summary = palette.dim(`${counts.join(" · ")} · ${plan.account}`);
|
|
2199
2310
|
const createRows = plan.missing.map((resource) => `${palette.pink("+")} ${cell(resource.kind, resourceName(resource))}`);
|
|
2311
|
+
const existsRows = plan.exists.map((ref) => `${palette.dim("~")} ${cell(ref.resource.kind, resourceName(ref.resource))} ${palette.dim("(exists)")}`);
|
|
2312
|
+
const shipsRows = plan.ships.map((resource) => `${palette.dim("~")} ${cell(resource.kind, resourceName(resource))} ${palette.dim(SHIPS_WITH_WORKER)}`);
|
|
2200
2313
|
ui.heading("Infra plan");
|
|
2201
2314
|
ui.box([
|
|
2202
2315
|
summary,
|
|
2203
2316
|
"",
|
|
2204
2317
|
...createRows,
|
|
2205
|
-
...existsRows
|
|
2318
|
+
...existsRows,
|
|
2319
|
+
...shipsRows
|
|
2206
2320
|
]);
|
|
2207
2321
|
};
|
|
2208
2322
|
/**
|
|
2209
2323
|
* Render the provision result as a branded panel — a green `✓` per created resource, a dim `~` per
|
|
2210
|
-
* skipped, a red `✗` per failure, then a summary
|
|
2211
|
-
* when anything failed, by a detail block printing
|
|
2212
|
-
* word-wrapped) so it is actually readable instead of
|
|
2324
|
+
* skipped, a dim `~ (ships with worker)` per Durable Object, a red `✗` per failure, then a summary
|
|
2325
|
+
* line (failed count red when non-zero) — followed, when anything failed, by a detail block printing
|
|
2326
|
+
* each failure's FULL reason (ANSI-stripped and word-wrapped) so it is actually readable instead of
|
|
2327
|
+
* truncated inside the box.
|
|
2213
2328
|
*
|
|
2214
2329
|
* @param ui - The branded console to render through.
|
|
2215
2330
|
* @param result - The provision result from provisionInfra()/the deploy pipeline.
|
|
@@ -2222,13 +2337,17 @@ const renderProvisionResult = (ui, result) => {
|
|
|
2222
2337
|
const { palette } = ui;
|
|
2223
2338
|
const createdRows = result.created.map((ref) => `${palette.green("✓")} ${cell(ref.resource.kind, resourceName(ref.resource))}`);
|
|
2224
2339
|
const skippedRows = result.skipped.map((ref) => `${palette.dim("~")} ${cell(ref.resource.kind, resourceName(ref.resource))} ${palette.dim("(exists)")}`);
|
|
2340
|
+
const bundledRows = result.bundled.map((resource) => `${palette.dim("~")} ${cell(resource.kind, resourceName(resource))} ${palette.dim(SHIPS_WITH_WORKER)}`);
|
|
2225
2341
|
const failedRows = result.failed.map((failure) => `${palette.red("✗")} ${cell(failure.resource.kind, resourceName(failure.resource))}`);
|
|
2226
2342
|
const failedCount = result.failed.length > 0 ? palette.red(`${String(result.failed.length)} failed`) : "0 failed";
|
|
2227
|
-
const
|
|
2343
|
+
const counts = [`${String(result.created.length)} created`, `${String(result.skipped.length)} exist`];
|
|
2344
|
+
if (result.bundled.length > 0) counts.push(`${String(result.bundled.length)} with worker`);
|
|
2345
|
+
const summary = `${counts.join(" · ")} · ${failedCount}`;
|
|
2228
2346
|
ui.heading("Provisioned");
|
|
2229
2347
|
ui.box([
|
|
2230
2348
|
...createdRows,
|
|
2231
2349
|
...skippedRows,
|
|
2350
|
+
...bundledRows,
|
|
2232
2351
|
...failedRows,
|
|
2233
2352
|
"",
|
|
2234
2353
|
summary
|
|
@@ -2271,16 +2390,19 @@ const formatDuration = (ms) => {
|
|
|
2271
2390
|
* @param summary.stage - The target stage the worker deployed to.
|
|
2272
2391
|
* @param summary.created - How many resources were created this run.
|
|
2273
2392
|
* @param summary.exists - How many resources already existed (skipped).
|
|
2393
|
+
* @param summary.bundled - How many Durable Objects shipped with the Worker.
|
|
2274
2394
|
* @param summary.failed - How many resources failed to provision.
|
|
2275
2395
|
* @param summary.elapsedMs - The wall-clock deploy duration in milliseconds.
|
|
2276
2396
|
* @example
|
|
2277
2397
|
* ```ts
|
|
2278
|
-
* renderDeploySummary(ui, { url, stage: "production", created: 0, exists: 5, failed: 0, elapsedMs: 4234 });
|
|
2398
|
+
* renderDeploySummary(ui, { url, stage: "production", created: 0, exists: 5, bundled: 1, failed: 0, elapsedMs: 4234 });
|
|
2279
2399
|
* ```
|
|
2280
2400
|
*/
|
|
2281
2401
|
const renderDeploySummary = (ui, summary) => {
|
|
2282
2402
|
const { palette } = ui;
|
|
2283
|
-
const
|
|
2403
|
+
const parts = [`${String(summary.exists)} exist`, `${String(summary.created)} created`];
|
|
2404
|
+
if (summary.bundled > 0) parts.push(`${String(summary.bundled)} with worker`);
|
|
2405
|
+
const tally = parts.join(" · ");
|
|
2284
2406
|
const failedLabel = palette.red(`${String(summary.failed)} failed`);
|
|
2285
2407
|
const resources = summary.failed > 0 ? `${tally} · ${failedLabel}` : tally;
|
|
2286
2408
|
ui.heading("Deployed");
|
|
@@ -2912,32 +3034,25 @@ const assembleManifest = (ctx, stage) => {
|
|
|
2912
3034
|
};
|
|
2913
3035
|
};
|
|
2914
3036
|
/**
|
|
2915
|
-
*
|
|
2916
|
-
*
|
|
2917
|
-
*
|
|
2918
|
-
*
|
|
3037
|
+
* Create the still-missing resources one at a time: provision each, fold its captured id (kv/d1) into
|
|
3038
|
+
* the shared `ids` map, and announce it via provision:resource. Resilient — a single failure is
|
|
3039
|
+
* CAPTURED (not thrown), so one bad resource never aborts the rest. Extracted from {@link applyPlan}
|
|
3040
|
+
* so that orchestrator stays flat (skip existing, skip DOs, create missing).
|
|
2919
3041
|
*
|
|
2920
3042
|
* @param ctx - The deploy plugin context.
|
|
2921
|
-
* @param
|
|
3043
|
+
* @param missing - The resources the plan flagged as not-yet-existing.
|
|
2922
3044
|
* @param ci - Whether provisioning runs non-interactively (forwarded to each provider).
|
|
2923
|
-
* @
|
|
3045
|
+
* @param ids - The binding → Cloudflare id map, mutated in place with each created kv/d1 id.
|
|
3046
|
+
* @returns The created refs and any captured per-resource failures.
|
|
2924
3047
|
* @example
|
|
2925
3048
|
* ```ts
|
|
2926
|
-
* const { created, failed } = await
|
|
3049
|
+
* const { created, failed } = await provisionMissing(ctx, plan.missing, false, ids);
|
|
2927
3050
|
* ```
|
|
2928
3051
|
*/
|
|
2929
|
-
const
|
|
2930
|
-
const ids = {};
|
|
2931
|
-
for (const ref of plan.exists) {
|
|
2932
|
-
if (ref.id !== void 0 && (ref.resource.kind === "kv" || ref.resource.kind === "d1")) ids[ref.resource.binding] = ref.id;
|
|
2933
|
-
ctx.emit("provision:skip", {
|
|
2934
|
-
kind: ref.resource.kind,
|
|
2935
|
-
name: resourceName(ref.resource)
|
|
2936
|
-
});
|
|
2937
|
-
}
|
|
3052
|
+
const provisionMissing = async (ctx, missing, ci, ids) => {
|
|
2938
3053
|
const created = [];
|
|
2939
3054
|
const failed = [];
|
|
2940
|
-
for (const resource of
|
|
3055
|
+
for (const resource of missing) try {
|
|
2941
3056
|
const { id } = await provisionResource(resource, ci);
|
|
2942
3057
|
if (id !== void 0 && (resource.kind === "kv" || resource.kind === "d1")) ids[resource.binding] = id;
|
|
2943
3058
|
created.push(id === void 0 ? { resource } : {
|
|
@@ -2954,9 +3069,45 @@ const applyPlan = async (ctx, plan, ci) => {
|
|
|
2954
3069
|
error: error instanceof Error ? error.message : String(error)
|
|
2955
3070
|
});
|
|
2956
3071
|
}
|
|
3072
|
+
return {
|
|
3073
|
+
created,
|
|
3074
|
+
failed
|
|
3075
|
+
};
|
|
3076
|
+
};
|
|
3077
|
+
/**
|
|
3078
|
+
* Act on an infra plan: skip the resources that already exist (reusing their ids), skip the Durable
|
|
3079
|
+
* Objects that ship with the Worker, create the missing ones (capturing each new id), and announce
|
|
3080
|
+
* each via provision:skip / :resource. Resilient — a single resource that fails to create is CAPTURED
|
|
3081
|
+
* in `failed` (not thrown), so one bad resource (e.g. an invalid bucket name) never aborts the whole
|
|
3082
|
+
* run and the caller can report a clear result.
|
|
3083
|
+
*
|
|
3084
|
+
* @param ctx - The deploy plugin context.
|
|
3085
|
+
* @param plan - The infra plan from planInfra (existing vs missing vs ships-with-Worker).
|
|
3086
|
+
* @param ci - Whether provisioning runs non-interactively (forwarded to each provider).
|
|
3087
|
+
* @returns The provisioning result: created, skipped, bundled, failed, and the merged binding → id map.
|
|
3088
|
+
* @example
|
|
3089
|
+
* ```ts
|
|
3090
|
+
* const { created, failed } = await applyPlan(ctx, plan, false);
|
|
3091
|
+
* ```
|
|
3092
|
+
*/
|
|
3093
|
+
const applyPlan = async (ctx, plan, ci) => {
|
|
3094
|
+
const ids = {};
|
|
3095
|
+
for (const ref of plan.exists) {
|
|
3096
|
+
if (ref.id !== void 0 && (ref.resource.kind === "kv" || ref.resource.kind === "d1")) ids[ref.resource.binding] = ref.id;
|
|
3097
|
+
ctx.emit("provision:skip", {
|
|
3098
|
+
kind: ref.resource.kind,
|
|
3099
|
+
name: resourceName(ref.resource)
|
|
3100
|
+
});
|
|
3101
|
+
}
|
|
3102
|
+
for (const resource of plan.ships) ctx.emit("provision:skip", {
|
|
3103
|
+
kind: resource.kind,
|
|
3104
|
+
name: resourceName(resource)
|
|
3105
|
+
});
|
|
3106
|
+
const { created, failed } = await provisionMissing(ctx, plan.missing, ci, ids);
|
|
2957
3107
|
return {
|
|
2958
3108
|
created,
|
|
2959
3109
|
skipped: plan.exists,
|
|
3110
|
+
bundled: plan.ships,
|
|
2960
3111
|
failed,
|
|
2961
3112
|
ids
|
|
2962
3113
|
};
|
|
@@ -2974,17 +3125,33 @@ const HINTS = {
|
|
|
2974
3125
|
deploy: "wrangler deploy failed — review the output above, then retry."
|
|
2975
3126
|
};
|
|
2976
3127
|
/**
|
|
2977
|
-
* Emit the terminal `aborted` phase
|
|
2978
|
-
* the user stops the deploy
|
|
3128
|
+
* Emit the terminal `aborted` phase AND build the matching {@link DeployReport} — the single exit
|
|
3129
|
+
* every guided gate/retry funnels through when the user stops the deploy (or auth was never set up).
|
|
3130
|
+
* Centralizing it keeps every abort path emitting one consistent line and returning the same shaped
|
|
3131
|
+
* report: `status: "aborted"`, both post-steps `"skipped"`, no errors — so a calling script sees a
|
|
3132
|
+
* clean stop, never a half-filled success, and the remote-DB migration/seed are guaranteed unrun.
|
|
2979
3133
|
*
|
|
2980
3134
|
* @param ctx - The deploy plugin context.
|
|
2981
|
-
* @
|
|
3135
|
+
* @param stage - The resolved deploy stage (echoed into the report).
|
|
3136
|
+
* @param startedAt - The run's start timestamp, for the elapsed field.
|
|
3137
|
+
* @returns The aborted deploy report.
|
|
2982
3138
|
* @example
|
|
2983
3139
|
* ```ts
|
|
2984
|
-
* if (declined) return
|
|
3140
|
+
* if (declined) return aborted(ctx, stage, startedAt);
|
|
2985
3141
|
* ```
|
|
2986
3142
|
*/
|
|
2987
|
-
const
|
|
3143
|
+
const aborted = (ctx, stage, startedAt) => {
|
|
3144
|
+
ctx.emit("deploy:phase", { phase: "aborted" });
|
|
3145
|
+
return {
|
|
3146
|
+
ok: false,
|
|
3147
|
+
status: "aborted",
|
|
3148
|
+
stage,
|
|
3149
|
+
migration: "skipped",
|
|
3150
|
+
seed: "skipped",
|
|
3151
|
+
elapsedMs: Date.now() - startedAt,
|
|
3152
|
+
errors: []
|
|
3153
|
+
};
|
|
3154
|
+
};
|
|
2988
3155
|
/**
|
|
2989
3156
|
* The full guided token setup shown after an auth failure on a TTY. Offers to walk the user through
|
|
2990
3157
|
* it, and when accepted: prints WHERE to create the Cloudflare token (dashboard URL, which template,
|
|
@@ -3204,6 +3371,104 @@ const guidedDeployStep = async (ctx, manifest, stage, deps) => {
|
|
|
3204
3371
|
return url;
|
|
3205
3372
|
};
|
|
3206
3373
|
/**
|
|
3374
|
+
* Apply pending D1 migrations to the REMOTE database for every configured d1 instance that ships a
|
|
3375
|
+
* migrations dir — the generic, deploy-owned analogue of `wrangler d1 migrations apply <binding>
|
|
3376
|
+
* --remote`. The wrangler config was written earlier in the pipeline, so each binding resolves. The
|
|
3377
|
+
* caller runs this only AFTER a successful deploy, so a deploy that never happened never migrates a
|
|
3378
|
+
* remote DB. Streams wrangler's output; throws on the first non-zero exit (the caller folds it into
|
|
3379
|
+
* the report).
|
|
3380
|
+
*
|
|
3381
|
+
* @param ctx - The deploy plugin context.
|
|
3382
|
+
* @returns Resolves once every configured database's remote migrations have been applied.
|
|
3383
|
+
* @example
|
|
3384
|
+
* ```ts
|
|
3385
|
+
* await applyRemoteMigrations(ctx);
|
|
3386
|
+
* ```
|
|
3387
|
+
*/
|
|
3388
|
+
const applyRemoteMigrations = async (ctx) => {
|
|
3389
|
+
if (!ctx.has("d1")) return;
|
|
3390
|
+
for (const database of ctx.require(d1Plugin).deployManifest()) if (database.migrations !== void 0) await runWranglerInherit([
|
|
3391
|
+
"d1",
|
|
3392
|
+
"migrations",
|
|
3393
|
+
"apply",
|
|
3394
|
+
database.binding,
|
|
3395
|
+
"--remote"
|
|
3396
|
+
]);
|
|
3397
|
+
};
|
|
3398
|
+
/**
|
|
3399
|
+
* Render a post-deploy step's failure as a branded line and capture its message into `errors` —
|
|
3400
|
+
* folding the failure into the report instead of throwing, so a deploy that already went live still
|
|
3401
|
+
* yields a complete, honest report when a later remote step (migration/seed) fails.
|
|
3402
|
+
*
|
|
3403
|
+
* @param ui - The branded console to render the error through.
|
|
3404
|
+
* @param errors - The accumulator the captured message is pushed into.
|
|
3405
|
+
* @param error - The thrown error (or value) to brand and capture.
|
|
3406
|
+
* @returns The captured (branded) message.
|
|
3407
|
+
* @example
|
|
3408
|
+
* ```ts
|
|
3409
|
+
* captureFailure(ui, errors, new Error("[moku-worker] seed failed"));
|
|
3410
|
+
* ```
|
|
3411
|
+
*/
|
|
3412
|
+
const captureFailure = (ui, errors, error) => {
|
|
3413
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3414
|
+
ui.error(message);
|
|
3415
|
+
errors.push(message);
|
|
3416
|
+
return message;
|
|
3417
|
+
};
|
|
3418
|
+
/**
|
|
3419
|
+
* Run the post-deploy remote steps — REACHED ONLY ON A SUCCESSFUL DEPLOY (every gate in `run` returns
|
|
3420
|
+
* early before here), so a deploy that never happened never touches a remote DB. Applies remote D1
|
|
3421
|
+
* migrations (when requested), then loads the configured seed (when requested) — but skips the seed
|
|
3422
|
+
* if the migration it depends on failed. Each step's failure is RENDERED inline and CAPTURED in
|
|
3423
|
+
* `errors` (never thrown), so one failed step still yields a complete, honest report.
|
|
3424
|
+
*
|
|
3425
|
+
* @param ctx - The deploy plugin context.
|
|
3426
|
+
* @param want - Which post-steps the caller requested.
|
|
3427
|
+
* @param want.migration - Whether to apply pending remote D1 migrations.
|
|
3428
|
+
* @param want.seed - Whether to load the configured remote seed (and reset its KV keys).
|
|
3429
|
+
* @returns The migration + seed outcomes and any captured branded errors.
|
|
3430
|
+
* @example
|
|
3431
|
+
* ```ts
|
|
3432
|
+
* const post = await runPostDeploy(ctx, { migration: true, seed: true });
|
|
3433
|
+
* ```
|
|
3434
|
+
*/
|
|
3435
|
+
const runPostDeploy = async (ctx, want) => {
|
|
3436
|
+
const ui = (0, _moku_labs_common_cli.createBrandConsole)();
|
|
3437
|
+
const errors = [];
|
|
3438
|
+
let migration = "skipped";
|
|
3439
|
+
if (want.migration) try {
|
|
3440
|
+
await applyRemoteMigrations(ctx);
|
|
3441
|
+
migration = "applied";
|
|
3442
|
+
ui.check(true, "migrated", "remote D1");
|
|
3443
|
+
} catch (error) {
|
|
3444
|
+
migration = "failed";
|
|
3445
|
+
captureFailure(ui, errors, error);
|
|
3446
|
+
}
|
|
3447
|
+
let seed = "skipped";
|
|
3448
|
+
if (want.seed && migration === "failed") {
|
|
3449
|
+
seed = "failed";
|
|
3450
|
+
captureFailure(ui, errors, /* @__PURE__ */ new Error("[moku-worker] seed skipped — the remote migration it depends on failed."));
|
|
3451
|
+
} else if (want.seed) {
|
|
3452
|
+
const config = ctx.config.seed;
|
|
3453
|
+
if (config === void 0) {
|
|
3454
|
+
seed = "failed";
|
|
3455
|
+
captureFailure(ui, errors, /* @__PURE__ */ new Error("[moku-worker] deploy({ seed: true }) but no seed is configured — set pluginConfigs.deploy.seed."));
|
|
3456
|
+
} else try {
|
|
3457
|
+
await runConfiguredSeed(ctx, runWranglerInherit, config, "--remote");
|
|
3458
|
+
seed = "applied";
|
|
3459
|
+
ui.check(true, "seeded", config.file);
|
|
3460
|
+
} catch (error) {
|
|
3461
|
+
seed = "failed";
|
|
3462
|
+
captureFailure(ui, errors, error);
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
return {
|
|
3466
|
+
migration,
|
|
3467
|
+
seed,
|
|
3468
|
+
errors
|
|
3469
|
+
};
|
|
3470
|
+
};
|
|
3471
|
+
/**
|
|
3207
3472
|
* Create the deploy api. Assembles the manifest from each resource plugin's own deployManifest(),
|
|
3208
3473
|
* runs an infra preflight (check-before-create + id capture), generates config, uploads, and runs
|
|
3209
3474
|
* `wrangler deploy`, emitting global deploy events along the way.
|
|
@@ -3219,13 +3484,19 @@ const guidedDeployStep = async (ctx, manifest, stage, deps) => {
|
|
|
3219
3484
|
const createDeployApi = (ctx) => ({
|
|
3220
3485
|
/**
|
|
3221
3486
|
* Run the full deploy pipeline: detect → preflight (check-before-create) → provision (only the
|
|
3222
|
-
* missing) → wrangler-config (with real ids) → upload → deploy
|
|
3223
|
-
*
|
|
3487
|
+
* missing) → wrangler-config (with real ids) → upload → deploy, then — ONLY on a successful
|
|
3488
|
+
* deploy — the requested post-deploy remote steps (migration, seed). When opts.manifest is
|
|
3489
|
+
* supplied it is used verbatim (universal path).
|
|
3224
3490
|
*
|
|
3225
3491
|
* On a TTY the run is GUIDED end to end: each gate is confirmed, and every failure is recovered
|
|
3226
|
-
* interactively rather than thrown — a missing/invalid token offers `
|
|
3227
|
-
* infra, upload, and `wrangler deploy` steps offer a retry. In CI/pipes it fails fast
|
|
3228
|
-
* the first error propagates to the branded CLI handler).
|
|
3492
|
+
* interactively rather than thrown — a missing/invalid token offers a `.env.local` scaffold, and
|
|
3493
|
+
* the build, infra, upload, and `wrangler deploy` steps offer a retry. In CI/pipes it fails fast
|
|
3494
|
+
* (no prompt, the first error propagates to the branded CLI handler).
|
|
3495
|
+
*
|
|
3496
|
+
* Resolves to a {@link DeployReport}. Every abort path (a declined gate, or auth never set up)
|
|
3497
|
+
* returns `status: "aborted"` BEFORE the post-deploy steps, so `migration`/`seed` run only when
|
|
3498
|
+
* the worker actually went live — a first `deploy --seed` with no token aborts cleanly instead of
|
|
3499
|
+
* falling through to a raw `wrangler … --remote` auth error.
|
|
3229
3500
|
*
|
|
3230
3501
|
* @param opts - Optional run options.
|
|
3231
3502
|
* @param opts.ci - CI/automated mode: never prompts, auto-confirms every gate, fails fast. When
|
|
@@ -3234,11 +3505,15 @@ const createDeployApi = (ctx) => ({
|
|
|
3234
3505
|
* @param opts.stage - Target stage; suffixes resource names (`production` = bare). Falls back to the app stage.
|
|
3235
3506
|
* @param opts.webBuild - Build the web site first (e.g. `() => webApp.cli.build()`), before deploy.
|
|
3236
3507
|
* @param opts.manifest - Caller-supplied manifest (bypasses deployManifest() assembly).
|
|
3237
|
-
* @
|
|
3508
|
+
* @param opts.migration - After a successful deploy, apply pending remote D1 migrations for every
|
|
3509
|
+
* configured d1 instance that ships migrations. Skipped (not attempted) on an aborted deploy.
|
|
3510
|
+
* @param opts.seed - After a successful deploy (+ migration), load the seed configured under
|
|
3511
|
+
* `pluginConfigs.deploy.seed` into the remote D1 and reset its cached KV keys. Skipped on abort.
|
|
3512
|
+
* @returns The deploy report (status, url, resource tally, migration/seed outcome, errors).
|
|
3238
3513
|
* @example
|
|
3239
3514
|
* ```ts
|
|
3240
|
-
* await api.run({ webBuild: () => web.cli.build()
|
|
3241
|
-
*
|
|
3515
|
+
* const report = await api.run({ webBuild: () => web.cli.build(), migration: true, seed: true });
|
|
3516
|
+
* if (!report.ok) process.exitCode = 1; // aborted or a post-step failed
|
|
3242
3517
|
* ```
|
|
3243
3518
|
*/
|
|
3244
3519
|
async run(opts) {
|
|
@@ -3251,26 +3526,45 @@ const createDeployApi = (ctx) => ({
|
|
|
3251
3526
|
};
|
|
3252
3527
|
const startedAt = Date.now();
|
|
3253
3528
|
ctx.emit("deploy:phase", { phase: "auth" });
|
|
3254
|
-
if (!await guidedAuth(ctx, deps)) return
|
|
3255
|
-
if (!await guidedWebBuild(ctx, opts?.webBuild ?? ctx.config.webBuild, deps)) return
|
|
3529
|
+
if (!await guidedAuth(ctx, deps)) return aborted(ctx, stage, startedAt);
|
|
3530
|
+
if (!await guidedWebBuild(ctx, opts?.webBuild ?? ctx.config.webBuild, deps)) return aborted(ctx, stage, startedAt);
|
|
3256
3531
|
ctx.emit("deploy:phase", { phase: "detect" });
|
|
3257
3532
|
const manifest = opts?.manifest ?? assembleManifest(ctx, stage);
|
|
3258
3533
|
ctx.emit("deploy:phase", { phase: "provision" });
|
|
3259
3534
|
const provisioned = await guidedProvision(ctx, manifest, ci, deps);
|
|
3260
|
-
if (provisioned === ABORTED) return
|
|
3535
|
+
if (provisioned === ABORTED) return aborted(ctx, stage, startedAt);
|
|
3261
3536
|
ctx.emit("deploy:phase", { phase: "wrangler-config" });
|
|
3262
3537
|
await writeWranglerConfig(ctx.config.configFile, manifest, provisioned.ids, wranglerExtra(ctx.config));
|
|
3263
|
-
if (!await guidedUpload(ctx, manifest, deps)) return
|
|
3538
|
+
if (!await guidedUpload(ctx, manifest, deps)) return aborted(ctx, stage, startedAt);
|
|
3264
3539
|
const url = await guidedDeployStep(ctx, manifest, stage, deps);
|
|
3265
|
-
if (url === void 0) return
|
|
3540
|
+
if (url === void 0) return aborted(ctx, stage, startedAt);
|
|
3541
|
+
const resources = {
|
|
3542
|
+
created: provisioned.created.length,
|
|
3543
|
+
exists: provisioned.skipped.length,
|
|
3544
|
+
bundled: provisioned.bundled.length,
|
|
3545
|
+
failed: provisioned.failed.length
|
|
3546
|
+
};
|
|
3266
3547
|
renderDeploySummary((0, _moku_labs_common_cli.createBrandConsole)(), {
|
|
3267
3548
|
url,
|
|
3268
3549
|
stage,
|
|
3269
|
-
|
|
3270
|
-
exists: provisioned.skipped.length,
|
|
3271
|
-
failed: provisioned.failed.length,
|
|
3550
|
+
...resources,
|
|
3272
3551
|
elapsedMs: Date.now() - startedAt
|
|
3273
3552
|
});
|
|
3553
|
+
const post = await runPostDeploy(ctx, {
|
|
3554
|
+
migration: opts?.migration === true,
|
|
3555
|
+
seed: opts?.seed === true
|
|
3556
|
+
});
|
|
3557
|
+
return {
|
|
3558
|
+
ok: post.errors.length === 0,
|
|
3559
|
+
status: post.errors.length === 0 ? "deployed" : "failed",
|
|
3560
|
+
stage,
|
|
3561
|
+
url,
|
|
3562
|
+
resources,
|
|
3563
|
+
migration: post.migration,
|
|
3564
|
+
seed: post.seed,
|
|
3565
|
+
elapsedMs: Date.now() - startedAt,
|
|
3566
|
+
errors: post.errors
|
|
3567
|
+
};
|
|
3274
3568
|
},
|
|
3275
3569
|
/**
|
|
3276
3570
|
* Start a long-lived local dev session: cold-build the Moku site, spawn `wrangler dev
|
|
@@ -3283,10 +3577,12 @@ const createDeployApi = (ctx) => ({
|
|
|
3283
3577
|
* @param opts.webBuild - Cold-build the web site (e.g. `() => webApp.cli.build()`); also the
|
|
3284
3578
|
* per-change rebuild when `onChange` is omitted.
|
|
3285
3579
|
* @param opts.onChange - Incremental per-change rebuild (e.g. `changes => webApp.cli.update(changes)`).
|
|
3580
|
+
* @param opts.seed - Load the configured seed (`pluginConfigs.deploy.seed`) into the LOCAL D1 and
|
|
3581
|
+
* reset its cached KV keys before serving — the local analogue of `deploy({ seed: true })`.
|
|
3286
3582
|
* @returns Resolves when the dev session ends.
|
|
3287
3583
|
* @example
|
|
3288
3584
|
* ```ts
|
|
3289
|
-
* await api.dev({ port: 8787, webBuild: () => web.cli.build(), onChange: c => web.cli.update(c) });
|
|
3585
|
+
* await api.dev({ port: 8787, seed: true, webBuild: () => web.cli.build(), onChange: c => web.cli.update(c) });
|
|
3290
3586
|
* ```
|
|
3291
3587
|
*/
|
|
3292
3588
|
async dev(opts) {
|
|
@@ -3316,11 +3612,7 @@ const createDeployApi = (ctx) => ({
|
|
|
3316
3612
|
if (!ctx.has("d1")) throw new Error("[moku-worker] seed: no d1 database is configured.");
|
|
3317
3613
|
const stage = opts?.stage ?? ctx.global.stage;
|
|
3318
3614
|
await writeWranglerConfig(ctx.config.configFile, assembleManifest(ctx, stage), {}, wranglerExtra(ctx.config));
|
|
3319
|
-
const
|
|
3320
|
-
const wanted = opts?.binding;
|
|
3321
|
-
const matched = wanted === void 0 ? databases : databases.filter((database) => database.binding === wanted);
|
|
3322
|
-
const target = matched.length === 1 ? matched[0] : void 0;
|
|
3323
|
-
if (target === void 0) throw new Error(wanted === void 0 ? `[moku-worker] seed: ${String(databases.length)} d1 databases configured — pass { binding } to choose one.` : `[moku-worker] seed: no d1 database is bound to "${wanted}".`);
|
|
3615
|
+
const target = resolveD1(ctx, opts?.binding);
|
|
3324
3616
|
const scope = opts?.remote === true ? "--remote" : "--local";
|
|
3325
3617
|
if (scope === "--local" && target.migrations !== void 0) await runWranglerInherit([
|
|
3326
3618
|
"d1",
|
|
@@ -3543,10 +3835,12 @@ const createCliApi = (ctx) => ({
|
|
|
3543
3835
|
* per-change rebuild when `onChange` is omitted.
|
|
3544
3836
|
* @param opts.onChange - Incremental per-change rebuild (e.g. `changes => webApp.cli.update(changes)`),
|
|
3545
3837
|
* so each change rebuilds only the changed paths instead of a full `webBuild()`.
|
|
3838
|
+
* @param opts.seed - Load the configured seed (`pluginConfigs.deploy.seed`) into the LOCAL D1 and
|
|
3839
|
+
* reset its cached KV keys before serving — the local analogue of `deploy({ seed: true })`.
|
|
3546
3840
|
* @returns Resolves when the dev session ends.
|
|
3547
3841
|
* @example
|
|
3548
3842
|
* ```ts
|
|
3549
|
-
* await api.dev({ port: 7878, webBuild: () => web.cli.build(), onChange: c => web.cli.update(c) });
|
|
3843
|
+
* await api.dev({ port: 7878, seed: true, webBuild: () => web.cli.build(), onChange: c => web.cli.update(c) });
|
|
3550
3844
|
* ```
|
|
3551
3845
|
*/
|
|
3552
3846
|
async dev(opts) {
|
|
@@ -3561,7 +3855,8 @@ const createCliApi = (ctx) => ({
|
|
|
3561
3855
|
...opts?.port === void 0 ? {} : { port: opts.port },
|
|
3562
3856
|
...stage === void 0 ? {} : { stage },
|
|
3563
3857
|
...opts?.webBuild ? { webBuild: opts.webBuild } : {},
|
|
3564
|
-
...opts?.onChange ? { onChange: opts.onChange } : {}
|
|
3858
|
+
...opts?.onChange ? { onChange: opts.onChange } : {},
|
|
3859
|
+
...opts?.seed ? { seed: opts.seed } : {}
|
|
3565
3860
|
});
|
|
3566
3861
|
ui.check(true, "dev session stopped cleanly");
|
|
3567
3862
|
} catch (error) {
|
|
@@ -3570,32 +3865,48 @@ const createCliApi = (ctx) => ({
|
|
|
3570
3865
|
}
|
|
3571
3866
|
},
|
|
3572
3867
|
/**
|
|
3573
|
-
* One-command Cloudflare deploy; forwards opts verbatim to deploy.run
|
|
3574
|
-
*
|
|
3575
|
-
*
|
|
3576
|
-
*
|
|
3868
|
+
* One-command Cloudflare deploy; forwards opts verbatim to deploy.run, then — only on a successful
|
|
3869
|
+
* deploy — the requested post-deploy migration/seed. Guided/interactive by default; `{ ci: true }`
|
|
3870
|
+
* runs the automated path (CI). A `webBuild` hook builds the web site first (before `wrangler
|
|
3871
|
+
* deploy`). RETURNS the structured {@link DeployReport}; on a failure it also renders a branded `✗`
|
|
3872
|
+
* line + sets a non-zero exit code (matching cli.auth/doctor), never a raw stack trace.
|
|
3577
3873
|
*
|
|
3578
3874
|
* @param opts - Optional deploy options.
|
|
3579
3875
|
* @param opts.ci - Automated mode: never prompts, auto-confirms. Omit/false → guided on a TTY.
|
|
3580
3876
|
* @param opts.stage - Target stage (resource-name suffix); falls back to `--stage` then the app stage.
|
|
3581
3877
|
* @param opts.webBuild - Build the web site first (e.g. `() => webApp.cli.build()`), before deploy.
|
|
3582
|
-
* @
|
|
3878
|
+
* @param opts.migration - Apply pending remote D1 migrations after a successful deploy (skipped on abort).
|
|
3879
|
+
* @param opts.seed - Load the configured remote seed (`pluginConfigs.deploy.seed`) after a
|
|
3880
|
+
* successful deploy (+ migration); skipped on an aborted deploy.
|
|
3881
|
+
* @returns The deploy report (status, url, resource tally, migration/seed outcome, errors).
|
|
3583
3882
|
* @example
|
|
3584
3883
|
* ```ts
|
|
3585
|
-
* await api.deploy({ webBuild: () => web.cli.build()
|
|
3586
|
-
*
|
|
3884
|
+
* const report = await api.deploy({ webBuild: () => web.cli.build(), migration: true, seed: true });
|
|
3885
|
+
* if (report.status === "aborted") return; // creds not set up yet — nothing shipped
|
|
3587
3886
|
* ```
|
|
3588
3887
|
*/
|
|
3589
3888
|
async deploy(opts) {
|
|
3590
3889
|
const stage = opts?.stage ?? parseStageArg(process.argv);
|
|
3591
3890
|
try {
|
|
3592
|
-
await ctx.require(deployPlugin).run({
|
|
3891
|
+
const report = await ctx.require(deployPlugin).run({
|
|
3593
3892
|
...opts,
|
|
3594
3893
|
...stage === void 0 ? {} : { stage }
|
|
3595
3894
|
});
|
|
3895
|
+
if (report.status === "failed") process.exitCode = 1;
|
|
3896
|
+
return report;
|
|
3596
3897
|
} catch (error) {
|
|
3597
|
-
|
|
3898
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3899
|
+
(0, _moku_labs_common_cli.createBrandConsole)().error(message);
|
|
3598
3900
|
process.exitCode = 1;
|
|
3901
|
+
return {
|
|
3902
|
+
ok: false,
|
|
3903
|
+
status: "failed",
|
|
3904
|
+
stage: stage ?? "production",
|
|
3905
|
+
migration: "skipped",
|
|
3906
|
+
seed: "skipped",
|
|
3907
|
+
elapsedMs: 0,
|
|
3908
|
+
errors: [message]
|
|
3909
|
+
};
|
|
3599
3910
|
}
|
|
3600
3911
|
},
|
|
3601
3912
|
/**
|