@solcreek/cli 0.4.14 → 0.4.15

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.
@@ -6,10 +6,28 @@ import { existsSync, readFileSync } from "node:fs";
6
6
  import { join } from "node:path";
7
7
  import { parseConfig } from "@solcreek/sdk";
8
8
  import { globalArgs, resolveJsonMode, jsonOutput, AUTH_BREADCRUMBS, NO_PROJECT_BREADCRUMBS } from "../utils/output.js";
9
- export const deploymentsCommand = defineCommand({
9
+ function resolveSlug(argSlug, jsonMode) {
10
+ if (argSlug)
11
+ return argSlug;
12
+ const configPath = join(process.cwd(), "creek.toml");
13
+ if (!existsSync(configPath)) {
14
+ if (jsonMode) {
15
+ jsonOutput({
16
+ ok: false,
17
+ error: "no_project",
18
+ message: "No creek.toml found. Use --project <slug> or run from a project directory.",
19
+ }, 1, NO_PROJECT_BREADCRUMBS);
20
+ }
21
+ consola.error("No creek.toml found. Use --project <slug> or run from a project directory.");
22
+ process.exit(1);
23
+ }
24
+ return parseConfig(readFileSync(configPath, "utf-8")).project.name;
25
+ }
26
+ // --- List (default behaviour) ---
27
+ const deploymentsList = defineCommand({
10
28
  meta: {
11
- name: "deployments",
12
- description: "List recent deployments for the current project",
29
+ name: "list",
30
+ description: "List recent deployments",
13
31
  },
14
32
  args: {
15
33
  project: {
@@ -28,18 +46,7 @@ export const deploymentsCommand = defineCommand({
28
46
  consola.error("Not authenticated. Run `creek login` first.");
29
47
  process.exit(1);
30
48
  }
31
- // Resolve project slug
32
- let slug = args.project;
33
- if (!slug) {
34
- const configPath = join(process.cwd(), "creek.toml");
35
- if (!existsSync(configPath)) {
36
- if (jsonMode)
37
- jsonOutput({ ok: false, error: "no_project", message: "No creek.toml found. Use --project <slug> or run from a project directory." }, 1, NO_PROJECT_BREADCRUMBS);
38
- consola.error("No creek.toml found. Use --project <slug> or run from a project directory.");
39
- process.exit(1);
40
- }
41
- slug = parseConfig(readFileSync(configPath, "utf-8")).project.name;
42
- }
49
+ const slug = resolveSlug(args.project, jsonMode);
43
50
  const client = new CreekClient(getApiUrl(), token);
44
51
  let deployments;
45
52
  try {
@@ -74,6 +81,194 @@ export const deploymentsCommand = defineCommand({
74
81
  }
75
82
  },
76
83
  });
84
+ // --- Logs subcommand ---
85
+ const deploymentsLogs = defineCommand({
86
+ meta: {
87
+ name: "logs",
88
+ description: "Read the build log for a deployment (production + branch + preview). Use --project to target a different project; --raw to print ndjson for piping. Designed so AI coding agents can diagnose failed deploys without a human relay.",
89
+ },
90
+ args: {
91
+ id: {
92
+ type: "positional",
93
+ description: "Deployment id (8-char short id or full uuid)",
94
+ required: true,
95
+ },
96
+ project: {
97
+ type: "string",
98
+ description: "Project slug (default: from creek.toml)",
99
+ required: false,
100
+ },
101
+ raw: {
102
+ type: "boolean",
103
+ description: "Print raw ndjson lines instead of step-grouped output",
104
+ default: false,
105
+ },
106
+ ...globalArgs,
107
+ },
108
+ async run({ args }) {
109
+ const jsonMode = resolveJsonMode(args);
110
+ const token = getToken();
111
+ if (!token) {
112
+ if (jsonMode)
113
+ jsonOutput({ ok: false, error: "not_authenticated" }, 1, AUTH_BREADCRUMBS);
114
+ consola.error("Not authenticated. Run `creek login` first.");
115
+ process.exit(1);
116
+ }
117
+ const slug = resolveSlug(args.project, jsonMode);
118
+ const client = new CreekClient(getApiUrl(), token);
119
+ // If the user passed a short id, look up the full uuid. GET endpoint
120
+ // requires the full id.
121
+ let fullId = args.id;
122
+ if (fullId.length < 36) {
123
+ try {
124
+ const list = await client.listDeployments(slug);
125
+ const match = list.find((d) => d.id.startsWith(fullId));
126
+ if (!match) {
127
+ if (jsonMode)
128
+ jsonOutput({ ok: false, error: "not_found", message: `No deployment matches id prefix '${fullId}'` }, 1);
129
+ consola.error(`No deployment matches id prefix '${fullId}'`);
130
+ process.exit(1);
131
+ }
132
+ fullId = match.id;
133
+ }
134
+ catch (err) {
135
+ const msg = err instanceof Error ? err.message : "Failed to resolve deployment id";
136
+ if (jsonMode)
137
+ jsonOutput({ ok: false, error: "api_error", message: msg }, 1);
138
+ consola.error(msg);
139
+ process.exit(1);
140
+ }
141
+ }
142
+ let log;
143
+ try {
144
+ log = await client.getBuildLog(slug, fullId);
145
+ }
146
+ catch (err) {
147
+ const msg = err instanceof Error ? err.message : "Failed to read build log";
148
+ if (jsonMode)
149
+ jsonOutput({ ok: false, error: "api_error", message: msg }, 1);
150
+ consola.error(msg);
151
+ process.exit(1);
152
+ }
153
+ if (jsonMode) {
154
+ jsonOutput({ ok: true, deploymentId: fullId, ...log }, 0);
155
+ }
156
+ if (!log.metadata) {
157
+ consola.info(log.message ?? "No build log available for this deployment.");
158
+ return;
159
+ }
160
+ if (args.raw) {
161
+ for (const e of log.entries)
162
+ consola.log(JSON.stringify(e));
163
+ return;
164
+ }
165
+ // Grouped printout: step ▸ lines. Headers carry status + duration + CK-code.
166
+ const grouped = groupByStep(log.entries);
167
+ const headerLabels = {
168
+ clone: "Clone",
169
+ detect: "Detect",
170
+ install: "Install",
171
+ build: "Build",
172
+ bundle: "Bundle",
173
+ upload: "Upload",
174
+ provision: "Provision",
175
+ activate: "Activate",
176
+ cleanup: "Cleanup",
177
+ };
178
+ const order = [
179
+ "clone",
180
+ "detect",
181
+ "install",
182
+ "build",
183
+ "bundle",
184
+ "upload",
185
+ "provision",
186
+ "activate",
187
+ "cleanup",
188
+ ];
189
+ const meta = log.metadata;
190
+ const statusColour = meta.status === "success" ? "\x1b[32m" :
191
+ meta.status === "failed" ? "\x1b[31m" :
192
+ "\x1b[33m";
193
+ consola.log(`\n deployment ${fullId.slice(0, 8)} ${statusColour}${meta.status}\x1b[0m`);
194
+ if (meta.errorCode)
195
+ consola.log(` error code: ${meta.errorCode}`);
196
+ if (meta.errorStep)
197
+ consola.log(` failed at: ${meta.errorStep}`);
198
+ consola.log("");
199
+ for (const step of order) {
200
+ const lines = grouped.get(step);
201
+ if (!lines)
202
+ continue;
203
+ const header = headerLabels[step] ?? step;
204
+ const duration = stepDuration(lines);
205
+ const failed = meta.status === "failed" && meta.errorStep === step;
206
+ const icon = failed ? "\x1b[31m✗\x1b[0m" : "\x1b[32m✓\x1b[0m";
207
+ const label = failed ? `\x1b[31m${header}\x1b[0m` : header;
208
+ consola.log(` ${icon} ${label}${duration ? ` (${duration})` : ""}`);
209
+ for (const l of lines) {
210
+ const lineColour = l.level === "error" || l.level === "fatal" ? "\x1b[31m" :
211
+ l.level === "warn" ? "\x1b[33m" :
212
+ "\x1b[90m";
213
+ consola.log(` ${lineColour}${l.msg}\x1b[0m`);
214
+ }
215
+ }
216
+ if (meta.truncated) {
217
+ consola.warn(" (log was truncated at 5MB / 200k lines)");
218
+ }
219
+ },
220
+ });
221
+ function groupByStep(entries) {
222
+ const map = new Map();
223
+ for (const e of entries) {
224
+ let bucket = map.get(e.step);
225
+ if (!bucket) {
226
+ bucket = [];
227
+ map.set(e.step, bucket);
228
+ }
229
+ bucket.push(e);
230
+ }
231
+ return map;
232
+ }
233
+ function stepDuration(lines) {
234
+ if (lines.length < 2)
235
+ return null;
236
+ const sorted = [...lines].sort((a, b) => a.ts - b.ts);
237
+ const ms = sorted[sorted.length - 1].ts - sorted[0].ts;
238
+ if (ms < 1000)
239
+ return `${ms}ms`;
240
+ if (ms < 60_000)
241
+ return `${(ms / 1000).toFixed(1)}s`;
242
+ return `${Math.floor(ms / 60_000)}m ${Math.floor((ms % 60_000) / 1000)}s`;
243
+ }
244
+ // --- Public export ---
245
+ export const deploymentsCommand = defineCommand({
246
+ meta: {
247
+ name: "deployments",
248
+ description: "List deployments and read build logs",
249
+ },
250
+ args: {
251
+ project: {
252
+ type: "string",
253
+ description: "Project slug (default: from creek.toml)",
254
+ required: false,
255
+ },
256
+ ...globalArgs,
257
+ },
258
+ subCommands: {
259
+ list: deploymentsList,
260
+ logs: deploymentsLogs,
261
+ },
262
+ // Default behaviour (no subcommand) = list. Citty resolves
263
+ // subcommands first, so this only fires when the user types
264
+ // `creek deployments` with no trailing verb.
265
+ async run(ctx) {
266
+ const run = deploymentsList.run;
267
+ if (!run)
268
+ return;
269
+ return run(ctx);
270
+ },
271
+ });
77
272
  function timeAgo(dateStr) {
78
273
  const diff = Date.now() - new Date(dateStr).getTime();
79
274
  const mins = Math.floor(diff / 60_000);
@@ -25,5 +25,15 @@ export declare const doctorCommand: import("citty").CommandDef<{
25
25
  description: string;
26
26
  required: false;
27
27
  };
28
+ last: {
29
+ type: "boolean";
30
+ description: string;
31
+ default: false;
32
+ };
33
+ project: {
34
+ type: "string";
35
+ description: string;
36
+ required: false;
37
+ };
28
38
  }>;
29
39
  //# sourceMappingURL=doctor.d.ts.map
@@ -3,7 +3,9 @@ import consola from "consola";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
4
  import { join, resolve } from "node:path";
5
5
  import { runDoctor, resolveConfig, ConfigNotFoundError, } from "@solcreek/sdk";
6
- import { globalArgs, resolveJsonMode, jsonOutput, } from "../utils/output.js";
6
+ import { CreekClient } from "@solcreek/sdk";
7
+ import { getToken, getApiUrl } from "../utils/config.js";
8
+ import { globalArgs, resolveJsonMode, jsonOutput, AUTH_BREADCRUMBS, } from "../utils/output.js";
7
9
  /**
8
10
  * `creek doctor` — pre-deploy sanity check.
9
11
  *
@@ -26,11 +28,29 @@ export const doctorCommand = defineCommand({
26
28
  description: "Project directory to analyze. Defaults to cwd.",
27
29
  required: false,
28
30
  },
31
+ last: {
32
+ type: "boolean",
33
+ description: "Diagnose the most recent FAILED deployment instead of running pre-deploy checks. Fetches the build log and matches errorCode against the CK-* fix table.",
34
+ default: false,
35
+ },
36
+ project: {
37
+ type: "string",
38
+ description: "Project slug for --last (default: read from creek.toml in cwd)",
39
+ required: false,
40
+ },
29
41
  ...globalArgs,
30
42
  },
31
43
  async run({ args }) {
32
- const cwd = resolve(args.path ?? process.cwd());
33
44
  const jsonMode = resolveJsonMode(args);
45
+ if (args.last) {
46
+ await runLastFailureDiagnosis({
47
+ project: args.project,
48
+ cwd: resolve(args.path ?? process.cwd()),
49
+ jsonMode,
50
+ });
51
+ return;
52
+ }
53
+ const cwd = resolve(args.path ?? process.cwd());
34
54
  const ctx = buildContext(cwd);
35
55
  const report = runDoctor(ctx);
36
56
  if (jsonMode) {
@@ -171,4 +191,154 @@ function groupBy(arr, key) {
171
191
  function s(n) {
172
192
  return n === 1 ? "" : "s";
173
193
  }
194
+ // ─── `--last` failure diagnosis ─────────────────────────────────────────
195
+ //
196
+ // CK-code → one-line fix hint. Source of truth in product terms is
197
+ // skills/creek/references/diagnosis.md; the MCP server's get_build_log
198
+ // tool carries the same mapping (packages/mcp-server/src/tools.ts
199
+ // CK_FIX_HINTS). Keep all three in sync when adding a new CK-* rule.
200
+ const CK_FIX_HINTS = {
201
+ "CK-NO-CONFIG": "Run `creek init` to scaffold a creek.toml, or cd to a directory that contains creek.toml / wrangler.* / package.json / index.html.",
202
+ "CK-NOTHING-TO-DEPLOY": "Run the project's build command so there's output in [build].output, or set [build].command in creek.toml if the project needs one.",
203
+ "CK-DB-DUAL-DRIVER-SPLIT": "Consolidate the split db.local.ts + db.prod.ts files. Share schema.ts and routes.ts; keep only thin boot files (server/local.ts for dev, server/worker.ts for prod) that differ in driver setup. See examples/vite-react-drizzle.",
204
+ "CK-SYNC-SQLITE": "better-sqlite3 is synchronous and won't run on Workers. Migrate to an async ORM with a D1 adapter — Drizzle or Kysely are the drop-in paths.",
205
+ "CK-PRISMA-SQLITE": "Prisma's SQLite datasource isn't supported on Cloudflare Workers. Switch to Drizzle or Kysely with a D1 adapter.",
206
+ "CK-RUNTIME-LOCKIN": "The project imports from @solcreek/* runtime packages. For a portable build that can deploy outside Creek, replace those with driver-level imports (e.g. drizzle-orm/d1 instead of creek's db re-export).",
207
+ "CK-CONFIG-OVERLAP": "Both creek.toml and wrangler.* are present. Pick one as the source of truth — creek.toml is preferred; remove wrangler.* or update any shared fields to match.",
208
+ };
209
+ function suggestFix(code) {
210
+ if (!code)
211
+ return null;
212
+ return CK_FIX_HINTS[code] ?? null;
213
+ }
214
+ async function runLastFailureDiagnosis(opts) {
215
+ const token = getToken();
216
+ if (!token) {
217
+ if (opts.jsonMode) {
218
+ jsonOutput({ ok: false, error: "not_authenticated" }, 1, AUTH_BREADCRUMBS);
219
+ }
220
+ consola.error("Not authenticated. Run `creek login` first.");
221
+ process.exit(1);
222
+ }
223
+ // Resolve project slug — prefer --project, fall back to creek.toml
224
+ let slug = opts.project;
225
+ if (!slug) {
226
+ const creekToml = join(opts.cwd, "creek.toml");
227
+ if (existsSync(creekToml)) {
228
+ const raw = safeRead(creekToml);
229
+ const match = raw?.match(/^\s*name\s*=\s*["']([^"']+)["']/m);
230
+ if (match)
231
+ slug = match[1];
232
+ }
233
+ }
234
+ if (!slug) {
235
+ const msg = "No project slug. Pass --project <slug> or run from a directory with creek.toml.";
236
+ if (opts.jsonMode)
237
+ jsonOutput({ ok: false, error: "no_project", message: msg }, 1);
238
+ consola.error(msg);
239
+ process.exit(1);
240
+ }
241
+ const client = new CreekClient(getApiUrl(), token);
242
+ // Find the most recent failed deployment.
243
+ let deployments;
244
+ try {
245
+ deployments = await client.listDeployments(slug);
246
+ }
247
+ catch (err) {
248
+ const msg = err instanceof Error ? err.message : "Failed to list deployments";
249
+ if (opts.jsonMode)
250
+ jsonOutput({ ok: false, error: "api_error", message: msg }, 1);
251
+ consola.error(msg);
252
+ process.exit(1);
253
+ }
254
+ const failed = deployments.find((d) => d.status === "failed");
255
+ if (!failed) {
256
+ const msg = `No failed deployments for ${slug}. Last ${deployments.length} deploys succeeded.`;
257
+ if (opts.jsonMode)
258
+ jsonOutput({ ok: true, project: slug, failed: null, message: msg }, 0);
259
+ consola.log("");
260
+ consola.log(` ${c("⬡ creek doctor --last", "bold")} ${c(slug, "dim")}`);
261
+ consola.log(` ${c("✓", "green")} ${msg}`);
262
+ consola.log("");
263
+ return;
264
+ }
265
+ // Pull the build log.
266
+ let log;
267
+ try {
268
+ log = await client.getBuildLog(slug, failed.id);
269
+ }
270
+ catch (err) {
271
+ const msg = err instanceof Error ? err.message : "Failed to read build log";
272
+ if (opts.jsonMode)
273
+ jsonOutput({ ok: false, error: "api_error", message: msg }, 1);
274
+ consola.error(msg);
275
+ process.exit(1);
276
+ }
277
+ const meta = log.metadata;
278
+ const errorCode = meta?.errorCode ?? null;
279
+ const errorStep = meta?.errorStep ?? null;
280
+ const fix = suggestFix(errorCode);
281
+ if (opts.jsonMode) {
282
+ jsonOutput({
283
+ ok: true,
284
+ project: slug,
285
+ failed: {
286
+ id: failed.id,
287
+ version: failed.version,
288
+ branch: failed.branch,
289
+ commitSha: failed.commit_sha,
290
+ errorCode,
291
+ errorStep,
292
+ suggestedFix: fix,
293
+ failedStep: failed.failed_step,
294
+ errorMessage: failed.error_message,
295
+ },
296
+ }, 0, [
297
+ {
298
+ command: `creek deployments logs ${failed.id.slice(0, 8)} --json`,
299
+ description: "Read the full build log",
300
+ },
301
+ { command: `creek deploy --json`, description: "Redeploy after fixing" },
302
+ ]);
303
+ return;
304
+ }
305
+ // Human output.
306
+ consola.log("");
307
+ consola.log(` ${c("⬡ creek doctor --last", "bold")} ${c(slug, "dim")}`);
308
+ consola.log(` ${c("failed deploy:", "dim")} ${failed.id.slice(0, 8)}${failed.branch ? ` (${failed.branch})` : ""}`);
309
+ consola.log("");
310
+ if (errorCode) {
311
+ consola.log(` ${c("✗", "red")} ${c(errorCode, "bold")} ${c(`at step: ${errorStep ?? "unknown"}`, "gray")}`);
312
+ }
313
+ else if (errorStep) {
314
+ consola.log(` ${c("✗", "red")} Failed at step: ${c(errorStep, "bold")}`);
315
+ }
316
+ else {
317
+ consola.log(` ${c("✗", "red")} Deploy failed — no structured errorCode available.`);
318
+ }
319
+ consola.log("");
320
+ if (fix) {
321
+ consola.log(` ${c("→ fix:", "cyan")}`);
322
+ for (const line of fix.split("\n"))
323
+ consola.log(` ${line}`);
324
+ consola.log("");
325
+ }
326
+ else if (errorCode) {
327
+ consola.log(` ${c("→ no mapped fix for", "dim")} ${c(errorCode, "bold")}${c(". Inspect the full log:", "dim")}`);
328
+ consola.log(` creek deployments logs ${failed.id.slice(0, 8)}`);
329
+ consola.log("");
330
+ }
331
+ else {
332
+ consola.log(` ${c("→ inspect the full log:", "cyan")}`);
333
+ consola.log(` creek deployments logs ${failed.id.slice(0, 8)}`);
334
+ consola.log("");
335
+ }
336
+ if (failed.error_message) {
337
+ consola.log(` ${c("error message:", "dim")}`);
338
+ for (const line of failed.error_message.split("\n").slice(0, 8)) {
339
+ consola.log(` ${c(line, "gray")}`);
340
+ }
341
+ consola.log("");
342
+ }
343
+ }
174
344
  //# sourceMappingURL=doctor.js.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Migration logic for `creek db migrate`.
3
+ *
4
+ * Pure functions are exported for testing. The CLI command wires
5
+ * them together with the SDK client in db.ts.
6
+ */
7
+ /**
8
+ * Find the migration directory relative to cwd.
9
+ * Returns the absolute path of the first candidate that exists and
10
+ * contains at least one .sql file, or null if none found.
11
+ */
12
+ export declare function detectMigrationDir(cwd: string): string | null;
13
+ export interface MigrationFile {
14
+ /** File name without directory (e.g. "0001_init.sql") */
15
+ name: string;
16
+ /** Absolute path */
17
+ path: string;
18
+ }
19
+ /**
20
+ * Read .sql files from a directory, sorted lexicographically.
21
+ * Non-.sql files and empty .sql files are skipped.
22
+ */
23
+ export declare function parseMigrationFiles(dir: string): MigrationFile[];
24
+ /**
25
+ * Split a migration file's SQL content into individual statements.
26
+ *
27
+ * If the content contains Drizzle's `--> statement-breakpoint` marker,
28
+ * split on those. Otherwise split on semicolons. Empty statements are
29
+ * filtered out.
30
+ */
31
+ export declare function splitStatements(sql: string): string[];
32
+ /**
33
+ * Given files on disk and names already applied, return the pending
34
+ * migrations in order.
35
+ */
36
+ export declare function computePending(files: MigrationFile[], applied: Set<string>): MigrationFile[];
37
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Migration logic for `creek db migrate`.
3
+ *
4
+ * Pure functions are exported for testing. The CLI command wires
5
+ * them together with the SDK client in db.ts.
6
+ */
7
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
8
+ import { join, resolve } from "node:path";
9
+ // --- Auto-detect migration directory ---
10
+ const CANDIDATE_DIRS = [
11
+ "drizzle",
12
+ "drizzle/migrations",
13
+ "migrations",
14
+ "sql",
15
+ ];
16
+ /**
17
+ * Find the migration directory relative to cwd.
18
+ * Returns the absolute path of the first candidate that exists and
19
+ * contains at least one .sql file, or null if none found.
20
+ */
21
+ export function detectMigrationDir(cwd) {
22
+ for (const dir of CANDIDATE_DIRS) {
23
+ const abs = resolve(cwd, dir);
24
+ if (existsSync(abs)) {
25
+ try {
26
+ const files = readdirSync(abs);
27
+ if (files.some((f) => f.endsWith(".sql")))
28
+ return abs;
29
+ }
30
+ catch {
31
+ // Permission error or not a directory — skip
32
+ }
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+ /**
38
+ * Read .sql files from a directory, sorted lexicographically.
39
+ * Non-.sql files and empty .sql files are skipped.
40
+ */
41
+ export function parseMigrationFiles(dir) {
42
+ let entries;
43
+ try {
44
+ entries = readdirSync(dir);
45
+ }
46
+ catch {
47
+ return [];
48
+ }
49
+ return entries
50
+ .filter((f) => f.endsWith(".sql"))
51
+ .sort()
52
+ .map((name) => ({ name, path: join(dir, name) }))
53
+ .filter((f) => {
54
+ try {
55
+ const content = readFileSync(f.path, "utf-8").trim();
56
+ return content.length > 0;
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ });
62
+ }
63
+ // --- Split SQL statements ---
64
+ const DRIZZLE_BREAKPOINT = "--> statement-breakpoint";
65
+ /**
66
+ * Split a migration file's SQL content into individual statements.
67
+ *
68
+ * If the content contains Drizzle's `--> statement-breakpoint` marker,
69
+ * split on those. Otherwise split on semicolons. Empty statements are
70
+ * filtered out.
71
+ */
72
+ export function splitStatements(sql) {
73
+ const trimmed = sql.trim();
74
+ if (!trimmed)
75
+ return [];
76
+ let parts;
77
+ if (trimmed.includes(DRIZZLE_BREAKPOINT)) {
78
+ parts = trimmed.split(DRIZZLE_BREAKPOINT);
79
+ }
80
+ else {
81
+ // Split on semicolons but preserve them — each statement should
82
+ // include its trailing semicolon for D1 execution.
83
+ parts = trimmed
84
+ .split(/;(?=\s|$)/)
85
+ .map((s) => s.trim())
86
+ .filter((s) => s.length > 0)
87
+ .map((s) => (s.endsWith(";") ? s : s + ";"));
88
+ }
89
+ return parts.map((s) => s.trim()).filter((s) => s.length > 0);
90
+ }
91
+ // --- Compute pending migrations ---
92
+ /**
93
+ * Given files on disk and names already applied, return the pending
94
+ * migrations in order.
95
+ */
96
+ export function computePending(files, applied) {
97
+ return files.filter((f) => !applied.has(f.name));
98
+ }
99
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Factory for resource subcommands (creek db, creek storage, creek cache).
3
+ *
4
+ * All resource kinds share the same CRUD shape:
5
+ * ls, create, attach, detach, rename, delete
6
+ *
7
+ * Only the `kind` value and display labels differ.
8
+ */
9
+ interface ResourceCmdOptions {
10
+ kind: "database" | "storage" | "cache" | "ai";
11
+ /** Singular label for display, e.g. "database", "storage bucket", "cache namespace" */
12
+ label: string;
13
+ /** Default binding name, e.g. "DB", "STORAGE", "KV" */
14
+ defaultBinding: string;
15
+ }
16
+ export declare function createResourceCommand(opts: ResourceCmdOptions): import("citty").CommandDef<import("citty").ArgsDef>;
17
+ export {};
18
+ //# sourceMappingURL=resource-cmd.d.ts.map