@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.
- package/dist/commands/deploy.d.ts +5 -5
- package/dist/commands/deploy.js +86 -98
- package/dist/commands/init.js +60 -4
- package/dist/commands/ops.d.ts +18 -0
- package/dist/commands/ops.js +122 -0
- package/dist/index.js +2 -0
- package/dist/utils/nextjs.js +9 -4
- package/package.json +3 -2
|
@@ -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
|
package/dist/commands/deploy.js
CHANGED
|
@@ -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.,
|
|
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
|
-
// ---
|
|
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
|
-
|
|
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
|
|
134
|
-
consola.info(" creek
|
|
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
|
|
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
|
|
300
|
-
consola.info(" creek
|
|
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
|
|
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([
|
package/dist/commands/init.js
CHANGED
|
@@ -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:
|
|
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);
|
package/dist/utils/nextjs.js
CHANGED
|
@@ -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
|
-
|
|
103
|
-
|
|
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
|
|
115
|
+
// Patch: getMiddlewareManifest() { return [__]require(this.middlewareManifestPath); }
|
|
114
116
|
// → getMiddlewareManifest() { return <inline manifest>; }
|
|
115
|
-
|
|
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.
|
|
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.
|
|
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",
|