@solcreek/cli 0.4.15 → 0.4.17

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.
@@ -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
@@ -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: wouldDeploy
124
- ? "Run without --dry-run to execute: npx creek deploy"
125
- : "No project config or build output found. Run `creek init` or `npm create vite@latest` first.",
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)`);
@@ -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, resolveConfig, ConfigNotFoundError, } from "@solcreek/sdk";
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 = buildContext(cwd);
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",
@@ -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
@@ -51,7 +51,7 @@ export function createResourceCommand(opts) {
51
51
  },
52
52
  });
53
53
  const create = defineCommand({
54
- meta: { name: "create", description: `Create a new team ${label}. Backing CF resource is auto-provisioned.` },
54
+ meta: { name: "create", description: `Create a new team ${label}. Backing infrastructure is auto-provisioned.` },
55
55
  args: {
56
56
  name: { type: "positional", description: `${label} name (lowercase, dash/underscore, ≤63 chars)`, required: true },
57
57
  ...globalArgs,
@@ -0,0 +1,3 @@
1
+ import { type DoctorContext } from "@solcreek/sdk";
2
+ export declare function buildDoctorContext(cwd: string): DoctorContext;
3
+ //# sourceMappingURL=doctor-context.d.ts.map
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solcreek/cli",
3
- "version": "0.4.15",
3
+ "version": "0.4.17",
4
4
  "description": "CLI for the Creek deployment platform",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",