@solcreek/cli 0.3.8 → 0.4.0
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 -0
- package/dist/commands/deploy.js +58 -5
- package/dist/commands/ops.d.ts +22 -0
- package/dist/commands/ops.js +130 -0
- package/dist/index.js +2 -0
- package/dist/utils/nextjs.js +9 -4
- package/package.json +3 -5
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";
|
|
@@ -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.,
|
|
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
|
-
|
|
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
|
|
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,22 @@
|
|
|
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
|
+
env: {
|
|
18
|
+
type: "string";
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
}>;
|
|
22
|
+
//# sourceMappingURL=ops.d.ts.map
|
|
@@ -0,0 +1,130 @@
|
|
|
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
|
+
env: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "Filter by environment: sandbox | production",
|
|
18
|
+
},
|
|
19
|
+
...globalArgs,
|
|
20
|
+
},
|
|
21
|
+
async run({ args }) {
|
|
22
|
+
const jsonMode = resolveJsonMode(args);
|
|
23
|
+
const sub = args.sub || "deployments";
|
|
24
|
+
if (sub === "deployments") {
|
|
25
|
+
return await listDeployments(jsonMode, args.env);
|
|
26
|
+
}
|
|
27
|
+
if (sub === "health") {
|
|
28
|
+
return await health(jsonMode);
|
|
29
|
+
}
|
|
30
|
+
jsonOutput({ error: `Unknown subcommand: ${sub}`, usage: "creek ops [deployments|health]" }, 1);
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
async function listDeployments(jsonMode, envFilter) {
|
|
34
|
+
const apiUrl = getApiUrl();
|
|
35
|
+
const token = getToken();
|
|
36
|
+
if (!token) {
|
|
37
|
+
jsonOutput({ error: "Not authenticated", hint: "Run `creek login` first" }, 1);
|
|
38
|
+
}
|
|
39
|
+
const res = await fetch(`${apiUrl}/web-deploy/list`, {
|
|
40
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
jsonOutput({ error: `Failed to fetch deployments: ${res.status}` }, 1);
|
|
44
|
+
}
|
|
45
|
+
let deploys = await res.json();
|
|
46
|
+
if (envFilter) {
|
|
47
|
+
deploys = deploys.filter((d) => d.environment === envFilter);
|
|
48
|
+
}
|
|
49
|
+
if (jsonMode) {
|
|
50
|
+
jsonOutput({
|
|
51
|
+
ok: true,
|
|
52
|
+
count: deploys.length,
|
|
53
|
+
deploys,
|
|
54
|
+
breadcrumbs: [
|
|
55
|
+
{ command: "creek ops health", description: "Check platform health" },
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
// Human-readable output
|
|
60
|
+
if (deploys.length === 0) {
|
|
61
|
+
console.log("\n No recent deployments (last 1 hour)\n");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const label = envFilter ? `Deployments — ${envFilter}` : "Deployments";
|
|
65
|
+
console.log(`\n ${label} (${deploys.length})\n`);
|
|
66
|
+
const statusColors = {
|
|
67
|
+
active: "\x1b[32m", // green
|
|
68
|
+
building: "\x1b[34m", // blue
|
|
69
|
+
deploying: "\x1b[33m", // yellow
|
|
70
|
+
failed: "\x1b[31m", // red
|
|
71
|
+
};
|
|
72
|
+
const reset = "\x1b[0m";
|
|
73
|
+
for (const d of deploys) {
|
|
74
|
+
const color = statusColors[d.status] || "";
|
|
75
|
+
const time = d.createdAt ? timeAgo(d.createdAt) : "";
|
|
76
|
+
const preview = d.previewUrl ? ` → ${d.previewUrl}` : "";
|
|
77
|
+
const error = d.error ? `\n ${"\x1b[31m"}${d.error.slice(0, 80)}${reset}` : "";
|
|
78
|
+
console.log(` ${color}●${reset} ${d.buildId} ${color}${d.status.padEnd(9)}${reset} ${d.type || ""} ${time}${preview}${error}`);
|
|
79
|
+
}
|
|
80
|
+
console.log();
|
|
81
|
+
// Summary
|
|
82
|
+
const active = deploys.filter((d) => d.status === "active").length;
|
|
83
|
+
const failed = deploys.filter((d) => d.status === "failed").length;
|
|
84
|
+
const building = deploys.filter((d) => d.status === "building" || d.status === "deploying").length;
|
|
85
|
+
console.log(` Active: ${active} Failed: ${failed} In progress: ${building}\n`);
|
|
86
|
+
}
|
|
87
|
+
async function health(jsonMode) {
|
|
88
|
+
const apiUrl = getApiUrl();
|
|
89
|
+
const checks = {};
|
|
90
|
+
// Control plane
|
|
91
|
+
try {
|
|
92
|
+
const res = await fetch(`${apiUrl}/web-deploy/nonexistent`);
|
|
93
|
+
checks["control-plane"] = res.status === 404 ? "ok" : `unexpected ${res.status}`;
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
checks["control-plane"] = `unreachable: ${err instanceof Error ? err.message : String(err)}`;
|
|
97
|
+
}
|
|
98
|
+
// Sandbox API — probe with empty POST (expect 400 validation error = alive)
|
|
99
|
+
try {
|
|
100
|
+
const res = await fetch("https://sandbox-api.creek.dev/api/sandbox/deploy", {
|
|
101
|
+
method: "POST",
|
|
102
|
+
headers: { "Content-Type": "application/json" },
|
|
103
|
+
body: "{}",
|
|
104
|
+
});
|
|
105
|
+
checks["sandbox-api"] = res.status === 400 || res.status === 429 ? "ok" : `unexpected ${res.status}`;
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
checks["sandbox-api"] = `unreachable: ${err instanceof Error ? err.message : String(err)}`;
|
|
109
|
+
}
|
|
110
|
+
if (jsonMode) {
|
|
111
|
+
const allOk = Object.values(checks).every((v) => v === "ok");
|
|
112
|
+
jsonOutput({ ok: allOk, checks });
|
|
113
|
+
}
|
|
114
|
+
console.log("\n Platform Health\n");
|
|
115
|
+
for (const [name, status] of Object.entries(checks)) {
|
|
116
|
+
const icon = status === "ok" ? "\x1b[32m✓\x1b[0m" : "\x1b[31m✗\x1b[0m";
|
|
117
|
+
console.log(` ${icon} ${name}: ${status}`);
|
|
118
|
+
}
|
|
119
|
+
console.log();
|
|
120
|
+
}
|
|
121
|
+
function timeAgo(iso) {
|
|
122
|
+
const seconds = Math.floor((Date.now() - new Date(iso).getTime()) / 1000);
|
|
123
|
+
if (seconds < 60)
|
|
124
|
+
return `${seconds}s ago`;
|
|
125
|
+
const minutes = Math.floor(seconds / 60);
|
|
126
|
+
if (minutes < 60)
|
|
127
|
+
return `${minutes}m ago`;
|
|
128
|
+
return `${Math.floor(minutes / 60)}h ago`;
|
|
129
|
+
}
|
|
130
|
+
//# 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
|
+
"version": "0.4.0",
|
|
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",
|
|
@@ -40,9 +41,6 @@
|
|
|
40
41
|
"smol-toml": "^1.3.1",
|
|
41
42
|
"ws": "^8.20.0"
|
|
42
43
|
},
|
|
43
|
-
"optionalDependencies": {
|
|
44
|
-
"@solcreek/adapter-creek": "*"
|
|
45
|
-
},
|
|
46
44
|
"devDependencies": {
|
|
47
45
|
"@testing-library/dom": "^10.4.1",
|
|
48
46
|
"@testing-library/react": "^16.3.2",
|