koguma 2.2.3 → 2.2.4
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/cli/dev-sync.ts +12 -2
- package/cli/preflight.ts +9 -2
- package/cli/wrangler.ts +70 -25
- package/package.json +1 -1
- package/src/admin/_bundle.ts +1 -1
package/cli/dev-sync.ts
CHANGED
|
@@ -233,8 +233,13 @@ function startMediaWatcher(opts: MediaWatcherOptions): { stop: () => void } {
|
|
|
233
233
|
// Remove from local R2
|
|
234
234
|
const { ensureWranglerConfig } = require('./config.ts');
|
|
235
235
|
const configPath = ensureWranglerConfig(root);
|
|
236
|
+
const localBin = existsSync(
|
|
237
|
+
resolve(root, 'node_modules/.bin/wrangler')
|
|
238
|
+
)
|
|
239
|
+
? resolve(root, 'node_modules/.bin/wrangler')
|
|
240
|
+
: 'bunx wrangler';
|
|
236
241
|
run(
|
|
237
|
-
|
|
242
|
+
`${localBin} r2 object delete ${key} --config ${configPath} --local`,
|
|
238
243
|
{ cwd: root, silent: true }
|
|
239
244
|
);
|
|
240
245
|
} catch {
|
|
@@ -250,8 +255,13 @@ function startMediaWatcher(opts: MediaWatcherOptions): { stop: () => void } {
|
|
|
250
255
|
const { run } = require('./exec.ts');
|
|
251
256
|
const { ensureWranglerConfig } = require('./config.ts');
|
|
252
257
|
const configPath = ensureWranglerConfig(root);
|
|
258
|
+
const localBin = existsSync(
|
|
259
|
+
resolve(root, 'node_modules/.bin/wrangler')
|
|
260
|
+
)
|
|
261
|
+
? resolve(root, 'node_modules/.bin/wrangler')
|
|
262
|
+
: 'bunx wrangler';
|
|
253
263
|
run(
|
|
254
|
-
|
|
264
|
+
`${localBin} d1 execute ${dbName} --local --config ${configPath} --command "DELETE FROM assets WHERE id = '${id}'"`,
|
|
255
265
|
{ cwd: root, silent: true }
|
|
256
266
|
);
|
|
257
267
|
} catch {
|
package/cli/preflight.ts
CHANGED
|
@@ -14,6 +14,12 @@ import { fail, warn, log, ANSI } from './log.ts';
|
|
|
14
14
|
import { CONFIG_FILE, SITE_CONFIG_FILE, KOGUMA_DIR } from './constants.ts';
|
|
15
15
|
import { runCapture } from './exec.ts';
|
|
16
16
|
|
|
17
|
+
/** Prefer locally installed wrangler; fall back to bunx. */
|
|
18
|
+
function wranglerBin(root: string): string {
|
|
19
|
+
const local = resolve(root, 'node_modules/.bin/wrangler');
|
|
20
|
+
return existsSync(local) ? local : 'bunx wrangler';
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
export interface PreflightOptions {
|
|
18
24
|
/** Command needs wrangler login (remote D1/R2 operations) */
|
|
19
25
|
needsAuth?: boolean;
|
|
@@ -67,12 +73,13 @@ function requireSiteConfig(root: string): void {
|
|
|
67
73
|
}
|
|
68
74
|
|
|
69
75
|
function requireWranglerAuth(root: string): void {
|
|
76
|
+
const bin = wranglerBin(root);
|
|
70
77
|
try {
|
|
71
|
-
runCapture(
|
|
78
|
+
runCapture(`${bin} whoami`, root);
|
|
72
79
|
} catch {
|
|
73
80
|
fail(
|
|
74
81
|
`Not logged in to Cloudflare.\n` +
|
|
75
|
-
` Run ${ANSI.CYAN}
|
|
82
|
+
` Run ${ANSI.CYAN}${bin} login${ANSI.RESET} first, ` +
|
|
76
83
|
`or ${ANSI.CYAN}koguma init${ANSI.RESET} which handles login for you.`
|
|
77
84
|
);
|
|
78
85
|
process.exit(1);
|
package/cli/wrangler.ts
CHANGED
|
@@ -14,6 +14,21 @@ import { run, runCapture, runAsync } from './exec.ts';
|
|
|
14
14
|
import { ok, warn, fail, log } from './log.ts';
|
|
15
15
|
import { DB_DIR, MIGRATION_FILE } from './constants.ts';
|
|
16
16
|
|
|
17
|
+
// ── Wrangler binary resolution ─────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolve the wrangler binary to use.
|
|
21
|
+
*
|
|
22
|
+
* Prefers the locally installed copy (node_modules/.bin/wrangler) so the
|
|
23
|
+
* project controls its own wrangler version via package.json devDependencies.
|
|
24
|
+
* Falls back to `bunx wrangler` when no local binary is found (e.g. before
|
|
25
|
+
* `bun install` has been run or in CI with a fresh checkout).
|
|
26
|
+
*/
|
|
27
|
+
function wranglerBin(root: string): string {
|
|
28
|
+
const localBin = resolve(root, 'node_modules/.bin/wrangler');
|
|
29
|
+
return existsSync(localBin) ? localBin : 'bunx wrangler';
|
|
30
|
+
}
|
|
31
|
+
|
|
17
32
|
// ── Preflight checks ───────────────────────────────────────────────
|
|
18
33
|
|
|
19
34
|
/**
|
|
@@ -21,11 +36,12 @@ import { DB_DIR, MIGRATION_FILE } from './constants.ts';
|
|
|
21
36
|
* Gives a clear, actionable error message if not logged in.
|
|
22
37
|
*/
|
|
23
38
|
export function checkWranglerAuth(root: string): void {
|
|
39
|
+
const bin = wranglerBin(root);
|
|
24
40
|
try {
|
|
25
|
-
runCapture(
|
|
41
|
+
runCapture(`${bin} whoami`, root);
|
|
26
42
|
} catch {
|
|
27
43
|
fail(
|
|
28
|
-
|
|
44
|
+
`Not logged in to Cloudflare. Run '${bin} login' first, ` +
|
|
29
45
|
"or run 'koguma init' which handles login for you."
|
|
30
46
|
);
|
|
31
47
|
process.exit(1);
|
|
@@ -56,7 +72,7 @@ export function d1Execute(
|
|
|
56
72
|
command: string
|
|
57
73
|
): void {
|
|
58
74
|
run(
|
|
59
|
-
|
|
75
|
+
`${wranglerBin(root)} d1 execute ${dbName} ${target} ${configFlag(root)} --command "${wrapForShell(command)}"`,
|
|
60
76
|
{ cwd: root, silent: true }
|
|
61
77
|
);
|
|
62
78
|
}
|
|
@@ -71,7 +87,7 @@ export function d1ExecuteFile(
|
|
|
71
87
|
filePath: string
|
|
72
88
|
): void {
|
|
73
89
|
run(
|
|
74
|
-
|
|
90
|
+
`${wranglerBin(root)} d1 execute ${dbName} ${target} ${configFlag(root)} --file=${filePath}`,
|
|
75
91
|
{ cwd: root, silent: true }
|
|
76
92
|
);
|
|
77
93
|
}
|
|
@@ -86,7 +102,7 @@ export function d1Query(
|
|
|
86
102
|
query: string
|
|
87
103
|
): Record<string, unknown>[] {
|
|
88
104
|
const output = runCapture(
|
|
89
|
-
|
|
105
|
+
`${wranglerBin(root)} d1 execute ${dbName} ${target} ${configFlag(root)} --command "${query}" --json`,
|
|
90
106
|
root
|
|
91
107
|
);
|
|
92
108
|
const parsed = JSON.parse(output);
|
|
@@ -161,7 +177,7 @@ export async function applySchemaAsync(
|
|
|
161
177
|
const sqlFile = resolve(dbDir, MIGRATION_FILE);
|
|
162
178
|
writeFileSync(sqlFile, INIT_SQL);
|
|
163
179
|
await runAsync(
|
|
164
|
-
|
|
180
|
+
`${wranglerBin(root)} d1 execute ${dbName} ${target} ${configFlag(root)} --file=${sqlFile}`,
|
|
165
181
|
{ cwd: root, silent: true }
|
|
166
182
|
);
|
|
167
183
|
}
|
|
@@ -185,7 +201,7 @@ export async function d1InsertBatchAsync(
|
|
|
185
201
|
const statements = rows.map(row => buildInsertSql(table, row) + ';');
|
|
186
202
|
writeFileSync(sqlFile, statements.join('\n'));
|
|
187
203
|
await runAsync(
|
|
188
|
-
|
|
204
|
+
`${wranglerBin(root)} d1 execute ${dbName} ${target} ${configFlag(root)} --file=${sqlFile}`,
|
|
189
205
|
{ cwd: root, silent: true }
|
|
190
206
|
);
|
|
191
207
|
}
|
|
@@ -212,7 +228,7 @@ export async function d1ExecuteBatchSqlAsync(
|
|
|
212
228
|
statements.map(s => (s.endsWith(';') ? s : s + ';')).join('\n')
|
|
213
229
|
);
|
|
214
230
|
await runAsync(
|
|
215
|
-
|
|
231
|
+
`${wranglerBin(root)} d1 execute ${dbName} ${target} ${configFlag(root)} --file=${sqlFile}`,
|
|
216
232
|
{ cwd: root, silent: true }
|
|
217
233
|
);
|
|
218
234
|
}
|
|
@@ -227,7 +243,7 @@ export function r2PutLocal(
|
|
|
227
243
|
filePath: string
|
|
228
244
|
): void {
|
|
229
245
|
run(
|
|
230
|
-
|
|
246
|
+
`${wranglerBin(root)} r2 object put ${bucketName}/${key} ${configFlag(root)} --file=${filePath} --local`,
|
|
231
247
|
{ cwd: root, silent: true }
|
|
232
248
|
);
|
|
233
249
|
}
|
|
@@ -242,7 +258,7 @@ export async function r2PutLocalAsync(
|
|
|
242
258
|
filePath: string
|
|
243
259
|
): Promise<void> {
|
|
244
260
|
await runAsync(
|
|
245
|
-
|
|
261
|
+
`${wranglerBin(root)} r2 object put ${bucketName}/${key} ${configFlag(root)} --file=${filePath} --local`,
|
|
246
262
|
{ cwd: root, silent: true }
|
|
247
263
|
);
|
|
248
264
|
}
|
|
@@ -257,7 +273,7 @@ export function r2PutRemote(
|
|
|
257
273
|
filePath: string
|
|
258
274
|
): void {
|
|
259
275
|
run(
|
|
260
|
-
|
|
276
|
+
`${wranglerBin(root)} r2 object put ${bucketName}/${key} ${configFlag(root)} --file=${filePath}`,
|
|
261
277
|
{ cwd: root, silent: true }
|
|
262
278
|
);
|
|
263
279
|
}
|
|
@@ -272,7 +288,7 @@ export async function r2PutRemoteAsync(
|
|
|
272
288
|
filePath: string
|
|
273
289
|
): Promise<void> {
|
|
274
290
|
await runAsync(
|
|
275
|
-
|
|
291
|
+
`${wranglerBin(root)} r2 object put ${bucketName}/${key} ${configFlag(root)} --file=${filePath}`,
|
|
276
292
|
{ cwd: root, silent: true }
|
|
277
293
|
);
|
|
278
294
|
}
|
|
@@ -329,21 +345,49 @@ export function wranglerDev(
|
|
|
329
345
|
const shouldSuppress = (line: string): boolean =>
|
|
330
346
|
suppressPatterns.some(p => p.test(line));
|
|
331
347
|
|
|
332
|
-
// ──
|
|
348
|
+
// ── Animated spinner for reload status ──
|
|
349
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
333
350
|
let reloadCount = 0;
|
|
334
351
|
let hasStatusLine = false;
|
|
352
|
+
let spinnerFrame = 0;
|
|
353
|
+
let spinnerTimer: ReturnType<typeof setInterval> | null = null;
|
|
335
354
|
const isTTY = process.stdout.isTTY;
|
|
336
355
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
356
|
+
const drawStatus = (suffix = '') => {
|
|
357
|
+
if (!isTTY) return;
|
|
358
|
+
const frame = SPINNER_FRAMES[spinnerFrame % SPINNER_FRAMES.length]!;
|
|
359
|
+
process.stdout.write(`\r\x1b[K ${frame} reloading${suffix}`);
|
|
360
|
+
hasStatusLine = true;
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const startSpinner = () => {
|
|
364
|
+
if (!isTTY) return;
|
|
365
|
+
if (spinnerTimer) return; // already running
|
|
366
|
+
drawStatus();
|
|
367
|
+
spinnerTimer = setInterval(() => {
|
|
368
|
+
spinnerFrame++;
|
|
369
|
+
drawStatus();
|
|
370
|
+
}, 80);
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const stopSpinner = (finalMsg?: string) => {
|
|
374
|
+
if (spinnerTimer) {
|
|
375
|
+
clearInterval(spinnerTimer);
|
|
376
|
+
spinnerTimer = null;
|
|
377
|
+
}
|
|
378
|
+
if (!isTTY) return;
|
|
379
|
+
if (finalMsg) {
|
|
380
|
+
process.stdout.write(`\r\x1b[K ✓ ${finalMsg}\n`);
|
|
381
|
+
hasStatusLine = false;
|
|
342
382
|
}
|
|
343
383
|
};
|
|
344
384
|
|
|
345
385
|
/** Clear the status line before printing a permanent line */
|
|
346
386
|
const clearStatus = () => {
|
|
387
|
+
if (spinnerTimer) {
|
|
388
|
+
clearInterval(spinnerTimer);
|
|
389
|
+
spinnerTimer = null;
|
|
390
|
+
}
|
|
347
391
|
if (hasStatusLine && isTTY) {
|
|
348
392
|
process.stdout.write('\r\x1b[K');
|
|
349
393
|
hasStatusLine = false;
|
|
@@ -360,19 +404,20 @@ export function wranglerDev(
|
|
|
360
404
|
if (trimmed.includes('Ready on http')) {
|
|
361
405
|
const urlMatch = trimmed.match(/(https?:\/\/[^\s]+)/);
|
|
362
406
|
const url = urlMatch?.[1] ?? 'http://localhost:8787';
|
|
407
|
+
stopSpinner();
|
|
363
408
|
clearStatus();
|
|
364
409
|
ok(`Server ready → ${url}`);
|
|
365
410
|
continue;
|
|
366
411
|
}
|
|
367
412
|
|
|
368
|
-
// Reload events →
|
|
413
|
+
// Reload events → animated spinner (overwrites in place)
|
|
369
414
|
if (/Reloading local server/.test(trimmed)) {
|
|
370
415
|
reloadCount++;
|
|
371
|
-
|
|
416
|
+
startSpinner();
|
|
372
417
|
continue;
|
|
373
418
|
}
|
|
374
419
|
if (/Local server updated/.test(trimmed)) {
|
|
375
|
-
|
|
420
|
+
stopSpinner(`reload #${reloadCount}`);
|
|
376
421
|
continue;
|
|
377
422
|
}
|
|
378
423
|
|
|
@@ -411,7 +456,7 @@ export function wranglerDev(
|
|
|
411
456
|
* Deploy via wrangler.
|
|
412
457
|
*/
|
|
413
458
|
export function wranglerDeploy(root: string): void {
|
|
414
|
-
run(
|
|
459
|
+
run(`${wranglerBin(root)} deploy ${configFlag(root)}`, { cwd: root });
|
|
415
460
|
}
|
|
416
461
|
|
|
417
462
|
// ── D1 / R2 resource creation ──────────────────────────────────────
|
|
@@ -421,7 +466,7 @@ export function wranglerDeploy(root: string): void {
|
|
|
421
466
|
*/
|
|
422
467
|
export function createD1Database(root: string, dbName: string): string | null {
|
|
423
468
|
try {
|
|
424
|
-
const output = runCapture(
|
|
469
|
+
const output = runCapture(`${wranglerBin(root)} d1 create ${dbName}`, root);
|
|
425
470
|
const idMatch = output.match(/database_id\s*=\s*"([^"]+)"/);
|
|
426
471
|
return idMatch?.[1] ?? null;
|
|
427
472
|
} catch {
|
|
@@ -434,9 +479,9 @@ export function createD1Database(root: string, dbName: string): string | null {
|
|
|
434
479
|
*/
|
|
435
480
|
export function ensureR2Bucket(root: string, bucketName: string): boolean {
|
|
436
481
|
try {
|
|
437
|
-
const buckets = runCapture(
|
|
482
|
+
const buckets = runCapture(`${wranglerBin(root)} r2 bucket list`, root);
|
|
438
483
|
if (buckets.includes(bucketName)) return true;
|
|
439
|
-
runCapture(
|
|
484
|
+
runCapture(`${wranglerBin(root)} r2 bucket create ${bucketName}`, root);
|
|
440
485
|
return true;
|
|
441
486
|
} catch {
|
|
442
487
|
return false;
|