@blinkk/root 3.1.1 → 3.1.2

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.
@@ -47,7 +47,7 @@ import {
47
47
  } from "./chunk-TM6QRBGS.js";
48
48
 
49
49
  // src/cli/cli.ts
50
- import { Command, InvalidArgumentError } from "commander";
50
+ import { Command, InvalidArgumentError, Option } from "commander";
51
51
  import { bgGreen, black } from "kleur/colors";
52
52
 
53
53
  // src/cli/build.ts
@@ -55,7 +55,7 @@ import os from "node:os";
55
55
  import path2 from "node:path";
56
56
  import { fileURLToPath as fileURLToPath2 } from "node:url";
57
57
  import fsExtra from "fs-extra";
58
- import { dim as dim2, cyan } from "kleur/colors";
58
+ import { dim as dim3, cyan as cyan2 } from "kleur/colors";
59
59
  import glob from "tiny-glob";
60
60
  import { build as viteBuild } from "vite";
61
61
 
@@ -142,11 +142,179 @@ async function batchAsyncCalls(data, concurrency, asyncFunction) {
142
142
  return result;
143
143
  }
144
144
 
145
+ // src/cli/build-progress.ts
146
+ import { cyan, dim as dim2, green } from "kleur/colors";
147
+ function parseBuildLogMode(value) {
148
+ if (value === void 0 || value === null || value === "") {
149
+ return "progress";
150
+ }
151
+ if (value === "progress" || value === "verbose" || value === "quiet") {
152
+ return value;
153
+ }
154
+ throw new Error(
155
+ `invalid --log value: "${value}" (expected "progress", "verbose", or "quiet")`
156
+ );
157
+ }
158
+ function formatBytes(bytes) {
159
+ const k = 1024;
160
+ if (bytes < k) {
161
+ return (bytes / k).toFixed(2) + " kB";
162
+ }
163
+ const units = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
164
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
165
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + units[i];
166
+ }
167
+ function formatDuration(ms) {
168
+ const secs = ms / 1e3;
169
+ if (secs < 60) {
170
+ return `${secs.toFixed(1)}s`;
171
+ }
172
+ const mins = Math.floor(secs / 60);
173
+ const rem = Math.round(secs - mins * 60);
174
+ return `${mins}m${String(rem).padStart(2, "0")}s`;
175
+ }
176
+ var BAR_WIDTH = 20;
177
+ var BuildProgress = class {
178
+ total;
179
+ mode;
180
+ itemLabel;
181
+ outputDirLabel;
182
+ stream;
183
+ now;
184
+ intervalMs;
185
+ summaryTopN;
186
+ isTTY;
187
+ startedAt;
188
+ completed = 0;
189
+ totalBytes = 0;
190
+ files = [];
191
+ lastPrintAt = 0;
192
+ lastMilestone = 0;
193
+ ttyLineActive = false;
194
+ done = false;
195
+ constructor(options) {
196
+ this.total = options.total;
197
+ this.mode = options.mode;
198
+ this.itemLabel = options.itemLabel ?? "pages";
199
+ this.outputDirLabel = options.outputDirLabel ?? "";
200
+ this.stream = options.stream ?? process.stdout;
201
+ this.now = options.now ?? Date.now;
202
+ this.isTTY = !!this.stream.isTTY;
203
+ this.intervalMs = options.intervalMs ?? (this.isTTY ? 80 : 5e3);
204
+ this.summaryTopN = options.summaryTopN ?? 5;
205
+ this.startedAt = this.now();
206
+ }
207
+ /** Records a completed output file. */
208
+ add(outputFile, sizeBytes) {
209
+ this.completed += 1;
210
+ this.totalBytes += sizeBytes;
211
+ this.files.push({ path: outputFile, size: sizeBytes });
212
+ if (this.mode === "verbose") {
213
+ const paddedSize = formatBytes(sizeBytes).padStart(9, " ");
214
+ this.println(
215
+ ` ${dim2(paddedSize)} ${dim2(this.outputDirLabel)}${cyan(outputFile)}`
216
+ );
217
+ return;
218
+ }
219
+ if (this.mode === "progress") {
220
+ this.maybePrintProgress();
221
+ }
222
+ }
223
+ /** Clears any in-place progress line (call before printing errors). */
224
+ abort() {
225
+ this.clearTtyLine();
226
+ this.done = true;
227
+ }
228
+ /** Prints the final summary. */
229
+ finish() {
230
+ if (this.done) {
231
+ return;
232
+ }
233
+ this.done = true;
234
+ this.clearTtyLine();
235
+ const elapsed = formatDuration(this.now() - this.startedAt);
236
+ const summary = `${this.completed} ${this.itemLabel} (${formatBytes(
237
+ this.totalBytes
238
+ )}) in ${elapsed}`;
239
+ this.println(` ${green("\u2713")} ${summary}`);
240
+ if (this.mode === "progress" && this.summaryTopN > 0) {
241
+ const largest = [...this.files].sort((a, b) => b.size - a.size).slice(0, this.summaryTopN);
242
+ if (largest.length > 0) {
243
+ this.println(` ${dim2(`largest ${this.itemLabel}:`)}`);
244
+ for (const file of largest) {
245
+ const paddedSize = formatBytes(file.size).padStart(9, " ");
246
+ this.println(
247
+ ` ${dim2(paddedSize)} ${dim2(this.outputDirLabel)}${cyan(
248
+ file.path
249
+ )}`
250
+ );
251
+ }
252
+ }
253
+ }
254
+ }
255
+ maybePrintProgress() {
256
+ const now = this.now();
257
+ if (this.isTTY) {
258
+ if (now - this.lastPrintAt < this.intervalMs) {
259
+ return;
260
+ }
261
+ this.lastPrintAt = now;
262
+ this.renderTtyLine(now);
263
+ return;
264
+ }
265
+ const milestone = Math.floor(this.completed / this.total * 10);
266
+ if (milestone <= this.lastMilestone || this.completed === this.total) {
267
+ return;
268
+ }
269
+ if (now - this.lastPrintAt < this.intervalMs) {
270
+ return;
271
+ }
272
+ this.lastMilestone = milestone;
273
+ this.lastPrintAt = now;
274
+ const pct = Math.floor(this.completed / this.total * 100);
275
+ this.println(
276
+ ` ${this.completed}/${this.total} ${this.itemLabel} (${pct}%) \xB7 ${formatBytes(this.totalBytes)} \xB7 ${formatDuration(now - this.startedAt)}${this.eta(now)}`
277
+ );
278
+ }
279
+ renderTtyLine(now) {
280
+ const ratio = this.total > 0 ? this.completed / this.total : 0;
281
+ const filled = Math.round(ratio * BAR_WIDTH);
282
+ const bar = "\u2595" + "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled) + "\u258F";
283
+ const pct = String(Math.floor(ratio * 100)).padStart(3, " ");
284
+ const line = ` ${bar} ${pct}% \xB7 ${this.completed}/${this.total} ${this.itemLabel} \xB7 ${formatBytes(this.totalBytes)} \xB7 ${formatDuration(now - this.startedAt)}${this.eta(now)}`;
285
+ this.stream.write(`\r\x1B[2K${line}`);
286
+ this.ttyLineActive = true;
287
+ }
288
+ /** Returns an " · eta 12.3s" suffix once enough progress exists to estimate. */
289
+ eta(now) {
290
+ if (this.completed < 10 || this.completed / this.total < 0.05) {
291
+ return "";
292
+ }
293
+ const elapsed = now - this.startedAt;
294
+ const remaining = elapsed / this.completed * (this.total - this.completed);
295
+ return ` \xB7 eta ${formatDuration(remaining)}`;
296
+ }
297
+ clearTtyLine() {
298
+ if (this.ttyLineActive) {
299
+ this.stream.write("\r\x1B[2K");
300
+ this.ttyLineActive = false;
301
+ }
302
+ }
303
+ println(line) {
304
+ this.clearTtyLine();
305
+ this.stream.write(line + "\n");
306
+ }
307
+ };
308
+
145
309
  // src/cli/build-worker-pool.ts
146
310
  import path from "node:path";
147
311
  import { fileURLToPath } from "node:url";
312
+ import v8 from "node:v8";
148
313
  import { Worker } from "node:worker_threads";
149
314
  var __dirname = path.dirname(fileURLToPath(import.meta.url));
315
+ var WORKER_HEAP_LIMIT_MB = Math.ceil(
316
+ v8.getHeapStatistics().heap_size_limit / (1024 * 1024)
317
+ );
150
318
  var BuildPageError = class extends Error {
151
319
  urlPath;
152
320
  params;
@@ -173,7 +341,8 @@ var BuildWorker = class {
173
341
  this.pool = pool;
174
342
  this.concurrency = options.workerConcurrency;
175
343
  this.worker = new Worker(path.resolve(__dirname, "./build-worker.js"), {
176
- workerData: { rootDir: options.rootDir, mode: options.mode }
344
+ workerData: { rootDir: options.rootDir, mode: options.mode },
345
+ resourceLimits: { maxOldGenerationSizeMb: WORKER_HEAP_LIMIT_MB }
177
346
  });
178
347
  let onReady;
179
348
  let onFailed;
@@ -216,10 +385,30 @@ var BuildWorker = class {
216
385
  }
217
386
  });
218
387
  }
388
+ /**
389
+ * Rejects all in-flight tasks after a worker-level failure (fatal error,
390
+ * crash, or non-zero exit — e.g. an OOM kill).
391
+ *
392
+ * Each task is rejected with its own `BuildPageError` so the build log
393
+ * names every page that was in flight when the worker died. Without this,
394
+ * worker deaths surface as a single generic error with no route context,
395
+ * making OOM crashes impossible to trace back to pages. Note the in-flight
396
+ * pages are the OOM *suspects*, not necessarily the cause (memory pressure
397
+ * is cumulative), which is why the message says "while rendering".
398
+ */
219
399
  failAll(err) {
220
400
  const pendingTasks = Array.from(this.inFlight.values());
221
401
  this.inFlight.clear();
222
- pendingTasks.forEach((pending) => pending.reject(err));
402
+ pendingTasks.forEach(
403
+ (pending) => pending.reject(
404
+ new BuildPageError(
405
+ pending.task.urlPath,
406
+ pending.task.params,
407
+ pending.task.routeSrc,
408
+ `worker thread died while rendering this page (${pendingTasks.length} page(s) were in flight): ` + String(err.message || err)
409
+ )
410
+ )
411
+ );
223
412
  }
224
413
  /** Pulls tasks off the pool's queue until at max concurrency. */
225
414
  next() {
@@ -274,18 +463,22 @@ var BuildWorkerPool = class {
274
463
 
275
464
  // src/cli/build.ts
276
465
  var __dirname2 = path2.dirname(fileURLToPath2(import.meta.url));
466
+ var activeLogMode = "progress";
277
467
  async function build(rootProjectDir, options) {
278
468
  const mode = options?.mode || "production";
279
469
  process.env.NODE_ENV = mode;
470
+ activeLogMode = options?.quiet ? "quiet" : parseBuildLogMode(options?.log);
280
471
  const rootDir = path2.resolve(rootProjectDir || process.cwd());
281
472
  const rootConfig = await loadRootConfig(rootDir, { command: "build" });
282
473
  const distDir = path2.join(rootDir, "dist");
283
474
  const ssrOnly = options?.ssrOnly || false;
284
- console.log();
285
- console.log(`${dim2("\u2503")} project: ${rootDir}`);
286
- console.log(`${dim2("\u2503")} output: ${distDir}/html`);
287
- console.log(`${dim2("\u2503")} mode: ${mode}`);
288
- console.log();
475
+ if (activeLogMode !== "quiet") {
476
+ console.log();
477
+ console.log(`${dim3("\u2503")} project: ${rootDir}`);
478
+ console.log(`${dim3("\u2503")} output: ${distDir}/html`);
479
+ console.log(`${dim3("\u2503")} mode: ${mode}`);
480
+ console.log();
481
+ }
289
482
  await rmDir(distDir);
290
483
  await makeDir(distDir);
291
484
  const pods = await collectPods(rootConfig);
@@ -341,7 +534,9 @@ async function build(rootProjectDir, options) {
341
534
  ...viteConfig,
342
535
  root: rootDir,
343
536
  mode,
344
- plugins: vitePlugins
537
+ plugins: vitePlugins,
538
+ // In quiet mode, only surface vite warnings and errors.
539
+ logLevel: activeLogMode === "quiet" ? "warn" : viteConfig.logLevel
345
540
  };
346
541
  const ssrInput = {
347
542
  render: path2.resolve(__dirname2, "./render.js")
@@ -540,7 +735,9 @@ async function build(rootProjectDir, options) {
540
735
  await fsExtra.copy(assetFrom, assetTo);
541
736
  printFileOutput(fileSize(assetTo), "dist/html/", assetRelPath);
542
737
  }
543
- console.log("\njs/css output:");
738
+ if (activeLogMode !== "quiet") {
739
+ console.log("\njs/css output:");
740
+ }
544
741
  await Promise.all(
545
742
  Object.keys(rootManifest).map(async (src) => {
546
743
  const assetData = rootManifest[src];
@@ -609,10 +806,9 @@ async function build(rootProjectDir, options) {
609
806
  if (!result.staticContent) {
610
807
  addSitemapXmlItem(result.urlPath, sitemapItem, result.outFilePath);
611
808
  }
612
- printFileOutput(
613
- fileSize(path2.join(buildDir, result.outFilePath)),
614
- "dist/html/",
615
- result.outFilePath
809
+ progress.add(
810
+ result.outFilePath,
811
+ fileSizeBytes(path2.join(buildDir, result.outFilePath))
616
812
  );
617
813
  };
618
814
  const sitemapEntries = Object.entries(sitemap).filter(([, sitemapItem]) => {
@@ -624,76 +820,100 @@ async function build(rootProjectDir, options) {
624
820
  }
625
821
  return true;
626
822
  });
627
- console.log("\nhtml output:");
823
+ if (activeLogMode !== "quiet") {
824
+ console.log("\nhtml output:");
825
+ }
628
826
  const concurrency = Number(options?.concurrency || 10);
629
- const numThreads = resolveNumThreads(
827
+ const resolvedThreads = resolveNumThreads(
630
828
  options?.threads,
631
829
  sitemapEntries.length
632
830
  );
633
- if (numThreads > 0) {
634
- const pool = new BuildWorkerPool({
635
- numWorkers: numThreads,
636
- workerConcurrency: Math.max(Math.ceil(concurrency / numThreads), 1),
637
- rootDir,
638
- mode
639
- });
640
- try {
641
- await pool.ready();
642
- await Promise.all(
643
- sitemapEntries.map(async ([urlPath, sitemapItem]) => {
831
+ const numThreads = resolvedThreads.count;
832
+ const workerConcurrency = numThreads > 0 ? Math.max(Math.ceil(concurrency / numThreads), 1) : 0;
833
+ if (activeLogMode !== "quiet") {
834
+ const threadsDesc = numThreads > 0 ? `${numThreads} workers x ${workerConcurrency} pages/worker` : `in-process, ${concurrency} concurrent pages`;
835
+ console.log(
836
+ ` ${dim3(`threads: ${threadsDesc} (${resolvedThreads.source})`)}`
837
+ );
838
+ }
839
+ const progress = new BuildProgress({
840
+ total: sitemapEntries.length,
841
+ mode: activeLogMode,
842
+ itemLabel: "pages",
843
+ outputDirLabel: "dist/html/"
844
+ });
845
+ try {
846
+ if (numThreads > 0) {
847
+ const pool = new BuildWorkerPool({
848
+ numWorkers: numThreads,
849
+ workerConcurrency,
850
+ rootDir,
851
+ mode
852
+ });
853
+ try {
854
+ await pool.ready();
855
+ await Promise.all(
856
+ sitemapEntries.map(async ([urlPath, sitemapItem]) => {
857
+ try {
858
+ const result = await pool.run({
859
+ urlPath,
860
+ params: sitemapItem.params,
861
+ routeSrc: sitemapItem.route.src,
862
+ locale: sitemapItem.locale
863
+ });
864
+ onPageResult(sitemapItem, result);
865
+ } catch (e) {
866
+ if (e instanceof BuildPageError) {
867
+ progress.abort();
868
+ logBuildError(
869
+ {
870
+ route: sitemapItem.route,
871
+ params: sitemapItem.params,
872
+ urlPath
873
+ },
874
+ new Error(e.workerError)
875
+ );
876
+ }
877
+ throw e;
878
+ }
879
+ })
880
+ );
881
+ } finally {
882
+ await pool.terminate();
883
+ }
884
+ } else {
885
+ await batchAsyncCalls(
886
+ sitemapEntries,
887
+ concurrency,
888
+ async ([urlPath, sitemapItem]) => {
644
889
  try {
645
- const result = await pool.run({
646
- urlPath,
647
- params: sitemapItem.params,
648
- routeSrc: sitemapItem.route.src,
649
- locale: sitemapItem.locale
650
- });
890
+ const result = await buildPage(
891
+ renderer,
892
+ rootConfig,
893
+ buildDir,
894
+ sitemapItem.route,
895
+ { urlPath, params: sitemapItem.params }
896
+ );
651
897
  onPageResult(sitemapItem, result);
652
898
  } catch (e) {
653
- if (e instanceof BuildPageError) {
654
- logBuildError(
655
- {
656
- route: sitemapItem.route,
657
- params: sitemapItem.params,
658
- urlPath
659
- },
660
- new Error(e.workerError)
661
- );
662
- }
663
- throw e;
899
+ progress.abort();
900
+ logBuildError(
901
+ { route: sitemapItem.route, params: sitemapItem.params, urlPath },
902
+ e
903
+ );
904
+ throw new Error(
905
+ `BuildError: ${urlPath} (${sitemapItem.route.src}) failed to build.`,
906
+ { cause: e }
907
+ );
664
908
  }
665
- })
909
+ }
666
910
  );
667
- } finally {
668
- await pool.terminate();
669
911
  }
670
- } else {
671
- await batchAsyncCalls(
672
- sitemapEntries,
673
- concurrency,
674
- async ([urlPath, sitemapItem]) => {
675
- try {
676
- const result = await buildPage(
677
- renderer,
678
- rootConfig,
679
- buildDir,
680
- sitemapItem.route,
681
- { urlPath, params: sitemapItem.params }
682
- );
683
- onPageResult(sitemapItem, result);
684
- } catch (e) {
685
- logBuildError(
686
- { route: sitemapItem.route, params: sitemapItem.params, urlPath },
687
- e
688
- );
689
- throw new Error(
690
- `BuildError: ${urlPath} (${sitemapItem.route.src}) failed to build.`,
691
- { cause: e }
692
- );
693
- }
694
- }
695
- );
912
+ } catch (e) {
913
+ progress.abort();
914
+ throw e;
696
915
  }
916
+ progress.finish();
697
917
  if (rootConfig.sitemap) {
698
918
  const sitemapXmlBuilder = [];
699
919
  sitemapXmlItems.sort((a, b) => a.url.localeCompare(b.url));
@@ -734,42 +954,45 @@ function isRouteFile(filepath) {
734
954
  return filepath.startsWith("routes") && isJsFile(filepath);
735
955
  }
736
956
  var MIN_PAGES_PER_THREAD = 10;
957
+ var MEMORY_PER_THREAD = 1024 * 1024 * 1024;
737
958
  function resolveNumThreads(threads, numPages) {
738
959
  if (threads === void 0 || threads === false) {
739
- return 0;
960
+ return { count: 0, source: "--threads not set" };
740
961
  }
741
962
  if (threads === true || threads === "auto") {
742
963
  const maxByCpu = Math.max(os.availableParallelism() - 1, 1);
743
964
  const maxByPages = Math.floor(numPages / MIN_PAGES_PER_THREAD);
744
- const numThreads = Math.min(maxByCpu, maxByPages);
965
+ const maxByMemory = Math.max(
966
+ Math.floor(os.freemem() / MEMORY_PER_THREAD) - 1,
967
+ 1
968
+ );
969
+ const numThreads = Math.min(maxByCpu, maxByPages, maxByMemory);
970
+ const source = `auto = min(cpu ${maxByCpu}, pages ${maxByPages}, memory ${maxByMemory})`;
745
971
  if (numThreads <= 1) {
746
- return 0;
972
+ return { count: 0, source };
747
973
  }
748
- console.log(`rendering with ${numThreads} worker threads`);
749
- return numThreads;
974
+ return { count: numThreads, source };
750
975
  }
751
976
  const num = parseInt(threads);
752
977
  if (isNaN(num) || num < 0) {
753
978
  throw new Error(`invalid --threads value: ${threads}`);
754
979
  }
755
- return num;
980
+ return { count: num, source: `--threads=${num}` };
981
+ }
982
+ function fileSizeBytes(filepath) {
983
+ return fsExtra.statSync(filepath).size;
756
984
  }
757
985
  function fileSize(filepath) {
758
- const stats = fsExtra.statSync(filepath);
759
- const bytes = stats.size;
760
- const k = 1024;
761
- if (bytes < k) {
762
- return (bytes / k).toFixed(2) + " kB";
763
- }
764
- const units = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
765
- const i = Math.floor(Math.log(bytes) / Math.log(k));
766
- return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + units[i];
986
+ return formatBytes(fileSizeBytes(filepath));
767
987
  }
768
988
  function printFileOutput(fileSize2, outputDir, outputFile) {
989
+ if (activeLogMode === "quiet") {
990
+ return;
991
+ }
769
992
  const indent = " ".repeat(2);
770
993
  const paddedSize = fileSize2.padStart(9, " ");
771
994
  console.log(
772
- `${indent}${dim2(paddedSize)} ${dim2(outputDir)}${cyan(outputFile)}`
995
+ `${indent}${dim3(paddedSize)} ${dim3(outputDir)}${cyan2(outputFile)}`
773
996
  );
774
997
  }
775
998
  function sanitizeFileName(name) {
@@ -1021,7 +1244,7 @@ import path11 from "node:path";
1021
1244
  import { fileURLToPath as fileURLToPath4 } from "node:url";
1022
1245
  import cookieParser from "cookie-parser";
1023
1246
  import { default as express } from "express";
1024
- import { dim as dim5 } from "kleur/colors";
1247
+ import { dim as dim6 } from "kleur/colors";
1025
1248
  import sirv from "sirv";
1026
1249
  import glob3 from "tiny-glob";
1027
1250
 
@@ -1306,7 +1529,7 @@ import { spawn as spawn3 } from "node:child_process";
1306
1529
  import fs6 from "node:fs";
1307
1530
  import path9 from "node:path";
1308
1531
  import * as readline from "node:readline";
1309
- import { dim as dim3, green, red, yellow } from "kleur/colors";
1532
+ import { dim as dim4, green as green2, red, yellow } from "kleur/colors";
1310
1533
 
1311
1534
  // src/secrets/manifest.ts
1312
1535
  import fs3 from "node:fs";
@@ -2079,7 +2302,7 @@ function getGitEmail() {
2079
2302
  }
2080
2303
 
2081
2304
  // src/cli/secrets.ts
2082
- var BAR = dim3("\u2503");
2305
+ var BAR = dim4("\u2503");
2083
2306
  var DEV_SYNC_TIMEOUT_MS = 1e4;
2084
2307
  function registerSecretsCommands(program) {
2085
2308
  const secrets = program.command("secrets").description(
@@ -2140,13 +2363,13 @@ async function secretsInit(opts) {
2140
2363
  await writeManifest(target, manifest);
2141
2364
  const addedGitignore = await ensureRootGitignored(rootDir);
2142
2365
  console.log();
2143
- console.log(`${BAR} ${green("secrets:")} created ${rel(rootDir, target)}`);
2366
+ console.log(`${BAR} ${green2("secrets:")} created ${rel(rootDir, target)}`);
2144
2367
  if (addedGitignore) {
2145
- console.log(`${BAR} added ${dim3(".root/")} to .gitignore`);
2368
+ console.log(`${BAR} added ${dim4(".root/")} to .gitignore`);
2146
2369
  }
2147
2370
  await warnIfEnvNotIgnored(rootDir);
2148
2371
  console.log(
2149
- `${BAR} ${dim3("next: `root secrets set <NAME>` to store a value")}`
2372
+ `${BAR} ${dim4("next: `root secrets set <NAME>` to store a value")}`
2150
2373
  );
2151
2374
  console.log();
2152
2375
  }
@@ -2158,18 +2381,18 @@ async function secretsSet(name, opts) {
2158
2381
  throw new Error("no value provided on stdin");
2159
2382
  }
2160
2383
  await setSecret({ rootDir, manifestFilePath, name, value: secretValue });
2161
- console.log(`${BAR} ${green("secrets:")} set ${name}`);
2384
+ console.log(`${BAR} ${green2("secrets:")} set ${name}`);
2162
2385
  console.log(
2163
- `${BAR} ${dim3(`commit ${rel(rootDir, manifestFilePath)} to share this change`)}`
2386
+ `${BAR} ${dim4(`commit ${rel(rootDir, manifestFilePath)} to share this change`)}`
2164
2387
  );
2165
2388
  }
2166
2389
  async function secretsRm(name, opts) {
2167
2390
  const rootDir = process.cwd();
2168
2391
  const manifestFilePath = opts.manifest ? path9.resolve(rootDir, opts.manifest) : manifestPath(rootDir);
2169
2392
  await removeSecret({ rootDir, manifestFilePath, name });
2170
- console.log(`${BAR} ${green("secrets:")} removed ${name}`);
2393
+ console.log(`${BAR} ${green2("secrets:")} removed ${name}`);
2171
2394
  console.log(
2172
- `${BAR} ${dim3(`commit ${rel(rootDir, manifestFilePath)} to share this change`)}`
2395
+ `${BAR} ${dim4(`commit ${rel(rootDir, manifestFilePath)} to share this change`)}`
2173
2396
  );
2174
2397
  }
2175
2398
  async function secretsPush(opts) {
@@ -2184,7 +2407,7 @@ async function secretsPush(opts) {
2184
2407
  }
2185
2408
  console.log();
2186
2409
  console.log(
2187
- `${BAR} ${green("secrets:")} push ${names.length} value(s) from .env to "${gsmKey}":`
2410
+ `${BAR} ${green2("secrets:")} push ${names.length} value(s) from .env to "${gsmKey}":`
2188
2411
  );
2189
2412
  for (const name of names) {
2190
2413
  console.log(`${BAR} ${name}`);
@@ -2227,13 +2450,13 @@ async function secretsStatus() {
2227
2450
  }
2228
2451
  console.log();
2229
2452
  console.log(
2230
- `${BAR} ${green("secrets:")} ${resolved.site.gsmKey} ${dim3(`(${resolved.site.gcpProjectId})`)}`
2453
+ `${BAR} ${green2("secrets:")} ${resolved.site.gsmKey} ${dim4(`(${resolved.site.gcpProjectId})`)}`
2231
2454
  );
2232
2455
  if (resolved.shared) {
2233
- console.log(`${BAR} imports ${dim3(resolved.shared.gsmKey)}`);
2456
+ console.log(`${BAR} imports ${dim4(resolved.shared.gsmKey)}`);
2234
2457
  }
2235
2458
  if (keys.length === 0) {
2236
- console.log(`${BAR} ${dim3("no managed keys yet")}`);
2459
+ console.log(`${BAR} ${dim4("no managed keys yet")}`);
2237
2460
  }
2238
2461
  for (const key of keys) {
2239
2462
  console.log(`${BAR} ${formatStatus(key)}`);
@@ -2261,15 +2484,15 @@ async function syncSecretsOnDev(rootDir) {
2261
2484
  function formatStatus(key) {
2262
2485
  switch (key.kind) {
2263
2486
  case "in-sync":
2264
- return `${green("\u2713")} ${key.name}`;
2487
+ return `${green2("\u2713")} ${key.name}`;
2265
2488
  case "remote-newer":
2266
- return `${yellow("\u2193")} ${key.name} ${dim3("(update available \u2014 run `root secrets sync`)")}`;
2489
+ return `${yellow("\u2193")} ${key.name} ${dim4("(update available \u2014 run `root secrets sync`)")}`;
2267
2490
  case "locally-edited":
2268
- return `${dim3("\u2022")} ${key.name} ${dim3("(locally edited)")}`;
2491
+ return `${dim4("\u2022")} ${key.name} ${dim4("(locally edited)")}`;
2269
2492
  case "conflict":
2270
- return `${yellow("!")} ${key.name} ${dim3("(changed locally & remotely)")}`;
2493
+ return `${yellow("!")} ${key.name} ${dim4("(changed locally & remotely)")}`;
2271
2494
  case "not-pulled":
2272
- return `${yellow("\u2193")} ${key.name} ${dim3("(not pulled yet)")}`;
2495
+ return `${yellow("\u2193")} ${key.name} ${dim4("(not pulled yet)")}`;
2273
2496
  default:
2274
2497
  return key.name;
2275
2498
  }
@@ -2281,13 +2504,13 @@ function printSyncSummary(result, opts = {}) {
2281
2504
  }
2282
2505
  const parts = [];
2283
2506
  if (result.changed.length) {
2284
- parts.push(green(`${result.changed.length} updated`));
2507
+ parts.push(green2(`${result.changed.length} updated`));
2285
2508
  }
2286
2509
  if (result.removed.length) {
2287
2510
  parts.push(`${result.removed.length} removed`);
2288
2511
  }
2289
2512
  if (result.kept.length) {
2290
- parts.push(dim3(`${result.kept.length} local`));
2513
+ parts.push(dim4(`${result.kept.length} local`));
2291
2514
  }
2292
2515
  if (result.conflicts.length) {
2293
2516
  parts.push(yellow(`${result.conflicts.length} conflict`));
@@ -2297,22 +2520,22 @@ function printSyncSummary(result, opts = {}) {
2297
2520
  }
2298
2521
  console.log();
2299
2522
  console.log(
2300
- `${BAR} ${green("secrets:")} ${parts.length ? parts.join(", ") : "up to date"}`
2523
+ `${BAR} ${green2("secrets:")} ${parts.length ? parts.join(", ") : "up to date"}`
2301
2524
  );
2302
2525
  for (const name of result.changed) {
2303
- console.log(`${BAR} ${green("+")} ${name}`);
2526
+ console.log(`${BAR} ${green2("+")} ${name}`);
2304
2527
  }
2305
2528
  for (const name of result.removed) {
2306
- console.log(`${BAR} ${dim3("-")} ${name} ${dim3("(removed)")}`);
2529
+ console.log(`${BAR} ${dim4("-")} ${name} ${dim4("(removed)")}`);
2307
2530
  }
2308
2531
  for (const name of result.overwritten) {
2309
2532
  console.log(
2310
- `${BAR} ${yellow("!")} ${name} ${dim3("(local value replaced)")}`
2533
+ `${BAR} ${yellow("!")} ${name} ${dim4("(local value replaced)")}`
2311
2534
  );
2312
2535
  }
2313
2536
  for (const name of result.conflicts) {
2314
2537
  console.log(
2315
- `${BAR} ${yellow("!")} ${name} ${dim3(
2538
+ `${BAR} ${yellow("!")} ${name} ${dim4(
2316
2539
  "(changed locally & remotely; keeping yours \u2014 `root secrets pull` to take remote)"
2317
2540
  )}`
2318
2541
  );
@@ -2334,18 +2557,18 @@ function printPushResult(result, rootDir, manifestFilePath) {
2334
2557
  console.log(`${BAR} ${yellow("secrets:")} nothing to push`);
2335
2558
  } else {
2336
2559
  console.log(
2337
- `${BAR} ${green("secrets:")} pushed ${result.pushed.length} value(s) to "${result.gsmKey}"`
2560
+ `${BAR} ${green2("secrets:")} pushed ${result.pushed.length} value(s) to "${result.gsmKey}"`
2338
2561
  );
2339
2562
  for (const name of result.pushed) {
2340
- console.log(`${BAR} ${green("+")} ${name}`);
2563
+ console.log(`${BAR} ${green2("+")} ${name}`);
2341
2564
  }
2342
2565
  }
2343
2566
  for (const skip of result.skipped) {
2344
- console.log(`${BAR} ${dim3("-")} ${skip.name} ${dim3(`(${skip.reason})`)}`);
2567
+ console.log(`${BAR} ${dim4("-")} ${skip.name} ${dim4(`(${skip.reason})`)}`);
2345
2568
  }
2346
2569
  if (result.pushed.length > 0) {
2347
2570
  console.log(
2348
- `${BAR} ${dim3(`commit ${rel(rootDir, manifestFilePath)} to share`)}`
2571
+ `${BAR} ${dim4(`commit ${rel(rootDir, manifestFilePath)} to share`)}`
2349
2572
  );
2350
2573
  }
2351
2574
  console.log();
@@ -2371,7 +2594,7 @@ async function warnIfEnvNotIgnored(rootDir) {
2371
2594
  const ignored = await isPathGitIgnored(rootDir, ".env");
2372
2595
  if (ignored === false) {
2373
2596
  console.log(
2374
- `${BAR} ${yellow("warning:")} ${dim3(".env is not git-ignored \u2014 add it to .gitignore")}`
2597
+ `${BAR} ${yellow("warning:")} ${dim4(".env is not git-ignored \u2014 add it to .gitignore")}`
2375
2598
  );
2376
2599
  }
2377
2600
  }
@@ -2512,7 +2735,7 @@ import os2 from "node:os";
2512
2735
  import path10 from "node:path";
2513
2736
 
2514
2737
  // src/cli/startup/check-version.ts
2515
- import { dim as dim4, green as green2, yellow as yellow2 } from "kleur/colors";
2738
+ import { dim as dim5, green as green3, yellow as yellow2 } from "kleur/colors";
2516
2739
  var PACKAGE_NAME = "@blinkk/root";
2517
2740
  var ONE_DAY_MS = 24 * 60 * 60 * 1e3;
2518
2741
  var REGISTRY_TIMEOUT_MS = 3e3;
@@ -2583,10 +2806,10 @@ function parseVersion(version) {
2583
2806
  return [toInt(parts[0]), toInt(parts[1]), toInt(parts[2])];
2584
2807
  }
2585
2808
  function printUpdateNotice(current, latest) {
2586
- const bar = dim4("\u2503");
2809
+ const bar = dim5("\u2503");
2587
2810
  const label = yellow2(`Update available for ${PACKAGE_NAME}:`);
2588
2811
  console.log();
2589
- console.log(`${bar} ${label} ${dim4(current)} \u2192 ${green2(latest)}`);
2812
+ console.log(`${bar} ${label} ${dim5(current)} \u2192 ${green3(latest)}`);
2590
2813
  console.log();
2591
2814
  }
2592
2815
 
@@ -2665,12 +2888,12 @@ async function dev(rootProjectDir, options) {
2665
2888
  const basePath = rootConfig.base || "";
2666
2889
  const homePagePath = rootConfig.server?.homePagePath || basePath;
2667
2890
  console.log();
2668
- console.log(`${dim5("\u2503")} project: ${rootDir}`);
2669
- console.log(`${dim5("\u2503")} server: http://${host}:${port}${homePagePath}`);
2891
+ console.log(`${dim6("\u2503")} project: ${rootDir}`);
2892
+ console.log(`${dim6("\u2503")} server: http://${host}:${port}${homePagePath}`);
2670
2893
  if (testCmsEnabled(rootConfig)) {
2671
- console.log(`${dim5("\u2503")} cms: http://${host}:${port}/cms/`);
2894
+ console.log(`${dim6("\u2503")} cms: http://${host}:${port}/cms/`);
2672
2895
  }
2673
- console.log(`${dim5("\u2503")} mode: development`);
2896
+ console.log(`${dim6("\u2503")} mode: development`);
2674
2897
  console.log();
2675
2898
  currentServer = server.listen(port, host);
2676
2899
  const rootConfigDependencies = server.get(
@@ -2685,7 +2908,7 @@ async function dev(rootProjectDir, options) {
2685
2908
  if (dependencies.includes(file)) {
2686
2909
  console.log(
2687
2910
  `
2688
- ${dim5("\u2503")} root.config.ts changed. restarting server...`
2911
+ ${dim6("\u2503")} root.config.ts changed. restarting server...`
2689
2912
  );
2690
2913
  await restart();
2691
2914
  }
@@ -3080,7 +3303,7 @@ function testManagedVersion(service, appInfo, prefix) {
3080
3303
  import path12 from "node:path";
3081
3304
  import cookieParser2 from "cookie-parser";
3082
3305
  import { default as express2 } from "express";
3083
- import { dim as dim6 } from "kleur/colors";
3306
+ import { dim as dim7 } from "kleur/colors";
3084
3307
  import sirv2 from "sirv";
3085
3308
  async function preview(rootProjectDir, options) {
3086
3309
  process.env.NODE_ENV = "development";
@@ -3089,9 +3312,9 @@ async function preview(rootProjectDir, options) {
3089
3312
  const port = parseInt(process.env.PORT || "4007");
3090
3313
  const host = options?.host || "localhost";
3091
3314
  console.log();
3092
- console.log(`${dim6("\u2503")} project: ${rootDir}`);
3093
- console.log(`${dim6("\u2503")} server: http://${host}:${port}`);
3094
- console.log(`${dim6("\u2503")} mode: preview`);
3315
+ console.log(`${dim7("\u2503")} project: ${rootDir}`);
3316
+ console.log(`${dim7("\u2503")} server: http://${host}:${port}`);
3317
+ console.log(`${dim7("\u2503")} mode: preview`);
3095
3318
  console.log();
3096
3319
  server.listen(port, host);
3097
3320
  }
@@ -3226,7 +3449,7 @@ function rootPreviewServer500Middleware() {
3226
3449
  import path13 from "node:path";
3227
3450
  import cookieParser3 from "cookie-parser";
3228
3451
  import { default as express3 } from "express";
3229
- import { dim as dim7 } from "kleur/colors";
3452
+ import { dim as dim8 } from "kleur/colors";
3230
3453
  import sirv3 from "sirv";
3231
3454
  async function start(rootProjectDir, options) {
3232
3455
  process.env.NODE_ENV = "production";
@@ -3235,9 +3458,9 @@ async function start(rootProjectDir, options) {
3235
3458
  const port = parseInt(process.env.PORT || "4007");
3236
3459
  const host = options?.host || "localhost";
3237
3460
  console.log();
3238
- console.log(`${dim7("\u2503")} project: ${rootDir}`);
3239
- console.log(`${dim7("\u2503")} server: http://${host}:${port}`);
3240
- console.log(`${dim7("\u2503")} mode: production`);
3461
+ console.log(`${dim8("\u2503")} project: ${rootDir}`);
3462
+ console.log(`${dim8("\u2503")} server: http://${host}:${port}`);
3463
+ console.log(`${dim8("\u2503")} mode: production`);
3241
3464
  console.log();
3242
3465
  server.listen(port, host);
3243
3466
  }
@@ -3403,7 +3626,14 @@ var CliRunner = class {
3403
3626
  "--filter <urlPathRegex>",
3404
3627
  'builds the url paths that match the given regex, e.g. "/products/.*"',
3405
3628
  ""
3406
- ).action(build);
3629
+ ).addOption(
3630
+ new Option(
3631
+ "--log <mode>",
3632
+ 'build log output: "progress" (default) shows a progress indicator and a final summary, "verbose" prints one line per output file, "quiet" prints only the final summary'
3633
+ ).choices(["progress", "verbose", "quiet"])
3634
+ ).action(
3635
+ (rootProjectDir, options, cmd) => build(rootProjectDir, cmd.optsWithGlobals())
3636
+ );
3407
3637
  program.command("codegen [type] [name]").description("generates boilerplate code").option("--out <outdir>", "output dir").action(codegen);
3408
3638
  program.command("create-package [path]").alias("package").description(
3409
3639
  "creates a standalone npm package for deployment to various hosting services"
@@ -3465,4 +3695,4 @@ export {
3465
3695
  createProdServer,
3466
3696
  CliRunner
3467
3697
  };
3468
- //# sourceMappingURL=chunk-E4F6CMO3.js.map
3698
+ //# sourceMappingURL=chunk-GK3TDYUY.js.map