@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.
- package/dist/commands/cache.d.ts +2 -0
- package/dist/commands/cache.js +7 -0
- package/dist/commands/db.d.ts +2 -0
- package/dist/commands/db.js +334 -0
- package/dist/commands/deploy.js +51 -4
- package/dist/commands/deployments.js +210 -15
- package/dist/commands/doctor.d.ts +10 -0
- package/dist/commands/doctor.js +172 -2
- package/dist/commands/migrate.d.ts +37 -0
- package/dist/commands/migrate.js +99 -0
- package/dist/commands/resource-cmd.d.ts +18 -0
- package/dist/commands/resource-cmd.js +216 -0
- package/dist/commands/storage.d.ts +2 -0
- package/dist/commands/storage.js +7 -0
- package/dist/index.js +6 -0
- package/dist/utils/build-log.d.ts +36 -0
- package/dist/utils/build-log.js +40 -0
- package/package.json +2 -2
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { defineCommand } from "citty";
|
|
2
|
+
import consola from "consola";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
5
|
+
import { resolve } from "node:path";
|
|
6
|
+
import { CreekClient } from "@solcreek/sdk";
|
|
7
|
+
import { createResourceCommand } from "./resource-cmd.js";
|
|
8
|
+
import { detectMigrationDir, parseMigrationFiles, splitStatements, computePending } from "./migrate.js";
|
|
9
|
+
import { getToken, getApiUrl } from "../utils/config.js";
|
|
10
|
+
import { globalArgs, resolveJsonMode, jsonOutput, AUTH_BREADCRUMBS } from "../utils/output.js";
|
|
11
|
+
const base = createResourceCommand({
|
|
12
|
+
kind: "database",
|
|
13
|
+
label: "database",
|
|
14
|
+
defaultBinding: "DB",
|
|
15
|
+
});
|
|
16
|
+
const shellCommand = defineCommand({
|
|
17
|
+
meta: {
|
|
18
|
+
name: "shell",
|
|
19
|
+
description: "Execute SQL against a team database. Interactive REPL or single query with --sql.",
|
|
20
|
+
},
|
|
21
|
+
args: {
|
|
22
|
+
name: {
|
|
23
|
+
type: "positional",
|
|
24
|
+
description: "Database name (as shown by `creek db ls`)",
|
|
25
|
+
required: true,
|
|
26
|
+
},
|
|
27
|
+
sql: {
|
|
28
|
+
type: "string",
|
|
29
|
+
description: "SQL query to execute (non-interactive mode). Omit for interactive REPL.",
|
|
30
|
+
required: false,
|
|
31
|
+
},
|
|
32
|
+
...globalArgs,
|
|
33
|
+
},
|
|
34
|
+
async run({ args }) {
|
|
35
|
+
const jsonMode = resolveJsonMode(args);
|
|
36
|
+
const token = getToken();
|
|
37
|
+
if (!token) {
|
|
38
|
+
if (jsonMode)
|
|
39
|
+
jsonOutput({ ok: false, error: "not_authenticated" }, 1, AUTH_BREADCRUMBS);
|
|
40
|
+
consola.error("Not authenticated. Run `creek login` first.");
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
const client = new CreekClient(getApiUrl(), token);
|
|
44
|
+
// Resolve name → resource ID
|
|
45
|
+
const { resources } = await client.listResources();
|
|
46
|
+
const db = resources.find((r) => r.name === args.name && r.kind === "database");
|
|
47
|
+
if (!db) {
|
|
48
|
+
if (jsonMode)
|
|
49
|
+
jsonOutput({ ok: false, error: "not_found", message: `No database named "${args.name}"` }, 1);
|
|
50
|
+
consola.error(`No database named "${args.name}"`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
// Single query mode
|
|
54
|
+
if (args.sql) {
|
|
55
|
+
await executeAndPrint(client, db.id, args.sql, jsonMode);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Interactive REPL
|
|
59
|
+
consola.info(`Connected to ${args.name} (${db.id.slice(0, 8)})`);
|
|
60
|
+
consola.info("Type SQL and press Enter. Use .exit to quit.\n");
|
|
61
|
+
const rl = createInterface({
|
|
62
|
+
input: process.stdin,
|
|
63
|
+
output: process.stdout,
|
|
64
|
+
prompt: "sql> ",
|
|
65
|
+
});
|
|
66
|
+
rl.prompt();
|
|
67
|
+
let buffer = "";
|
|
68
|
+
rl.on("line", async (line) => {
|
|
69
|
+
const trimmed = line.trim();
|
|
70
|
+
if (trimmed === ".exit" || trimmed === ".quit") {
|
|
71
|
+
rl.close();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (trimmed === ".tables") {
|
|
75
|
+
buffer = "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
|
|
76
|
+
}
|
|
77
|
+
else if (trimmed === ".schema") {
|
|
78
|
+
buffer = "SELECT sql FROM sqlite_master WHERE sql IS NOT NULL ORDER BY name";
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
buffer += (buffer ? "\n" : "") + line;
|
|
82
|
+
}
|
|
83
|
+
// Execute when line ends with semicolon or is a dot-command
|
|
84
|
+
if (!buffer.endsWith(";") && !trimmed.startsWith(".")) {
|
|
85
|
+
rl.setPrompt("...> ");
|
|
86
|
+
rl.prompt();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
await executeAndPrint(client, db.id, buffer, false);
|
|
90
|
+
buffer = "";
|
|
91
|
+
rl.setPrompt("sql> ");
|
|
92
|
+
rl.prompt();
|
|
93
|
+
});
|
|
94
|
+
rl.on("close", () => {
|
|
95
|
+
consola.info("Bye.");
|
|
96
|
+
process.exit(0);
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
async function executeAndPrint(client, resourceId, sql, jsonMode) {
|
|
101
|
+
try {
|
|
102
|
+
const result = await client.queryDatabase(resourceId, sql);
|
|
103
|
+
if (jsonMode) {
|
|
104
|
+
jsonOutput({ ok: true, ...result }, 0);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (result.rows.length === 0) {
|
|
108
|
+
if (result.meta.changes > 0) {
|
|
109
|
+
consola.info(`${result.meta.changes} row(s) changed (${result.meta.duration.toFixed(1)}ms)`);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
consola.info("No results.");
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// Print table
|
|
117
|
+
const cols = result.columns;
|
|
118
|
+
const widths = cols.map((c) => c.length);
|
|
119
|
+
for (const row of result.rows) {
|
|
120
|
+
for (let i = 0; i < cols.length; i++) {
|
|
121
|
+
const val = String(row[cols[i]] ?? "NULL");
|
|
122
|
+
widths[i] = Math.max(widths[i], val.length);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Cap column widths
|
|
126
|
+
const maxWidth = 40;
|
|
127
|
+
const cappedWidths = widths.map((w) => Math.min(w, maxWidth));
|
|
128
|
+
const header = cols.map((c, i) => c.padEnd(cappedWidths[i])).join(" ");
|
|
129
|
+
const separator = cappedWidths.map((w) => "─".repeat(w)).join("──");
|
|
130
|
+
console.log(header);
|
|
131
|
+
console.log(separator);
|
|
132
|
+
for (const row of result.rows) {
|
|
133
|
+
const line = cols
|
|
134
|
+
.map((c, i) => {
|
|
135
|
+
const val = String(row[c] ?? "NULL");
|
|
136
|
+
return val.length > maxWidth ? val.slice(0, maxWidth - 1) + "…" : val.padEnd(cappedWidths[i]);
|
|
137
|
+
})
|
|
138
|
+
.join(" ");
|
|
139
|
+
console.log(line);
|
|
140
|
+
}
|
|
141
|
+
consola.info(`\n${result.rows.length} row(s) · ${result.meta.duration.toFixed(1)}ms`);
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
145
|
+
if (jsonMode) {
|
|
146
|
+
jsonOutput({ ok: false, error: "query_failed", message: msg }, 1);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
consola.error(msg);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const migrateCommand = defineCommand({
|
|
154
|
+
meta: {
|
|
155
|
+
name: "migrate",
|
|
156
|
+
description: "Apply pending SQL migrations to a team database. Reads .sql files from a migration directory, tracks applied state, executes in order.",
|
|
157
|
+
},
|
|
158
|
+
args: {
|
|
159
|
+
name: {
|
|
160
|
+
type: "positional",
|
|
161
|
+
description: "Database name (as shown by `creek db ls`)",
|
|
162
|
+
required: true,
|
|
163
|
+
},
|
|
164
|
+
dir: {
|
|
165
|
+
type: "string",
|
|
166
|
+
description: "Migration directory path. Default: auto-detect drizzle/, drizzle/migrations/, migrations/, sql/",
|
|
167
|
+
required: false,
|
|
168
|
+
},
|
|
169
|
+
"dry-run": {
|
|
170
|
+
type: "boolean",
|
|
171
|
+
description: "Show pending migrations without executing them.",
|
|
172
|
+
default: false,
|
|
173
|
+
},
|
|
174
|
+
...globalArgs,
|
|
175
|
+
},
|
|
176
|
+
async run({ args }) {
|
|
177
|
+
const jsonMode = resolveJsonMode(args);
|
|
178
|
+
const token = getToken();
|
|
179
|
+
if (!token) {
|
|
180
|
+
if (jsonMode)
|
|
181
|
+
jsonOutput({ ok: false, error: "not_authenticated" }, 1, AUTH_BREADCRUMBS);
|
|
182
|
+
consola.error("Not authenticated. Run `creek login` first.");
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
const client = new CreekClient(getApiUrl(), token);
|
|
186
|
+
// 1. Resolve database name → resource ID
|
|
187
|
+
const { resources } = await client.listResources();
|
|
188
|
+
const db = resources.find((r) => r.name === args.name && r.kind === "database");
|
|
189
|
+
if (!db) {
|
|
190
|
+
if (jsonMode)
|
|
191
|
+
jsonOutput({ ok: false, error: "not_found", message: `No database named "${args.name}"` }, 1);
|
|
192
|
+
consola.error(`No database named "${args.name}"`);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
// 2. Find migration directory
|
|
196
|
+
const cwd = process.cwd();
|
|
197
|
+
const migrationDir = args.dir ? resolve(cwd, args.dir) : detectMigrationDir(cwd);
|
|
198
|
+
if (!migrationDir) {
|
|
199
|
+
const msg = args.dir
|
|
200
|
+
? `Migration directory not found: ${args.dir}`
|
|
201
|
+
: "No migration directory found. Looked for: drizzle/, drizzle/migrations/, migrations/, sql/. Use --dir to specify.";
|
|
202
|
+
if (jsonMode)
|
|
203
|
+
jsonOutput({ ok: false, error: "no_migration_dir", message: msg }, 1);
|
|
204
|
+
consola.error(msg);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
// 3. Read migration files
|
|
208
|
+
const files = parseMigrationFiles(migrationDir);
|
|
209
|
+
if (files.length === 0) {
|
|
210
|
+
if (jsonMode)
|
|
211
|
+
jsonOutput({ ok: true, message: "No .sql files found", applied: 0, pending: 0 }, 0);
|
|
212
|
+
consola.info(`No .sql files found in ${migrationDir}`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// 4. Create tracking table + query applied migrations
|
|
216
|
+
try {
|
|
217
|
+
await client.queryDatabase(db.id, `CREATE TABLE IF NOT EXISTS _creek_migrations (
|
|
218
|
+
name TEXT PRIMARY KEY,
|
|
219
|
+
applied_at INTEGER NOT NULL
|
|
220
|
+
);`);
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
const msg = `Failed to create migration tracking table: ${err instanceof Error ? err.message : String(err)}`;
|
|
224
|
+
if (jsonMode)
|
|
225
|
+
jsonOutput({ ok: false, error: "tracking_table_failed", message: msg }, 1);
|
|
226
|
+
consola.error(msg);
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
let appliedRows;
|
|
230
|
+
try {
|
|
231
|
+
const result = await client.queryDatabase(db.id, "SELECT name FROM _creek_migrations ORDER BY name;");
|
|
232
|
+
appliedRows = result.rows;
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
const msg = `Failed to query applied migrations: ${err instanceof Error ? err.message : String(err)}`;
|
|
236
|
+
if (jsonMode)
|
|
237
|
+
jsonOutput({ ok: false, error: "query_failed", message: msg }, 1);
|
|
238
|
+
consola.error(msg);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
const appliedSet = new Set(appliedRows.map((r) => r.name));
|
|
242
|
+
const pending = computePending(files, appliedSet);
|
|
243
|
+
// 5. Dry-run
|
|
244
|
+
if (args["dry-run"]) {
|
|
245
|
+
if (jsonMode) {
|
|
246
|
+
jsonOutput({
|
|
247
|
+
ok: true,
|
|
248
|
+
dryRun: true,
|
|
249
|
+
total: files.length,
|
|
250
|
+
applied: appliedSet.size,
|
|
251
|
+
pending: pending.map((f) => f.name),
|
|
252
|
+
}, 0);
|
|
253
|
+
}
|
|
254
|
+
else if (pending.length === 0) {
|
|
255
|
+
consola.success(`Database "${args.name}" is up to date (${appliedSet.size} applied)`);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
consola.info(`${pending.length} pending migration(s):\n`);
|
|
259
|
+
for (const f of pending) {
|
|
260
|
+
consola.log(` ${f.name}`);
|
|
261
|
+
}
|
|
262
|
+
consola.info(`\nRun without --dry-run to apply.`);
|
|
263
|
+
}
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// 6. Apply pending
|
|
267
|
+
if (pending.length === 0) {
|
|
268
|
+
if (jsonMode)
|
|
269
|
+
jsonOutput({ ok: true, message: "up to date", applied: appliedSet.size, migrated: 0 }, 0);
|
|
270
|
+
consola.success(`Database "${args.name}" is up to date (${appliedSet.size} applied)`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
consola.info(`Migrating "${args.name}": ${pending.length} pending of ${files.length} total\n`);
|
|
274
|
+
let migrated = 0;
|
|
275
|
+
for (const file of pending) {
|
|
276
|
+
const sql = readFileSync(file.path, "utf-8");
|
|
277
|
+
const statements = splitStatements(sql);
|
|
278
|
+
if (statements.length === 0) {
|
|
279
|
+
consola.warn(` ${file.name} — empty, skipping`);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
consola.start(` ${file.name} (${statements.length} statement${statements.length > 1 ? "s" : ""})`);
|
|
283
|
+
let failed = false;
|
|
284
|
+
for (let i = 0; i < statements.length; i++) {
|
|
285
|
+
try {
|
|
286
|
+
await client.queryDatabase(db.id, statements[i]);
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
290
|
+
consola.error(` ${file.name} — statement ${i + 1}/${statements.length} failed: ${msg}`);
|
|
291
|
+
if (jsonMode) {
|
|
292
|
+
jsonOutput({
|
|
293
|
+
ok: false,
|
|
294
|
+
error: "migration_failed",
|
|
295
|
+
file: file.name,
|
|
296
|
+
statement: i + 1,
|
|
297
|
+
totalStatements: statements.length,
|
|
298
|
+
message: msg,
|
|
299
|
+
migrated,
|
|
300
|
+
remaining: pending.length - migrated,
|
|
301
|
+
}, 1);
|
|
302
|
+
}
|
|
303
|
+
consola.error(`\n${migrated} migration(s) applied before failure. Fix the SQL and re-run.`);
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
// Record success
|
|
308
|
+
try {
|
|
309
|
+
await client.queryDatabase(db.id, "INSERT INTO _creek_migrations (name, applied_at) VALUES (?, ?);", [file.name, Date.now()]);
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
// Non-fatal — migration ran but tracking failed. It won't re-run
|
|
313
|
+
// because the schema changes already happened. Warn and continue.
|
|
314
|
+
consola.warn(` ${file.name} — applied but failed to record in tracking table`);
|
|
315
|
+
}
|
|
316
|
+
migrated++;
|
|
317
|
+
consola.success(` ${file.name}`);
|
|
318
|
+
}
|
|
319
|
+
if (jsonMode) {
|
|
320
|
+
jsonOutput({ ok: true, migrated, total: files.length, applied: appliedSet.size + migrated }, 0);
|
|
321
|
+
}
|
|
322
|
+
consola.success(`\n${migrated} migration(s) applied successfully`);
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
// Merge subcommands into the base resource command
|
|
326
|
+
export const dbCommand = defineCommand({
|
|
327
|
+
meta: base.meta,
|
|
328
|
+
subCommands: {
|
|
329
|
+
...base.subCommands,
|
|
330
|
+
shell: shellCommand,
|
|
331
|
+
migrate: migrateCommand,
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
//# sourceMappingURL=db.js.map
|
package/dist/commands/deploy.js
CHANGED
|
@@ -4,11 +4,12 @@ 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,
|
|
7
|
+
import { CreekClient, CreekAuthError, detectFramework, resolveConfig, formatDetectionSummary, resolvedConfigToBindingRequirements, ConfigNotFoundError, detectNextjsMode, detectMonorepo, } from "@solcreek/sdk";
|
|
8
8
|
import { getToken, getApiUrl } from "../utils/config.js";
|
|
9
9
|
import { collectAssets } from "../utils/bundle.js";
|
|
10
10
|
import { sandboxDeploy, pollSandboxStatus, printSandboxSuccess } from "../utils/sandbox.js";
|
|
11
11
|
import { prepareDeployBundle } from "../utils/prepare-bundle.js";
|
|
12
|
+
import { BuildLogEmitter } from "../utils/build-log.js";
|
|
12
13
|
import { isTTY, jsonOutput, resolveJsonMode, globalArgs, shouldAutoConfirm, AUTH_BREADCRUMBS, NO_PROJECT_BREADCRUMBS } from "../utils/output.js";
|
|
13
14
|
import { ensureTosAccepted } from "../utils/tos.js";
|
|
14
15
|
import { hasAdapterOutput } from "../utils/nextjs.js";
|
|
@@ -917,11 +918,30 @@ async function deployAuthenticated(cwd, resolved, token, skipBuild, jsonMode = f
|
|
|
917
918
|
const prepared = await prepareDeployBundle({ cwd, resolved, skipBuild });
|
|
918
919
|
const { plan, framework: detectedFramework, effectiveRenderMode, effectiveEntrypoint, fileList, assets: clientAssets, serverFiles, } = prepared;
|
|
919
920
|
void detectedFramework; // framework var above is the source of truth here
|
|
921
|
+
// Resource / runtime anchors — inline lines that pre-empt the most
|
|
922
|
+
// common wrong assumptions an AI agent reads from Creek running on
|
|
923
|
+
// Cloudflare. Cheap to print, and they parse them directly.
|
|
924
|
+
const dbDeps = !!resolved.bindings.find((b) => b.type === "d1") ||
|
|
925
|
+
fileList.some((f) => /\.(db|sqlite)$/i.test(f));
|
|
926
|
+
if (!jsonMode && dbDeps) {
|
|
927
|
+
consola.info(" ℹ Database: Creek uses the portable driver — better-sqlite3 locally, D1 remotely. Your code reads env.DB in both. Do NOT rewrite for D1 manually; `creek db attach` wires the binding.");
|
|
928
|
+
}
|
|
920
929
|
section("Upload");
|
|
921
930
|
consola.info(` ${fileList.length} assets (${assetSummary(fileList)})`);
|
|
922
931
|
section("Deploy");
|
|
923
932
|
consola.start(" Creating deployment...");
|
|
924
933
|
const { deployment } = await client.createDeployment(project.id);
|
|
934
|
+
// Collect a minimal structured build log for the dashboard. We emit
|
|
935
|
+
// one line per high-level phase — verbose stdout capture is a
|
|
936
|
+
// Phase 2 concern. The log is POSTed once we reach a terminal
|
|
937
|
+
// status (success / failed), so the dashboard panel shows something
|
|
938
|
+
// useful for every authenticated deploy.
|
|
939
|
+
const buildLog = new BuildLogEmitter();
|
|
940
|
+
buildLog.info("detect", `framework=${framework ?? "none"} renderMode=${effectiveRenderMode} entrypoint=${effectiveEntrypoint ?? "none"}`);
|
|
941
|
+
if (resolved.buildCommand) {
|
|
942
|
+
buildLog.info("build", `ran: ${resolved.buildCommand}`);
|
|
943
|
+
}
|
|
944
|
+
buildLog.info("bundle", `${fileList.length} assets (${assetSummary(fileList)})`);
|
|
925
945
|
consola.start(" Uploading bundle...");
|
|
926
946
|
const effectiveHasWorker = serverFiles !== undefined;
|
|
927
947
|
const bundle = {
|
|
@@ -935,9 +955,7 @@ async function deployAuthenticated(cwd, resolved, token, skipBuild, jsonMode = f
|
|
|
935
955
|
workerScript: null,
|
|
936
956
|
assets: clientAssets,
|
|
937
957
|
serverFiles,
|
|
938
|
-
//
|
|
939
|
-
resources: resolvedConfigToResources(resolved),
|
|
940
|
-
// New: binding declarations with user-defined names
|
|
958
|
+
// Binding declarations with user-defined names
|
|
941
959
|
bindings: resolvedConfigToBindingRequirements(resolved),
|
|
942
960
|
// Pass through wrangler vars and compat settings
|
|
943
961
|
...(Object.keys(resolved.vars).length > 0 ? { vars: resolved.vars } : {}),
|
|
@@ -966,6 +984,7 @@ async function deployAuthenticated(cwd, resolved, token, skipBuild, jsonMode = f
|
|
|
966
984
|
};
|
|
967
985
|
let lastStatus = "";
|
|
968
986
|
const start = Date.now();
|
|
987
|
+
buildLog.info("upload", `bundle uploaded (${fileList.length} files)`);
|
|
969
988
|
while (Date.now() - start < POLL_TIMEOUT) {
|
|
970
989
|
const res = await client.getDeploymentStatus(project.id, deployment.id);
|
|
971
990
|
const { status, failed_step, error_message } = res.deployment;
|
|
@@ -976,9 +995,27 @@ async function deployAuthenticated(cwd, resolved, token, skipBuild, jsonMode = f
|
|
|
976
995
|
if (!TERMINAL.has(status) && STEP_LABELS[status]) {
|
|
977
996
|
consola.start(STEP_LABELS[status]);
|
|
978
997
|
}
|
|
998
|
+
// Map server-side phase transitions into build-log steps so
|
|
999
|
+
// the dashboard timeline shows what happened after upload.
|
|
1000
|
+
if (status === "provisioning")
|
|
1001
|
+
buildLog.info("provision", "provisioning resources");
|
|
1002
|
+
if (status === "deploying")
|
|
1003
|
+
buildLog.info("activate", "activating at edge");
|
|
979
1004
|
lastStatus = status;
|
|
980
1005
|
}
|
|
981
1006
|
if (status === "active") {
|
|
1007
|
+
buildLog.info("activate", `deployed: ${res.url ?? res.previewUrl}`);
|
|
1008
|
+
// Fire-and-forget the build log upload — don't block the user
|
|
1009
|
+
// on it or make a slow/failing log API take down a successful
|
|
1010
|
+
// deploy.
|
|
1011
|
+
void client
|
|
1012
|
+
.uploadBuildLog(deployment.id, buildLog.toNdjson(), {
|
|
1013
|
+
status: "success",
|
|
1014
|
+
startedAt: buildLog.startedAt,
|
|
1015
|
+
})
|
|
1016
|
+
.catch(() => {
|
|
1017
|
+
// Silent — build log is best-effort for now.
|
|
1018
|
+
});
|
|
982
1019
|
if (jsonMode) {
|
|
983
1020
|
jsonOutput({
|
|
984
1021
|
ok: true,
|
|
@@ -1013,6 +1050,16 @@ async function deployAuthenticated(cwd, resolved, token, skipBuild, jsonMode = f
|
|
|
1013
1050
|
if (status === "failed") {
|
|
1014
1051
|
const step = failed_step ? ` at ${failed_step}` : "";
|
|
1015
1052
|
const msg = error_message ?? "Unknown error";
|
|
1053
|
+
buildLog.error(failed_step ?? "activate", msg);
|
|
1054
|
+
void client
|
|
1055
|
+
.uploadBuildLog(deployment.id, buildLog.toNdjson(), {
|
|
1056
|
+
status: "failed",
|
|
1057
|
+
startedAt: buildLog.startedAt,
|
|
1058
|
+
errorStep: failed_step ?? null,
|
|
1059
|
+
})
|
|
1060
|
+
.catch(() => {
|
|
1061
|
+
// Silent — build log is best-effort for now.
|
|
1062
|
+
});
|
|
1016
1063
|
if (jsonMode)
|
|
1017
1064
|
jsonOutput({ ok: false, error: "deploy_failed", message: msg, failedStep: failed_step }, 1, [
|
|
1018
1065
|
{ command: `creek deployments --project ${project.slug}`, description: "Check previous deployments" },
|