koguma 2.3.3 → 2.3.5

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 CHANGED
@@ -37,7 +37,22 @@ import {
37
37
  writeContentDir,
38
38
  type ContentTypeInfo
39
39
  } from './content.ts';
40
- import { CONTENT_DIR, SITE_CONFIG_FILE } from './constants.ts';
40
+ import { CONTENT_DIR, SITE_CONFIG_FILE, KOGUMA_DIR } from './constants.ts';
41
+
42
+ // ── HMR signal ─────────────────────────────────────────────────────
43
+ // Writes a timestamp to .koguma/dbhash after each successful sync.
44
+ // The kogumaHMR Vite plugin watches this file and triggers a full
45
+ // browser reload — much cleaner than watching the SQLite WAL files.
46
+
47
+ function touchDbHash(root: string): void {
48
+ try {
49
+ const dir = resolve(root, KOGUMA_DIR);
50
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
51
+ writeFileSync(resolve(dir, 'dbhash'), String(Date.now()));
52
+ } catch {
53
+ // non-critical — worst case Vite won't auto-reload
54
+ }
55
+ }
41
56
 
42
57
  // ── Constants ──────────────────────────────────────────────────────
43
58
 
@@ -153,6 +168,7 @@ function startFileWatcher(opts: FileWatcherOptions): { stop: () => void } {
153
168
  ...(publishAt !== undefined ? { publish_at: publishAt } : {})
154
169
  });
155
170
  markRecentWrite(entryId);
171
+ touchDbHash(root);
156
172
 
157
173
  ok(
158
174
  `${ANSI.DIM}sync:${ANSI.RESET} ${contentTypeId}/${parts.slice(1).join('/')} → D1`
@@ -396,6 +412,7 @@ function startSyncServer(opts: SyncServerOptions): {
396
412
 
397
413
  if (count > 0) {
398
414
  markRecentWrite(entryId);
415
+ touchDbHash(root);
399
416
  ok(
400
417
  `${ANSI.DIM}sync:${ANSI.RESET} D1 → ${payload.contentType}/${slug || 'index'}`
401
418
  );
package/cli/log.ts CHANGED
@@ -27,23 +27,30 @@ export const ANSI = {
27
27
  } as const;
28
28
 
29
29
  // ── Logging functions ──────────────────────────────────────────────
30
+ // On TTY, prepend \r\x1b[K (carriage return + clear line) so any
31
+ // in-place status line (spinner, reload counter) is cleared before
32
+ // the permanent message, preventing interleaved output.
33
+
34
+ const CR = process.stdout.isTTY ? '\r\x1b[K' : '';
30
35
 
31
36
  export function log(msg: string): void {
32
- console.log(` ${msg}`);
37
+ process.stdout.write(`${CR} ${msg}\n`);
33
38
  }
34
39
 
35
40
  export function ok(msg: string): void {
36
- console.log(` ${ANSI.BRAND_TEAL}✓${ANSI.RESET} ${msg}`);
41
+ process.stdout.write(`${CR} ${ANSI.BRAND_TEAL}✓${ANSI.RESET} ${msg}\n`);
37
42
  }
38
43
 
39
44
  export function warn(msg: string): void {
40
- console.log(` ${ANSI.YELLOW}⚠${ANSI.RESET} ${msg}`);
45
+ process.stdout.write(`${CR} ${ANSI.YELLOW}⚠${ANSI.RESET} ${msg}\n`);
41
46
  }
42
47
 
43
48
  export function fail(msg: string): void {
44
- console.error(` ${ANSI.BRAND_RED}✗${ANSI.RESET} ${msg}`);
49
+ process.stderr.write(`${CR} ${ANSI.BRAND_RED}✗${ANSI.RESET} ${msg}\n`);
45
50
  }
46
51
 
47
52
  export function header(msg: string): void {
48
- console.log(`\n${ANSI.BOLD}${ANSI.BRAND_TEAL}🐻 ${msg}${ANSI.RESET}\n`);
53
+ process.stdout.write(
54
+ `\n${ANSI.BOLD}${ANSI.BRAND_TEAL}🐻 ${msg}${ANSI.RESET}\n\n`
55
+ );
49
56
  }
package/cli/wrangler.ts CHANGED
@@ -337,99 +337,32 @@ export function wranglerDev(
337
337
  /^▲\s+/, // npm dep install warning lines
338
338
  /^>\s+/, // npm progress lines
339
339
  /^\s*$/, // blank lines
340
- /Reloading local server/, // wrangler dev reload spam
341
- /Local server updated/, // wrangler dev reload (handled as status line)
340
+ /Reloading local server/, // wrangler dev reload — suppress
341
+ /Local server updated/, // wrangler dev reload suppress
342
342
  /Unable to find and open the program executable/ // benign diagnostic
343
343
  ];
344
344
 
345
345
  const shouldSuppress = (line: string): boolean =>
346
346
  suppressPatterns.some(p => p.test(line));
347
347
 
348
- // ── Animated spinner for reload status ──
349
- const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
350
- let reloadCount = 0;
351
- let hasStatusLine = false;
352
- let spinnerFrame = 0;
353
- let spinnerTimer: ReturnType<typeof setInterval> | null = null;
354
- const isTTY = process.stdout.isTTY;
355
-
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
- // Keep as transient — stays visible but will be overwritten
381
- // by the next spinner or cleared by sync/error events
382
- process.stdout.write(`\r\x1b[K ✓ ${finalMsg}`);
383
- hasStatusLine = true;
384
- }
385
- };
386
-
387
- /** Clear the status line before printing a permanent line */
388
- const clearStatus = () => {
389
- if (spinnerTimer) {
390
- clearInterval(spinnerTimer);
391
- spinnerTimer = null;
392
- }
393
- if (hasStatusLine && isTTY) {
394
- process.stdout.write('\r\x1b[K');
395
- hasStatusLine = false;
396
- }
397
- };
398
-
399
348
  const handleOutput = (data: Buffer, isErr: boolean) => {
400
349
  const text = data.toString();
401
350
  for (const line of text.split('\n')) {
402
351
  const trimmed = line.trim();
403
352
  if (!trimmed) continue;
404
353
 
405
- // Rebrand the "Ready" message before suppress check
354
+ // Rebrand the "Ready" message
406
355
  if (trimmed.includes('Ready on http')) {
407
356
  const urlMatch = trimmed.match(/(https?:\/\/[^\s]+)/);
408
357
  const url = urlMatch?.[1] ?? 'http://localhost:8787';
409
- stopSpinner();
410
- clearStatus();
411
358
  ok(`Server ready → ${url}`);
412
359
  continue;
413
360
  }
414
361
 
415
- // Reload events → animated spinner (overwrites in place)
416
- if (/Reloading local server/.test(trimmed)) {
417
- reloadCount++;
418
- startSpinner();
419
- continue;
420
- }
421
- if (/Local server updated/.test(trimmed)) {
422
- stopSpinner(`reload #${reloadCount}`);
423
- continue;
424
- }
425
-
426
362
  if (shouldSuppress(line)) continue;
427
-
428
363
  if (trimmed.includes('Starting local server')) continue;
429
364
  if (trimmed.includes('Shutting down')) continue;
430
365
 
431
- // Permanent line — clear status first
432
- clearStatus();
433
366
  if (isErr) {
434
367
  warn(trimmed);
435
368
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koguma",
3
- "version": "2.3.3",
3
+ "version": "2.3.5",
4
4
  "description": "🐻 A little CMS with big heart — schema-driven, runs on Cloudflare's free tier",
5
5
  "type": "module",
6
6
  "license": "MIT",