@solcreek/cli 0.3.7 → 0.3.9

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.
@@ -4,6 +4,11 @@ export declare const deployCommand: import("citty").CommandDef<{
4
4
  description: string;
5
5
  required: false;
6
6
  };
7
+ data: {
8
+ type: "string";
9
+ description: string;
10
+ required: false;
11
+ };
7
12
  path: {
8
13
  type: "string";
9
14
  description: string;
@@ -29,11 +34,6 @@ export declare const deployCommand: import("citty").CommandDef<{
29
34
  description: string;
30
35
  default: false;
31
36
  };
32
- demo: {
33
- type: "boolean";
34
- description: string;
35
- default: false;
36
- };
37
37
  }>;
38
38
  /**
39
39
  * Patch bare Node.js module imports (e.g. `from "fs"`) to use the `node:` prefix
@@ -1,6 +1,7 @@
1
1
  import { defineCommand } from "citty";
2
2
  import consola from "consola";
3
- import { existsSync, readFileSync, readdirSync, statSync, rmSync } from "node:fs";
3
+ import { existsSync, readFileSync, writeFileSync, readdirSync, statSync, rmSync } from "node:fs";
4
+ // ajv is lazy-imported only when --template --data is used (avoid top-level crash if deps missing)
4
5
  import { join, resolve } from "node:path";
5
6
  import { execSync, execFileSync } from "node:child_process";
6
7
  import { CreekClient, CreekAuthError, isSSRFramework, getSSRServerEntry, getClientAssetsDir, getDefaultBuildOutput, detectFramework, resolveConfig, formatDetectionSummary, resolvedConfigToResources, resolvedConfigToBindingRequirements, ConfigNotFoundError, getSSRServerDir, collectServerFiles, isPreBundledFramework, detectNextjsMode, detectMonorepo, } from "@solcreek/sdk";
@@ -45,15 +46,15 @@ export const deployCommand = defineCommand({
45
46
  description: "Skip the build step",
46
47
  default: false,
47
48
  },
48
- demo: {
49
- type: "boolean",
50
- description: "Deploy a sample site to see Creek in action",
51
- default: false,
52
- },
53
49
  ...globalArgs,
54
50
  template: {
55
51
  type: "string",
56
- description: "Deploy a template (e.g., react-dashboard, astro-landing)",
52
+ description: "Deploy a template (e.g., landing, blog, todo)",
53
+ required: false,
54
+ },
55
+ data: {
56
+ type: "string",
57
+ description: "JSON data for template params (used with --template)",
57
58
  required: false,
58
59
  },
59
60
  path: {
@@ -64,16 +65,22 @@ export const deployCommand = defineCommand({
64
65
  },
65
66
  async run({ args }) {
66
67
  const jsonMode = resolveJsonMode(args);
67
- // --- Demo deploy (zero-friction showcase) ---
68
- if (args.demo) {
69
- return await deployDemo(jsonMode);
70
- }
71
- // --- Ensure ToS accepted (all non-demo paths) ---
68
+ // --- Ensure ToS accepted ---
72
69
  const autoConfirm = shouldAutoConfirm(args);
73
70
  const tos = await ensureTosAccepted(autoConfirm);
74
71
  // --- Template deploy ---
75
72
  if (args.template) {
76
- return await deployTemplate(args.template);
73
+ let templateData;
74
+ if (args.data) {
75
+ try {
76
+ templateData = JSON.parse(args.data);
77
+ }
78
+ catch {
79
+ consola.error("Invalid JSON in --data");
80
+ process.exit(1);
81
+ }
82
+ }
83
+ return await deployTemplate(args.template, templateData);
77
84
  }
78
85
  // --- Repo URL deploy (creek deploy https://github.com/user/repo) ---
79
86
  if (args.dir && isRepoUrl(args.dir)) {
@@ -130,8 +137,8 @@ export const deployCommand = defineCommand({
130
137
  if (jsonMode)
131
138
  jsonOutput({ error: "no_project", message: "No project found in this directory" }, 1, NO_PROJECT_BREADCRUMBS);
132
139
  consola.info("No project found in this directory.\n");
133
- consola.info(" creek deploy ./dist Deploy a build output directory");
134
- consola.info(" creek deploy --demo Deploy a sample site (see Creek in 5 seconds)");
140
+ consola.info(" creek deploy ./dist Deploy a build output directory");
141
+ consola.info(" npx create-creek-app Create a new project from a template");
135
142
  consola.info("");
136
143
  consola.info("Or create a project first:");
137
144
  consola.info(" npm create vite@latest my-app && cd my-app && npx creek deploy");
@@ -199,7 +206,7 @@ async function deployRepoUrl(input, options) {
199
206
  if (err instanceof RepoUrlError || err instanceof GitCloneError) {
200
207
  if (jsonMode)
201
208
  jsonOutput({ error: "repo_deploy_failed", message: err.message }, 1, [
202
- { command: "creek deploy --demo", description: "Deploy a sample site instead" },
209
+ { command: "npx create-creek-app", description: "Create a new project from a template" },
203
210
  ]);
204
211
  consola.error(err.message);
205
212
  process.exit(1);
@@ -208,84 +215,6 @@ async function deployRepoUrl(input, options) {
208
215
  }
209
216
  }
210
217
  // ============================================================================
211
- // Demo deploy — pre-built page, zero dependencies, instant
212
- // ============================================================================
213
- const DEMO_HTML = `<!DOCTYPE html>
214
- <html lang="en">
215
- <head>
216
- <meta charset="utf-8">
217
- <meta name="viewport" content="width=device-width, initial-scale=1">
218
- <title>Creek Deploy Demo</title>
219
- <style>
220
- *{margin:0;padding:0;box-sizing:border-box}
221
- body{font-family:system-ui,-apple-system,sans-serif;background:#0a0a0a;color:#f5f5f5;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center}
222
- .card{max-width:480px;text-align:center;padding:3rem 2rem}
223
- h1{font-size:2rem;font-weight:700;letter-spacing:-0.03em;margin-bottom:0.5rem}
224
- .accent{background:linear-gradient(135deg,#38bdf8,#818cf8);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
225
- p{color:#888;line-height:1.6;margin-top:1rem}
226
- .meta{margin-top:2rem;font-size:0.85rem;color:#555}
227
- .cta{display:inline-block;margin-top:1.5rem;background:linear-gradient(135deg,#2563eb,#3b82f6);color:#fff;padding:10px 24px;border-radius:8px;text-decoration:none;font-weight:600;font-size:0.9rem;transition:opacity 0.15s}
228
- .cta:hover{opacity:0.9}
229
- code{background:#1a1a2e;padding:2px 8px;border-radius:4px;font-size:0.85rem;color:#a5b4fc}
230
- .pulse{width:12px;height:12px;background:#22c55e;border-radius:50%;display:inline-block;margin-right:8px;animation:pulse 2s infinite}
231
- @keyframes pulse{0%,100%{opacity:1}50%{opacity:0.4}}
232
- </style>
233
- </head>
234
- <body>
235
- <div class="card">
236
- <h1><span class="accent">Creek</span> is live.</h1>
237
- <p>This site was deployed to the edge in seconds — no build step, no config, no account.</p>
238
- <p style="margin-top:1.5rem"><span class="pulse"></span>Running on Cloudflare's global network</p>
239
- <p class="meta">Now try it with your own project:</p>
240
- <p style="margin-top:0.5rem"><code>cd your-project && npx creek deploy</code></p>
241
- <a href="https://creek.dev" class="cta">Learn more about Creek</a>
242
- </div>
243
- <script>document.title="Creek Deploy Demo — "+new Date().toLocaleTimeString()</script>
244
- </body>
245
- </html>`;
246
- async function deployDemo(jsonMode) {
247
- if (!jsonMode)
248
- consola.start("Deploying demo site...");
249
- try {
250
- const result = await sandboxDeploy({
251
- assets: { "index.html": Buffer.from(DEMO_HTML).toString("base64") },
252
- source: "cli-demo",
253
- });
254
- if (!jsonMode)
255
- consola.start("Waiting for deployment...");
256
- const status = await pollSandboxStatus(result.statusUrl);
257
- if (jsonMode) {
258
- jsonOutput({
259
- ok: true,
260
- sandboxId: result.sandboxId,
261
- url: status.previewUrl,
262
- deployDurationMs: status.deployDurationMs,
263
- expiresAt: result.expiresAt,
264
- mode: "demo",
265
- }, 0, [
266
- { command: `creek status ${result.sandboxId}`, description: "Check sandbox status" },
267
- { command: `creek claim ${result.sandboxId}`, description: "Claim as permanent project" },
268
- ]);
269
- }
270
- const duration = status.deployDurationMs
271
- ? `in ${(status.deployDurationMs / 1000).toFixed(1)}s`
272
- : "in seconds";
273
- consola.success(`⬡ Live ${duration} → ${status.previewUrl}`);
274
- consola.info("");
275
- consola.info("That's Creek. Now deploy your own project:");
276
- consola.info(" cd your-project && npx creek deploy");
277
- }
278
- catch (err) {
279
- const message = err instanceof Error ? err.message : "Demo deploy failed";
280
- if (jsonMode)
281
- jsonOutput({ ok: false, error: "deploy_failed", message }, 1, [
282
- { command: "creek deploy --demo", description: "Retry demo deploy" },
283
- ]);
284
- consola.error(message);
285
- process.exit(1);
286
- }
287
- }
288
- // ============================================================================
289
218
  // Directory deploy — deploy pre-built static files directly
290
219
  // ============================================================================
291
220
  async function deployDirectory(dir, jsonMode, tos) {
@@ -296,8 +225,8 @@ async function deployDirectory(dir, jsonMode, tos) {
296
225
  if (jsonMode)
297
226
  jsonOutput({ ok: false, error: "no_files", message: `No files found in ${dir}` }, 1, NO_PROJECT_BREADCRUMBS);
298
227
  consola.error(`No files found in ${dir}\n`);
299
- consola.info(" creek deploy ./dist Deploy a build output directory");
300
- consola.info(" creek deploy --demo Deploy a sample site (see Creek in 5 seconds)");
228
+ consola.info(" creek deploy ./dist Deploy a build output directory");
229
+ consola.info(" npx create-creek-app Create a new project from a template");
301
230
  process.exit(1);
302
231
  }
303
232
  if (!jsonMode) {
@@ -470,7 +399,7 @@ async function deploySandbox(cwd, skipBuild, jsonMode = false, resolved, tos) {
470
399
  // ============================================================================
471
400
  // Template deploy — clone + build + deploy to sandbox
472
401
  // ============================================================================
473
- async function deployTemplate(templateId) {
402
+ async function deployTemplate(templateId, data) {
474
403
  // Validate template ID — alphanumeric, hyphens, underscores only (no path traversal)
475
404
  if (!/^[a-zA-Z0-9_-]+$/.test(templateId)) {
476
405
  consola.error("Invalid template name. Use only letters, numbers, hyphens, and underscores.");
@@ -491,7 +420,7 @@ async function deployTemplate(templateId) {
491
420
  }
492
421
  catch {
493
422
  consola.error(`Template '${templateId}' not found.`);
494
- consola.info("Available templates: creek deploy --template");
423
+ consola.info("Available templates: npx create-creek-app --list");
495
424
  cleanupDir(tmpDir);
496
425
  process.exit(1);
497
426
  }
@@ -507,6 +436,43 @@ async function deployTemplate(templateId) {
507
436
  cleanupDir(tmpDir);
508
437
  process.exit(1);
509
438
  }
439
+ // Apply --data: validate against schema + merge into creek-data.json
440
+ if (data) {
441
+ const configPath = join(templateDir, "creek-template.json");
442
+ const dataPath = join(templateDir, "creek-data.json");
443
+ // Read defaults
444
+ let defaults = {};
445
+ if (existsSync(dataPath)) {
446
+ defaults = JSON.parse(readFileSync(dataPath, "utf-8"));
447
+ }
448
+ const merged = { ...defaults, ...data };
449
+ // Validate against schema if creek-template.json exists
450
+ if (existsSync(configPath)) {
451
+ const templateConfig = JSON.parse(readFileSync(configPath, "utf-8"));
452
+ if (templateConfig.schema) {
453
+ const { default: Ajv } = await import("ajv");
454
+ const ajv = new Ajv({ allErrors: true, useDefaults: true });
455
+ const { $schema: _, ...schemaWithoutMeta } = templateConfig.schema;
456
+ const validate = ajv.compile(schemaWithoutMeta);
457
+ if (!validate(merged)) {
458
+ consola.error("Data validation failed:");
459
+ for (const err of (validate.errors ?? [])) {
460
+ consola.error(` ${err.instancePath || "/"}: ${err.message}`);
461
+ }
462
+ cleanupDir(tmpDir);
463
+ process.exit(1);
464
+ }
465
+ }
466
+ }
467
+ // Write merged data into creek-data.json
468
+ writeFileSync(dataPath, JSON.stringify(merged, null, 2) + "\n");
469
+ consola.success("Applied custom data");
470
+ }
471
+ // Remove creek-template.json (metadata, not project file)
472
+ const templateConfigPath = join(templateDir, "creek-template.json");
473
+ if (existsSync(templateConfigPath)) {
474
+ rmSync(templateConfigPath);
475
+ }
510
476
  consola.start("Installing dependencies...");
511
477
  try {
512
478
  execFileSync("npm", ["install"], { cwd: templateDir, stdio: "pipe" });
@@ -814,6 +780,10 @@ async function deployAuthenticated(cwd, resolved, token, skipBuild, jsonMode = f
814
780
  if (res.url && res.previewUrl) {
815
781
  consola.info(` Preview: ${res.previewUrl}`);
816
782
  }
783
+ // Contextual next-step hints (non-JSON only)
784
+ if (!jsonMode) {
785
+ printNextStepHint(renderMode, resolved);
786
+ }
817
787
  return;
818
788
  }
819
789
  if (status === "failed") {
@@ -853,6 +823,24 @@ async function deployAuthenticated(cwd, resolved, token, skipBuild, jsonMode = f
853
823
  throw err;
854
824
  }
855
825
  }
826
+ // --- Hints ---
827
+ /**
828
+ * Print next-step hints after deploy.
829
+ * Shows capability overview — lets agents and developers discover what Creek offers.
830
+ */
831
+ function printNextStepHint(renderMode, config) {
832
+ const hasDb = config.bindings.some((b) => b.type === "d1");
833
+ if (hasDb)
834
+ return; // Already using Creek runtime — no hint needed
835
+ console.log("");
836
+ consola.info(" Next steps:");
837
+ consola.info(" import { db } from \"creek\" Database (managed, no config needed)");
838
+ consola.info(" import { define } from \"d1-schema\" Define your tables (auto-created)");
839
+ consola.info(" import { kv } from \"creek\" Key-value storage");
840
+ consola.info(" import { ai } from \"creek\" AI inference");
841
+ consola.info(" creek dev Local development");
842
+ consola.info(" https://creek.dev/docs Documentation");
843
+ }
856
844
  // --- Helpers ---
857
845
  /** Node.js built-in modules that may appear as bare imports in bundled workers. */
858
846
  const NODE_BUILTINS = new Set([
@@ -1,6 +1,6 @@
1
1
  import { defineCommand } from "citty";
2
2
  import consola from "consola";
3
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
4
4
  import { join, basename } from "node:path";
5
5
  import { stringify } from "smol-toml";
6
6
  import { detectFramework } from "@solcreek/sdk";
@@ -44,6 +44,11 @@ export const initCommand = defineCommand({
44
44
  }
45
45
  const defaultName = basename(cwd).toLowerCase().replace(/[^a-z0-9-]/g, "-");
46
46
  const name = args.name ?? defaultName;
47
+ // Ask about database
48
+ let useDb = false;
49
+ if (!jsonMode && !shouldAutoConfirm(args)) {
50
+ useDb = await consola.prompt("Add a database?", { type: "confirm" });
51
+ }
47
52
  const config = {
48
53
  project: {
49
54
  name,
@@ -52,22 +57,73 @@ export const initCommand = defineCommand({
52
57
  build: {
53
58
  command: "npm run build",
54
59
  output: "dist",
60
+ ...(useDb ? { worker: "worker/index.ts" } : {}),
55
61
  },
56
62
  resources: {
57
- d1: false,
63
+ d1: useDb,
58
64
  kv: false,
59
65
  r2: false,
60
66
  },
61
67
  };
62
68
  writeFileSync(configPath, stringify(config));
69
+ // Scaffold worker + d1-schema example when database enabled
70
+ if (useDb) {
71
+ const workerDir = join(cwd, "worker");
72
+ const workerFile = join(workerDir, "index.ts");
73
+ if (!existsSync(workerFile)) {
74
+ mkdirSync(workerDir, { recursive: true });
75
+ writeFileSync(workerFile, `import { Hono } from "hono";
76
+ import { db } from "creek";
77
+ import { define } from "d1-schema";
78
+
79
+ const app = new Hono();
80
+
81
+ // Define your tables — auto-created on first request
82
+ app.use("*", async (c, next) => {
83
+ await define(c.env.DB, {
84
+ users: {
85
+ id: "text primary key",
86
+ email: "text unique not null",
87
+ name: "text not null",
88
+ created_at: "text default (datetime('now'))",
89
+ },
90
+ });
91
+ return next();
92
+ });
93
+
94
+ app.get("/api/users", async (c) => {
95
+ const users = await db.query("SELECT * FROM users");
96
+ return c.json(users);
97
+ });
98
+
99
+ app.post("/api/users", async (c) => {
100
+ const { email, name } = await c.req.json();
101
+ const id = crypto.randomUUID().slice(0, 16);
102
+ await db.mutate("INSERT INTO users (id, email, name) VALUES (?, ?, ?)", id, email, name);
103
+ return c.json({ id, email, name });
104
+ });
105
+
106
+ export default app;
107
+ `);
108
+ if (!jsonMode) {
109
+ consola.success("Created worker/index.ts with database example");
110
+ consola.info(" Install dependencies: npm install hono creek d1-schema");
111
+ }
112
+ }
113
+ }
63
114
  if (jsonMode) {
64
- jsonOutput({ ok: true, name, framework: framework ?? null, path: configPath }, 0, [
115
+ jsonOutput({ ok: true, name, framework: framework ?? null, database: useDb, path: configPath }, 0, [
65
116
  { command: "creek deploy", description: "Deploy the project" },
66
- { command: "creek env set <KEY> <VALUE>", description: "Set an environment variable" },
67
117
  { command: "creek dev", description: "Start local development server" },
68
118
  ]);
69
119
  }
70
120
  consola.success(`Created creek.toml for "${name}"`);
121
+ if (!jsonMode) {
122
+ console.log("");
123
+ consola.info(" Next steps:");
124
+ consola.info(" creek deploy Deploy to production");
125
+ consola.info(" creek dev Start local development");
126
+ }
71
127
  },
72
128
  });
73
129
  //# sourceMappingURL=init.js.map
@@ -0,0 +1,18 @@
1
+ export declare const opsCommand: import("citty").CommandDef<{
2
+ json: {
3
+ type: "boolean";
4
+ description: string;
5
+ default: boolean;
6
+ };
7
+ yes: {
8
+ type: "boolean";
9
+ description: string;
10
+ default: boolean;
11
+ };
12
+ sub: {
13
+ type: "positional";
14
+ description: string;
15
+ required: false;
16
+ };
17
+ }>;
18
+ //# sourceMappingURL=ops.d.ts.map
@@ -0,0 +1,122 @@
1
+ import { defineCommand } from "citty";
2
+ import { getToken, getApiUrl } from "../utils/config.js";
3
+ import { globalArgs, resolveJsonMode, jsonOutput } from "../utils/output.js";
4
+ export const opsCommand = defineCommand({
5
+ meta: {
6
+ name: "ops",
7
+ description: "Platform admin (self-hosted) — deployments, health",
8
+ },
9
+ args: {
10
+ sub: {
11
+ type: "positional",
12
+ description: "Subcommand: deployments | health",
13
+ required: false,
14
+ },
15
+ ...globalArgs,
16
+ },
17
+ async run({ args }) {
18
+ const jsonMode = resolveJsonMode(args);
19
+ const sub = args.sub || "deployments";
20
+ if (sub === "deployments") {
21
+ return await listDeployments(jsonMode);
22
+ }
23
+ if (sub === "health") {
24
+ return await health(jsonMode);
25
+ }
26
+ jsonOutput({ error: `Unknown subcommand: ${sub}`, usage: "creek ops [deployments|health]" }, 1);
27
+ },
28
+ });
29
+ async function listDeployments(jsonMode) {
30
+ const apiUrl = getApiUrl();
31
+ const token = getToken();
32
+ if (!token) {
33
+ jsonOutput({ error: "Not authenticated", hint: "Run `creek login` first" }, 1);
34
+ }
35
+ const res = await fetch(`${apiUrl}/web-deploy/list`, {
36
+ headers: { Authorization: `Bearer ${token}` },
37
+ });
38
+ if (!res.ok) {
39
+ jsonOutput({ error: `Failed to fetch deployments: ${res.status}` }, 1);
40
+ }
41
+ const deploys = await res.json();
42
+ if (jsonMode) {
43
+ jsonOutput({
44
+ ok: true,
45
+ count: deploys.length,
46
+ deploys,
47
+ breadcrumbs: [
48
+ { command: "creek ops health", description: "Check platform health" },
49
+ ],
50
+ });
51
+ }
52
+ // Human-readable output
53
+ if (deploys.length === 0) {
54
+ console.log("\n No recent deployments (last 1 hour)\n");
55
+ return;
56
+ }
57
+ console.log(`\n Web Deploys (${deploys.length} in last hour)\n`);
58
+ const statusColors = {
59
+ active: "\x1b[32m", // green
60
+ building: "\x1b[34m", // blue
61
+ deploying: "\x1b[33m", // yellow
62
+ failed: "\x1b[31m", // red
63
+ };
64
+ const reset = "\x1b[0m";
65
+ for (const d of deploys) {
66
+ const color = statusColors[d.status] || "";
67
+ const time = d.createdAt ? timeAgo(d.createdAt) : "";
68
+ const preview = d.previewUrl ? ` → ${d.previewUrl}` : "";
69
+ const error = d.error ? `\n ${"\x1b[31m"}${d.error.slice(0, 80)}${reset}` : "";
70
+ console.log(` ${color}●${reset} ${d.buildId} ${color}${d.status.padEnd(9)}${reset} ${d.type || ""} ${time}${preview}${error}`);
71
+ }
72
+ console.log();
73
+ // Summary
74
+ const active = deploys.filter((d) => d.status === "active").length;
75
+ const failed = deploys.filter((d) => d.status === "failed").length;
76
+ const building = deploys.filter((d) => d.status === "building" || d.status === "deploying").length;
77
+ console.log(` Active: ${active} Failed: ${failed} In progress: ${building}\n`);
78
+ }
79
+ async function health(jsonMode) {
80
+ const apiUrl = getApiUrl();
81
+ const checks = {};
82
+ // Control plane
83
+ try {
84
+ const res = await fetch(`${apiUrl}/web-deploy/nonexistent`);
85
+ checks["control-plane"] = res.status === 404 ? "ok" : `unexpected ${res.status}`;
86
+ }
87
+ catch (err) {
88
+ checks["control-plane"] = `unreachable: ${err instanceof Error ? err.message : String(err)}`;
89
+ }
90
+ // Sandbox API — probe with empty POST (expect 400 validation error = alive)
91
+ try {
92
+ const res = await fetch("https://sandbox-api.creek.dev/api/sandbox/deploy", {
93
+ method: "POST",
94
+ headers: { "Content-Type": "application/json" },
95
+ body: "{}",
96
+ });
97
+ checks["sandbox-api"] = res.status === 400 || res.status === 429 ? "ok" : `unexpected ${res.status}`;
98
+ }
99
+ catch (err) {
100
+ checks["sandbox-api"] = `unreachable: ${err instanceof Error ? err.message : String(err)}`;
101
+ }
102
+ if (jsonMode) {
103
+ const allOk = Object.values(checks).every((v) => v === "ok");
104
+ jsonOutput({ ok: allOk, checks });
105
+ }
106
+ console.log("\n Platform Health\n");
107
+ for (const [name, status] of Object.entries(checks)) {
108
+ const icon = status === "ok" ? "\x1b[32m✓\x1b[0m" : "\x1b[31m✗\x1b[0m";
109
+ console.log(` ${icon} ${name}: ${status}`);
110
+ }
111
+ console.log();
112
+ }
113
+ function timeAgo(iso) {
114
+ const seconds = Math.floor((Date.now() - new Date(iso).getTime()) / 1000);
115
+ if (seconds < 60)
116
+ return `${seconds}s ago`;
117
+ const minutes = Math.floor(seconds / 60);
118
+ if (minutes < 60)
119
+ return `${minutes}m ago`;
120
+ return `${Math.floor(minutes / 60)}h ago`;
121
+ }
122
+ //# sourceMappingURL=ops.js.map
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ import { deploymentsCommand } from "./commands/deployments.js";
16
16
  import { statusCommand } from "./commands/status.js";
17
17
  import { devCommand } from "./commands/dev.js";
18
18
  import { rollbackCommand } from "./commands/rollback.js";
19
+ import { opsCommand } from "./commands/ops.js";
19
20
  const __dirname = dirname(fileURLToPath(import.meta.url));
20
21
  const cliPkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
21
22
  // Read version from the "creek" facade package (what users install),
@@ -48,6 +49,7 @@ const main = defineCommand({
48
49
  env: envCommand,
49
50
  domains: domainsCommand,
50
51
  rollback: rollbackCommand,
52
+ ops: opsCommand,
51
53
  },
52
54
  });
53
55
  runMain(main);
@@ -99,8 +99,10 @@ export function hasAdapterOutput(cwd) {
99
99
  * the Creek adapter handles middleware via typed AdapterOutputs.
100
100
  */
101
101
  export function patchBundledWorker(bundleDir, openNextDir) {
102
- const workerPath = join(bundleDir, "worker.js");
103
- if (!existsSync(workerPath))
102
+ // Try worker.js (wrangler --dry-run output) and handler.mjs (opennext server function)
103
+ const candidates = [join(bundleDir, "worker.js"), join(bundleDir, "handler.mjs")];
104
+ const workerPath = candidates.find((p) => existsSync(p));
105
+ if (!workerPath)
104
106
  return;
105
107
  let code = readFileSync(workerPath, "utf-8");
106
108
  let patched = false;
@@ -110,9 +112,10 @@ export function patchBundledWorker(bundleDir, openNextDir) {
110
112
  if (existsSync(manifestPath)) {
111
113
  manifest = readFileSync(manifestPath, "utf-8").trim();
112
114
  }
113
- // Patch: getMiddlewareManifest() { return __require(this.middlewareManifestPath); }
115
+ // Patch: getMiddlewareManifest() { return [__]require(this.middlewareManifestPath); }
114
116
  // → getMiddlewareManifest() { return <inline manifest>; }
115
- const pattern = /getMiddlewareManifest\(\)\s*\{[^}]*__require\(this\.middlewareManifestPath\)[^}]*\}/;
117
+ // Note: Next.js versions may use `require()` or `__require()` — match both
118
+ const pattern = /getMiddlewareManifest\(\)\s*\{[^}]*(?:__)?require\(this\.middlewareManifestPath\)[^}]*\}/;
116
119
  if (pattern.test(code)) {
117
120
  code = code.replace(pattern, `getMiddlewareManifest() { return ${manifest}; }`);
118
121
  patched = true;
@@ -359,6 +362,8 @@ export function buildNextjsForWorkers(cwd, isMonorepo, projectName = "app") {
359
362
  cwd,
360
363
  stdio: "inherit",
361
364
  });
365
+ // Step 6: Patch handler.mjs to fix dynamic require issues in Workers runtime
366
+ patchBundledWorker(join(cwd, ".open-next/server-functions/default"), join(cwd, ".open-next"));
362
367
  }
363
368
  finally {
364
369
  // Restore original next.config
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solcreek/cli",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "CLI for the Creek deployment platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -32,7 +32,8 @@
32
32
  "directory": "packages/cli"
33
33
  },
34
34
  "dependencies": {
35
- "@solcreek/sdk": ">=0.1.0-alpha.4",
35
+ "@solcreek/sdk": ">=0.1.0-alpha.5",
36
+ "ajv": "^8.17.1",
36
37
  "citty": "^0.1.6",
37
38
  "consola": "^3.4.2",
38
39
  "esbuild": "^0.25.0",