@solcreek/cli 0.4.15 → 0.4.16
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/db.js +109 -1
- package/dist/commands/deploy.js +31 -4
- package/dist/commands/doctor.js +3 -42
- package/dist/commands/init.js +1 -5
- package/dist/utils/doctor-context.d.ts +3 -0
- package/dist/utils/doctor-context.js +48 -0
- package/package.json +1 -1
package/dist/commands/db.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineCommand } from "citty";
|
|
2
2
|
import consola from "consola";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { createInterface } from "node:readline";
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
import { CreekClient } from "@solcreek/sdk";
|
|
@@ -322,6 +322,113 @@ const migrateCommand = defineCommand({
|
|
|
322
322
|
consola.success(`\n${migrated} migration(s) applied successfully`);
|
|
323
323
|
},
|
|
324
324
|
});
|
|
325
|
+
const SEED_CANDIDATES = [
|
|
326
|
+
"drizzle/seed.sql",
|
|
327
|
+
"drizzle/migrations/seed.sql",
|
|
328
|
+
"migrations/seed.sql",
|
|
329
|
+
"sql/seed.sql",
|
|
330
|
+
"seed.sql",
|
|
331
|
+
];
|
|
332
|
+
const seedCommand = defineCommand({
|
|
333
|
+
meta: {
|
|
334
|
+
name: "seed",
|
|
335
|
+
description: "Execute a seed SQL file against a team database. Looks for seed.sql in common locations or use --file to specify.",
|
|
336
|
+
},
|
|
337
|
+
args: {
|
|
338
|
+
name: {
|
|
339
|
+
type: "positional",
|
|
340
|
+
description: "Database name (as shown by `creek db ls`)",
|
|
341
|
+
required: true,
|
|
342
|
+
},
|
|
343
|
+
file: {
|
|
344
|
+
type: "string",
|
|
345
|
+
description: "Path to seed SQL file. Default: auto-detect seed.sql in drizzle/, migrations/, sql/, or project root.",
|
|
346
|
+
required: false,
|
|
347
|
+
},
|
|
348
|
+
...globalArgs,
|
|
349
|
+
},
|
|
350
|
+
async run({ args }) {
|
|
351
|
+
const jsonMode = resolveJsonMode(args);
|
|
352
|
+
const token = getToken();
|
|
353
|
+
if (!token) {
|
|
354
|
+
if (jsonMode)
|
|
355
|
+
jsonOutput({ ok: false, error: "not_authenticated" }, 1, AUTH_BREADCRUMBS);
|
|
356
|
+
consola.error("Not authenticated. Run `creek login` first.");
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
const client = new CreekClient(getApiUrl(), token);
|
|
360
|
+
// Resolve database
|
|
361
|
+
const { resources } = await client.listResources();
|
|
362
|
+
const db = resources.find((r) => r.name === args.name && r.kind === "database");
|
|
363
|
+
if (!db) {
|
|
364
|
+
if (jsonMode)
|
|
365
|
+
jsonOutput({ ok: false, error: "not_found", message: `No database named "${args.name}"` }, 1);
|
|
366
|
+
consola.error(`No database named "${args.name}"`);
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
// Find seed file
|
|
370
|
+
const cwd = process.cwd();
|
|
371
|
+
let seedPath = null;
|
|
372
|
+
if (args.file) {
|
|
373
|
+
seedPath = resolve(cwd, args.file);
|
|
374
|
+
if (!existsSync(seedPath)) {
|
|
375
|
+
if (jsonMode)
|
|
376
|
+
jsonOutput({ ok: false, error: "file_not_found", message: `Seed file not found: ${args.file}` }, 1);
|
|
377
|
+
consola.error(`Seed file not found: ${args.file}`);
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
for (const candidate of SEED_CANDIDATES) {
|
|
383
|
+
const abs = resolve(cwd, candidate);
|
|
384
|
+
if (existsSync(abs)) {
|
|
385
|
+
seedPath = abs;
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (!seedPath) {
|
|
390
|
+
const msg = "No seed file found. Looked for: " + SEED_CANDIDATES.join(", ") + ". Use --file to specify.";
|
|
391
|
+
if (jsonMode)
|
|
392
|
+
jsonOutput({ ok: false, error: "no_seed_file", message: msg }, 1);
|
|
393
|
+
consola.error(msg);
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// Read and execute
|
|
398
|
+
const sql = readFileSync(seedPath, "utf-8");
|
|
399
|
+
const statements = splitStatements(sql);
|
|
400
|
+
if (statements.length === 0) {
|
|
401
|
+
if (jsonMode)
|
|
402
|
+
jsonOutput({ ok: true, message: "Seed file is empty", executed: 0 }, 0);
|
|
403
|
+
consola.info("Seed file is empty — nothing to execute.");
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
consola.info(`Seeding "${args.name}" from ${seedPath.replace(cwd + "/", "")}`);
|
|
407
|
+
consola.info(`${statements.length} statement(s)\n`);
|
|
408
|
+
for (let i = 0; i < statements.length; i++) {
|
|
409
|
+
try {
|
|
410
|
+
await client.queryDatabase(db.id, statements[i]);
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
414
|
+
if (jsonMode) {
|
|
415
|
+
jsonOutput({
|
|
416
|
+
ok: false,
|
|
417
|
+
error: "seed_failed",
|
|
418
|
+
statement: i + 1,
|
|
419
|
+
totalStatements: statements.length,
|
|
420
|
+
message: msg,
|
|
421
|
+
}, 1);
|
|
422
|
+
}
|
|
423
|
+
consola.error(`Statement ${i + 1}/${statements.length} failed: ${msg}`);
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (jsonMode)
|
|
428
|
+
jsonOutput({ ok: true, executed: statements.length }, 0);
|
|
429
|
+
consola.success(`Seed complete (${statements.length} statement${statements.length > 1 ? "s" : ""})`);
|
|
430
|
+
},
|
|
431
|
+
});
|
|
325
432
|
// Merge subcommands into the base resource command
|
|
326
433
|
export const dbCommand = defineCommand({
|
|
327
434
|
meta: base.meta,
|
|
@@ -329,6 +436,7 @@ export const dbCommand = defineCommand({
|
|
|
329
436
|
...base.subCommands,
|
|
330
437
|
shell: shellCommand,
|
|
331
438
|
migrate: migrateCommand,
|
|
439
|
+
seed: seedCommand,
|
|
332
440
|
},
|
|
333
441
|
});
|
|
334
442
|
//# sourceMappingURL=db.js.map
|
package/dist/commands/deploy.js
CHANGED
|
@@ -4,7 +4,8 @@ import { existsSync, readFileSync, writeFileSync, rmSync } from "node:fs";
|
|
|
4
4
|
// ajv is lazy-imported only when --template --data is used (avoid top-level crash if deps missing)
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
6
|
import { execSync, execFileSync } from "node:child_process";
|
|
7
|
-
import { CreekClient, CreekAuthError, detectFramework, resolveConfig, formatDetectionSummary, resolvedConfigToBindingRequirements, ConfigNotFoundError, detectNextjsMode, detectMonorepo, } from "@solcreek/sdk";
|
|
7
|
+
import { CreekClient, CreekAuthError, detectFramework, resolveConfig, formatDetectionSummary, resolvedConfigToBindingRequirements, ConfigNotFoundError, detectNextjsMode, detectMonorepo, runDoctor, } from "@solcreek/sdk";
|
|
8
|
+
import { buildDoctorContext } from "../utils/doctor-context.js";
|
|
8
9
|
import { getToken, getApiUrl } from "../utils/config.js";
|
|
9
10
|
import { collectAssets } from "../utils/bundle.js";
|
|
10
11
|
import { sandboxDeploy, pollSandboxStatus, printSandboxSuccess } from "../utils/sandbox.js";
|
|
@@ -89,6 +90,13 @@ async function dryRunPlan(cwd, args, jsonMode) {
|
|
|
89
90
|
type: b.type,
|
|
90
91
|
}))
|
|
91
92
|
: [];
|
|
93
|
+
// Run the same rule engine as `creek doctor` so dry-run surfaces
|
|
94
|
+
// config errors (e.g. CK-RESOURCES-KEYS) that would otherwise only
|
|
95
|
+
// reveal themselves at runtime when the missing binding crashes the
|
|
96
|
+
// worker. Agents following the SKILL.md "dry-run first" rule need
|
|
97
|
+
// these findings here, not after a 500.
|
|
98
|
+
const doctorReport = runDoctor(buildDoctorContext(cwd));
|
|
99
|
+
const blockingFindings = doctorReport.findings.filter((f) => f.severity === "error");
|
|
92
100
|
const plan = {
|
|
93
101
|
mode: "dry-run",
|
|
94
102
|
supported: true,
|
|
@@ -113,6 +121,7 @@ async function dryRunPlan(cwd, args, jsonMode) {
|
|
|
113
121
|
: null,
|
|
114
122
|
buildOutputFallback,
|
|
115
123
|
bindings,
|
|
124
|
+
findings: doctorReport.findings,
|
|
116
125
|
wouldDeploy,
|
|
117
126
|
sideEffects: {
|
|
118
127
|
networkCalls: false,
|
|
@@ -120,9 +129,11 @@ async function dryRunPlan(cwd, args, jsonMode) {
|
|
|
120
129
|
buildExecuted: false,
|
|
121
130
|
tosPromptShown: false,
|
|
122
131
|
},
|
|
123
|
-
nextStep:
|
|
124
|
-
? "
|
|
125
|
-
:
|
|
132
|
+
nextStep: blockingFindings.length > 0
|
|
133
|
+
? `Fix ${blockingFindings.length} blocking issue${blockingFindings.length === 1 ? "" : "s"} first (see findings), then re-run. For details: creek doctor --json`
|
|
134
|
+
: wouldDeploy
|
|
135
|
+
? "Run without --dry-run to execute: npx creek deploy"
|
|
136
|
+
: "No project config or build output found. Run `creek init` or `npm create vite@latest` first.",
|
|
126
137
|
};
|
|
127
138
|
if (jsonMode) {
|
|
128
139
|
jsonOutput(plan, 0, []);
|
|
@@ -155,6 +166,22 @@ async function dryRunPlan(cwd, args, jsonMode) {
|
|
|
155
166
|
.map((b) => `${b.name} (${b.type})`)
|
|
156
167
|
.join(", ")} — would be skipped`);
|
|
157
168
|
}
|
|
169
|
+
if (doctorReport.findings.length > 0) {
|
|
170
|
+
const errCount = doctorReport.summary.error;
|
|
171
|
+
const warnCount = doctorReport.summary.warn;
|
|
172
|
+
const infoCount = doctorReport.summary.info;
|
|
173
|
+
const parts = [];
|
|
174
|
+
if (errCount)
|
|
175
|
+
parts.push(`${errCount} error${errCount === 1 ? "" : "s"}`);
|
|
176
|
+
if (warnCount)
|
|
177
|
+
parts.push(`${warnCount} warning${warnCount === 1 ? "" : "s"}`);
|
|
178
|
+
if (infoCount)
|
|
179
|
+
parts.push(`${infoCount} info`);
|
|
180
|
+
consola.log(` Doctor findings: ${parts.join(", ")} — run \`creek doctor\` for details`);
|
|
181
|
+
for (const f of doctorReport.findings.filter((f) => f.severity === "error")) {
|
|
182
|
+
consola.log(` \x1b[31m✗\x1b[0m [${f.code}] ${f.title}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
158
185
|
}
|
|
159
186
|
else if (buildOutputFallback) {
|
|
160
187
|
consola.log(` Detected: prebuilt assets in ${buildOutputFallback}/ (no creek.toml, no wrangler config)`);
|
package/dist/commands/doctor.js
CHANGED
|
@@ -2,7 +2,8 @@ import { defineCommand } from "citty";
|
|
|
2
2
|
import consola from "consola";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
|
-
import { runDoctor,
|
|
5
|
+
import { runDoctor, } from "@solcreek/sdk";
|
|
6
|
+
import { buildDoctorContext } from "../utils/doctor-context.js";
|
|
6
7
|
import { CreekClient } from "@solcreek/sdk";
|
|
7
8
|
import { getToken, getApiUrl } from "../utils/config.js";
|
|
8
9
|
import { globalArgs, resolveJsonMode, jsonOutput, AUTH_BREADCRUMBS, } from "../utils/output.js";
|
|
@@ -51,7 +52,7 @@ export const doctorCommand = defineCommand({
|
|
|
51
52
|
return;
|
|
52
53
|
}
|
|
53
54
|
const cwd = resolve(args.path ?? process.cwd());
|
|
54
|
-
const ctx =
|
|
55
|
+
const ctx = buildDoctorContext(cwd);
|
|
55
56
|
const report = runDoctor(ctx);
|
|
56
57
|
if (jsonMode) {
|
|
57
58
|
jsonOutput({
|
|
@@ -68,35 +69,6 @@ export const doctorCommand = defineCommand({
|
|
|
68
69
|
process.exit(1);
|
|
69
70
|
},
|
|
70
71
|
});
|
|
71
|
-
function buildContext(cwd) {
|
|
72
|
-
const fileExists = (relPath) => existsSync(join(cwd, relPath));
|
|
73
|
-
const creekTomlPath = join(cwd, "creek.toml");
|
|
74
|
-
const creekTomlRaw = existsSync(creekTomlPath)
|
|
75
|
-
? safeRead(creekTomlPath)
|
|
76
|
-
: null;
|
|
77
|
-
const pkgPath = join(cwd, "package.json");
|
|
78
|
-
const packageJson = existsSync(pkgPath)
|
|
79
|
-
? safeParseJson(pkgPath)
|
|
80
|
-
: null;
|
|
81
|
-
const resolved = resolveConfigSafely(cwd);
|
|
82
|
-
const allDeps = {
|
|
83
|
-
...(packageJson?.dependencies ?? {}),
|
|
84
|
-
...(packageJson?.devDependencies ?? {}),
|
|
85
|
-
};
|
|
86
|
-
return { cwd, resolved, packageJson, creekTomlRaw, fileExists, allDeps };
|
|
87
|
-
}
|
|
88
|
-
function resolveConfigSafely(cwd) {
|
|
89
|
-
try {
|
|
90
|
-
return resolveConfig(cwd);
|
|
91
|
-
}
|
|
92
|
-
catch (err) {
|
|
93
|
-
if (err instanceof ConfigNotFoundError)
|
|
94
|
-
return null;
|
|
95
|
-
// Other errors (parse failures) bubble as null — the rules will
|
|
96
|
-
// still pick up partial info from creekTomlRaw + packageJson.
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
72
|
function safeRead(path) {
|
|
101
73
|
try {
|
|
102
74
|
return readFileSync(path, "utf8");
|
|
@@ -105,17 +77,6 @@ function safeRead(path) {
|
|
|
105
77
|
return null;
|
|
106
78
|
}
|
|
107
79
|
}
|
|
108
|
-
function safeParseJson(path) {
|
|
109
|
-
const raw = safeRead(path);
|
|
110
|
-
if (raw === null)
|
|
111
|
-
return null;
|
|
112
|
-
try {
|
|
113
|
-
return JSON.parse(raw);
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
80
|
// ─── Human output ───────────────────────────────────────────────────────
|
|
120
81
|
const COLOR = {
|
|
121
82
|
reset: "\x1b[0m",
|
package/dist/commands/init.js
CHANGED
|
@@ -59,11 +59,7 @@ export const initCommand = defineCommand({
|
|
|
59
59
|
output: "dist",
|
|
60
60
|
...(useDb ? { worker: "worker/index.ts" } : {}),
|
|
61
61
|
},
|
|
62
|
-
resources: {
|
|
63
|
-
d1: useDb,
|
|
64
|
-
kv: false,
|
|
65
|
-
r2: false,
|
|
66
|
-
},
|
|
62
|
+
...(useDb ? { resources: { database: true } } : {}),
|
|
67
63
|
};
|
|
68
64
|
writeFileSync(configPath, stringify(config));
|
|
69
65
|
// Scaffold worker + d1-schema example when database enabled
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { resolveConfig, ConfigNotFoundError, } from "@solcreek/sdk";
|
|
4
|
+
export function buildDoctorContext(cwd) {
|
|
5
|
+
const fileExists = (relPath) => existsSync(join(cwd, relPath));
|
|
6
|
+
const creekTomlPath = join(cwd, "creek.toml");
|
|
7
|
+
const creekTomlRaw = existsSync(creekTomlPath) ? safeRead(creekTomlPath) : null;
|
|
8
|
+
const pkgPath = join(cwd, "package.json");
|
|
9
|
+
const packageJson = existsSync(pkgPath)
|
|
10
|
+
? safeParseJson(pkgPath)
|
|
11
|
+
: null;
|
|
12
|
+
const resolved = resolveConfigSafely(cwd);
|
|
13
|
+
const allDeps = {
|
|
14
|
+
...(packageJson?.dependencies ?? {}),
|
|
15
|
+
...(packageJson?.devDependencies ?? {}),
|
|
16
|
+
};
|
|
17
|
+
return { cwd, resolved, packageJson, creekTomlRaw, fileExists, allDeps };
|
|
18
|
+
}
|
|
19
|
+
function resolveConfigSafely(cwd) {
|
|
20
|
+
try {
|
|
21
|
+
return resolveConfig(cwd);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
if (err instanceof ConfigNotFoundError)
|
|
25
|
+
return null;
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function safeRead(path) {
|
|
30
|
+
try {
|
|
31
|
+
return readFileSync(path, "utf8");
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function safeParseJson(path) {
|
|
38
|
+
const raw = safeRead(path);
|
|
39
|
+
if (raw === null)
|
|
40
|
+
return null;
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(raw);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=doctor-context.js.map
|