@solcreek/cli 0.3.8 → 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;
@@ -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";
@@ -48,7 +49,12 @@ export const deployCommand = defineCommand({
48
49
  ...globalArgs,
49
50
  template: {
50
51
  type: "string",
51
- 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)",
52
58
  required: false,
53
59
  },
54
60
  path: {
@@ -64,7 +70,17 @@ export const deployCommand = defineCommand({
64
70
  const tos = await ensureTosAccepted(autoConfirm);
65
71
  // --- Template deploy ---
66
72
  if (args.template) {
67
- 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);
68
84
  }
69
85
  // --- Repo URL deploy (creek deploy https://github.com/user/repo) ---
70
86
  if (args.dir && isRepoUrl(args.dir)) {
@@ -383,7 +399,7 @@ async function deploySandbox(cwd, skipBuild, jsonMode = false, resolved, tos) {
383
399
  // ============================================================================
384
400
  // Template deploy — clone + build + deploy to sandbox
385
401
  // ============================================================================
386
- async function deployTemplate(templateId) {
402
+ async function deployTemplate(templateId, data) {
387
403
  // Validate template ID — alphanumeric, hyphens, underscores only (no path traversal)
388
404
  if (!/^[a-zA-Z0-9_-]+$/.test(templateId)) {
389
405
  consola.error("Invalid template name. Use only letters, numbers, hyphens, and underscores.");
@@ -404,7 +420,7 @@ async function deployTemplate(templateId) {
404
420
  }
405
421
  catch {
406
422
  consola.error(`Template '${templateId}' not found.`);
407
- consola.info("Available templates: creek deploy --template");
423
+ consola.info("Available templates: npx create-creek-app --list");
408
424
  cleanupDir(tmpDir);
409
425
  process.exit(1);
410
426
  }
@@ -420,6 +436,43 @@ async function deployTemplate(templateId) {
420
436
  cleanupDir(tmpDir);
421
437
  process.exit(1);
422
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
+ }
423
476
  consola.start("Installing dependencies...");
424
477
  try {
425
478
  execFileSync("npm", ["install"], { cwd: templateDir, stdio: "pipe" });
@@ -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.8",
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",