@nuvio/cli 1.0.0 → 1.1.0

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.
Files changed (2) hide show
  1. package/dist/cli-entry.js +887 -117
  2. package/package.json +4 -3
package/dist/cli-entry.js CHANGED
@@ -1,37 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { resolve } from "path";
4
+ import { resolve as resolve4 } from "path";
5
5
 
6
- // src/detect-pm.ts
7
- import { existsSync } from "fs";
8
- import { join } from "path";
9
- function detectPackageManager(root, override) {
10
- if (override) return override;
11
- if (existsSync(join(root, "pnpm-lock.yaml"))) return "pnpm";
12
- if (existsSync(join(root, "package-lock.json"))) return "npm";
13
- if (existsSync(join(root, "yarn.lock"))) return "yarn";
14
- if (existsSync(join(root, "bun.lockb")) || existsSync(join(root, "bun.lock")))
15
- return "bun";
16
- return "npm";
17
- }
18
- function installCommand(pm, version) {
19
- const pkgs = `@nuvio/vite-plugin@${version} @nuvio/overlay@${version}`;
20
- switch (pm) {
21
- case "pnpm":
22
- return `pnpm add -D ${pkgs}`;
23
- case "yarn":
24
- return `yarn add -D ${pkgs}`;
25
- case "bun":
26
- return `bun add -d ${pkgs}`;
27
- default:
28
- return `npm install -D ${pkgs}`;
29
- }
30
- }
6
+ // src/brand-apply.ts
7
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
8
+ import { join as join2, resolve } from "path";
9
+ import { applyPatchToSource } from "@nuvio/ast-engine";
10
+ import {
11
+ buildBrandPatchOps,
12
+ brandFragmentHostHint,
13
+ DEFAULT_BRAND_CONFIG,
14
+ isHostPatchable,
15
+ isPccBrandableCategory,
16
+ normalizeBrandConfig
17
+ } from "@nuvio/shared";
18
+ import {
19
+ listPccManifestFiles,
20
+ loadPccManifestFromFile,
21
+ resolvePccManifestPath
22
+ } from "@nuvio/shared/load-pcc-manifest";
31
23
 
32
24
  // src/detect-project.ts
33
- import { existsSync as existsSync2, readFileSync } from "fs";
34
- import { join as join2 } from "path";
25
+ import { existsSync, readFileSync } from "fs";
26
+ import { join } from "path";
35
27
 
36
28
  // src/messages.ts
37
29
  var MSG = {
@@ -73,8 +65,8 @@ function detectTailwind(root, pkg) {
73
65
  if (hasDep(pkg, "tailwindcss")) return true;
74
66
  const cssCandidates = ["src/index.css", "src/App.css"];
75
67
  for (const rel of cssCandidates) {
76
- const p = join2(root, rel);
77
- if (!existsSync2(p)) continue;
68
+ const p = join(root, rel);
69
+ if (!existsSync(p)) continue;
78
70
  const text = readFileSync(p, "utf8");
79
71
  if (text.includes("@tailwind") || text.includes('@import "tailwindcss"') || text.includes("@import 'tailwindcss'")) {
80
72
  return true;
@@ -83,8 +75,8 @@ function detectTailwind(root, pkg) {
83
75
  return false;
84
76
  }
85
77
  function detectProject(root) {
86
- const packageJsonPath = join2(root, "package.json");
87
- if (!existsSync2(packageJsonPath)) {
78
+ const packageJsonPath = join(root, "package.json");
79
+ if (!existsSync(packageJsonPath)) {
88
80
  throw new PreflightError(MSG.noPackageJson);
89
81
  }
90
82
  const packageJson = JSON.parse(
@@ -99,8 +91,8 @@ function detectProject(root) {
99
91
  let viteConfigPath = "";
100
92
  let viteConfigName = "";
101
93
  for (const name of VITE_CONFIGS) {
102
- const p = join2(root, name);
103
- if (existsSync2(p)) {
94
+ const p = join(root, name);
95
+ if (existsSync(p)) {
104
96
  viteConfigPath = p;
105
97
  viteConfigName = name;
106
98
  break;
@@ -127,32 +119,6 @@ function detectProject(root) {
127
119
  };
128
120
  }
129
121
 
130
- // src/nuvio-deps.ts
131
- import { readFileSync as readFileSync2 } from "fs";
132
- function readPackageJson(packageJsonPath) {
133
- return JSON.parse(readFileSync2(packageJsonPath, "utf8"));
134
- }
135
- function getDependencyVersion(pkg, name) {
136
- const deps = pkg.dependencies;
137
- const devDeps = pkg.devDependencies;
138
- return deps?.[name] ?? devDeps?.[name];
139
- }
140
- function hasNuvioDependency(pkg, name) {
141
- return Boolean(getDependencyVersion(pkg, name));
142
- }
143
- function hasNuvioPackages(pkg) {
144
- return hasNuvioDependency(pkg, "@nuvio/vite-plugin") && hasNuvioDependency(pkg, "@nuvio/overlay");
145
- }
146
- function isWorkspaceLinkedVersion(version) {
147
- if (!version) return false;
148
- return version.startsWith("workspace:") || version.startsWith("link:") || version.startsWith("file:");
149
- }
150
- function nuvioOverlayLinkKind(pkg) {
151
- const raw = getDependencyVersion(pkg, "@nuvio/overlay");
152
- if (!raw) return "missing";
153
- return isWorkspaceLinkedVersion(raw) ? "workspace" : "npm";
154
- }
155
-
156
122
  // src/project-scan.ts
157
123
  import { relative } from "path";
158
124
  import {
@@ -182,10 +148,639 @@ function aggregateClassNameModes(entries) {
182
148
  return counts;
183
149
  }
184
150
 
151
+ // src/brand-apply.ts
152
+ var BRAND_RELATIVE = "nuvio/brand.json";
153
+ function readProjectBrandConfig(cwd) {
154
+ const filePath = join2(resolve(cwd), BRAND_RELATIVE);
155
+ if (!existsSync2(filePath)) {
156
+ return { ...DEFAULT_BRAND_CONFIG };
157
+ }
158
+ try {
159
+ const raw = readFileSync2(filePath, "utf8");
160
+ return normalizeBrandConfig(JSON.parse(raw));
161
+ } catch {
162
+ return { ...DEFAULT_BRAND_CONFIG };
163
+ }
164
+ }
165
+ function duplicateIdSet(errors) {
166
+ return new Set(errors.map((error) => error.id));
167
+ }
168
+ function collectApplyTargets(manifest, entries, duplicateIds) {
169
+ const byId = new Map(entries.map((entry) => [entry.id, entry]));
170
+ const targets = [];
171
+ let skipped = 0;
172
+ for (const category of Object.keys(manifest.categories)) {
173
+ if (!isPccBrandableCategory(category)) {
174
+ continue;
175
+ }
176
+ const config = manifest.categories[category];
177
+ if (!config) {
178
+ continue;
179
+ }
180
+ for (const hostId of config.hosts) {
181
+ const entry = byId.get(hostId);
182
+ if (!entry) {
183
+ skipped += 1;
184
+ continue;
185
+ }
186
+ const patch = isHostPatchable(entry, duplicateIds);
187
+ if (!patch.patchable) {
188
+ skipped += 1;
189
+ continue;
190
+ }
191
+ targets.push({
192
+ hostId,
193
+ category,
194
+ action: category,
195
+ entry
196
+ });
197
+ }
198
+ }
199
+ return { targets, skipped };
200
+ }
201
+ async function applyTargetsToProject(projectRoot, targets, brand, dryRun) {
202
+ const root = resolve(projectRoot);
203
+ const byFile = /* @__PURE__ */ new Map();
204
+ for (const target of targets) {
205
+ const filePath = resolve(root, target.entry.file);
206
+ const list = byFile.get(filePath) ?? [];
207
+ list.push(target);
208
+ byFile.set(filePath, list);
209
+ }
210
+ let applied = 0;
211
+ const failed = [];
212
+ for (const [filePath, fileTargets] of byFile) {
213
+ if (!existsSync2(filePath)) {
214
+ for (const target of fileTargets) {
215
+ failed.push({ hostId: target.hostId, reason: "file_missing" });
216
+ }
217
+ continue;
218
+ }
219
+ if (dryRun) {
220
+ applied += fileTargets.length;
221
+ continue;
222
+ }
223
+ let source = readFileSync2(filePath, "utf8");
224
+ for (const target of fileTargets) {
225
+ const ops = buildBrandPatchOps(
226
+ target.action,
227
+ brand,
228
+ brandFragmentHostHint(target.entry)
229
+ );
230
+ const result = await applyPatchToSource(source, filePath, target.hostId, ops, {
231
+ classNameMode: target.entry.classNameMode
232
+ });
233
+ if (!result.ok) {
234
+ failed.push({ hostId: target.hostId, reason: result.message ?? result.code });
235
+ continue;
236
+ }
237
+ source = result.source;
238
+ applied += 1;
239
+ }
240
+ writeFileSync(filePath, source, "utf8");
241
+ }
242
+ return { applied, failed };
243
+ }
244
+ function printHumanReport(result) {
245
+ console.log(`Page: ${result.page}`);
246
+ console.log(`Route: ${result.route}`);
247
+ console.log(`Manifest: ${result.manifestPath}`);
248
+ console.log(`Applied: ${result.applied}`);
249
+ console.log(`Skipped: ${result.skipped}`);
250
+ if (result.failed.length > 0) {
251
+ console.log("Failed:");
252
+ for (const failure of result.failed) {
253
+ console.log(`- ${failure.hostId}: ${failure.reason}`);
254
+ }
255
+ }
256
+ }
257
+ async function applyLoadedManifest(manifestPath, manifest, entries, duplicateIds, brand, projectRoot, opts) {
258
+ const { targets, skipped } = collectApplyTargets(manifest, entries, duplicateIds);
259
+ const { applied, failed } = await applyTargetsToProject(
260
+ projectRoot,
261
+ targets,
262
+ brand,
263
+ opts.dryRun === true
264
+ );
265
+ return {
266
+ page: manifest.page,
267
+ route: manifest.route,
268
+ manifestPath,
269
+ applied,
270
+ skipped,
271
+ failed
272
+ };
273
+ }
274
+ async function runBrandApplyAll(opts) {
275
+ const manifestPaths = listPccManifestFiles(opts.cwd);
276
+ if (manifestPaths.length === 0) {
277
+ console.error(`No PCC manifests found under ${resolve(opts.cwd)}/nuvio/pages`);
278
+ return 2;
279
+ }
280
+ const brand = readProjectBrandConfig(opts.cwd);
281
+ let scan;
282
+ try {
283
+ scan = scanProject(opts.cwd);
284
+ } catch (e) {
285
+ if (e instanceof PreflightError) {
286
+ console.error(e.message);
287
+ return 3;
288
+ }
289
+ throw e;
290
+ }
291
+ const duplicateIds = duplicateIdSet(scan.index.duplicateErrors);
292
+ const pages = [];
293
+ for (const manifestPath of manifestPaths) {
294
+ const loaded = loadPccManifestFromFile(manifestPath);
295
+ if (!loaded.ok) {
296
+ console.error(`Invalid PCC manifest (${manifestPath}): ${loaded.error.message}`);
297
+ return 2;
298
+ }
299
+ pages.push(
300
+ await applyLoadedManifest(
301
+ manifestPath,
302
+ loaded.manifest,
303
+ scan.index.entries,
304
+ duplicateIds,
305
+ brand,
306
+ scan.ctx.root,
307
+ opts
308
+ )
309
+ );
310
+ }
311
+ const pass = pages.every((page) => page.failed.length === 0);
312
+ if (opts.json) {
313
+ console.log(JSON.stringify({ pass, dryRun: opts.dryRun === true, pages }, null, 2));
314
+ return pass ? 0 : 1;
315
+ }
316
+ console.log(`Nuvio Brand Apply${opts.dryRun ? " (dry run)" : ""}
317
+ `);
318
+ for (const page of pages) {
319
+ printHumanReport(page);
320
+ console.log("");
321
+ }
322
+ console.log(`Result: ${pass ? "PASS" : "FAIL"}`);
323
+ return pass ? 0 : 1;
324
+ }
325
+ async function runBrandApply(opts) {
326
+ if (opts.all) {
327
+ return runBrandApplyAll(opts);
328
+ }
329
+ let manifestPath;
330
+ try {
331
+ manifestPath = resolve(
332
+ resolvePccManifestPath(opts.cwd, { page: opts.page, manifest: opts.manifest })
333
+ );
334
+ } catch (e) {
335
+ console.error(e instanceof Error ? e.message : String(e));
336
+ return 2;
337
+ }
338
+ const loaded = loadPccManifestFromFile(manifestPath);
339
+ if (!loaded.ok) {
340
+ console.error(`Invalid PCC manifest (${manifestPath}): ${loaded.error.message}`);
341
+ return 2;
342
+ }
343
+ const brand = readProjectBrandConfig(opts.cwd);
344
+ let scan;
345
+ try {
346
+ scan = scanProject(opts.cwd);
347
+ } catch (e) {
348
+ if (e instanceof PreflightError) {
349
+ console.error(e.message);
350
+ return 3;
351
+ }
352
+ throw e;
353
+ }
354
+ const duplicateIds = duplicateIdSet(scan.index.duplicateErrors);
355
+ const result = await applyLoadedManifest(
356
+ manifestPath,
357
+ loaded.manifest,
358
+ scan.index.entries,
359
+ duplicateIds,
360
+ brand,
361
+ scan.ctx.root,
362
+ opts
363
+ );
364
+ const pass = result.failed.length === 0;
365
+ if (opts.json) {
366
+ console.log(JSON.stringify({ pass, dryRun: opts.dryRun === true, ...result }, null, 2));
367
+ return pass ? 0 : 1;
368
+ }
369
+ console.log(`Nuvio Brand Apply${opts.dryRun ? " (dry run)" : ""}
370
+ `);
371
+ printHumanReport(result);
372
+ console.log(`
373
+ Result: ${pass ? "PASS" : "FAIL"}`);
374
+ return pass ? 0 : 1;
375
+ }
376
+
377
+ // src/brand-scan.ts
378
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
379
+ import { join as join3, resolve as resolve2 } from "path";
380
+ import {
381
+ DEFAULT_BRAND_CONFIG as DEFAULT_BRAND_CONFIG2,
382
+ evaluateBrandPageScan,
383
+ getBrandColorLabel,
384
+ getBrandDensityLabel,
385
+ getBrandRadiusLabel,
386
+ getBrandTypographyLabel,
387
+ normalizeBrandConfig as normalizeBrandConfig2,
388
+ pccCategoryLabel
389
+ } from "@nuvio/shared";
390
+ import {
391
+ listPccManifestFiles as listPccManifestFiles2,
392
+ loadPccManifestFromFile as loadPccManifestFromFile2,
393
+ resolvePccManifestPath as resolvePccManifestPath2
394
+ } from "@nuvio/shared/load-pcc-manifest";
395
+ var BRAND_RELATIVE2 = "nuvio/brand.json";
396
+ function readProjectBrandConfig2(cwd) {
397
+ const filePath = join3(resolve2(cwd), BRAND_RELATIVE2);
398
+ if (!existsSync3(filePath)) {
399
+ return { ...DEFAULT_BRAND_CONFIG2 };
400
+ }
401
+ try {
402
+ const raw = readFileSync3(filePath, "utf8");
403
+ return normalizeBrandConfig2(JSON.parse(raw));
404
+ } catch {
405
+ return { ...DEFAULT_BRAND_CONFIG2 };
406
+ }
407
+ }
408
+ function formatBrandSummary(brand) {
409
+ return [
410
+ getBrandColorLabel(brand.color),
411
+ getBrandRadiusLabel(brand.radius),
412
+ getBrandDensityLabel(brand.density),
413
+ getBrandTypographyLabel(brand.typography)
414
+ ].join(" \xB7 ");
415
+ }
416
+ function printHumanReport2(result, manifestPath) {
417
+ console.log("Nuvio Brand Scan\n");
418
+ console.log(`Page: ${result.page}`);
419
+ console.log(`Route: ${result.route}`);
420
+ console.log(`Manifest: ${manifestPath}`);
421
+ console.log(`Saved brand: ${formatBrandSummary(result.brand)}
422
+ `);
423
+ console.log("Category summary:");
424
+ for (const category of result.categories) {
425
+ const label = pccCategoryLabel(category.category).padEnd(10);
426
+ const status = category.pass ? "PASS" : "FAIL";
427
+ console.log(
428
+ `${label} ${status} on-brand ${category.onBrand}/${category.expected} \xB7 off-brand ${category.offBrand} \xB7 no-traits ${category.noTraits}`
429
+ );
430
+ }
431
+ console.log("\nTotals:");
432
+ console.log(`On-brand ${result.onBrandCount}`);
433
+ console.log(`Off-brand ${result.offBrandCount}`);
434
+ console.log(`No traits ${result.noTraitsCount}`);
435
+ console.log(`Missing ${result.missingCount}`);
436
+ const offBrandHosts = result.hosts.filter((host) => host.status === "off_brand");
437
+ if (offBrandHosts.length > 0) {
438
+ console.log("\nOff-brand hosts:");
439
+ for (const host of offBrandHosts.slice(0, 12)) {
440
+ const headline = host.inspect?.headline ?? "Off-brand";
441
+ console.log(`- ${host.hostId} (${host.category}) \u2014 ${headline}`);
442
+ }
443
+ if (offBrandHosts.length > 12) {
444
+ console.log(`\u2026and ${offBrandHosts.length - 12} more`);
445
+ }
446
+ }
447
+ console.log(`
448
+ Result: ${result.pass ? "PASS" : "FAIL"}`);
449
+ }
450
+ function printAllHumanReport(pages) {
451
+ console.log("Nuvio Brand Scan (all pages)\n");
452
+ for (const page of pages) {
453
+ const status = page.result.pass ? "PASS" : "FAIL";
454
+ const summary = `${page.result.onBrandCount} on-brand \xB7 ${page.result.offBrandCount} off-brand`;
455
+ console.log(`${page.result.page.padEnd(16)} ${status} ${summary}`);
456
+ }
457
+ const pass = pages.every((page) => page.result.pass);
458
+ console.log(`
459
+ Result: ${pass ? "PASS" : "FAIL"}`);
460
+ }
461
+ function scanLoadedManifest(manifestPath, entries, brand) {
462
+ const loaded = loadPccManifestFromFile2(manifestPath);
463
+ if (!loaded.ok) {
464
+ return {
465
+ ok: false,
466
+ code: 2,
467
+ message: `Invalid PCC manifest (${manifestPath}): ${loaded.error.message}`
468
+ };
469
+ }
470
+ return {
471
+ ok: true,
472
+ result: evaluateBrandPageScan(loaded.manifest, entries, brand)
473
+ };
474
+ }
475
+ function runBrandScanAll(opts) {
476
+ const manifestPaths = listPccManifestFiles2(opts.cwd);
477
+ if (manifestPaths.length === 0) {
478
+ console.error(`No PCC manifests found under ${resolve2(opts.cwd)}/nuvio/pages`);
479
+ return 2;
480
+ }
481
+ const brand = readProjectBrandConfig2(opts.cwd);
482
+ let scan;
483
+ try {
484
+ scan = scanProject(opts.cwd);
485
+ } catch (e) {
486
+ if (e instanceof PreflightError) {
487
+ console.error(e.message);
488
+ return 3;
489
+ }
490
+ throw e;
491
+ }
492
+ const pages = [];
493
+ for (const manifestPath of manifestPaths) {
494
+ const scanned = scanLoadedManifest(manifestPath, scan.index.entries, brand);
495
+ if (!scanned.ok) {
496
+ console.error(scanned.message);
497
+ return scanned.code;
498
+ }
499
+ pages.push({ manifestPath, result: scanned.result });
500
+ }
501
+ const pass = pages.every((page) => page.result.pass);
502
+ if (opts.json) {
503
+ console.log(JSON.stringify({ pass, brand, pages }, null, 2));
504
+ return pass ? 0 : 1;
505
+ }
506
+ printAllHumanReport(pages);
507
+ return pass ? 0 : 1;
508
+ }
509
+ function runBrandScan(opts) {
510
+ if (opts.all) {
511
+ return runBrandScanAll(opts);
512
+ }
513
+ let manifestPath;
514
+ try {
515
+ manifestPath = resolve2(
516
+ resolvePccManifestPath2(opts.cwd, { page: opts.page, manifest: opts.manifest })
517
+ );
518
+ } catch (e) {
519
+ console.error(e instanceof Error ? e.message : String(e));
520
+ return 2;
521
+ }
522
+ const brand = readProjectBrandConfig2(opts.cwd);
523
+ let scan;
524
+ try {
525
+ scan = scanProject(opts.cwd);
526
+ } catch (e) {
527
+ if (e instanceof PreflightError) {
528
+ console.error(e.message);
529
+ return 3;
530
+ }
531
+ throw e;
532
+ }
533
+ const scanned = scanLoadedManifest(manifestPath, scan.index.entries, brand);
534
+ if (!scanned.ok) {
535
+ console.error(scanned.message);
536
+ return scanned.code;
537
+ }
538
+ if (opts.json) {
539
+ console.log(
540
+ JSON.stringify(
541
+ {
542
+ manifestPath,
543
+ ...scanned.result
544
+ },
545
+ null,
546
+ 2
547
+ )
548
+ );
549
+ return scanned.result.pass ? 0 : 1;
550
+ }
551
+ printHumanReport2(scanned.result, manifestPath);
552
+ return scanned.result.pass ? 0 : 1;
553
+ }
554
+
555
+ // src/coverage-verify.ts
556
+ import { resolve as resolve3 } from "path";
557
+ import {
558
+ evaluatePageCoverage,
559
+ pccCategoryLabel as pccCategoryLabel2
560
+ } from "@nuvio/shared";
561
+ import {
562
+ listPccManifestFiles as listPccManifestFiles3,
563
+ loadPccManifestFromFile as loadPccManifestFromFile3,
564
+ resolvePccManifestPath as resolvePccManifestPath3
565
+ } from "@nuvio/shared/load-pcc-manifest";
566
+ function formatCategoryLine(summary) {
567
+ const label = pccCategoryLabel2(summary.category).padEnd(10);
568
+ const status = summary.pass ? "PASS" : "FAIL";
569
+ return `${label} ${status} ${summary.indexed}/${summary.expected}`;
570
+ }
571
+ function printHumanReport3(result, manifestPath) {
572
+ console.log("Nuvio Coverage Report\n");
573
+ console.log(`Page: ${result.page}`);
574
+ console.log(`Route: ${result.route}`);
575
+ console.log(`Manifest: ${manifestPath}
576
+ `);
577
+ console.log("Category summary:");
578
+ for (const category of result.categories) {
579
+ console.log(formatCategoryLine(category));
580
+ }
581
+ console.log("\nCoverage gates:");
582
+ const g = result.gates;
583
+ console.log(`Indexed ${g.indexed}/${g.expected}`);
584
+ console.log(`Patchable ${g.patchable}/${g.expected}`);
585
+ console.log(`Categorized ${g.categorized}/${g.expected}`);
586
+ console.log(`Editable ${g.editable}/${g.expected}`);
587
+ console.log(`Brandable ${g.brandable}/${g.expected}`);
588
+ console.log("\nBrandability:");
589
+ console.log(`Brandable ${result.brandableCount}`);
590
+ console.log(`Editable-only ${result.editableOnlyCount}`);
591
+ const missing = result.issues.filter((i) => i.kind === "missing");
592
+ const unpatchable = result.issues.filter((i) => i.kind === "unpatchable");
593
+ const duplicates = result.issues.filter((i) => i.kind === "duplicate_id");
594
+ if (missing.length > 0) {
595
+ console.log("\nMissing hosts:");
596
+ for (const issue of missing) {
597
+ console.log(`- ${issue.hostId} (${issue.category})`);
598
+ }
599
+ }
600
+ if (unpatchable.length > 0) {
601
+ console.log("\nUnpatchable hosts:");
602
+ for (const issue of unpatchable) {
603
+ console.log(`- ${issue.hostId}`);
604
+ if (issue.reason) {
605
+ console.log(` reason: ${issue.reason}`);
606
+ }
607
+ }
608
+ }
609
+ if (duplicates.length > 0) {
610
+ console.log("\nDuplicate id hosts:");
611
+ for (const issue of duplicates) {
612
+ console.log(`- ${issue.hostId}`);
613
+ }
614
+ }
615
+ console.log(`
616
+ Result: ${result.pass ? "PASS" : "FAIL"}`);
617
+ }
618
+ function printAllHumanReport2(summary) {
619
+ console.log("Nuvio Coverage Report (all pages)\n");
620
+ for (const entry of summary.pages) {
621
+ const status = entry.result.pass ? "PASS" : "FAIL";
622
+ console.log(`${entry.result.page.padEnd(16)} ${status} ${entry.manifestPath}`);
623
+ }
624
+ console.log(`
625
+ Result: ${summary.pass ? "PASS" : "FAIL"}`);
626
+ }
627
+ function verifyLoadedManifest(manifestPath, entries, duplicateErrors) {
628
+ const loaded = loadPccManifestFromFile3(manifestPath);
629
+ if (!loaded.ok) {
630
+ return {
631
+ ok: false,
632
+ code: 2,
633
+ message: `Invalid PCC manifest (${manifestPath}): ${loaded.error.message}`
634
+ };
635
+ }
636
+ const result = evaluatePageCoverage(loaded.manifest, entries, duplicateErrors);
637
+ return { ok: true, result };
638
+ }
639
+ function runCoverageVerifyAll(opts) {
640
+ const manifestPaths = listPccManifestFiles3(opts.cwd);
641
+ if (manifestPaths.length === 0) {
642
+ console.error(`No PCC manifests found under ${resolve3(opts.cwd)}/nuvio/pages`);
643
+ return 2;
644
+ }
645
+ let scan;
646
+ try {
647
+ scan = scanProject(opts.cwd);
648
+ } catch (e) {
649
+ if (e instanceof PreflightError) {
650
+ console.error(e.message);
651
+ return 3;
652
+ }
653
+ throw e;
654
+ }
655
+ const pages = [];
656
+ for (const manifestPath of manifestPaths) {
657
+ const verified = verifyLoadedManifest(
658
+ manifestPath,
659
+ scan.index.entries,
660
+ scan.index.duplicateErrors
661
+ );
662
+ if (!verified.ok) {
663
+ console.error(verified.message);
664
+ return verified.code;
665
+ }
666
+ pages.push({ manifestPath, result: verified.result });
667
+ }
668
+ const summary = {
669
+ pass: pages.every((page) => page.result.pass),
670
+ pages
671
+ };
672
+ if (opts.json) {
673
+ console.log(JSON.stringify(summary, null, 2));
674
+ return summary.pass ? 0 : 1;
675
+ }
676
+ printAllHumanReport2(summary);
677
+ return summary.pass ? 0 : 1;
678
+ }
679
+ function runCoverageVerify(opts) {
680
+ if (opts.all) {
681
+ return runCoverageVerifyAll(opts);
682
+ }
683
+ let manifestPath;
684
+ try {
685
+ manifestPath = resolve3(
686
+ resolvePccManifestPath3(opts.cwd, { page: opts.page, manifest: opts.manifest })
687
+ );
688
+ } catch (e) {
689
+ console.error(e instanceof Error ? e.message : String(e));
690
+ return 2;
691
+ }
692
+ let scan;
693
+ try {
694
+ scan = scanProject(opts.cwd);
695
+ } catch (e) {
696
+ if (e instanceof PreflightError) {
697
+ console.error(e.message);
698
+ return 3;
699
+ }
700
+ throw e;
701
+ }
702
+ const verified = verifyLoadedManifest(
703
+ manifestPath,
704
+ scan.index.entries,
705
+ scan.index.duplicateErrors
706
+ );
707
+ if (!verified.ok) {
708
+ console.error(verified.message);
709
+ return verified.code;
710
+ }
711
+ if (opts.json) {
712
+ console.log(
713
+ JSON.stringify(
714
+ {
715
+ manifestPath,
716
+ ...verified.result
717
+ },
718
+ null,
719
+ 2
720
+ )
721
+ );
722
+ return verified.result.pass ? 0 : 1;
723
+ }
724
+ printHumanReport3(verified.result, manifestPath);
725
+ return verified.result.pass ? 0 : 1;
726
+ }
727
+
728
+ // src/detect-pm.ts
729
+ import { existsSync as existsSync4 } from "fs";
730
+ import { join as join4 } from "path";
731
+ function detectPackageManager(root, override) {
732
+ if (override) return override;
733
+ if (existsSync4(join4(root, "pnpm-lock.yaml"))) return "pnpm";
734
+ if (existsSync4(join4(root, "package-lock.json"))) return "npm";
735
+ if (existsSync4(join4(root, "yarn.lock"))) return "yarn";
736
+ if (existsSync4(join4(root, "bun.lockb")) || existsSync4(join4(root, "bun.lock")))
737
+ return "bun";
738
+ return "npm";
739
+ }
740
+ function installCommand(pm, version) {
741
+ const pkgs = `@nuvio/vite-plugin@${version} @nuvio/overlay@${version}`;
742
+ switch (pm) {
743
+ case "pnpm":
744
+ return `pnpm add -D ${pkgs}`;
745
+ case "yarn":
746
+ return `yarn add -D ${pkgs}`;
747
+ case "bun":
748
+ return `bun add -d ${pkgs}`;
749
+ default:
750
+ return `npm install -D ${pkgs}`;
751
+ }
752
+ }
753
+
754
+ // src/nuvio-deps.ts
755
+ import { readFileSync as readFileSync4 } from "fs";
756
+ function readPackageJson(packageJsonPath) {
757
+ return JSON.parse(readFileSync4(packageJsonPath, "utf8"));
758
+ }
759
+ function getDependencyVersion(pkg, name) {
760
+ const deps = pkg.dependencies;
761
+ const devDeps = pkg.devDependencies;
762
+ return deps?.[name] ?? devDeps?.[name];
763
+ }
764
+ function hasNuvioDependency(pkg, name) {
765
+ return Boolean(getDependencyVersion(pkg, name));
766
+ }
767
+ function hasNuvioPackages(pkg) {
768
+ return hasNuvioDependency(pkg, "@nuvio/vite-plugin") && hasNuvioDependency(pkg, "@nuvio/overlay");
769
+ }
770
+ function isWorkspaceLinkedVersion(version) {
771
+ if (!version) return false;
772
+ return version.startsWith("workspace:") || version.startsWith("link:") || version.startsWith("file:");
773
+ }
774
+ function nuvioOverlayLinkKind(pkg) {
775
+ const raw = getDependencyVersion(pkg, "@nuvio/overlay");
776
+ if (!raw) return "missing";
777
+ return isWorkspaceLinkedVersion(raw) ? "workspace" : "npm";
778
+ }
779
+
185
780
  // src/telemetry.ts
186
- import { mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
781
+ import { mkdirSync, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
187
782
  import { homedir } from "os";
188
- import { join as join3 } from "path";
783
+ import { join as join5 } from "path";
189
784
  import { randomUUID } from "crypto";
190
785
  import os from "os";
191
786
  import { PostHog } from "posthog-node";
@@ -201,7 +796,7 @@ var NUVIO_VERSION = require2("../package.json").version;
201
796
  // src/telemetry.ts
202
797
  var POSTHOG_HOST = "https://us.i.posthog.com";
203
798
  function telemetryFilePath() {
204
- return join3(homedir(), ".nuvio", "telemetry.json");
799
+ return join5(homedir(), ".nuvio", "telemetry.json");
205
800
  }
206
801
  var FORBIDDEN_PROP_KEYS = /* @__PURE__ */ new Set([
207
802
  "cwd",
@@ -240,7 +835,7 @@ function tokenIsConfigured(token) {
240
835
  function readOrCreateAnonymousId() {
241
836
  if (sessionAnonymousId) return sessionAnonymousId;
242
837
  try {
243
- const raw = readFileSync3(telemetryFilePath(), "utf8");
838
+ const raw = readFileSync5(telemetryFilePath(), "utf8");
244
839
  const parsed = JSON.parse(raw);
245
840
  if (parsed.anonymousId) {
246
841
  sessionAnonymousId = parsed.anonymousId;
@@ -251,8 +846,8 @@ function readOrCreateAnonymousId() {
251
846
  const id = randomUUID();
252
847
  sessionAnonymousId = id;
253
848
  try {
254
- mkdirSync(join3(homedir(), ".nuvio"), { recursive: true, mode: 448 });
255
- writeFileSync(
849
+ mkdirSync(join5(homedir(), ".nuvio"), { recursive: true, mode: 448 });
850
+ writeFileSync2(
256
851
  telemetryFilePath(),
257
852
  JSON.stringify({ anonymousId: id }, null, 2),
258
853
  { mode: 384 }
@@ -408,8 +1003,8 @@ var babel_traverse_default = traverse;
408
1003
  // src/patch-app-root.ts
409
1004
  import * as t from "@babel/types";
410
1005
  import fg from "fast-glob";
411
- import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
412
- import { join as join4 } from "path";
1006
+ import { existsSync as existsSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
1007
+ import { join as join6 } from "path";
413
1008
 
414
1009
  // src/babel-generator.ts
415
1010
  import generateImport from "@babel/generator";
@@ -443,8 +1038,8 @@ var APP_CANDIDATES = [
443
1038
  ];
444
1039
  function resolveAppFile(root) {
445
1040
  for (const rel of APP_CANDIDATES) {
446
- const p = join4(root, rel);
447
- if (existsSync3(p)) return p;
1041
+ const p = join6(root, rel);
1042
+ if (existsSync5(p)) return p;
448
1043
  }
449
1044
  return null;
450
1045
  }
@@ -502,7 +1097,7 @@ function appendDevShell(ast) {
502
1097
  return patched;
503
1098
  }
504
1099
  function patchAppRootFile(filePath) {
505
- const source = readFileSync4(filePath, "utf8");
1100
+ const source = readFileSync6(filePath, "utf8");
506
1101
  let ast;
507
1102
  try {
508
1103
  ast = parseTs(source, filePath);
@@ -528,12 +1123,12 @@ function patchAppRootFile(filePath) {
528
1123
  if (!hasDevShell(ast) && !appendDevShell(ast)) {
529
1124
  return { ok: false, error: "no JSX return to patch" };
530
1125
  }
531
- writeFileSync2(filePath, printTs(ast, source), "utf8");
1126
+ writeFileSync3(filePath, printTs(ast, source), "utf8");
532
1127
  return { ok: true };
533
1128
  }
534
1129
  function appHasDevShell(filePath) {
535
- if (!existsSync3(filePath)) return false;
536
- const source = readFileSync4(filePath, "utf8");
1130
+ if (!existsSync5(filePath)) return false;
1131
+ const source = readFileSync6(filePath, "utf8");
537
1132
  try {
538
1133
  const ast = parseTs(source, filePath);
539
1134
  return hasOverlayImport(ast) && hasDevShell(ast);
@@ -556,8 +1151,8 @@ function projectHasDevShell(root) {
556
1151
  }
557
1152
 
558
1153
  // src/patch-main-styles.ts
559
- import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
560
- import { join as join5 } from "path";
1154
+ import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
1155
+ import { join as join7 } from "path";
561
1156
  var MAIN_CANDIDATES = ["src/main.tsx", "src/main.jsx", "main.tsx", "main.jsx"];
562
1157
  var STYLE_IMPORT = 'import "@nuvio/overlay/style.css";';
563
1158
  function overlayInstalledFromNpm(packageJsonPath) {
@@ -566,17 +1161,17 @@ function overlayInstalledFromNpm(packageJsonPath) {
566
1161
  }
567
1162
  function resolveMainEntry(root) {
568
1163
  for (const rel of MAIN_CANDIDATES) {
569
- const p = join5(root, rel);
570
- if (existsSync4(p)) return p;
1164
+ const p = join7(root, rel);
1165
+ if (existsSync6(p)) return p;
571
1166
  }
572
1167
  return null;
573
1168
  }
574
1169
  function mainHasOverlayStyles(mainPath) {
575
- const text = readFileSync5(mainPath, "utf8");
1170
+ const text = readFileSync7(mainPath, "utf8");
576
1171
  return text.includes("@nuvio/overlay/style.css") || text.includes("@nuvio/overlay/dist/style.css");
577
1172
  }
578
1173
  function patchMainOverlayStyles(mainPath) {
579
- const text = readFileSync5(mainPath, "utf8");
1174
+ const text = readFileSync7(mainPath, "utf8");
580
1175
  if (text.includes("@nuvio/overlay/style.css") || text.includes("@nuvio/overlay/dist/style.css")) {
581
1176
  return { ok: true, skipped: true };
582
1177
  }
@@ -590,13 +1185,13 @@ function patchMainOverlayStyles(mainPath) {
590
1185
  } else {
591
1186
  lines.unshift(STYLE_IMPORT, "");
592
1187
  }
593
- writeFileSync3(mainPath, lines.join("\n"));
1188
+ writeFileSync4(mainPath, lines.join("\n"));
594
1189
  return { ok: true };
595
1190
  }
596
1191
 
597
1192
  // src/patch-vite-config.ts
598
1193
  import * as t2 from "@babel/types";
599
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
1194
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "fs";
600
1195
  function hasNuvioImport(ast) {
601
1196
  let found = false;
602
1197
  babel_traverse_default(ast, {
@@ -679,7 +1274,7 @@ function ensureOptimizeDepsExclude(ast) {
679
1274
  return patched;
680
1275
  }
681
1276
  function viteConfigHasOverlayOptimizeExclude(filePath) {
682
- const source = readFileSync6(filePath, "utf8");
1277
+ const source = readFileSync8(filePath, "utf8");
683
1278
  return /optimizeDeps\s*:\s*\{[^}]*exclude\s*:\s*\[[^\]]*@nuvio\/overlay/.test(
684
1279
  source
685
1280
  ) || /exclude\s*:\s*\[[^\]]*["']@nuvio\/overlay["']/.test(source);
@@ -697,7 +1292,7 @@ function appendNuvioPlugin(ast) {
697
1292
  return patched;
698
1293
  }
699
1294
  function patchViteConfigFile(filePath) {
700
- const source = readFileSync6(filePath, "utf8");
1295
+ const source = readFileSync8(filePath, "utf8");
701
1296
  let ast;
702
1297
  try {
703
1298
  ast = parseTs(source, filePath);
@@ -722,11 +1317,11 @@ function patchViteConfigFile(filePath) {
722
1317
  return { ok: false, error: "no static plugins array" };
723
1318
  }
724
1319
  }
725
- writeFileSync4(filePath, printTs(ast, source), "utf8");
1320
+ writeFileSync5(filePath, printTs(ast, source), "utf8");
726
1321
  return { ok: true, skipped: alreadyPlugin && depsPatched };
727
1322
  }
728
1323
  function viteConfigHasNuvio(filePath) {
729
- const source = readFileSync6(filePath, "utf8");
1324
+ const source = readFileSync8(filePath, "utf8");
730
1325
  try {
731
1326
  const ast = parseTs(source, filePath);
732
1327
  return hasNuvioImport(ast) && hasNuvioPluginCall(ast);
@@ -736,14 +1331,14 @@ function viteConfigHasNuvio(filePath) {
736
1331
  }
737
1332
 
738
1333
  // src/scan-ids.ts
739
- import { readFileSync as readFileSync7 } from "fs";
740
- import { join as join6 } from "path";
1334
+ import { readFileSync as readFileSync9 } from "fs";
1335
+ import { join as join8 } from "path";
741
1336
  import fg2 from "fast-glob";
742
1337
  var ID_GLOB = ["src/**/*.{tsx,jsx}"];
743
1338
  function projectHasPageTitleId(root) {
744
1339
  const files = fg2.sync(ID_GLOB, { cwd: root, absolute: true });
745
1340
  for (const file of files) {
746
- const text = readFileSync7(file, "utf8");
1341
+ const text = readFileSync9(file, "utf8");
747
1342
  if (/data-nuvio-id=["']page\.title["']/.test(text)) {
748
1343
  return true;
749
1344
  }
@@ -753,8 +1348,8 @@ function projectHasPageTitleId(root) {
753
1348
  function findHeadingFiles(root) {
754
1349
  const files = fg2.sync(ID_GLOB, { cwd: root, absolute: true });
755
1350
  const ordered = [
756
- join6(root, "src/App.tsx"),
757
- join6(root, "src/App.jsx"),
1351
+ join8(root, "src/App.tsx"),
1352
+ join8(root, "src/App.jsx"),
758
1353
  ...files.filter(
759
1354
  (f) => !f.endsWith("App.tsx") && !f.endsWith("App.jsx")
760
1355
  )
@@ -967,7 +1562,7 @@ import { createInterface } from "readline";
967
1562
 
968
1563
  // src/install-packages.ts
969
1564
  import { spawnSync } from "child_process";
970
- import { readFileSync as readFileSync8 } from "fs";
1565
+ import { readFileSync as readFileSync10 } from "fs";
971
1566
  function parseInstalledVersion(pkg, name) {
972
1567
  const dev = pkg.devDependencies;
973
1568
  const deps = pkg.dependencies;
@@ -976,7 +1571,7 @@ function parseInstalledVersion(pkg, name) {
976
1571
  return raw.replace(/^[\^~]/, "");
977
1572
  }
978
1573
  function packagesNeedInstall(packageJsonPath, targetVersion) {
979
- const pkg = JSON.parse(readFileSync8(packageJsonPath, "utf8"));
1574
+ const pkg = JSON.parse(readFileSync10(packageJsonPath, "utf8"));
980
1575
  for (const name of ["@nuvio/vite-plugin", "@nuvio/overlay"]) {
981
1576
  const v = parseInstalledVersion(pkg, name);
982
1577
  if (v !== targetVersion) return true;
@@ -1003,9 +1598,9 @@ function runInstall(root, pm, version) {
1003
1598
 
1004
1599
  // src/patch-starter-id.ts
1005
1600
  import * as t3 from "@babel/types";
1006
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
1601
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
1007
1602
  function patchFirstHeading(filePath) {
1008
- const source = readFileSync9(filePath, "utf8");
1603
+ const source = readFileSync11(filePath, "utf8");
1009
1604
  let ast;
1010
1605
  try {
1011
1606
  ast = parseTs(source, filePath);
@@ -1034,13 +1629,13 @@ function patchFirstHeading(filePath) {
1034
1629
  }
1035
1630
  });
1036
1631
  if (!patched) return { ok: false, error: "no h1/h2" };
1037
- writeFileSync5(filePath, printTs(ast, source), "utf8");
1632
+ writeFileSync6(filePath, printTs(ast, source), "utf8");
1038
1633
  return { ok: true };
1039
1634
  }
1040
1635
  function patchStarterId(root) {
1041
1636
  const files = findHeadingFiles(root);
1042
1637
  for (const file of files) {
1043
- const source = readFileSync9(file, "utf8");
1638
+ const source = readFileSync11(file, "utf8");
1044
1639
  if (!/<h[12][\s>]/.test(source) && !/<>[\s\S]*<h[12]/.test(source)) {
1045
1640
  try {
1046
1641
  const ast = parseTs(source, file);
@@ -1080,12 +1675,13 @@ function createPlan(root, pm) {
1080
1675
  }
1081
1676
 
1082
1677
  // src/write-nuvio-folder.ts
1083
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
1084
- import { dirname, join as join7 } from "path";
1678
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
1679
+ import { dirname, join as join9 } from "path";
1085
1680
  import { fileURLToPath } from "url";
1086
- var CLI_ROOT = join7(dirname(fileURLToPath(import.meta.url)), "..");
1681
+ import { DEFAULT_BRAND_CONFIG as DEFAULT_BRAND_CONFIG3, serializeBrandConfig } from "@nuvio/shared";
1682
+ var CLI_ROOT = join9(dirname(fileURLToPath(import.meta.url)), "..");
1087
1683
  function loadTemplate(name) {
1088
- return readFileSync10(join7(CLI_ROOT, "templates", name), "utf8");
1684
+ return readFileSync12(join9(CLI_ROOT, "templates", name), "utf8");
1089
1685
  }
1090
1686
  function render(tpl, vars) {
1091
1687
  let out = tpl;
@@ -1095,7 +1691,7 @@ function render(tpl, vars) {
1095
1691
  return out;
1096
1692
  }
1097
1693
  function writeNuvioFolder(opts) {
1098
- const dir = join7(opts.root, "nuvio");
1694
+ const dir = join9(opts.root, "nuvio");
1099
1695
  const created = [];
1100
1696
  mkdirSync2(dir, { recursive: true });
1101
1697
  const vars = {
@@ -1103,34 +1699,40 @@ function writeNuvioFolder(opts) {
1103
1699
  PM_RUN: opts.pmRun,
1104
1700
  FAILED_STEPS: opts.failedSteps.join(", ") || "(none)"
1105
1701
  };
1106
- const startHere = join7(dir, "START_HERE.md");
1107
- writeFileSync6(
1702
+ const startHere = join9(dir, "START_HERE.md");
1703
+ writeFileSync7(
1108
1704
  startHere,
1109
1705
  render(loadTemplate("START_HERE.md.tpl"), vars),
1110
1706
  "utf8"
1111
1707
  );
1112
1708
  created.push("nuvio/START_HERE.md");
1113
- const readme = join7(dir, "README.md");
1114
- writeFileSync6(
1709
+ const readme = join9(dir, "README.md");
1710
+ writeFileSync7(
1115
1711
  readme,
1116
1712
  render(loadTemplate("README.pointer.md.tpl"), vars),
1117
1713
  "utf8"
1118
1714
  );
1119
1715
  created.push("nuvio/README.md");
1120
- const agent = join7(dir, "AGENT.md");
1121
- if (!existsSync5(agent) || opts.forceAgent) {
1122
- writeFileSync6(agent, render(loadTemplate("AGENT.md.tpl"), vars), "utf8");
1716
+ const agent = join9(dir, "AGENT.md");
1717
+ if (!existsSync7(agent) || opts.forceAgent) {
1718
+ writeFileSync7(agent, render(loadTemplate("AGENT.md.tpl"), vars), "utf8");
1123
1719
  created.push("nuvio/AGENT.md");
1124
1720
  }
1125
1721
  if (opts.failedSteps.length > 0) {
1126
- const todo = join7(dir, "SETUP_TODO.md");
1127
- writeFileSync6(
1722
+ const todo = join9(dir, "SETUP_TODO.md");
1723
+ writeFileSync7(
1128
1724
  todo,
1129
1725
  render(loadTemplate("SETUP_TODO.md.tpl"), vars),
1130
1726
  "utf8"
1131
1727
  );
1132
1728
  created.push("nuvio/SETUP_TODO.md");
1133
1729
  }
1730
+ const brand = join9(dir, "brand.json");
1731
+ if (!existsSync7(brand)) {
1732
+ writeFileSync7(brand, `${JSON.stringify(serializeBrandConfig(DEFAULT_BRAND_CONFIG3), null, 2)}
1733
+ `, "utf8");
1734
+ created.push("nuvio/brand.json");
1735
+ }
1134
1736
  return created;
1135
1737
  }
1136
1738
 
@@ -1151,10 +1753,10 @@ async function confirm(plan) {
1151
1753
  console.log(`
1152
1754
  Install: ${plan.installCommand || "(skip)"}`);
1153
1755
  const rl = createInterface({ input: process.stdin, output: process.stdout });
1154
- return new Promise((resolve2) => {
1756
+ return new Promise((resolve5) => {
1155
1757
  rl.question("\nProceed? [y/N] ", (answer) => {
1156
1758
  rl.close();
1157
- resolve2(/^y(es)?$/i.test(answer.trim()));
1759
+ resolve5(/^y(es)?$/i.test(answer.trim()));
1158
1760
  });
1159
1761
  });
1160
1762
  }
@@ -1527,6 +2129,9 @@ Usage:
1527
2129
  nuvio doctor [options]
1528
2130
  nuvio scan [options]
1529
2131
  nuvio stats [options]
2132
+ nuvio coverage verify [options]
2133
+ nuvio brand scan [options]
2134
+ nuvio brand apply [options]
1530
2135
 
1531
2136
  Common options:
1532
2137
  --cwd <path> Project root (default: current directory)
@@ -1546,11 +2151,32 @@ Init options:
1546
2151
  Doctor options:
1547
2152
  --skip-dev-server Skip localhost dev-server health check
1548
2153
 
2154
+ Coverage verify options:
2155
+ --page <slug> Page slug (loads nuvio/pages/<slug>.pcc.yaml)
2156
+ --manifest <path> Explicit PCC manifest path (overrides --page)
2157
+ --all Verify every manifest in nuvio/pages/
2158
+
2159
+ Brand scan options:
2160
+ --page <slug> Page slug (loads nuvio/pages/<slug>.pcc.yaml)
2161
+ --manifest <path> Explicit PCC manifest path (overrides --page)
2162
+ --all Scan every manifest in nuvio/pages/
2163
+
2164
+ Brand apply options:
2165
+ --page <slug> Page slug (loads nuvio/pages/<slug>.pcc.yaml)
2166
+ --manifest <path> Explicit PCC manifest path (overrides --page)
2167
+ --all Apply to every manifest in nuvio/pages/
2168
+ --dry-run Report targets without writing source files
2169
+
1549
2170
  Examples:
1550
2171
  pnpm dlx @nuvio/cli init --yes
1551
2172
  pnpm dlx @nuvio/cli doctor
1552
2173
  pnpm dlx @nuvio/cli scan --json
1553
2174
  pnpm dlx @nuvio/cli stats
2175
+ pnpm dlx @nuvio/cli coverage verify --page dashboard --cwd apps/tailadmin-dogfood
2176
+ pnpm dlx @nuvio/cli coverage verify --all --cwd apps/tailadmin-dogfood
2177
+ pnpm dlx @nuvio/cli brand scan --page dashboard --cwd apps/tailadmin-dogfood
2178
+ pnpm dlx @nuvio/cli brand scan --all --cwd apps/tailadmin-dogfood
2179
+ pnpm dlx @nuvio/cli brand apply --all --cwd apps/tailadmin-dogfood
1554
2180
  `);
1555
2181
  }
1556
2182
  function parseInitArgs(argv) {
@@ -1578,7 +2204,7 @@ function parseInitArgs(argv) {
1578
2204
  else if (arg === "--pm") {
1579
2205
  opts.pm = args[++i];
1580
2206
  } else if (arg === "--cwd") {
1581
- opts.cwd = resolve(args[++i] ?? ".");
2207
+ opts.cwd = resolve4(args[++i] ?? ".");
1582
2208
  } else if (arg.startsWith("-")) {
1583
2209
  console.error(`Unknown option: ${arg}`);
1584
2210
  help = true;
@@ -1605,7 +2231,7 @@ function parseProjectCommandArgs(argv, command) {
1605
2231
  common.verbose = true;
1606
2232
  doctor.verbose = true;
1607
2233
  } else if (arg === "--cwd") {
1608
- const cwd = resolve(args[++i] ?? ".");
2234
+ const cwd = resolve4(args[++i] ?? ".");
1609
2235
  common.cwd = cwd;
1610
2236
  doctor.cwd = cwd;
1611
2237
  } else if (arg === "--skip-dev-server") {
@@ -1617,16 +2243,114 @@ function parseProjectCommandArgs(argv, command) {
1617
2243
  }
1618
2244
  return { command, common, doctor, help };
1619
2245
  }
2246
+ function parseCoverageVerifyArgs(argv) {
2247
+ const args = argv.slice(2);
2248
+ const common = { cwd: process.cwd() };
2249
+ let help = false;
2250
+ let page;
2251
+ let manifest;
2252
+ let all = false;
2253
+ let i = 0;
2254
+ if (args[0] === "coverage") {
2255
+ i = 1;
2256
+ }
2257
+ const subcommand = args[i] === "verify" ? "verify" : "";
2258
+ if (subcommand) {
2259
+ i += 1;
2260
+ }
2261
+ for (; i < args.length; i++) {
2262
+ const arg = args[i];
2263
+ if (arg === "-h" || arg === "--help") {
2264
+ help = true;
2265
+ continue;
2266
+ }
2267
+ if (arg === "--json") {
2268
+ common.json = true;
2269
+ } else if (arg === "--verbose") {
2270
+ common.verbose = true;
2271
+ } else if (arg === "--cwd") {
2272
+ common.cwd = resolve4(args[++i] ?? ".");
2273
+ } else if (arg === "--page") {
2274
+ page = args[++i];
2275
+ } else if (arg === "--manifest") {
2276
+ manifest = resolve4(args[++i] ?? "");
2277
+ } else if (arg === "--all") {
2278
+ all = true;
2279
+ } else if (arg.startsWith("-")) {
2280
+ console.error(`Unknown option: ${arg}`);
2281
+ help = true;
2282
+ }
2283
+ }
2284
+ return { command: "coverage", subcommand, common, page, manifest, all, help };
2285
+ }
2286
+ function parseBrandArgs(argv) {
2287
+ const args = argv.slice(2);
2288
+ const common = { cwd: process.cwd() };
2289
+ let help = false;
2290
+ let page;
2291
+ let manifest;
2292
+ let all = false;
2293
+ let dryRun = false;
2294
+ let i = 0;
2295
+ if (args[0] === "brand") {
2296
+ i = 1;
2297
+ }
2298
+ const subArg = args[i];
2299
+ const subcommand = subArg === "scan" || subArg === "apply" ? subArg : "";
2300
+ if (subcommand) {
2301
+ i += 1;
2302
+ }
2303
+ for (; i < args.length; i++) {
2304
+ const arg = args[i];
2305
+ if (arg === "-h" || arg === "--help") {
2306
+ help = true;
2307
+ continue;
2308
+ }
2309
+ if (arg === "--json") {
2310
+ common.json = true;
2311
+ } else if (arg === "--verbose") {
2312
+ common.verbose = true;
2313
+ } else if (arg === "--cwd") {
2314
+ common.cwd = resolve4(args[++i] ?? ".");
2315
+ } else if (arg === "--page") {
2316
+ page = args[++i];
2317
+ } else if (arg === "--manifest") {
2318
+ manifest = resolve4(args[++i] ?? "");
2319
+ } else if (arg === "--all") {
2320
+ all = true;
2321
+ } else if (arg === "--dry-run") {
2322
+ dryRun = true;
2323
+ } else if (arg.startsWith("-")) {
2324
+ console.error(`Unknown option: ${arg}`);
2325
+ help = true;
2326
+ }
2327
+ }
2328
+ return { command: "brand", subcommand, common, page, manifest, all, dryRun, help };
2329
+ }
1620
2330
  async function runCli(argv) {
1621
2331
  registerTelemetrySignalHandlers();
1622
2332
  const rawCommand = argv[2] ?? null;
1623
- const isProjectCmd = rawCommand === "doctor" || rawCommand === "scan" || rawCommand === "stats";
2333
+ const isCoverageCmd = rawCommand === "coverage";
2334
+ const isBrandCmd = rawCommand === "brand";
2335
+ const isProjectCmd = rawCommand === "doctor" || rawCommand === "scan" || rawCommand === "stats" || isCoverageCmd || isBrandCmd;
1624
2336
  let help = false;
1625
2337
  let command = rawCommand;
1626
2338
  let initOpts = { cwd: process.cwd() };
1627
2339
  let commonOpts = { cwd: process.cwd() };
1628
2340
  let doctorOpts = { cwd: process.cwd() };
1629
- if (isProjectCmd) {
2341
+ let coverageOpts = null;
2342
+ let brandOpts = null;
2343
+ if (isBrandCmd) {
2344
+ brandOpts = parseBrandArgs(argv);
2345
+ help = brandOpts.help;
2346
+ command = brandOpts.command;
2347
+ commonOpts = brandOpts.common;
2348
+ } else if (isCoverageCmd) {
2349
+ coverageOpts = parseCoverageVerifyArgs(argv);
2350
+ help = coverageOpts.help;
2351
+ command = coverageOpts.command;
2352
+ commonOpts = coverageOpts.common;
2353
+ } else if (isProjectCmd) {
1630
2354
  const parsed = parseProjectCommandArgs(argv, rawCommand);
1631
2355
  help = parsed.help;
1632
2356
  command = parsed.command;
@@ -1665,6 +2389,52 @@ async function runCli(argv) {
1665
2389
  return runScan({ cwd: commonOpts.cwd, json: commonOpts.json });
1666
2390
  case "stats":
1667
2391
  return runStats({ cwd: commonOpts.cwd, json: commonOpts.json });
2392
+ case "coverage": {
2393
+ if (!coverageOpts || coverageOpts.subcommand !== "verify") {
2394
+ console.error("Usage: nuvio coverage verify --page <slug>");
2395
+ printHelp();
2396
+ return 1;
2397
+ }
2398
+ if (!coverageOpts.page && !coverageOpts.manifest && !coverageOpts.all) {
2399
+ console.error("Either --page, --manifest, or --all is required");
2400
+ return 2;
2401
+ }
2402
+ return runCoverageVerify({
2403
+ cwd: coverageOpts.common.cwd,
2404
+ page: coverageOpts.page,
2405
+ manifest: coverageOpts.manifest,
2406
+ all: coverageOpts.all,
2407
+ json: coverageOpts.common.json
2408
+ });
2409
+ }
2410
+ case "brand": {
2411
+ if (!brandOpts || brandOpts.subcommand !== "scan" && brandOpts.subcommand !== "apply") {
2412
+ console.error("Usage: nuvio brand scan|apply --page <slug>");
2413
+ printHelp();
2414
+ return 1;
2415
+ }
2416
+ if (!brandOpts.page && !brandOpts.manifest && !brandOpts.all) {
2417
+ console.error("Either --page, --manifest, or --all is required");
2418
+ return 2;
2419
+ }
2420
+ if (brandOpts.subcommand === "scan") {
2421
+ return runBrandScan({
2422
+ cwd: brandOpts.common.cwd,
2423
+ page: brandOpts.page,
2424
+ manifest: brandOpts.manifest,
2425
+ all: brandOpts.all,
2426
+ json: brandOpts.common.json
2427
+ });
2428
+ }
2429
+ return runBrandApply({
2430
+ cwd: brandOpts.common.cwd,
2431
+ page: brandOpts.page,
2432
+ manifest: brandOpts.manifest,
2433
+ all: brandOpts.all,
2434
+ dryRun: brandOpts.dryRun,
2435
+ json: brandOpts.common.json
2436
+ });
2437
+ }
1668
2438
  default:
1669
2439
  console.error(`Unknown command: ${command}`);
1670
2440
  printHelp();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuvio/cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Nuvio CLI 1.0 — Vite + Tailwind onboarding and diagnostics (init, doctor, scan, stats).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -41,8 +41,9 @@
41
41
  "@babel/types": "^7.26.9",
42
42
  "fast-glob": "^3.3.3",
43
43
  "posthog-node": "^5.36.2",
44
- "@nuvio/shared": "1.0.0",
45
- "@nuvio/vite-plugin": "1.0.0"
44
+ "@nuvio/shared": "1.1.0",
45
+ "@nuvio/ast-engine": "1.1.0",
46
+ "@nuvio/vite-plugin": "1.1.0"
46
47
  },
47
48
  "devDependencies": {
48
49
  "@types/babel__generator": "^7.6.8",