@loworbitstudio/visor 1.21.0 → 1.23.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.
- package/dist/CHANGELOG.json +1 -1
- package/dist/index.js +918 -449
- package/dist/registry.json +21 -21
- package/dist/visor-manifest.json +185 -4
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import { dirname as
|
|
4
|
+
import { readFileSync as readFileSync34 } from "fs";
|
|
5
|
+
import { dirname as dirname16, join as join34 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
7
7
|
import { Command as Command2 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/commands/check.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
|
-
import { statSync as
|
|
12
|
-
import { resolve as
|
|
11
|
+
import { statSync as statSync4 } from "fs";
|
|
12
|
+
import { resolve as resolve5, dirname as dirname4 } from "path";
|
|
13
13
|
|
|
14
14
|
// src/registry/resolve.ts
|
|
15
15
|
import { readFileSync } from "fs";
|
|
@@ -395,13 +395,160 @@ async function scanJsx(pathArg) {
|
|
|
395
395
|
}
|
|
396
396
|
|
|
397
397
|
// src/check/design.ts
|
|
398
|
-
import { readFileSync as
|
|
399
|
-
import { resolve as
|
|
398
|
+
import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync3, existsSync as existsSync2 } from "fs";
|
|
399
|
+
import { resolve as resolve3, extname as extname2, join as join4, basename, dirname as dirname3 } from "path";
|
|
400
|
+
|
|
401
|
+
// src/check/jsx-ast.ts
|
|
402
|
+
import { parse as babelParse } from "@babel/parser";
|
|
403
|
+
function parseJsx(source) {
|
|
404
|
+
try {
|
|
405
|
+
return babelParse(source, {
|
|
406
|
+
sourceType: "module",
|
|
407
|
+
plugins: ["jsx", "typescript"],
|
|
408
|
+
errorRecovery: true
|
|
409
|
+
});
|
|
410
|
+
} catch {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// src/check/taxonomy.ts
|
|
416
|
+
import { readFileSync as readFileSync3, existsSync, statSync as statSync2 } from "fs";
|
|
417
|
+
import { resolve as resolve2, join as join3, dirname as dirname2, isAbsolute } from "path";
|
|
418
|
+
var DISCOVERY_CANDIDATES = [
|
|
419
|
+
"taxonomy.json",
|
|
420
|
+
join3(".visor", "taxonomy.json"),
|
|
421
|
+
join3("design-prototypes", "admin-ui", "taxonomy.json")
|
|
422
|
+
];
|
|
423
|
+
function slugToIdentifier(slug2) {
|
|
424
|
+
const bare = slug2.includes("/") ? slug2.slice(slug2.lastIndexOf("/") + 1) : slug2;
|
|
425
|
+
return bare.split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
426
|
+
}
|
|
427
|
+
function refToSlug(ref) {
|
|
428
|
+
const bare = ref.includes("/") ? ref.slice(ref.lastIndexOf("/") + 1) : ref;
|
|
429
|
+
if (!/^[a-z][a-z0-9-]*$/.test(bare)) return null;
|
|
430
|
+
return bare;
|
|
431
|
+
}
|
|
432
|
+
function parseTaxonomy(data, path2) {
|
|
433
|
+
const root = data;
|
|
434
|
+
const tiers = Array.isArray(root?.tiers) ? root.tiers : null;
|
|
435
|
+
if (!tiers) {
|
|
436
|
+
throw new Error(`taxonomy.json at ${path2} has no "tiers" array \u2014 not a kit taxonomy.`);
|
|
437
|
+
}
|
|
438
|
+
const slugs = /* @__PURE__ */ new Set();
|
|
439
|
+
for (const tier of tiers) {
|
|
440
|
+
const families = tier.families;
|
|
441
|
+
if (!Array.isArray(families)) continue;
|
|
442
|
+
for (const family of families) {
|
|
443
|
+
const rows = family.rows;
|
|
444
|
+
if (!Array.isArray(rows)) continue;
|
|
445
|
+
for (const row of rows) {
|
|
446
|
+
const entries = row?.visor?.entries;
|
|
447
|
+
if (!Array.isArray(entries)) continue;
|
|
448
|
+
for (const entry of entries) {
|
|
449
|
+
if (entry?.kind !== "component" && entry?.kind !== "block") continue;
|
|
450
|
+
if (typeof entry.ref !== "string") continue;
|
|
451
|
+
const slug2 = refToSlug(entry.ref);
|
|
452
|
+
if (slug2) slugs.add(slug2);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (slugs.size === 0) {
|
|
458
|
+
throw new Error(`taxonomy.json at ${path2} declares no component or block elements.`);
|
|
459
|
+
}
|
|
460
|
+
const identifiers = /* @__PURE__ */ new Set();
|
|
461
|
+
for (const slug2 of slugs) identifiers.add(slugToIdentifier(slug2));
|
|
462
|
+
return { path: path2, slugs, identifiers };
|
|
463
|
+
}
|
|
464
|
+
function loadFrom(path2) {
|
|
465
|
+
const raw = readFileSync3(path2, "utf-8");
|
|
466
|
+
let data;
|
|
467
|
+
try {
|
|
468
|
+
data = JSON.parse(raw);
|
|
469
|
+
} catch {
|
|
470
|
+
throw new Error(`taxonomy.json at ${path2} is not valid JSON.`);
|
|
471
|
+
}
|
|
472
|
+
return parseTaxonomy(data, path2);
|
|
473
|
+
}
|
|
474
|
+
function discover(startDir) {
|
|
475
|
+
let dir = startDir;
|
|
476
|
+
for (; ; ) {
|
|
477
|
+
for (const candidate of DISCOVERY_CANDIDATES) {
|
|
478
|
+
const full = join3(dir, candidate);
|
|
479
|
+
if (existsSync(full)) return full;
|
|
480
|
+
}
|
|
481
|
+
if (existsSync(join3(dir, ".git"))) return null;
|
|
482
|
+
const parent = dirname2(dir);
|
|
483
|
+
if (parent === dir) return null;
|
|
484
|
+
dir = parent;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function resolveTaxonomy(options) {
|
|
488
|
+
const { explicitPath, visorrcPath, scanPath, env = process.env } = options;
|
|
489
|
+
const scanAbs = resolve2(scanPath);
|
|
490
|
+
let scanDir = scanAbs;
|
|
491
|
+
try {
|
|
492
|
+
if (!statSync2(scanAbs).isDirectory()) scanDir = dirname2(scanAbs);
|
|
493
|
+
} catch {
|
|
494
|
+
scanDir = dirname2(scanAbs);
|
|
495
|
+
}
|
|
496
|
+
const envPath = env.VISOR_TAXONOMY;
|
|
497
|
+
const configured = [];
|
|
498
|
+
if (explicitPath) configured.push({ path: resolve2(explicitPath), source: "flag" });
|
|
499
|
+
else if (envPath) configured.push({ path: resolve2(envPath), source: "env" });
|
|
500
|
+
else if (visorrcPath) {
|
|
501
|
+
configured.push({
|
|
502
|
+
path: isAbsolute(visorrcPath) ? visorrcPath : resolve2(scanDir, visorrcPath),
|
|
503
|
+
source: "visorrc"
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
if (configured.length > 0) {
|
|
507
|
+
const { path: path2, source } = configured[0];
|
|
508
|
+
if (!existsSync(path2)) {
|
|
509
|
+
return {
|
|
510
|
+
taxonomy: null,
|
|
511
|
+
path: path2,
|
|
512
|
+
source,
|
|
513
|
+
error: `Kit taxonomy not found at ${path2}.`,
|
|
514
|
+
requested: true
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
try {
|
|
518
|
+
return { taxonomy: loadFrom(path2), path: path2, source, error: null, requested: true };
|
|
519
|
+
} catch (err) {
|
|
520
|
+
return {
|
|
521
|
+
taxonomy: null,
|
|
522
|
+
path: path2,
|
|
523
|
+
source,
|
|
524
|
+
error: err instanceof Error ? err.message : String(err),
|
|
525
|
+
requested: true
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const found = discover(scanDir);
|
|
530
|
+
if (!found) {
|
|
531
|
+
return { taxonomy: null, path: null, source: null, error: null, requested: false };
|
|
532
|
+
}
|
|
533
|
+
try {
|
|
534
|
+
return { taxonomy: loadFrom(found), path: found, source: "discovered", error: null, requested: false };
|
|
535
|
+
} catch (err) {
|
|
536
|
+
return {
|
|
537
|
+
taxonomy: null,
|
|
538
|
+
path: found,
|
|
539
|
+
source: "discovered",
|
|
540
|
+
error: err instanceof Error ? err.message : String(err),
|
|
541
|
+
requested: false
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/check/design.ts
|
|
400
547
|
function loadVisorRc(dir) {
|
|
401
|
-
const rcPath =
|
|
402
|
-
if (!
|
|
548
|
+
const rcPath = join4(dir, ".visorrc.json");
|
|
549
|
+
if (!existsSync2(rcPath)) return {};
|
|
403
550
|
try {
|
|
404
|
-
const raw =
|
|
551
|
+
const raw = readFileSync4(rcPath, "utf-8");
|
|
405
552
|
return JSON.parse(raw);
|
|
406
553
|
} catch {
|
|
407
554
|
return {};
|
|
@@ -411,15 +558,15 @@ var CODE_EXTS = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js"]);
|
|
|
411
558
|
var STYLE_EXTS = /* @__PURE__ */ new Set([".css", ".module.css"]);
|
|
412
559
|
var ALL_EXTS = /* @__PURE__ */ new Set([...CODE_EXTS, ...STYLE_EXTS]);
|
|
413
560
|
function collectFiles2(pathArg) {
|
|
414
|
-
const abs =
|
|
561
|
+
const abs = resolve3(pathArg);
|
|
415
562
|
try {
|
|
416
|
-
const s =
|
|
563
|
+
const s = statSync3(abs);
|
|
417
564
|
if (s.isDirectory()) {
|
|
418
565
|
let recurse2 = function(dir) {
|
|
419
566
|
for (const entry of readdirSync2(dir)) {
|
|
420
567
|
if (entry.startsWith(".") || entry === "node_modules" || entry === "dist") continue;
|
|
421
|
-
const full =
|
|
422
|
-
const es =
|
|
568
|
+
const full = join4(dir, entry);
|
|
569
|
+
const es = statSync3(full);
|
|
423
570
|
if (es.isDirectory()) recurse2(full);
|
|
424
571
|
else if (ALL_EXTS.has(extname2(full))) files.push(full);
|
|
425
572
|
}
|
|
@@ -493,6 +640,93 @@ function ruleHardcodedHex(source, filePath) {
|
|
|
493
640
|
return found;
|
|
494
641
|
}
|
|
495
642
|
var PX_WHITELIST = /* @__PURE__ */ new Set(["0px", "1px", "2px", "3px"]);
|
|
643
|
+
var LEGACY_PX_CONTEXT_RE = /margin|padding|width|height|gap|top:|left:|right:|bottom:|font-size|line-height|min-width|max-width|min-height|max-height/;
|
|
644
|
+
var SIZING_PROPS = /* @__PURE__ */ new Set([
|
|
645
|
+
// box model
|
|
646
|
+
"margin",
|
|
647
|
+
"margintop",
|
|
648
|
+
"marginright",
|
|
649
|
+
"marginbottom",
|
|
650
|
+
"marginleft",
|
|
651
|
+
"marginblock",
|
|
652
|
+
"marginblockstart",
|
|
653
|
+
"marginblockend",
|
|
654
|
+
"margininline",
|
|
655
|
+
"margininlinestart",
|
|
656
|
+
"margininlineend",
|
|
657
|
+
"padding",
|
|
658
|
+
"paddingtop",
|
|
659
|
+
"paddingright",
|
|
660
|
+
"paddingbottom",
|
|
661
|
+
"paddingleft",
|
|
662
|
+
"paddingblock",
|
|
663
|
+
"paddingblockstart",
|
|
664
|
+
"paddingblockend",
|
|
665
|
+
"paddinginline",
|
|
666
|
+
"paddinginlinestart",
|
|
667
|
+
"paddinginlineend",
|
|
668
|
+
// intrinsic size
|
|
669
|
+
"width",
|
|
670
|
+
"minwidth",
|
|
671
|
+
"maxwidth",
|
|
672
|
+
"height",
|
|
673
|
+
"minheight",
|
|
674
|
+
"maxheight",
|
|
675
|
+
"size",
|
|
676
|
+
"blocksize",
|
|
677
|
+
"minblocksize",
|
|
678
|
+
"maxblocksize",
|
|
679
|
+
"inlinesize",
|
|
680
|
+
"mininlinesize",
|
|
681
|
+
"maxinlinesize",
|
|
682
|
+
// position
|
|
683
|
+
"top",
|
|
684
|
+
"right",
|
|
685
|
+
"bottom",
|
|
686
|
+
"left",
|
|
687
|
+
"inset",
|
|
688
|
+
"insetblock",
|
|
689
|
+
"insetinline",
|
|
690
|
+
// layout gaps
|
|
691
|
+
"gap",
|
|
692
|
+
"rowgap",
|
|
693
|
+
"columngap",
|
|
694
|
+
"gridgap",
|
|
695
|
+
"gridrowgap",
|
|
696
|
+
"gridcolumngap",
|
|
697
|
+
// typographic metrics
|
|
698
|
+
"fontsize",
|
|
699
|
+
"lineheight",
|
|
700
|
+
"letterspacing",
|
|
701
|
+
"wordspacing",
|
|
702
|
+
"textindent",
|
|
703
|
+
// corner radius
|
|
704
|
+
"borderradius",
|
|
705
|
+
"bordertopleftradius",
|
|
706
|
+
"bordertoprightradius",
|
|
707
|
+
"borderbottomleftradius",
|
|
708
|
+
"borderbottomrightradius",
|
|
709
|
+
"borderstartstartradius",
|
|
710
|
+
"borderstartendradius",
|
|
711
|
+
"borderendstartradius",
|
|
712
|
+
"borderendendradius",
|
|
713
|
+
// flex + scroll boxes
|
|
714
|
+
"flexbasis",
|
|
715
|
+
"scrollmargin",
|
|
716
|
+
"scrollpadding"
|
|
717
|
+
]);
|
|
718
|
+
var PROP_NAME_RE = /(^|[^\w$])(-{0,2}[A-Za-z][\w-]*)\s*:/g;
|
|
719
|
+
function normalizeProp(name) {
|
|
720
|
+
return name.replace(/[^A-Za-z0-9]/g, "").toLowerCase();
|
|
721
|
+
}
|
|
722
|
+
function hasSizingProp(line) {
|
|
723
|
+
PROP_NAME_RE.lastIndex = 0;
|
|
724
|
+
let m;
|
|
725
|
+
while ((m = PROP_NAME_RE.exec(line)) !== null) {
|
|
726
|
+
if (SIZING_PROPS.has(normalizeProp(m[2]))) return true;
|
|
727
|
+
}
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
496
730
|
function ruleHardcodedPx(source, filePath) {
|
|
497
731
|
const found = [];
|
|
498
732
|
const PX_RE = /\b(\d+(?:\.\d+)?)px\b/g;
|
|
@@ -500,24 +734,134 @@ function ruleHardcodedPx(source, filePath) {
|
|
|
500
734
|
for (let i = 0; i < src.length; i++) {
|
|
501
735
|
const l = src[i];
|
|
502
736
|
if (l.trim().startsWith("//") || l.trim().startsWith("*") || l.trim().startsWith("/*")) continue;
|
|
503
|
-
|
|
737
|
+
const legacy = LEGACY_PX_CONTEXT_RE.test(l);
|
|
738
|
+
if (!legacy && !hasSizingProp(l)) continue;
|
|
504
739
|
let m;
|
|
505
740
|
PX_RE.lastIndex = 0;
|
|
506
741
|
while ((m = PX_RE.exec(l)) !== null) {
|
|
507
742
|
const full = m[0];
|
|
508
743
|
if (PX_WHITELIST.has(full)) continue;
|
|
509
|
-
found.push(finding(
|
|
744
|
+
found.push(legacy ? finding(
|
|
510
745
|
filePath,
|
|
511
746
|
i + 1,
|
|
512
747
|
"hardcoded-px",
|
|
513
748
|
"error",
|
|
514
749
|
`Hardcoded pixel value "${full}" in spacing/sizing bypasses the Borealis spacing token system.`,
|
|
515
750
|
"Replace with a semantic spacing token: var(--space-1), var(--space-2), var(--space-4), etc."
|
|
751
|
+
) : finding(
|
|
752
|
+
filePath,
|
|
753
|
+
i + 1,
|
|
754
|
+
"hardcoded-px",
|
|
755
|
+
"warn",
|
|
756
|
+
`Hardcoded pixel value "${full}" in spacing/sizing bypasses the Borealis spacing token system. Reported as a warning because this property spelling was previously skipped (VI-631) \u2014 it will graduate to an error in a future major.`,
|
|
757
|
+
"Replace with a semantic spacing token: var(--space-1), var(--space-2), var(--space-4), etc."
|
|
516
758
|
));
|
|
517
759
|
}
|
|
518
760
|
}
|
|
519
761
|
return found;
|
|
520
762
|
}
|
|
763
|
+
var activeKit = null;
|
|
764
|
+
function ruleInlineStyleObject(source, filePath) {
|
|
765
|
+
if (!CODE_EXTS.has(extname2(filePath))) return [];
|
|
766
|
+
const ast = parseJsx(source);
|
|
767
|
+
if (!ast) return [];
|
|
768
|
+
const found = [];
|
|
769
|
+
walk(ast, (node) => {
|
|
770
|
+
if (node.type !== "JSXAttribute") return;
|
|
771
|
+
const nameNode = node.name;
|
|
772
|
+
if (nameNode?.type !== "JSXIdentifier" || nameNode.name !== "style") return;
|
|
773
|
+
const valueNode = node.value;
|
|
774
|
+
if (!valueNode || valueNode.type !== "JSXExpressionContainer") return;
|
|
775
|
+
const expr = valueNode.expression;
|
|
776
|
+
if (expr?.type !== "ObjectExpression") return;
|
|
777
|
+
const declared = [];
|
|
778
|
+
for (const raw of expr.properties ?? []) {
|
|
779
|
+
const prop = raw;
|
|
780
|
+
if (prop.type !== "ObjectProperty") continue;
|
|
781
|
+
if (prop.computed === true) continue;
|
|
782
|
+
const key = prop.key;
|
|
783
|
+
let keyName = null;
|
|
784
|
+
if (key?.type === "Identifier") keyName = String(key.name ?? "");
|
|
785
|
+
else if (key?.type === "StringLiteral") keyName = String(key.value ?? "");
|
|
786
|
+
if (!keyName) continue;
|
|
787
|
+
if (keyName.startsWith("--")) continue;
|
|
788
|
+
declared.push(keyName);
|
|
789
|
+
}
|
|
790
|
+
if (declared.length === 0) return;
|
|
791
|
+
const loc = node.loc;
|
|
792
|
+
found.push(finding(
|
|
793
|
+
filePath,
|
|
794
|
+
loc?.start?.line ?? 1,
|
|
795
|
+
"inline-style-object",
|
|
796
|
+
"warn",
|
|
797
|
+
`Inline style object declares ${declared.map((d) => `"${d}"`).join(", ")}. Inline styles introduce styling outside the kit \u2014 untokenized, unthemeable, and invisible to the design system.`,
|
|
798
|
+
`Move these declarations into the component's CSS Module and reference tokens, or pass a CSS-variable bridge: style={{ "--my-token": value }}.`
|
|
799
|
+
));
|
|
800
|
+
});
|
|
801
|
+
return found;
|
|
802
|
+
}
|
|
803
|
+
function ruleKitElementRedeclared(source, filePath) {
|
|
804
|
+
const kit = activeKit;
|
|
805
|
+
if (!kit) return [];
|
|
806
|
+
if (!CODE_EXTS.has(extname2(filePath))) return [];
|
|
807
|
+
if (isOwnedKitSource(filePath, kit)) return [];
|
|
808
|
+
const ast = parseJsx(source);
|
|
809
|
+
if (!ast) return [];
|
|
810
|
+
const program2 = ast.program;
|
|
811
|
+
const body = program2?.body ?? [];
|
|
812
|
+
const found = [];
|
|
813
|
+
for (const raw of body) {
|
|
814
|
+
for (const decl of moduleScopeDeclarations(raw)) {
|
|
815
|
+
if (!kit.identifiers.has(decl.name)) continue;
|
|
816
|
+
found.push(finding(
|
|
817
|
+
filePath,
|
|
818
|
+
decl.line,
|
|
819
|
+
"kit-element-redeclared",
|
|
820
|
+
"warn",
|
|
821
|
+
`Local re-declaration of the kit element "${decl.name}". This surface builds its own copy of a kit element instead of composing the kit's \u2014 the styling it introduces is outside the kit by construction.`,
|
|
822
|
+
`Compose the kit element instead (\`npx visor add ${identifierToSlug(decl.name)}\`), or rename this local component so it does not shadow a kit element.`
|
|
823
|
+
));
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return found;
|
|
827
|
+
}
|
|
828
|
+
function moduleScopeDeclarations(raw) {
|
|
829
|
+
let node = raw;
|
|
830
|
+
if (!node) return [];
|
|
831
|
+
if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
|
|
832
|
+
node = node.declaration;
|
|
833
|
+
if (!node) return [];
|
|
834
|
+
}
|
|
835
|
+
const line = node.loc?.start?.line ?? 1;
|
|
836
|
+
if (node.type === "FunctionDeclaration" || node.type === "ClassDeclaration") {
|
|
837
|
+
const id = node.id;
|
|
838
|
+
const name = id?.name;
|
|
839
|
+
return typeof name === "string" ? [{ name, line }] : [];
|
|
840
|
+
}
|
|
841
|
+
if (node.type === "VariableDeclaration") {
|
|
842
|
+
const out = [];
|
|
843
|
+
for (const d of node.declarations ?? []) {
|
|
844
|
+
const declarator = d;
|
|
845
|
+
const id = declarator.id;
|
|
846
|
+
if (id?.type !== "Identifier" || typeof id.name !== "string") continue;
|
|
847
|
+
const init = declarator.init;
|
|
848
|
+
const initType = init?.type;
|
|
849
|
+
if (initType !== "ArrowFunctionExpression" && initType !== "FunctionExpression" && initType !== "CallExpression") continue;
|
|
850
|
+
const dLine = declarator.loc?.start?.line ?? line;
|
|
851
|
+
out.push({ name: id.name, line: dLine });
|
|
852
|
+
}
|
|
853
|
+
return out;
|
|
854
|
+
}
|
|
855
|
+
return [];
|
|
856
|
+
}
|
|
857
|
+
function identifierToSlug(identifier) {
|
|
858
|
+
return identifier.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
859
|
+
}
|
|
860
|
+
function isOwnedKitSource(filePath, kit) {
|
|
861
|
+
const base = basename(filePath).replace(/\.(tsx|ts|jsx|js)$/, "").replace(/\.module$/, "");
|
|
862
|
+
if (kit.slugs.has(base)) return true;
|
|
863
|
+
return kit.slugs.has(basename(dirname3(filePath)));
|
|
864
|
+
}
|
|
521
865
|
function ruleMissingDarkModeBlock(source, filePath) {
|
|
522
866
|
const ext = extname2(filePath);
|
|
523
867
|
if (!STYLE_EXTS.has(ext)) return [];
|
|
@@ -836,10 +1180,10 @@ function resetBaseLayerCache() {
|
|
|
836
1180
|
baseLayerReported.clear();
|
|
837
1181
|
}
|
|
838
1182
|
function findProjectRoot(filePath) {
|
|
839
|
-
let dir =
|
|
1183
|
+
let dir = dirname3(resolve3(filePath));
|
|
840
1184
|
for (; ; ) {
|
|
841
|
-
if (
|
|
842
|
-
const parent =
|
|
1185
|
+
if (existsSync2(join4(dir, "package.json"))) return dir;
|
|
1186
|
+
const parent = dirname3(dir);
|
|
843
1187
|
if (parent === dir) return null;
|
|
844
1188
|
dir = parent;
|
|
845
1189
|
}
|
|
@@ -856,10 +1200,10 @@ function collectStylesheets(root) {
|
|
|
856
1200
|
}
|
|
857
1201
|
for (const entry of entries) {
|
|
858
1202
|
if (entry === "node_modules" || entry === "dist" || entry.startsWith(".")) continue;
|
|
859
|
-
const full =
|
|
1203
|
+
const full = join4(dir, entry);
|
|
860
1204
|
let s;
|
|
861
1205
|
try {
|
|
862
|
-
s =
|
|
1206
|
+
s = statSync3(full);
|
|
863
1207
|
} catch {
|
|
864
1208
|
continue;
|
|
865
1209
|
}
|
|
@@ -878,7 +1222,7 @@ function baseLayerState(root) {
|
|
|
878
1222
|
for (const sheet of collectStylesheets(root)) {
|
|
879
1223
|
let css;
|
|
880
1224
|
try {
|
|
881
|
-
css =
|
|
1225
|
+
css = readFileSync4(sheet, "utf-8");
|
|
882
1226
|
} catch {
|
|
883
1227
|
continue;
|
|
884
1228
|
}
|
|
@@ -886,12 +1230,12 @@ function baseLayerState(root) {
|
|
|
886
1230
|
if (PREFLIGHT_RE.test(css)) hasPreflight = true;
|
|
887
1231
|
if (hasReset && hasPreflight) break;
|
|
888
1232
|
}
|
|
889
|
-
const corePath =
|
|
1233
|
+
const corePath = join4(root, "node_modules", "@loworbitstudio", "visor-core");
|
|
890
1234
|
const state = {
|
|
891
1235
|
hasReset,
|
|
892
1236
|
hasPreflight,
|
|
893
|
-
visorCoreInstalled:
|
|
894
|
-
visorCoreExportsReset:
|
|
1237
|
+
visorCoreInstalled: existsSync2(corePath),
|
|
1238
|
+
visorCoreExportsReset: existsSync2(join4(corePath, "dist", "reset.css"))
|
|
895
1239
|
};
|
|
896
1240
|
baseLayerStateCache.set(root, state);
|
|
897
1241
|
return state;
|
|
@@ -954,12 +1298,35 @@ var RULES = [
|
|
|
954
1298
|
{ name: "line-length-over-75ch", severity: "warn", fn: ruleLineLengthOver75ch },
|
|
955
1299
|
{ name: "gradient-text", severity: "warn", fn: ruleGradientText },
|
|
956
1300
|
{ name: "excessive-card-nesting", severity: "warn", fn: ruleExcessiveCardNesting },
|
|
957
|
-
{ name: "missing-visor-base-layer", severity: "warn", fn: ruleMissingVisorBaseLayer }
|
|
1301
|
+
{ name: "missing-visor-base-layer", severity: "warn", fn: ruleMissingVisorBaseLayer },
|
|
1302
|
+
// Composition (VI-631) — warning-only by default (D3) so the camelCase fix and
|
|
1303
|
+
// the two new assertions can be adopted without escalating an existing
|
|
1304
|
+
// project's exit code on first run.
|
|
1305
|
+
{ name: "inline-style-object", severity: "warn", fn: ruleInlineStyleObject },
|
|
1306
|
+
{ name: "kit-element-redeclared", severity: "warn", fn: ruleKitElementRedeclared }
|
|
958
1307
|
];
|
|
1308
|
+
var COMPOSITION_SCOPE = {
|
|
1309
|
+
scope: "composition-only",
|
|
1310
|
+
asserts: "this surface introduced no styling outside the kit",
|
|
1311
|
+
doesNotAssert: [
|
|
1312
|
+
"arrangement (right elements, wrong order)",
|
|
1313
|
+
"content (wrong icon, dropped hint)",
|
|
1314
|
+
"data / reachability"
|
|
1315
|
+
],
|
|
1316
|
+
limit: "Green means this surface introduced no styling outside the kit. It does NOT mean the surface is on-design. Arrangement (right elements, wrong order), content (wrong icon, dropped hint), and data/reachability are not checked here and still need a human."
|
|
1317
|
+
};
|
|
959
1318
|
function scanDesign(pathArg, options = {}) {
|
|
960
1319
|
const files = collectFiles2(pathArg);
|
|
961
1320
|
const { disabledRules = [], errorsOnly = false } = options;
|
|
962
1321
|
resetBaseLayerCache();
|
|
1322
|
+
const resolution = resolveTaxonomy({
|
|
1323
|
+
explicitPath: options.taxonomyPath,
|
|
1324
|
+
visorrcPath: options.visorrcTaxonomyPath,
|
|
1325
|
+
scanPath: pathArg,
|
|
1326
|
+
env: options.env
|
|
1327
|
+
});
|
|
1328
|
+
const engaged = options.composition === true || resolution.requested || resolution.path !== null;
|
|
1329
|
+
activeKit = resolution.taxonomy;
|
|
963
1330
|
const activeRules = RULES.filter((r) => {
|
|
964
1331
|
if (disabledRules.includes(r.name)) return false;
|
|
965
1332
|
if (errorsOnly && r.severity !== "error") return false;
|
|
@@ -967,20 +1334,35 @@ function scanDesign(pathArg, options = {}) {
|
|
|
967
1334
|
});
|
|
968
1335
|
const errors = [];
|
|
969
1336
|
const warnings = [];
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
const
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1337
|
+
try {
|
|
1338
|
+
for (const file of files) {
|
|
1339
|
+
let source;
|
|
1340
|
+
try {
|
|
1341
|
+
source = readFileSync4(file, "utf-8");
|
|
1342
|
+
} catch {
|
|
1343
|
+
continue;
|
|
1344
|
+
}
|
|
1345
|
+
for (const rule of activeRules) {
|
|
1346
|
+
const ruleFindings = rule.fn(source, file);
|
|
1347
|
+
for (const f of ruleFindings) {
|
|
1348
|
+
if (errorsOnly && f.severity !== "error") continue;
|
|
1349
|
+
if (f.severity === "error") errors.push(f);
|
|
1350
|
+
else warnings.push(f);
|
|
1351
|
+
}
|
|
982
1352
|
}
|
|
983
1353
|
}
|
|
1354
|
+
} finally {
|
|
1355
|
+
activeKit = null;
|
|
1356
|
+
}
|
|
1357
|
+
if (engaged && !resolution.taxonomy && !disabledRules.includes("kit-taxonomy-missing")) {
|
|
1358
|
+
errors.push(finding(
|
|
1359
|
+
resolve3(pathArg),
|
|
1360
|
+
1,
|
|
1361
|
+
"kit-taxonomy-missing",
|
|
1362
|
+
"error",
|
|
1363
|
+
resolution.error ?? "Kit membership could not be asserted: no taxonomy.json resolved. The composition lint fails closed rather than reporting a green it did not earn.",
|
|
1364
|
+
'Point the lint at the kit definition: --taxonomy <path/to/taxonomy.json>, the VISOR_TAXONOMY environment variable, or a "taxonomy" key in .visorrc.json.'
|
|
1365
|
+
));
|
|
984
1366
|
}
|
|
985
1367
|
return {
|
|
986
1368
|
errors,
|
|
@@ -989,13 +1371,34 @@ function scanDesign(pathArg, options = {}) {
|
|
|
989
1371
|
errorCount: errors.length,
|
|
990
1372
|
warningCount: warnings.length,
|
|
991
1373
|
filesScanned: files.length
|
|
1374
|
+
},
|
|
1375
|
+
composition: {
|
|
1376
|
+
scope: COMPOSITION_SCOPE.scope,
|
|
1377
|
+
asserts: COMPOSITION_SCOPE.asserts,
|
|
1378
|
+
doesNotAssert: [...COMPOSITION_SCOPE.doesNotAssert],
|
|
1379
|
+
limit: COMPOSITION_SCOPE.limit,
|
|
1380
|
+
kitMembership: {
|
|
1381
|
+
asserted: resolution.taxonomy !== null,
|
|
1382
|
+
taxonomyPath: resolution.path,
|
|
1383
|
+
source: resolution.source,
|
|
1384
|
+
elementCount: resolution.taxonomy?.slugs.size ?? 0,
|
|
1385
|
+
reason: resolution.taxonomy ? `resolved from ${resolution.source} (${resolution.taxonomy.slugs.size} kit elements)` : resolution.error ?? "no taxonomy.json resolved; pass --taxonomy <path> to assert kit membership"
|
|
1386
|
+
}
|
|
992
1387
|
}
|
|
993
1388
|
};
|
|
994
1389
|
}
|
|
1390
|
+
function compositionScopeNotice() {
|
|
1391
|
+
return [
|
|
1392
|
+
"Composition scope \u2014 this check asserts one thing:",
|
|
1393
|
+
` ${COMPOSITION_SCOPE.asserts}.`,
|
|
1394
|
+
"It does NOT assert the surface is on-design. Uncovered residue:",
|
|
1395
|
+
...COMPOSITION_SCOPE.doesNotAssert.map((r) => ` \xB7 ${r}`)
|
|
1396
|
+
];
|
|
1397
|
+
}
|
|
995
1398
|
|
|
996
1399
|
// src/check/theme-mode.ts
|
|
997
|
-
import { readFileSync as
|
|
998
|
-
import { resolve as
|
|
1400
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
1401
|
+
import { resolve as resolve4 } from "path";
|
|
999
1402
|
import {
|
|
1000
1403
|
generateThemeData,
|
|
1001
1404
|
parseColor,
|
|
@@ -1044,8 +1447,8 @@ function checkThemeModeSource(yamlSource, threshold = LUMINANCE_THRESHOLD) {
|
|
|
1044
1447
|
};
|
|
1045
1448
|
}
|
|
1046
1449
|
function checkThemeModeFile(filePath, threshold = LUMINANCE_THRESHOLD) {
|
|
1047
|
-
const abs =
|
|
1048
|
-
const source =
|
|
1450
|
+
const abs = resolve4(filePath);
|
|
1451
|
+
const source = readFileSync5(abs, "utf-8");
|
|
1049
1452
|
return checkThemeModeSource(source, threshold);
|
|
1050
1453
|
}
|
|
1051
1454
|
|
|
@@ -1184,16 +1587,21 @@ function checkHasCommand(pattern2, options) {
|
|
|
1184
1587
|
logger.success(`${result.name} [${result.type}]${cmd}`);
|
|
1185
1588
|
logger.info(` ${result.description}`);
|
|
1186
1589
|
}
|
|
1590
|
+
function printCompositionScope() {
|
|
1591
|
+
logger.blank();
|
|
1592
|
+
for (const line of compositionScopeNotice()) logger.item(line);
|
|
1593
|
+
logger.blank();
|
|
1594
|
+
}
|
|
1187
1595
|
async function checkDiffCommand(pathArg, options) {
|
|
1188
1596
|
const result = await scanJsx(pathArg);
|
|
1189
1597
|
if (options.json) {
|
|
1190
|
-
console.log(JSON.stringify({ success: true, ...result }, null, 2));
|
|
1191
|
-
if (options.failOnHits && result.summary.hits > 0) process.
|
|
1192
|
-
process.exit(0);
|
|
1598
|
+
console.log(JSON.stringify({ success: true, ...result, composition: COMPOSITION_SCOPE }, null, 2));
|
|
1599
|
+
if (options.failOnHits && result.summary.hits > 0) process.exitCode = 1;
|
|
1193
1600
|
return;
|
|
1194
1601
|
}
|
|
1195
1602
|
if (result.summary.hits === 0) {
|
|
1196
1603
|
logger.success(`No native HTML primitives found \u2014 ${result.summary.scanned} file(s) scanned.`);
|
|
1604
|
+
printCompositionScope();
|
|
1197
1605
|
return;
|
|
1198
1606
|
}
|
|
1199
1607
|
logger.heading(`Found ${result.summary.hits} native HTML usage(s) in ${result.summary.scanned} file(s):
|
|
@@ -1204,27 +1612,40 @@ async function checkDiffCommand(pathArg, options) {
|
|
|
1204
1612
|
logger.warn(` <${f.nativeTag}> \u2192 use <${f.suggestedPrimitive}>${note}`);
|
|
1205
1613
|
logger.item(` ${loc} ${f.installCmd}`);
|
|
1206
1614
|
}
|
|
1207
|
-
|
|
1615
|
+
printCompositionScope();
|
|
1208
1616
|
if (options.failOnHits) process.exit(1);
|
|
1209
1617
|
}
|
|
1210
1618
|
function checkDesignCommand(pathArg, options) {
|
|
1211
|
-
const absPath =
|
|
1212
|
-
const rcDir =
|
|
1619
|
+
const absPath = resolve5(pathArg);
|
|
1620
|
+
const rcDir = statSync4(absPath).isDirectory() ? absPath : dirname4(absPath);
|
|
1213
1621
|
const rc = loadVisorRc(rcDir);
|
|
1214
1622
|
const result = scanDesign(absPath, {
|
|
1215
1623
|
disabledRules: rc.disabledRules ?? [],
|
|
1216
|
-
errorsOnly: options.errorsOnly ?? false
|
|
1624
|
+
errorsOnly: options.errorsOnly ?? false,
|
|
1625
|
+
taxonomyPath: options.taxonomy,
|
|
1626
|
+
visorrcTaxonomyPath: rc.taxonomy,
|
|
1627
|
+
composition: options.composition ?? false
|
|
1217
1628
|
});
|
|
1218
1629
|
const useJson = options.json || options.format === "json";
|
|
1219
1630
|
if (useJson) {
|
|
1220
1631
|
console.log(JSON.stringify({ success: true, ...result }, null, 2));
|
|
1221
|
-
if (!options.noFail && result.summary.errorCount > 0) process.
|
|
1222
|
-
process.exit(0);
|
|
1632
|
+
if (!options.noFail && result.summary.errorCount > 0) process.exitCode = 1;
|
|
1223
1633
|
return;
|
|
1224
1634
|
}
|
|
1225
|
-
const { errors, warnings, summary } = result;
|
|
1635
|
+
const { errors, warnings, summary, composition } = result;
|
|
1636
|
+
const printKitMembership = () => {
|
|
1637
|
+
const km = composition.kitMembership;
|
|
1638
|
+
if (km.asserted) {
|
|
1639
|
+
logger.item(`Kit membership: asserted against ${km.taxonomyPath} (${km.elementCount} element(s), via ${km.source}).`);
|
|
1640
|
+
} else {
|
|
1641
|
+
logger.item(`Kit membership: NOT asserted \u2014 ${km.reason}`);
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1226
1644
|
if (summary.errorCount === 0 && summary.warningCount === 0) {
|
|
1227
1645
|
logger.success(`No design anti-patterns found \u2014 ${summary.filesScanned} file(s) scanned.`);
|
|
1646
|
+
printCompositionScope();
|
|
1647
|
+
printKitMembership();
|
|
1648
|
+
logger.blank();
|
|
1228
1649
|
process.exit(0);
|
|
1229
1650
|
return;
|
|
1230
1651
|
}
|
|
@@ -1256,6 +1677,8 @@ function checkDesignCommand(pathArg, options) {
|
|
|
1256
1677
|
} else {
|
|
1257
1678
|
logger.warn(`${summary.warningCount} warning(s) (0 errors)`);
|
|
1258
1679
|
}
|
|
1680
|
+
printCompositionScope();
|
|
1681
|
+
printKitMembership();
|
|
1259
1682
|
logger.blank();
|
|
1260
1683
|
if (!options.noFail && summary.errorCount > 0) process.exit(1);
|
|
1261
1684
|
}
|
|
@@ -1307,7 +1730,7 @@ function checkCommand() {
|
|
|
1307
1730
|
check.command("diff").description("Scan JSX/TSX for native HTML elements that have Visor equivalents").argument("<path>", "file path, directory, or - for stdin").option("--fail-on-hits", "exit 1 if any native HTML usages are found (for CI use)").option("--json", "output structured JSON (for AI agents)").action(async (pathArg, options) => {
|
|
1308
1731
|
await checkDiffCommand(pathArg, options);
|
|
1309
1732
|
});
|
|
1310
|
-
check.command("design").description("Scan frontend code for Borealis design anti-patterns (deterministic, no LLM)").argument("<path>", "file path or directory to scan").option("--format <format>", "output format: json or human (default: human when TTY, json otherwise)").option("--errors-only", "report only error-severity rules (skip warnings)").option("--no-fail", "do not exit 1 on errors (advisory mode)").option("--json", "shorthand for --format json").action((pathArg, options) => {
|
|
1733
|
+
check.command("design").description("Scan frontend code for Borealis design anti-patterns (deterministic, no LLM). Asserts composition \u2014 that the surface introduced no styling outside the kit \u2014 never arrangement.").argument("<path>", "file path or directory to scan").option("--format <format>", "output format: json or human (default: human when TTY, json otherwise)").option("--errors-only", "report only error-severity rules (skip warnings)").option("--no-fail", "do not exit 1 on errors (advisory mode)").option("--json", "shorthand for --format json").option("--taxonomy <path>", "path to the kit's taxonomy.json \u2014 the definition kit membership is asserted against").option("--composition", "require the kit-membership assertion: fail closed when no taxonomy.json resolves").action((pathArg, options) => {
|
|
1311
1734
|
checkDesignCommand(pathArg, options);
|
|
1312
1735
|
});
|
|
1313
1736
|
check.command("theme-mode").description("Assert a theme's rendered app-root background matches its declared color-scheme (dark-only/light-only/adaptive)").argument("<path>", "path to a .visor.yaml theme file").option("--format <format>", "output format: json or human (default: human)").option("--no-fail", "do not exit 1 on a mode mismatch (advisory mode)").option("--json", "shorthand for --format json").action((pathArg, options) => {
|
|
@@ -1317,14 +1740,14 @@ function checkCommand() {
|
|
|
1317
1740
|
}
|
|
1318
1741
|
|
|
1319
1742
|
// src/commands/init.ts
|
|
1320
|
-
import { existsSync as
|
|
1321
|
-
import { join as
|
|
1743
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync10 } from "fs";
|
|
1744
|
+
import { join as join9, dirname as dirname6, basename as basename2 } from "path";
|
|
1322
1745
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1323
1746
|
import * as childProcess2 from "child_process";
|
|
1324
1747
|
|
|
1325
1748
|
// src/config/config.ts
|
|
1326
|
-
import { readFileSync as
|
|
1327
|
-
import { join as
|
|
1749
|
+
import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync3 } from "fs";
|
|
1750
|
+
import { join as join5 } from "path";
|
|
1328
1751
|
|
|
1329
1752
|
// src/config/defaults.ts
|
|
1330
1753
|
var DEFAULT_CONFIG = {
|
|
@@ -1341,19 +1764,19 @@ var CONFIG_FILE = "visor.json";
|
|
|
1341
1764
|
|
|
1342
1765
|
// src/config/config.ts
|
|
1343
1766
|
function getConfigPath(cwd) {
|
|
1344
|
-
return
|
|
1767
|
+
return join5(cwd, CONFIG_FILE);
|
|
1345
1768
|
}
|
|
1346
1769
|
function configExists(cwd) {
|
|
1347
|
-
return
|
|
1770
|
+
return existsSync3(getConfigPath(cwd));
|
|
1348
1771
|
}
|
|
1349
1772
|
function loadConfig(cwd) {
|
|
1350
1773
|
const configPath = getConfigPath(cwd);
|
|
1351
|
-
if (!
|
|
1774
|
+
if (!existsSync3(configPath)) {
|
|
1352
1775
|
throw new Error(
|
|
1353
1776
|
`No ${CONFIG_FILE} found. Run "visor init" first.`
|
|
1354
1777
|
);
|
|
1355
1778
|
}
|
|
1356
|
-
const raw =
|
|
1779
|
+
const raw = readFileSync6(configPath, "utf-8");
|
|
1357
1780
|
const parsed = JSON.parse(raw);
|
|
1358
1781
|
const knownKeys = /* @__PURE__ */ new Set(["paths"]);
|
|
1359
1782
|
for (const key of Object.keys(parsed)) {
|
|
@@ -1390,12 +1813,12 @@ function writeConfig(cwd, config) {
|
|
|
1390
1813
|
|
|
1391
1814
|
// src/utils/packages.ts
|
|
1392
1815
|
import { execFileSync } from "child_process";
|
|
1393
|
-
import { existsSync as
|
|
1394
|
-
import { join as
|
|
1816
|
+
import { existsSync as existsSync4, readFileSync as readFileSync7 } from "fs";
|
|
1817
|
+
import { join as join6 } from "path";
|
|
1395
1818
|
function readPackageJson(cwd) {
|
|
1396
|
-
const pkgPath =
|
|
1397
|
-
if (!
|
|
1398
|
-
return JSON.parse(
|
|
1819
|
+
const pkgPath = join6(cwd, "package.json");
|
|
1820
|
+
if (!existsSync4(pkgPath)) return null;
|
|
1821
|
+
return JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
1399
1822
|
}
|
|
1400
1823
|
function isPackageInstalled(packageName, cwd) {
|
|
1401
1824
|
const pkg2 = readPackageJson(cwd);
|
|
@@ -1505,8 +1928,8 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
1505
1928
|
}
|
|
1506
1929
|
|
|
1507
1930
|
// src/commands/init-plays/registry.ts
|
|
1508
|
-
import { existsSync as
|
|
1509
|
-
import { dirname as
|
|
1931
|
+
import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
|
|
1932
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
1510
1933
|
|
|
1511
1934
|
// src/commands/init-plays/pattern-build.ts
|
|
1512
1935
|
var patternBuildPlay = {
|
|
@@ -1545,24 +1968,24 @@ function knownPlayIds() {
|
|
|
1545
1968
|
return KNOWN_PLAYS.map((p) => p.id);
|
|
1546
1969
|
}
|
|
1547
1970
|
function playStatePaths(cwd, def, name) {
|
|
1548
|
-
const relDir =
|
|
1549
|
-
const dir =
|
|
1971
|
+
const relDir = join7(".lo", def.loSubdir, name);
|
|
1972
|
+
const dir = join7(cwd, relDir);
|
|
1550
1973
|
return {
|
|
1551
1974
|
dir,
|
|
1552
|
-
statePath:
|
|
1553
|
-
relStatePath:
|
|
1975
|
+
statePath: join7(dir, "state.json"),
|
|
1976
|
+
relStatePath: join7(relDir, "state.json")
|
|
1554
1977
|
};
|
|
1555
1978
|
}
|
|
1556
1979
|
function readPlayState(statePath) {
|
|
1557
|
-
if (!
|
|
1980
|
+
if (!existsSync5(statePath)) return null;
|
|
1558
1981
|
try {
|
|
1559
|
-
return JSON.parse(
|
|
1982
|
+
return JSON.parse(readFileSync8(statePath, "utf-8"));
|
|
1560
1983
|
} catch {
|
|
1561
1984
|
return null;
|
|
1562
1985
|
}
|
|
1563
1986
|
}
|
|
1564
1987
|
function writePlayState(statePath, state) {
|
|
1565
|
-
mkdirSync(
|
|
1988
|
+
mkdirSync(dirname5(statePath), { recursive: true });
|
|
1566
1989
|
writeFileSync2(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
1567
1990
|
}
|
|
1568
1991
|
|
|
@@ -1627,16 +2050,16 @@ function heuristicPort(name) {
|
|
|
1627
2050
|
}
|
|
1628
2051
|
|
|
1629
2052
|
// src/lib/lo-play-checklist.ts
|
|
1630
|
-
import { existsSync as
|
|
2053
|
+
import { existsSync as existsSync6, readFileSync as readFileSync9 } from "fs";
|
|
1631
2054
|
import { homedir } from "os";
|
|
1632
|
-
import { join as
|
|
2055
|
+
import { join as join8 } from "path";
|
|
1633
2056
|
function defaultChecklistRoot() {
|
|
1634
|
-
return
|
|
2057
|
+
return join8(homedir(), ".claude", "skills", "lo-play");
|
|
1635
2058
|
}
|
|
1636
2059
|
function readEntryChecklist(playType, options = {}) {
|
|
1637
2060
|
const root = options.root ?? defaultChecklistRoot();
|
|
1638
|
-
const path2 =
|
|
1639
|
-
if (!
|
|
2061
|
+
const path2 = join8(root, playType, "entry-checklist.md");
|
|
2062
|
+
if (!existsSync6(path2)) {
|
|
1640
2063
|
return {
|
|
1641
2064
|
found: false,
|
|
1642
2065
|
path: path2,
|
|
@@ -1644,7 +2067,7 @@ function readEntryChecklist(playType, options = {}) {
|
|
|
1644
2067
|
};
|
|
1645
2068
|
}
|
|
1646
2069
|
try {
|
|
1647
|
-
return { found: true, path: path2, content:
|
|
2070
|
+
return { found: true, path: path2, content: readFileSync9(path2, "utf-8") };
|
|
1648
2071
|
} catch {
|
|
1649
2072
|
return {
|
|
1650
2073
|
found: false,
|
|
@@ -1686,34 +2109,17 @@ function initCommand(cwd, options) {
|
|
|
1686
2109
|
process.exit(1);
|
|
1687
2110
|
}
|
|
1688
2111
|
}
|
|
1689
|
-
if (options?.template === "nextjs" &&
|
|
2112
|
+
if (options?.template === "nextjs" && existsSync7(join9(cwd, "package.json"))) {
|
|
1690
2113
|
emitError(
|
|
1691
2114
|
json,
|
|
1692
2115
|
"package.json already exists in this directory. visor init --template nextjs only scaffolds into empty directories. For an existing app, see the retrofit flow: https://visor.loworbit.studio/docs/guides/migration"
|
|
1693
2116
|
);
|
|
1694
2117
|
process.exit(1);
|
|
1695
2118
|
}
|
|
1696
|
-
if (configExists(cwd)) {
|
|
1697
|
-
filesSkipped.push("visor.json");
|
|
1698
|
-
if (!json) {
|
|
1699
|
-
logger.warn("visor.json already exists. Skipping config creation.");
|
|
1700
|
-
}
|
|
1701
|
-
} else {
|
|
1702
|
-
writeConfig(cwd, DEFAULT_CONFIG);
|
|
1703
|
-
filesCreated.push("visor.json");
|
|
1704
|
-
if (!json) {
|
|
1705
|
-
logger.success("Created visor.json");
|
|
1706
|
-
logger.blank();
|
|
1707
|
-
logger.info("Default paths:");
|
|
1708
|
-
logger.item(`components \u2192 ${DEFAULT_CONFIG.paths.components}`);
|
|
1709
|
-
logger.item(`deck components \u2192 ${DEFAULT_CONFIG.paths.deckComponents}`);
|
|
1710
|
-
logger.item(`blocks \u2192 ${DEFAULT_CONFIG.paths.blocks}`);
|
|
1711
|
-
logger.item(`hooks \u2192 ${DEFAULT_CONFIG.paths.hooks}`);
|
|
1712
|
-
logger.item(`lib \u2192 ${DEFAULT_CONFIG.paths.lib}`);
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
2119
|
if (options?.template === "nextjs") {
|
|
1716
2120
|
scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings);
|
|
2121
|
+
} else {
|
|
2122
|
+
writeVisorConfig(cwd, json, filesCreated, filesSkipped);
|
|
1717
2123
|
}
|
|
1718
2124
|
if (options?.template !== "nextjs") {
|
|
1719
2125
|
const missingTokens = !hasVisorTokens(cwd);
|
|
@@ -1845,15 +2251,37 @@ function emitError(json, message) {
|
|
|
1845
2251
|
logger.error(message);
|
|
1846
2252
|
}
|
|
1847
2253
|
}
|
|
2254
|
+
function writeVisorConfig(cwd, json, filesCreated, filesSkipped) {
|
|
2255
|
+
if (configExists(cwd)) {
|
|
2256
|
+
filesSkipped.push("visor.json");
|
|
2257
|
+
if (!json) {
|
|
2258
|
+
logger.warn("visor.json already exists. Skipping config creation.");
|
|
2259
|
+
}
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
writeConfig(cwd, DEFAULT_CONFIG);
|
|
2263
|
+
filesCreated.push("visor.json");
|
|
2264
|
+
if (!json) {
|
|
2265
|
+
logger.success("Created visor.json");
|
|
2266
|
+
logger.blank();
|
|
2267
|
+
logger.info("Default paths:");
|
|
2268
|
+
logger.item(`components \u2192 ${DEFAULT_CONFIG.paths.components}`);
|
|
2269
|
+
logger.item(`deck components \u2192 ${DEFAULT_CONFIG.paths.deckComponents}`);
|
|
2270
|
+
logger.item(`blocks \u2192 ${DEFAULT_CONFIG.paths.blocks}`);
|
|
2271
|
+
logger.item(`hooks \u2192 ${DEFAULT_CONFIG.paths.hooks}`);
|
|
2272
|
+
logger.item(`lib \u2192 ${DEFAULT_CONFIG.paths.lib}`);
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
1848
2275
|
function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
|
|
1849
2276
|
if (!json) {
|
|
1850
2277
|
logger.blank();
|
|
1851
2278
|
logger.info("Scaffolding a Borealis-native Next.js app...");
|
|
1852
2279
|
}
|
|
1853
2280
|
runCreateNextApp(cwd, json);
|
|
2281
|
+
writeVisorConfig(cwd, json, filesCreated, filesSkipped);
|
|
1854
2282
|
runInstallVisorDeps(cwd, json);
|
|
1855
|
-
const yamlPath =
|
|
1856
|
-
if (
|
|
2283
|
+
const yamlPath = join9(cwd, ".visor.yaml");
|
|
2284
|
+
if (existsSync7(yamlPath)) {
|
|
1857
2285
|
filesSkipped.push(".visor.yaml");
|
|
1858
2286
|
if (!json) {
|
|
1859
2287
|
logger.warn(".visor.yaml already exists. Skipping.");
|
|
@@ -1871,10 +2299,10 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
|
|
|
1871
2299
|
tokens: data.tokens,
|
|
1872
2300
|
config: data.config
|
|
1873
2301
|
});
|
|
1874
|
-
const globalsPath =
|
|
1875
|
-
const globalsDir =
|
|
2302
|
+
const globalsPath = join9(cwd, "app", "globals.css");
|
|
2303
|
+
const globalsDir = dirname6(globalsPath);
|
|
1876
2304
|
mkdirSync2(globalsDir, { recursive: true });
|
|
1877
|
-
if (
|
|
2305
|
+
if (existsSync7(globalsPath)) {
|
|
1878
2306
|
writeFileSync3(globalsPath, css, "utf-8");
|
|
1879
2307
|
filesCreated.push("app/globals.css");
|
|
1880
2308
|
} else {
|
|
@@ -1884,16 +2312,16 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
|
|
|
1884
2312
|
if (!json) {
|
|
1885
2313
|
logger.success("Created app/globals.css with theme tokens");
|
|
1886
2314
|
}
|
|
1887
|
-
const layoutPath =
|
|
2315
|
+
const layoutPath = join9(cwd, "app", "layout.tsx");
|
|
1888
2316
|
const colorScheme = extractColorScheme(NEXTJS_STARTER_YAML);
|
|
1889
2317
|
writeFileSync3(layoutPath, generateNextjsLayout(colorScheme), "utf-8");
|
|
1890
2318
|
filesCreated.push("app/layout.tsx");
|
|
1891
2319
|
if (!json) {
|
|
1892
2320
|
logger.success("Wired app/layout.tsx with FOWT prevention and theme tokens");
|
|
1893
2321
|
}
|
|
1894
|
-
const stampDir =
|
|
1895
|
-
const stampPath =
|
|
1896
|
-
if (
|
|
2322
|
+
const stampDir = join9(cwd, ".lo");
|
|
2323
|
+
const stampPath = join9(stampDir, "borealis.json");
|
|
2324
|
+
if (existsSync7(stampPath)) {
|
|
1897
2325
|
filesSkipped.push(".lo/borealis.json");
|
|
1898
2326
|
if (!json) {
|
|
1899
2327
|
logger.warn(".lo/borealis.json already exists. Skipping.");
|
|
@@ -1957,12 +2385,12 @@ function assertSpawnSuccess(result, label) {
|
|
|
1957
2385
|
}
|
|
1958
2386
|
function readVisorCliVersion() {
|
|
1959
2387
|
try {
|
|
1960
|
-
const here =
|
|
2388
|
+
const here = dirname6(fileURLToPath2(import.meta.url));
|
|
1961
2389
|
for (let i = 0; i < 5; i++) {
|
|
1962
2390
|
const segments = new Array(i).fill("..");
|
|
1963
|
-
const candidate =
|
|
2391
|
+
const candidate = join9(here, ...segments, "package.json");
|
|
1964
2392
|
try {
|
|
1965
|
-
const pkg2 = JSON.parse(
|
|
2393
|
+
const pkg2 = JSON.parse(readFileSync10(candidate, "utf-8"));
|
|
1966
2394
|
if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) {
|
|
1967
2395
|
return pkg2.version;
|
|
1968
2396
|
}
|
|
@@ -1977,52 +2405,52 @@ function readVisorCliVersion() {
|
|
|
1977
2405
|
// src/utils/fs.ts
|
|
1978
2406
|
import {
|
|
1979
2407
|
writeFileSync as writeFileSync4,
|
|
1980
|
-
readFileSync as
|
|
1981
|
-
existsSync as
|
|
2408
|
+
readFileSync as readFileSync11,
|
|
2409
|
+
existsSync as existsSync8,
|
|
1982
2410
|
mkdirSync as mkdirSync3
|
|
1983
2411
|
} from "fs";
|
|
1984
|
-
import { dirname as
|
|
2412
|
+
import { dirname as dirname7, join as join10 } from "path";
|
|
1985
2413
|
function resolveOutputPath(registryPath, type, config, cwd) {
|
|
1986
2414
|
let relativePath;
|
|
1987
2415
|
if (type === "registry:block") {
|
|
1988
2416
|
relativePath = registryPath.replace(/^blocks\//, "");
|
|
1989
|
-
return
|
|
2417
|
+
return join10(cwd, config.paths.blocks, relativePath);
|
|
1990
2418
|
}
|
|
1991
2419
|
if (type === "registry:ui") {
|
|
1992
2420
|
if (registryPath.startsWith("components/deck/")) {
|
|
1993
2421
|
relativePath = registryPath.replace(/^components\/deck\//, "");
|
|
1994
|
-
return
|
|
2422
|
+
return join10(cwd, config.paths.deckComponents, relativePath);
|
|
1995
2423
|
}
|
|
1996
2424
|
if (registryPath.startsWith("components/flutter/")) {
|
|
1997
2425
|
relativePath = registryPath.replace(/^components\/flutter\//, "");
|
|
1998
|
-
return
|
|
2426
|
+
return join10(cwd, config.paths.flutterComponents, relativePath);
|
|
1999
2427
|
}
|
|
2000
2428
|
relativePath = registryPath.replace(/^components\/ui\//, "");
|
|
2001
|
-
return
|
|
2429
|
+
return join10(cwd, config.paths.components, relativePath);
|
|
2002
2430
|
}
|
|
2003
2431
|
if (type === "registry:hook") {
|
|
2004
2432
|
relativePath = registryPath.replace(/^hooks\//, "");
|
|
2005
|
-
return
|
|
2433
|
+
return join10(cwd, config.paths.hooks, relativePath);
|
|
2006
2434
|
}
|
|
2007
2435
|
if (type === "registry:lib") {
|
|
2008
2436
|
relativePath = registryPath.replace(/^lib\//, "");
|
|
2009
|
-
return
|
|
2437
|
+
return join10(cwd, config.paths.lib, relativePath);
|
|
2010
2438
|
}
|
|
2011
|
-
return
|
|
2439
|
+
return join10(cwd, registryPath);
|
|
2012
2440
|
}
|
|
2013
2441
|
function writeFile(filePath, content) {
|
|
2014
|
-
const dir =
|
|
2015
|
-
if (!
|
|
2442
|
+
const dir = dirname7(filePath);
|
|
2443
|
+
if (!existsSync8(dir)) {
|
|
2016
2444
|
mkdirSync3(dir, { recursive: true });
|
|
2017
2445
|
}
|
|
2018
2446
|
writeFileSync4(filePath, content, "utf-8");
|
|
2019
2447
|
}
|
|
2020
2448
|
function readFile(filePath) {
|
|
2021
|
-
if (!
|
|
2022
|
-
return
|
|
2449
|
+
if (!existsSync8(filePath)) return null;
|
|
2450
|
+
return readFileSync11(filePath, "utf-8");
|
|
2023
2451
|
}
|
|
2024
2452
|
function fileExists(filePath) {
|
|
2025
|
-
return
|
|
2453
|
+
return existsSync8(filePath);
|
|
2026
2454
|
}
|
|
2027
2455
|
|
|
2028
2456
|
// src/commands/list.ts
|
|
@@ -2163,8 +2591,8 @@ function listCommand(cwd, options = {}) {
|
|
|
2163
2591
|
}
|
|
2164
2592
|
|
|
2165
2593
|
// src/utils/pubspec.ts
|
|
2166
|
-
import { existsSync as
|
|
2167
|
-
import { join as
|
|
2594
|
+
import { existsSync as existsSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
2595
|
+
import { join as join11 } from "path";
|
|
2168
2596
|
import { parseDocument, YAMLMap } from "yaml";
|
|
2169
2597
|
function mergePubspec(pubspecText, deps) {
|
|
2170
2598
|
const doc = parseDocument(pubspecText);
|
|
@@ -2186,14 +2614,14 @@ function mergePubspec(pubspecText, deps) {
|
|
|
2186
2614
|
return { text: doc.toString(), added, skipped };
|
|
2187
2615
|
}
|
|
2188
2616
|
function pubspecPath(cwd) {
|
|
2189
|
-
return
|
|
2617
|
+
return join11(cwd, "pubspec.yaml");
|
|
2190
2618
|
}
|
|
2191
2619
|
function pubspecExists(cwd) {
|
|
2192
|
-
return
|
|
2620
|
+
return existsSync9(pubspecPath(cwd));
|
|
2193
2621
|
}
|
|
2194
2622
|
function isPubPackageInstalled(packageName, cwd) {
|
|
2195
2623
|
if (!pubspecExists(cwd)) return false;
|
|
2196
|
-
const text =
|
|
2624
|
+
const text = readFileSync12(pubspecPath(cwd), "utf-8");
|
|
2197
2625
|
const doc = parseDocument(text);
|
|
2198
2626
|
const depsNode = doc.get("dependencies");
|
|
2199
2627
|
if (!(depsNode instanceof YAMLMap)) return false;
|
|
@@ -2204,12 +2632,12 @@ function getUninstalledPubDeps(deps, cwd) {
|
|
|
2204
2632
|
}
|
|
2205
2633
|
function addPubDependencies(deps, cwd) {
|
|
2206
2634
|
const path2 = pubspecPath(cwd);
|
|
2207
|
-
if (!
|
|
2635
|
+
if (!existsSync9(path2)) {
|
|
2208
2636
|
throw new Error(
|
|
2209
2637
|
`No pubspec.yaml found at ${path2}. Run this command from a Flutter project root.`
|
|
2210
2638
|
);
|
|
2211
2639
|
}
|
|
2212
|
-
const text =
|
|
2640
|
+
const text = readFileSync12(path2, "utf-8");
|
|
2213
2641
|
const result = mergePubspec(text, deps);
|
|
2214
2642
|
if (result.added.length > 0) {
|
|
2215
2643
|
writeFileSync5(path2, result.text, "utf-8");
|
|
@@ -2219,12 +2647,12 @@ function addPubDependencies(deps, cwd) {
|
|
|
2219
2647
|
|
|
2220
2648
|
// src/utils/flutter.ts
|
|
2221
2649
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
2222
|
-
import { existsSync as
|
|
2650
|
+
import { existsSync as existsSync10, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
|
|
2223
2651
|
import { homedir as homedir2 } from "os";
|
|
2224
|
-
import { join as
|
|
2652
|
+
import { join as join12 } from "path";
|
|
2225
2653
|
function isExecutable(path2) {
|
|
2226
2654
|
try {
|
|
2227
|
-
const s =
|
|
2655
|
+
const s = statSync5(path2);
|
|
2228
2656
|
return s.isFile();
|
|
2229
2657
|
} catch {
|
|
2230
2658
|
return false;
|
|
@@ -2236,20 +2664,20 @@ function fromPath(env) {
|
|
|
2236
2664
|
const bin = process.platform === "win32" ? "flutter.bat" : "flutter";
|
|
2237
2665
|
for (const dir of pathVar.split(sep2)) {
|
|
2238
2666
|
if (!dir) continue;
|
|
2239
|
-
const candidate =
|
|
2667
|
+
const candidate = join12(dir, bin);
|
|
2240
2668
|
if (isExecutable(candidate)) return candidate;
|
|
2241
2669
|
}
|
|
2242
2670
|
return null;
|
|
2243
2671
|
}
|
|
2244
2672
|
function fromFvm(home) {
|
|
2245
|
-
const fvmDefault =
|
|
2673
|
+
const fvmDefault = join12(home, "fvm", "default", "bin", "flutter");
|
|
2246
2674
|
if (isExecutable(fvmDefault)) return fvmDefault;
|
|
2247
|
-
const versionsDir =
|
|
2248
|
-
if (!
|
|
2675
|
+
const versionsDir = join12(home, "fvm", "versions");
|
|
2676
|
+
if (!existsSync10(versionsDir)) return null;
|
|
2249
2677
|
let best = null;
|
|
2250
2678
|
try {
|
|
2251
2679
|
for (const name of readdirSync3(versionsDir)) {
|
|
2252
|
-
const candidate =
|
|
2680
|
+
const candidate = join12(versionsDir, name, "bin", "flutter");
|
|
2253
2681
|
if (!isExecutable(candidate)) continue;
|
|
2254
2682
|
if (!best || compareSemver(name, best.version) > 0) {
|
|
2255
2683
|
best = { version: name, path: candidate };
|
|
@@ -2275,7 +2703,7 @@ function findFlutterBin(options = {}) {
|
|
|
2275
2703
|
const home = options.home ?? homedir2();
|
|
2276
2704
|
const envRoot = env.FLUTTER_ROOT;
|
|
2277
2705
|
if (envRoot) {
|
|
2278
|
-
const bin =
|
|
2706
|
+
const bin = join12(envRoot, "bin", "flutter");
|
|
2279
2707
|
if (isExecutable(bin)) return bin;
|
|
2280
2708
|
}
|
|
2281
2709
|
const fromPathBin = fromPath(env);
|
|
@@ -2938,8 +3366,8 @@ function infoCommand(name, cwd, options = {}) {
|
|
|
2938
3366
|
}
|
|
2939
3367
|
|
|
2940
3368
|
// src/commands/theme-apply.ts
|
|
2941
|
-
import { readFileSync as
|
|
2942
|
-
import { resolve as
|
|
3369
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
3370
|
+
import { resolve as resolve8, dirname as dirname9, join as join16 } from "path";
|
|
2943
3371
|
import { generateTheme, generateThemeData as generateThemeData3 } from "@loworbitstudio/visor-theme-engine";
|
|
2944
3372
|
import {
|
|
2945
3373
|
nextjsAdapter as nextjsAdapter2,
|
|
@@ -2950,8 +3378,8 @@ import {
|
|
|
2950
3378
|
} from "@loworbitstudio/visor-theme-engine/adapters";
|
|
2951
3379
|
|
|
2952
3380
|
// src/lib/blessed-manifest.ts
|
|
2953
|
-
import { existsSync as
|
|
2954
|
-
import { join as
|
|
3381
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
|
|
3382
|
+
import { join as join13 } from "path";
|
|
2955
3383
|
import { z } from "zod";
|
|
2956
3384
|
var BLESSED_MANIFEST_FILENAME = "blessed-manifest.json";
|
|
2957
3385
|
var themeApplyTargetSchema = z.discriminatedUnion("kind", [
|
|
@@ -2983,8 +3411,8 @@ var blessedManifestSchema = z.object({
|
|
|
2983
3411
|
theme_apply_target: themeApplyTargetSchema.optional()
|
|
2984
3412
|
}).strict();
|
|
2985
3413
|
function parseBlessedManifest(buildDir) {
|
|
2986
|
-
const manifestPath =
|
|
2987
|
-
if (!
|
|
3414
|
+
const manifestPath = join13(buildDir, BLESSED_MANIFEST_FILENAME);
|
|
3415
|
+
if (!existsSync11(manifestPath)) {
|
|
2988
3416
|
return {
|
|
2989
3417
|
ok: false,
|
|
2990
3418
|
error: `this directory is not a blessed build (missing ${BLESSED_MANIFEST_FILENAME}); see docs/blessed-builds.md`
|
|
@@ -2992,7 +3420,7 @@ function parseBlessedManifest(buildDir) {
|
|
|
2992
3420
|
}
|
|
2993
3421
|
let raw;
|
|
2994
3422
|
try {
|
|
2995
|
-
raw =
|
|
3423
|
+
raw = readFileSync13(manifestPath, "utf-8");
|
|
2996
3424
|
} catch {
|
|
2997
3425
|
return { ok: false, error: `could not read ${manifestPath}` };
|
|
2998
3426
|
}
|
|
@@ -3018,31 +3446,31 @@ function parseBlessedManifest(buildDir) {
|
|
|
3018
3446
|
}
|
|
3019
3447
|
|
|
3020
3448
|
// src/lib/theme-apply-targets/globals-css.ts
|
|
3021
|
-
import { existsSync as
|
|
3022
|
-
import { dirname as
|
|
3449
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
3450
|
+
import { dirname as dirname8, isAbsolute as isAbsolute2, join as join14, resolve as resolve6 } from "path";
|
|
3023
3451
|
function applyGlobalsCss(input) {
|
|
3024
3452
|
const outFile = input.path !== void 0 ? resolveInsideBuild(input.buildDir, input.path) : resolveDefaultGlobalsCss(input.buildDir);
|
|
3025
|
-
mkdirSync4(
|
|
3453
|
+
mkdirSync4(dirname8(outFile), { recursive: true });
|
|
3026
3454
|
writeFileSync6(outFile, input.adapterCss, "utf-8");
|
|
3027
3455
|
return outFile;
|
|
3028
3456
|
}
|
|
3029
3457
|
function resolveDefaultGlobalsCss(buildDir) {
|
|
3030
3458
|
const candidates = [
|
|
3031
|
-
|
|
3032
|
-
|
|
3459
|
+
join14(buildDir, "app", "globals.css"),
|
|
3460
|
+
join14(buildDir, "src", "app", "globals.css")
|
|
3033
3461
|
];
|
|
3034
|
-
return candidates.find((candidate) =>
|
|
3462
|
+
return candidates.find((candidate) => existsSync12(candidate)) ?? candidates[0];
|
|
3035
3463
|
}
|
|
3036
3464
|
function resolveInsideBuild(buildDir, relativeOrAbsolute) {
|
|
3037
|
-
return
|
|
3465
|
+
return isAbsolute2(relativeOrAbsolute) ? relativeOrAbsolute : resolve6(buildDir, relativeOrAbsolute);
|
|
3038
3466
|
}
|
|
3039
3467
|
|
|
3040
3468
|
// src/lib/theme-apply-targets/themes-css-dir.ts
|
|
3041
3469
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
3042
|
-
import { isAbsolute as
|
|
3470
|
+
import { isAbsolute as isAbsolute3, join as join15, resolve as resolve7 } from "path";
|
|
3043
3471
|
function applyThemesCssDir(input) {
|
|
3044
|
-
const dir =
|
|
3045
|
-
const outFile =
|
|
3472
|
+
const dir = isAbsolute3(input.path) ? input.path : resolve7(input.buildDir, input.path);
|
|
3473
|
+
const outFile = join15(dir, `${input.themeId}.css`);
|
|
3046
3474
|
mkdirSync5(dir, { recursive: true });
|
|
3047
3475
|
writeFileSync7(outFile, input.adapterCss, "utf-8");
|
|
3048
3476
|
return outFile;
|
|
@@ -3106,10 +3534,10 @@ function defaultOutputPath(adapter, themeName) {
|
|
|
3106
3534
|
}
|
|
3107
3535
|
}
|
|
3108
3536
|
function themeApplyCommand(file, cwd, options) {
|
|
3109
|
-
const filePath =
|
|
3537
|
+
const filePath = resolve8(cwd, file);
|
|
3110
3538
|
let yamlContent;
|
|
3111
3539
|
try {
|
|
3112
|
-
yamlContent =
|
|
3540
|
+
yamlContent = readFileSync14(filePath, "utf-8");
|
|
3113
3541
|
} catch {
|
|
3114
3542
|
if (options.json) {
|
|
3115
3543
|
console.log(
|
|
@@ -3203,7 +3631,7 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
3203
3631
|
else logger.error(message);
|
|
3204
3632
|
process.exit(1);
|
|
3205
3633
|
}
|
|
3206
|
-
const buildDir =
|
|
3634
|
+
const buildDir = resolve8(cwd, options.targetPath);
|
|
3207
3635
|
const manifestResult = parseBlessedManifest(buildDir);
|
|
3208
3636
|
if (!manifestResult.ok) {
|
|
3209
3637
|
if (options.json) {
|
|
@@ -3246,14 +3674,14 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
3246
3674
|
return;
|
|
3247
3675
|
}
|
|
3248
3676
|
const outputTarget = options.output ?? defaultOutputPath(options.adapter, themeName);
|
|
3249
|
-
const outputPath =
|
|
3677
|
+
const outputPath = resolve8(cwd, outputTarget);
|
|
3250
3678
|
if (fileMap) {
|
|
3251
3679
|
try {
|
|
3252
3680
|
mkdirSync6(outputPath, { recursive: true });
|
|
3253
3681
|
let totalBytes = 0;
|
|
3254
3682
|
for (const [relPath, content] of Object.entries(fileMap.files)) {
|
|
3255
|
-
const filePath2 =
|
|
3256
|
-
mkdirSync6(
|
|
3683
|
+
const filePath2 = join16(outputPath, relPath);
|
|
3684
|
+
mkdirSync6(dirname9(filePath2), { recursive: true });
|
|
3257
3685
|
writeFileSync8(filePath2, content, "utf-8");
|
|
3258
3686
|
totalBytes += content.length;
|
|
3259
3687
|
}
|
|
@@ -3291,7 +3719,7 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
3291
3719
|
if (css === null) {
|
|
3292
3720
|
process.exit(1);
|
|
3293
3721
|
}
|
|
3294
|
-
const outputDir =
|
|
3722
|
+
const outputDir = dirname9(outputPath);
|
|
3295
3723
|
try {
|
|
3296
3724
|
mkdirSync6(outputDir, { recursive: true });
|
|
3297
3725
|
writeFileSync8(outputPath, css, "utf-8");
|
|
@@ -3335,8 +3763,8 @@ function formatSize(bytes) {
|
|
|
3335
3763
|
}
|
|
3336
3764
|
|
|
3337
3765
|
// src/commands/theme-export.ts
|
|
3338
|
-
import { readFileSync as
|
|
3339
|
-
import { resolve as
|
|
3766
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
3767
|
+
import { resolve as resolve9 } from "path";
|
|
3340
3768
|
import {
|
|
3341
3769
|
parseConfig,
|
|
3342
3770
|
resolveConfig,
|
|
@@ -3344,10 +3772,10 @@ import {
|
|
|
3344
3772
|
exportTheme
|
|
3345
3773
|
} from "@loworbitstudio/visor-theme-engine";
|
|
3346
3774
|
function themeExportCommand(file, cwd, options) {
|
|
3347
|
-
const filePath =
|
|
3775
|
+
const filePath = resolve9(cwd, file ?? ".visor.yaml");
|
|
3348
3776
|
let yamlContent;
|
|
3349
3777
|
try {
|
|
3350
|
-
yamlContent =
|
|
3778
|
+
yamlContent = readFileSync15(filePath, "utf-8");
|
|
3351
3779
|
} catch {
|
|
3352
3780
|
if (options.json) {
|
|
3353
3781
|
console.log(
|
|
@@ -3399,16 +3827,16 @@ function themeExportCommand(file, cwd, options) {
|
|
|
3399
3827
|
}
|
|
3400
3828
|
|
|
3401
3829
|
// src/commands/theme-validate.ts
|
|
3402
|
-
import { readFileSync as
|
|
3403
|
-
import { resolve as
|
|
3830
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
3831
|
+
import { resolve as resolve10 } from "path";
|
|
3404
3832
|
import { parse as parseYaml2 } from "yaml";
|
|
3405
3833
|
import { validate } from "@loworbitstudio/visor-theme-engine";
|
|
3406
3834
|
import pc3 from "picocolors";
|
|
3407
3835
|
function themeValidateCommand(file, cwd, options) {
|
|
3408
|
-
const filePath =
|
|
3836
|
+
const filePath = resolve10(cwd, file);
|
|
3409
3837
|
let fileContent;
|
|
3410
3838
|
try {
|
|
3411
|
-
fileContent =
|
|
3839
|
+
fileContent = readFileSync16(filePath, "utf-8");
|
|
3412
3840
|
} catch {
|
|
3413
3841
|
if (options.json) {
|
|
3414
3842
|
console.log(
|
|
@@ -3497,8 +3925,8 @@ function printIssue(issue) {
|
|
|
3497
3925
|
|
|
3498
3926
|
// src/commands/theme-verify.ts
|
|
3499
3927
|
import { spawnSync as _spawnSync } from "child_process";
|
|
3500
|
-
import { existsSync as
|
|
3501
|
-
import { resolve as
|
|
3928
|
+
import { existsSync as existsSync13 } from "fs";
|
|
3929
|
+
import { resolve as resolve11 } from "path";
|
|
3502
3930
|
function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
3503
3931
|
const target = options.target ?? "flutter";
|
|
3504
3932
|
if (target !== "flutter") {
|
|
@@ -3520,8 +3948,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
|
3520
3948
|
}
|
|
3521
3949
|
process.exit(1);
|
|
3522
3950
|
}
|
|
3523
|
-
const dirPath =
|
|
3524
|
-
if (!
|
|
3951
|
+
const dirPath = resolve11(cwd, dir);
|
|
3952
|
+
if (!existsSync13(dirPath)) {
|
|
3525
3953
|
if (options.json) {
|
|
3526
3954
|
console.log(
|
|
3527
3955
|
JSON.stringify({
|
|
@@ -3608,8 +4036,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
|
3608
4036
|
}
|
|
3609
4037
|
|
|
3610
4038
|
// src/commands/theme-extract.ts
|
|
3611
|
-
import { readFileSync as
|
|
3612
|
-
import { resolve as
|
|
4039
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync9, existsSync as existsSync14, readdirSync as readdirSync4, statSync as statSync6 } from "fs";
|
|
4040
|
+
import { resolve as resolve12, join as join17, basename as basename3, extname as extname3, relative } from "path";
|
|
3613
4041
|
import { stringify as stringifyYaml } from "yaml";
|
|
3614
4042
|
import {
|
|
3615
4043
|
extractFromCSS,
|
|
@@ -3638,8 +4066,8 @@ var CSS_DIRS = [
|
|
|
3638
4066
|
"packages/design-tokens"
|
|
3639
4067
|
];
|
|
3640
4068
|
function themeExtractCommand(cwd, options) {
|
|
3641
|
-
const targetDir =
|
|
3642
|
-
if (!
|
|
4069
|
+
const targetDir = resolve12(cwd, options.from ?? ".");
|
|
4070
|
+
if (!existsSync14(targetDir)) {
|
|
3643
4071
|
if (options.json) {
|
|
3644
4072
|
console.log(JSON.stringify({ success: false, error: `Directory not found: ${targetDir}` }));
|
|
3645
4073
|
} else {
|
|
@@ -3693,16 +4121,16 @@ function collectCSSFiles(targetDir) {
|
|
|
3693
4121
|
const files = [];
|
|
3694
4122
|
const seen = /* @__PURE__ */ new Set();
|
|
3695
4123
|
for (const pattern2 of CSS_FILE_PATTERNS) {
|
|
3696
|
-
const rootPath =
|
|
4124
|
+
const rootPath = join17(targetDir, pattern2);
|
|
3697
4125
|
addFileIfExists(rootPath, files, seen);
|
|
3698
4126
|
for (const dir of CSS_DIRS) {
|
|
3699
|
-
const dirPath =
|
|
4127
|
+
const dirPath = join17(targetDir, dir, pattern2);
|
|
3700
4128
|
addFileIfExists(dirPath, files, seen);
|
|
3701
4129
|
}
|
|
3702
4130
|
}
|
|
3703
4131
|
for (const dir of CSS_DIRS) {
|
|
3704
|
-
const dirPath =
|
|
3705
|
-
if (
|
|
4132
|
+
const dirPath = join17(targetDir, dir);
|
|
4133
|
+
if (existsSync14(dirPath) && statSync6(dirPath).isDirectory()) {
|
|
3706
4134
|
scanDirForCSS(dirPath, files, seen, 2);
|
|
3707
4135
|
}
|
|
3708
4136
|
}
|
|
@@ -3710,11 +4138,11 @@ function collectCSSFiles(targetDir) {
|
|
|
3710
4138
|
return files;
|
|
3711
4139
|
}
|
|
3712
4140
|
function addFileIfExists(filePath, files, seen) {
|
|
3713
|
-
const resolved =
|
|
4141
|
+
const resolved = resolve12(filePath);
|
|
3714
4142
|
if (seen.has(resolved)) return;
|
|
3715
|
-
if (!
|
|
4143
|
+
if (!existsSync14(resolved)) return;
|
|
3716
4144
|
try {
|
|
3717
|
-
const content =
|
|
4145
|
+
const content = readFileSync17(resolved, "utf-8");
|
|
3718
4146
|
if (content.includes("--")) {
|
|
3719
4147
|
files.push({ path: resolved, content });
|
|
3720
4148
|
seen.add(resolved);
|
|
@@ -3723,7 +4151,7 @@ function addFileIfExists(filePath, files, seen) {
|
|
|
3723
4151
|
}
|
|
3724
4152
|
}
|
|
3725
4153
|
function scanDirForCSS(dir, files, seen, maxDepth) {
|
|
3726
|
-
if (!
|
|
4154
|
+
if (!existsSync14(dir)) return;
|
|
3727
4155
|
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
3728
4156
|
"node_modules",
|
|
3729
4157
|
".next",
|
|
@@ -3742,10 +4170,10 @@ function scanDirForCSS(dir, files, seen, maxDepth) {
|
|
|
3742
4170
|
if (entry.isDirectory()) {
|
|
3743
4171
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
3744
4172
|
if (maxDepth > 0) {
|
|
3745
|
-
scanDirForCSS(
|
|
4173
|
+
scanDirForCSS(join17(dir, entry.name), files, seen, maxDepth - 1);
|
|
3746
4174
|
}
|
|
3747
4175
|
} else if (entry.isFile() && extname3(entry.name) === ".css") {
|
|
3748
|
-
addFileIfExists(
|
|
4176
|
+
addFileIfExists(join17(dir, entry.name), files, seen);
|
|
3749
4177
|
}
|
|
3750
4178
|
}
|
|
3751
4179
|
} catch {
|
|
@@ -3827,10 +4255,10 @@ function extractVarName(varExpr) {
|
|
|
3827
4255
|
function parseNextFontFromLayouts(targetDir) {
|
|
3828
4256
|
const fontMap = /* @__PURE__ */ new Map();
|
|
3829
4257
|
for (const relPath of LAYOUT_FILE_PATHS) {
|
|
3830
|
-
const fullPath =
|
|
3831
|
-
if (!
|
|
4258
|
+
const fullPath = join17(targetDir, relPath);
|
|
4259
|
+
if (!existsSync14(fullPath)) continue;
|
|
3832
4260
|
try {
|
|
3833
|
-
const content =
|
|
4261
|
+
const content = readFileSync17(fullPath, "utf-8");
|
|
3834
4262
|
parseNextFontDeclarations(content, fontMap);
|
|
3835
4263
|
} catch {
|
|
3836
4264
|
}
|
|
@@ -3915,10 +4343,10 @@ var MONO_FONT_NAMES = /* @__PURE__ */ new Set([
|
|
|
3915
4343
|
"IBM Plex Mono"
|
|
3916
4344
|
]);
|
|
3917
4345
|
function extractFontHints(targetDir) {
|
|
3918
|
-
const pkgPath =
|
|
3919
|
-
if (!
|
|
4346
|
+
const pkgPath = join17(targetDir, "package.json");
|
|
4347
|
+
if (!existsSync14(pkgPath)) return void 0;
|
|
3920
4348
|
try {
|
|
3921
|
-
const pkg2 = JSON.parse(
|
|
4349
|
+
const pkg2 = JSON.parse(readFileSync17(pkgPath, "utf-8"));
|
|
3922
4350
|
const allDeps = { ...pkg2.dependencies, ...pkg2.devDependencies };
|
|
3923
4351
|
const fonts2 = [];
|
|
3924
4352
|
for (const [dep, _] of Object.entries(allDeps)) {
|
|
@@ -3954,10 +4382,10 @@ function extractFontHints(targetDir) {
|
|
|
3954
4382
|
}
|
|
3955
4383
|
}
|
|
3956
4384
|
function inferThemeName(targetDir) {
|
|
3957
|
-
const pkgPath =
|
|
3958
|
-
if (
|
|
4385
|
+
const pkgPath = join17(targetDir, "package.json");
|
|
4386
|
+
if (existsSync14(pkgPath)) {
|
|
3959
4387
|
try {
|
|
3960
|
-
const pkg2 = JSON.parse(
|
|
4388
|
+
const pkg2 = JSON.parse(readFileSync17(pkgPath, "utf-8"));
|
|
3961
4389
|
if (pkg2.name) {
|
|
3962
4390
|
const name = pkg2.name.replace(/^@[\w-]+\//, "");
|
|
3963
4391
|
return `${name}-theme`;
|
|
@@ -3994,7 +4422,7 @@ function outputJSON(result, validationResult) {
|
|
|
3994
4422
|
}
|
|
3995
4423
|
function outputYAML(result, outputPath, cwd, validationResult) {
|
|
3996
4424
|
const yamlStr = buildAnnotatedYAML(result);
|
|
3997
|
-
const outFile =
|
|
4425
|
+
const outFile = resolve12(cwd, outputPath ?? ".visor.yaml");
|
|
3998
4426
|
const high = result.tokens.filter((t) => t.confidence === "high").length;
|
|
3999
4427
|
const med = result.tokens.filter((t) => t.confidence === "medium").length;
|
|
4000
4428
|
const low = result.tokens.filter((t) => t.confidence === "low").length;
|
|
@@ -4080,14 +4508,14 @@ function buildAnnotatedYAML(result) {
|
|
|
4080
4508
|
}
|
|
4081
4509
|
|
|
4082
4510
|
// src/commands/theme-register.ts
|
|
4083
|
-
import { readFileSync as
|
|
4084
|
-
import { resolve as
|
|
4511
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync10, mkdirSync as mkdirSync7, existsSync as existsSync16 } from "fs";
|
|
4512
|
+
import { resolve as resolve14, join as join19 } from "path";
|
|
4085
4513
|
import { generateThemeData as generateThemeData4 } from "@loworbitstudio/visor-theme-engine";
|
|
4086
4514
|
import { docsAdapter as docsAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
4087
4515
|
|
|
4088
4516
|
// src/utils/theme-helpers.ts
|
|
4089
|
-
import { existsSync as
|
|
4090
|
-
import { resolve as
|
|
4517
|
+
import { existsSync as existsSync15, lstatSync, readdirSync as readdirSync5, readFileSync as readFileSync18, readlinkSync, realpathSync, statSync as statSync7 } from "fs";
|
|
4518
|
+
import { resolve as resolve13, dirname as dirname10, join as join18 } from "path";
|
|
4091
4519
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
4092
4520
|
function toSlug(name) {
|
|
4093
4521
|
return name.toLowerCase().replace(/\s+/g, "-");
|
|
@@ -4096,12 +4524,12 @@ function toLabel(name) {
|
|
|
4096
4524
|
return name.split(/[\s-]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
4097
4525
|
}
|
|
4098
4526
|
function findRepoRoot(startDir) {
|
|
4099
|
-
let current =
|
|
4527
|
+
let current = resolve13(startDir);
|
|
4100
4528
|
while (true) {
|
|
4101
|
-
if (
|
|
4529
|
+
if (existsSync15(join18(current, "packages", "docs"))) {
|
|
4102
4530
|
return current;
|
|
4103
4531
|
}
|
|
4104
|
-
const parent =
|
|
4532
|
+
const parent = dirname10(current);
|
|
4105
4533
|
if (parent === current) break;
|
|
4106
4534
|
current = parent;
|
|
4107
4535
|
}
|
|
@@ -4115,9 +4543,9 @@ function findMainRepoRoot(startDir) {
|
|
|
4115
4543
|
stdio: ["ignore", "pipe", "ignore"]
|
|
4116
4544
|
}).trim();
|
|
4117
4545
|
if (commonDir) {
|
|
4118
|
-
const absoluteCommonDir =
|
|
4119
|
-
const candidate =
|
|
4120
|
-
if (
|
|
4546
|
+
const absoluteCommonDir = resolve13(startDir, commonDir);
|
|
4547
|
+
const candidate = dirname10(absoluteCommonDir);
|
|
4548
|
+
if (existsSync15(join18(candidate, "packages", "docs"))) {
|
|
4121
4549
|
return candidate;
|
|
4122
4550
|
}
|
|
4123
4551
|
}
|
|
@@ -4136,10 +4564,10 @@ var BrokenSymlinkError = class extends Error {
|
|
|
4136
4564
|
code = "BROKEN_SYMLINK";
|
|
4137
4565
|
};
|
|
4138
4566
|
function assertNoBrokenSymlinks(dir) {
|
|
4139
|
-
if (!
|
|
4567
|
+
if (!existsSync15(dir)) return;
|
|
4140
4568
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
4141
4569
|
for (const entry of entries) {
|
|
4142
|
-
const entryPath =
|
|
4570
|
+
const entryPath = join18(dir, entry.name);
|
|
4143
4571
|
let lst;
|
|
4144
4572
|
try {
|
|
4145
4573
|
lst = lstatSync(entryPath);
|
|
@@ -4149,7 +4577,7 @@ function assertNoBrokenSymlinks(dir) {
|
|
|
4149
4577
|
if (lst.isSymbolicLink()) {
|
|
4150
4578
|
const target = readlinkSync(entryPath);
|
|
4151
4579
|
try {
|
|
4152
|
-
|
|
4580
|
+
statSync7(entryPath);
|
|
4153
4581
|
} catch {
|
|
4154
4582
|
throw new BrokenSymlinkError(entryPath, target);
|
|
4155
4583
|
}
|
|
@@ -4157,39 +4585,39 @@ function assertNoBrokenSymlinks(dir) {
|
|
|
4157
4585
|
}
|
|
4158
4586
|
}
|
|
4159
4587
|
function scanParentForPrivateThemes(parentDir) {
|
|
4160
|
-
if (!
|
|
4588
|
+
if (!existsSync15(parentDir)) return [];
|
|
4161
4589
|
const entries = readdirSync5(parentDir, { withFileTypes: true });
|
|
4162
4590
|
const matches = [];
|
|
4163
4591
|
for (const entry of entries) {
|
|
4164
4592
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
4165
|
-
const candidate =
|
|
4166
|
-
if (
|
|
4593
|
+
const candidate = join18(parentDir, entry.name, "visor-themes-private", "themes");
|
|
4594
|
+
if (existsSync15(candidate)) {
|
|
4167
4595
|
matches.push(candidate);
|
|
4168
4596
|
}
|
|
4169
4597
|
}
|
|
4170
4598
|
return matches.sort();
|
|
4171
4599
|
}
|
|
4172
4600
|
function scanNestedThemeDir(dir) {
|
|
4173
|
-
if (!
|
|
4601
|
+
if (!existsSync15(dir)) return [];
|
|
4174
4602
|
assertNoBrokenSymlinks(dir);
|
|
4175
4603
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
4176
4604
|
const out = [];
|
|
4177
4605
|
for (const entry of entries) {
|
|
4178
4606
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
4179
|
-
const themeFile =
|
|
4180
|
-
if (
|
|
4607
|
+
const themeFile = join18(dir, entry.name, "theme.visor.yaml");
|
|
4608
|
+
if (existsSync15(themeFile)) {
|
|
4181
4609
|
out.push({ filePath: themeFile, slug: entry.name });
|
|
4182
4610
|
}
|
|
4183
4611
|
}
|
|
4184
4612
|
return out;
|
|
4185
4613
|
}
|
|
4186
4614
|
function detectVisorWorkspace(cwd) {
|
|
4187
|
-
let current =
|
|
4615
|
+
let current = resolve13(cwd);
|
|
4188
4616
|
while (true) {
|
|
4189
|
-
const pkgPath =
|
|
4190
|
-
if (
|
|
4617
|
+
const pkgPath = join18(current, "package.json");
|
|
4618
|
+
if (existsSync15(pkgPath)) {
|
|
4191
4619
|
try {
|
|
4192
|
-
const pkg2 = JSON.parse(
|
|
4620
|
+
const pkg2 = JSON.parse(readFileSync18(pkgPath, "utf-8"));
|
|
4193
4621
|
const workspaces = pkg2.workspaces ?? [];
|
|
4194
4622
|
const hasCli = workspaces.some((w) => w.includes("packages/cli"));
|
|
4195
4623
|
const hasEngine = workspaces.some((w) => w.includes("packages/theme-engine"));
|
|
@@ -4199,7 +4627,7 @@ function detectVisorWorkspace(cwd) {
|
|
|
4199
4627
|
} catch {
|
|
4200
4628
|
}
|
|
4201
4629
|
}
|
|
4202
|
-
const parent =
|
|
4630
|
+
const parent = dirname10(current);
|
|
4203
4631
|
if (parent === current) break;
|
|
4204
4632
|
current = parent;
|
|
4205
4633
|
}
|
|
@@ -4207,18 +4635,18 @@ function detectVisorWorkspace(cwd) {
|
|
|
4207
4635
|
}
|
|
4208
4636
|
function isLocalVisorBinary(workspaceRoot, scriptPath) {
|
|
4209
4637
|
if (!scriptPath) return false;
|
|
4210
|
-
const expectedPrefix =
|
|
4638
|
+
const expectedPrefix = join18(workspaceRoot, "packages", "cli", "dist");
|
|
4211
4639
|
let resolvedScript;
|
|
4212
4640
|
let resolvedPrefix;
|
|
4213
4641
|
try {
|
|
4214
4642
|
resolvedScript = realpathSync(scriptPath);
|
|
4215
4643
|
} catch {
|
|
4216
|
-
resolvedScript =
|
|
4644
|
+
resolvedScript = resolve13(scriptPath);
|
|
4217
4645
|
}
|
|
4218
4646
|
try {
|
|
4219
4647
|
resolvedPrefix = realpathSync(expectedPrefix);
|
|
4220
4648
|
} catch {
|
|
4221
|
-
resolvedPrefix =
|
|
4649
|
+
resolvedPrefix = resolve13(expectedPrefix);
|
|
4222
4650
|
}
|
|
4223
4651
|
return resolvedScript.startsWith(resolvedPrefix + "/") || resolvedScript === resolvedPrefix;
|
|
4224
4652
|
}
|
|
@@ -4308,10 +4736,10 @@ ${indent}${newEntry},
|
|
|
4308
4736
|
return { updated, changed: true };
|
|
4309
4737
|
}
|
|
4310
4738
|
function themeRegisterCommand(file, cwd, options) {
|
|
4311
|
-
const filePath =
|
|
4739
|
+
const filePath = resolve14(cwd, file);
|
|
4312
4740
|
let yamlContent;
|
|
4313
4741
|
try {
|
|
4314
|
-
yamlContent =
|
|
4742
|
+
yamlContent = readFileSync19(filePath, "utf-8");
|
|
4315
4743
|
} catch {
|
|
4316
4744
|
if (options.json) {
|
|
4317
4745
|
console.log(JSON.stringify({ success: false, error: `Could not read file: ${filePath}` }));
|
|
@@ -4354,11 +4782,11 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
4354
4782
|
process.exit(1);
|
|
4355
4783
|
return;
|
|
4356
4784
|
}
|
|
4357
|
-
const docsAppDir =
|
|
4358
|
-
const cssFilePath =
|
|
4359
|
-
const globalsPath =
|
|
4360
|
-
const themeConfigPath =
|
|
4361
|
-
if (!
|
|
4785
|
+
const docsAppDir = join19(repoRoot, "packages", "docs", "app");
|
|
4786
|
+
const cssFilePath = join19(docsAppDir, `${slug2}-theme.css`);
|
|
4787
|
+
const globalsPath = join19(docsAppDir, "globals.css");
|
|
4788
|
+
const themeConfigPath = join19(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
4789
|
+
if (!existsSync16(docsAppDir)) {
|
|
4362
4790
|
const msg = `Docs app directory not found: ${docsAppDir}`;
|
|
4363
4791
|
if (options.json) {
|
|
4364
4792
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
@@ -4371,8 +4799,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
4371
4799
|
let globalsContent = "";
|
|
4372
4800
|
let themeConfigContent = "";
|
|
4373
4801
|
try {
|
|
4374
|
-
globalsContent =
|
|
4375
|
-
themeConfigContent =
|
|
4802
|
+
globalsContent = readFileSync19(globalsPath, "utf-8");
|
|
4803
|
+
themeConfigContent = readFileSync19(themeConfigPath, "utf-8");
|
|
4376
4804
|
} catch (err) {
|
|
4377
4805
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
4378
4806
|
if (options.json) {
|
|
@@ -4383,8 +4811,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
4383
4811
|
process.exit(1);
|
|
4384
4812
|
return;
|
|
4385
4813
|
}
|
|
4386
|
-
const cssExists =
|
|
4387
|
-
const cssChanged = !cssExists ||
|
|
4814
|
+
const cssExists = existsSync16(cssFilePath);
|
|
4815
|
+
const cssChanged = !cssExists || readFileSync19(cssFilePath, "utf-8") !== css;
|
|
4388
4816
|
const { updated: newGlobals, changed: globalsChanged } = insertGlobalsImport(globalsContent, slug2);
|
|
4389
4817
|
const { updated: newThemeConfig, changed: themeConfigChanged, error: configError } = insertThemeConfig(
|
|
4390
4818
|
themeConfigContent,
|
|
@@ -4468,8 +4896,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
4468
4896
|
}
|
|
4469
4897
|
|
|
4470
4898
|
// src/commands/theme-unregister.ts
|
|
4471
|
-
import { readFileSync as
|
|
4472
|
-
import { join as
|
|
4899
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync11, existsSync as existsSync17, unlinkSync } from "fs";
|
|
4900
|
+
import { join as join20 } from "path";
|
|
4473
4901
|
function removeGlobalsImport(content, slug2) {
|
|
4474
4902
|
const importLine = `@import './${slug2}-theme.css';`;
|
|
4475
4903
|
if (!content.includes(importLine)) {
|
|
@@ -4501,11 +4929,11 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
4501
4929
|
process.exit(1);
|
|
4502
4930
|
return;
|
|
4503
4931
|
}
|
|
4504
|
-
const docsAppDir =
|
|
4505
|
-
const cssFilePath =
|
|
4506
|
-
const globalsPath =
|
|
4507
|
-
const themeConfigPath =
|
|
4508
|
-
if (!
|
|
4932
|
+
const docsAppDir = join20(repoRoot, "packages", "docs", "app");
|
|
4933
|
+
const cssFilePath = join20(docsAppDir, `${slug2}-theme.css`);
|
|
4934
|
+
const globalsPath = join20(docsAppDir, "globals.css");
|
|
4935
|
+
const themeConfigPath = join20(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
4936
|
+
if (!existsSync17(docsAppDir)) {
|
|
4509
4937
|
const msg = `Docs app directory not found: ${docsAppDir}`;
|
|
4510
4938
|
if (options.json) {
|
|
4511
4939
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
@@ -4518,8 +4946,8 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
4518
4946
|
let globalsContent = "";
|
|
4519
4947
|
let themeConfigContent = "";
|
|
4520
4948
|
try {
|
|
4521
|
-
globalsContent =
|
|
4522
|
-
themeConfigContent =
|
|
4949
|
+
globalsContent = readFileSync20(globalsPath, "utf-8");
|
|
4950
|
+
themeConfigContent = readFileSync20(themeConfigPath, "utf-8");
|
|
4523
4951
|
} catch (err) {
|
|
4524
4952
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
4525
4953
|
if (options.json) {
|
|
@@ -4530,7 +4958,7 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
4530
4958
|
process.exit(1);
|
|
4531
4959
|
return;
|
|
4532
4960
|
}
|
|
4533
|
-
const cssExists =
|
|
4961
|
+
const cssExists = existsSync17(cssFilePath);
|
|
4534
4962
|
const { updated: newGlobals, changed: globalsChanged } = removeGlobalsImport(globalsContent, slug2);
|
|
4535
4963
|
const { updated: newThemeConfig, changed: themeConfigChanged } = removeThemeConfigEntry(themeConfigContent, slug2);
|
|
4536
4964
|
if (!cssExists && !globalsChanged && !themeConfigChanged) {
|
|
@@ -4571,15 +4999,15 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
4571
4999
|
|
|
4572
5000
|
// src/commands/theme-sync.ts
|
|
4573
5001
|
import {
|
|
4574
|
-
readFileSync as
|
|
5002
|
+
readFileSync as readFileSync21,
|
|
4575
5003
|
writeFileSync as writeFileSync12,
|
|
4576
5004
|
mkdirSync as mkdirSync8,
|
|
4577
|
-
existsSync as
|
|
5005
|
+
existsSync as existsSync18,
|
|
4578
5006
|
readdirSync as readdirSync6,
|
|
4579
5007
|
unlinkSync as unlinkSync2,
|
|
4580
5008
|
copyFileSync
|
|
4581
5009
|
} from "fs";
|
|
4582
|
-
import { join as
|
|
5010
|
+
import { join as join21, basename as basename4, resolve as resolve15, sep, relative as relative2 } from "path";
|
|
4583
5011
|
import { parse as parseYaml3 } from "yaml";
|
|
4584
5012
|
import { generateThemeData as generateThemeData5, validateFontCoverage, formatFontCoverageError } from "@loworbitstudio/visor-theme-engine";
|
|
4585
5013
|
import { docsAdapter as docsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
@@ -4593,9 +5021,9 @@ var CUSTOM_OVERLAY_CSS_PATH = "packages/docs/app/custom-themes.generated.css";
|
|
|
4593
5021
|
var CUSTOM_OVERLAY_TS_PATH = "packages/docs/lib/theme-config.custom.generated.ts";
|
|
4594
5022
|
var CUSTOM_OVERLAY_IMPORT_LINE = "@import './custom-themes.generated.css';";
|
|
4595
5023
|
function scanThemeDir(dir) {
|
|
4596
|
-
if (!
|
|
5024
|
+
if (!existsSync18(dir)) return [];
|
|
4597
5025
|
assertNoBrokenSymlinks(dir);
|
|
4598
|
-
return readdirSync6(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) =>
|
|
5026
|
+
return readdirSync6(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join21(dir, f));
|
|
4599
5027
|
}
|
|
4600
5028
|
function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
4601
5029
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -4619,20 +5047,20 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
|
4619
5047
|
};
|
|
4620
5048
|
const envPath = process.env[PRIVATE_THEMES_ENV_VAR];
|
|
4621
5049
|
if (envPath && envPath.trim() !== "") {
|
|
4622
|
-
const resolved =
|
|
4623
|
-
if (!
|
|
5050
|
+
const resolved = resolve15(envPath);
|
|
5051
|
+
if (!existsSync18(resolved)) {
|
|
4624
5052
|
throw new Error(
|
|
4625
5053
|
`${PRIVATE_THEMES_ENV_VAR} is set to "${envPath}" but the path does not exist. Expected a directory containing {slug}/theme.visor.yaml entries.`
|
|
4626
5054
|
);
|
|
4627
5055
|
}
|
|
4628
5056
|
addNested(resolved, "env");
|
|
4629
5057
|
}
|
|
4630
|
-
const siblingPath =
|
|
4631
|
-
const siblingExists =
|
|
5058
|
+
const siblingPath = join21(mainRepoRoot, "..", "visor-themes-private", "themes");
|
|
5059
|
+
const siblingExists = existsSync18(siblingPath);
|
|
4632
5060
|
if (siblingExists) {
|
|
4633
5061
|
addNested(siblingPath, "sibling");
|
|
4634
5062
|
}
|
|
4635
|
-
const parentDir =
|
|
5063
|
+
const parentDir = join21(mainRepoRoot, "..");
|
|
4636
5064
|
const parentGlobMatches = scanParentForPrivateThemes(parentDir).filter(
|
|
4637
5065
|
(path2) => !path2.startsWith(mainRepoRoot + sep)
|
|
4638
5066
|
);
|
|
@@ -4653,7 +5081,7 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
|
4653
5081
|
}
|
|
4654
5082
|
}
|
|
4655
5083
|
}
|
|
4656
|
-
const legacyDir =
|
|
5084
|
+
const legacyDir = join21(repoRoot, "custom-themes");
|
|
4657
5085
|
const legacyFiles = scanThemeDir(legacyDir);
|
|
4658
5086
|
for (const legacyFile of legacyFiles) {
|
|
4659
5087
|
const slug2 = basename4(legacyFile).replace(/\.visor\.yaml$/, "");
|
|
@@ -4710,14 +5138,14 @@ function reportBrokenSymlink(err, options) {
|
|
|
4710
5138
|
}
|
|
4711
5139
|
}
|
|
4712
5140
|
function buildEmptySourcesMessage(mainRepoRoot) {
|
|
4713
|
-
const expectedSibling =
|
|
5141
|
+
const expectedSibling = join21(mainRepoRoot, "..", "visor-themes-private");
|
|
4714
5142
|
return [
|
|
4715
5143
|
"No theme sources discovered. Cannot proceed \u2014 refusing to wipe generated CSS.",
|
|
4716
5144
|
"",
|
|
4717
5145
|
"Resolution order checked:",
|
|
4718
5146
|
` 1. Env var ${PRIVATE_THEMES_ENV_VAR} (unset or empty)`,
|
|
4719
5147
|
` 2. Sibling checkout at ${expectedSibling}/themes/ (not found)`,
|
|
4720
|
-
` 3. One-level-deeper at ${
|
|
5148
|
+
` 3. One-level-deeper at ${join21(mainRepoRoot, "..")}/<parent>/visor-themes-private/themes/ (not found)`,
|
|
4721
5149
|
" 4. Legacy custom-themes/ (no .visor.yaml files)",
|
|
4722
5150
|
"",
|
|
4723
5151
|
"To fix, clone the private themes repo as a sibling:",
|
|
@@ -4922,14 +5350,14 @@ function themeSyncCommand(cwd, options) {
|
|
|
4922
5350
|
return;
|
|
4923
5351
|
}
|
|
4924
5352
|
const mainRepoRoot = findMainRepoRoot(cwd) ?? repoRoot;
|
|
4925
|
-
const themesDir =
|
|
4926
|
-
const docsAppDir =
|
|
4927
|
-
const docsLibDir =
|
|
4928
|
-
const docsPublicThemesDir =
|
|
4929
|
-
const themeConfigPath =
|
|
4930
|
-
const globalsPath =
|
|
4931
|
-
const customOverlayCssPath =
|
|
4932
|
-
const customOverlayTsPath =
|
|
5353
|
+
const themesDir = join21(repoRoot, "themes");
|
|
5354
|
+
const docsAppDir = join21(repoRoot, "packages", "docs", "app");
|
|
5355
|
+
const docsLibDir = join21(repoRoot, "packages", "docs", "lib");
|
|
5356
|
+
const docsPublicThemesDir = join21(repoRoot, "packages", "docs", "public", "themes");
|
|
5357
|
+
const themeConfigPath = join21(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
5358
|
+
const globalsPath = join21(docsAppDir, "globals.css");
|
|
5359
|
+
const customOverlayCssPath = join21(repoRoot, CUSTOM_OVERLAY_CSS_PATH);
|
|
5360
|
+
const customOverlayTsPath = join21(repoRoot, CUSTOM_OVERLAY_TS_PATH);
|
|
4933
5361
|
let stockFiles;
|
|
4934
5362
|
try {
|
|
4935
5363
|
stockFiles = scanThemeDir(themesDir);
|
|
@@ -4986,7 +5414,7 @@ function themeSyncCommand(cwd, options) {
|
|
|
4986
5414
|
const processFile = (filePath, isCustom, slugOverride) => {
|
|
4987
5415
|
let yamlContent;
|
|
4988
5416
|
try {
|
|
4989
|
-
yamlContent =
|
|
5417
|
+
yamlContent = readFileSync21(filePath, "utf-8");
|
|
4990
5418
|
} catch {
|
|
4991
5419
|
failures.push({ filePath, error: `Could not read: ${filePath}` });
|
|
4992
5420
|
return;
|
|
@@ -5036,8 +5464,8 @@ function themeSyncCommand(cwd, options) {
|
|
|
5036
5464
|
let globalsContent;
|
|
5037
5465
|
let themeConfigContent;
|
|
5038
5466
|
try {
|
|
5039
|
-
globalsContent =
|
|
5040
|
-
themeConfigContent =
|
|
5467
|
+
globalsContent = readFileSync21(globalsPath, "utf-8");
|
|
5468
|
+
themeConfigContent = readFileSync21(themeConfigPath, "utf-8");
|
|
5041
5469
|
} catch (err) {
|
|
5042
5470
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
5043
5471
|
if (options.json) {
|
|
@@ -5052,12 +5480,12 @@ function themeSyncCommand(cwd, options) {
|
|
|
5052
5480
|
const newThemeConfig = updateStockThemeConfigBlock(themeConfigContent, stockManifest);
|
|
5053
5481
|
const newCustomOverlayCss = generateCustomOverlayCss(customManifest);
|
|
5054
5482
|
const newCustomOverlayTs = generateCustomOverlayTs(customManifest);
|
|
5055
|
-
const existingCssFiles =
|
|
5483
|
+
const existingCssFiles = existsSync18(docsAppDir) ? readdirSync6(docsAppDir).filter(
|
|
5056
5484
|
(f) => f.endsWith("-theme.css") && f !== "custom-themes.generated.css"
|
|
5057
5485
|
) : [];
|
|
5058
5486
|
const newCssSet = new Set(allSlugs.map((s) => `${s}-theme.css`));
|
|
5059
5487
|
const staleCssFiles = existingCssFiles.filter((f) => !newCssSet.has(f));
|
|
5060
|
-
const existingPublicYamls =
|
|
5488
|
+
const existingPublicYamls = existsSync18(docsPublicThemesDir) ? readdirSync6(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
|
|
5061
5489
|
const newPublicYamlSet = new Set(manifest.map((e) => `${e.yamlFilename}.visor.yaml`));
|
|
5062
5490
|
const stalePublicYamls = existingPublicYamls.filter((f) => !newPublicYamlSet.has(f));
|
|
5063
5491
|
if (options.dryRun) {
|
|
@@ -5101,24 +5529,24 @@ function themeSyncCommand(cwd, options) {
|
|
|
5101
5529
|
mkdirSync8(docsLibDir, { recursive: true });
|
|
5102
5530
|
mkdirSync8(docsPublicThemesDir, { recursive: true });
|
|
5103
5531
|
for (const entry of manifest) {
|
|
5104
|
-
writeFileSync12(
|
|
5532
|
+
writeFileSync12(join21(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
|
|
5105
5533
|
}
|
|
5106
5534
|
for (const stale of staleCssFiles) {
|
|
5107
|
-
unlinkSync2(
|
|
5535
|
+
unlinkSync2(join21(docsAppDir, stale));
|
|
5108
5536
|
}
|
|
5109
5537
|
writeFileSync12(customOverlayCssPath, newCustomOverlayCss, "utf-8");
|
|
5110
5538
|
writeFileSync12(customOverlayTsPath, newCustomOverlayTs, "utf-8");
|
|
5111
5539
|
writeFileSync12(themeConfigPath, newThemeConfig, "utf-8");
|
|
5112
5540
|
writeFileSync12(globalsPath, newGlobals, "utf-8");
|
|
5113
5541
|
for (const srcFile of stockFiles) {
|
|
5114
|
-
copyFileSync(srcFile,
|
|
5542
|
+
copyFileSync(srcFile, join21(docsPublicThemesDir, basename4(srcFile)));
|
|
5115
5543
|
}
|
|
5116
5544
|
for (const c of customSources) {
|
|
5117
5545
|
const targetName = c.origin === "legacy" ? basename4(c.filePath) : `${c.slug}.visor.yaml`;
|
|
5118
|
-
copyFileSync(c.filePath,
|
|
5546
|
+
copyFileSync(c.filePath, join21(docsPublicThemesDir, targetName));
|
|
5119
5547
|
}
|
|
5120
5548
|
for (const stale of stalePublicYamls) {
|
|
5121
|
-
unlinkSync2(
|
|
5549
|
+
unlinkSync2(join21(docsPublicThemesDir, stale));
|
|
5122
5550
|
}
|
|
5123
5551
|
} catch (err) {
|
|
5124
5552
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
@@ -5171,19 +5599,19 @@ function themeSyncCommand(cwd, options) {
|
|
|
5171
5599
|
|
|
5172
5600
|
// src/commands/theme-batch-apply-flutter.ts
|
|
5173
5601
|
import {
|
|
5174
|
-
readFileSync as
|
|
5602
|
+
readFileSync as readFileSync22,
|
|
5175
5603
|
writeFileSync as writeFileSync13,
|
|
5176
5604
|
mkdirSync as mkdirSync9,
|
|
5177
|
-
existsSync as
|
|
5605
|
+
existsSync as existsSync19,
|
|
5178
5606
|
readdirSync as readdirSync7,
|
|
5179
5607
|
rmSync
|
|
5180
5608
|
} from "fs";
|
|
5181
|
-
import { join as
|
|
5609
|
+
import { join as join22, basename as basename5, dirname as dirname11 } from "path";
|
|
5182
5610
|
import { generateThemeData as generateThemeData6 } from "@loworbitstudio/visor-theme-engine";
|
|
5183
5611
|
import { flutterAdapter as flutterAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
5184
5612
|
function scanThemeDir2(dir) {
|
|
5185
|
-
if (!
|
|
5186
|
-
return readdirSync7(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) =>
|
|
5613
|
+
if (!existsSync19(dir)) return [];
|
|
5614
|
+
return readdirSync7(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join22(dir, f)).sort();
|
|
5187
5615
|
}
|
|
5188
5616
|
function slugToCamel(slug2) {
|
|
5189
5617
|
return slug2.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
@@ -5316,9 +5744,9 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
5316
5744
|
process.exit(1);
|
|
5317
5745
|
return;
|
|
5318
5746
|
}
|
|
5319
|
-
const themesDir =
|
|
5320
|
-
const customThemesDir =
|
|
5321
|
-
const outputDir =
|
|
5747
|
+
const themesDir = join22(repoRoot, "themes");
|
|
5748
|
+
const customThemesDir = join22(repoRoot, "custom-themes");
|
|
5749
|
+
const outputDir = join22(repoRoot, "packages", "visor_themes");
|
|
5322
5750
|
const stockFiles = scanThemeDir2(themesDir);
|
|
5323
5751
|
const customFiles = scanThemeDir2(customThemesDir);
|
|
5324
5752
|
const allFiles = [...stockFiles, ...customFiles];
|
|
@@ -5339,7 +5767,7 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
5339
5767
|
for (const filePath of allFiles) {
|
|
5340
5768
|
let yamlContent;
|
|
5341
5769
|
try {
|
|
5342
|
-
yamlContent =
|
|
5770
|
+
yamlContent = readFileSync22(filePath, "utf-8");
|
|
5343
5771
|
} catch {
|
|
5344
5772
|
errors.push(`Could not read: ${filePath}`);
|
|
5345
5773
|
continue;
|
|
@@ -5418,8 +5846,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
5418
5846
|
return;
|
|
5419
5847
|
}
|
|
5420
5848
|
const slugs = processed.map((p) => p.slug);
|
|
5421
|
-
const libSrcDir =
|
|
5422
|
-
if (
|
|
5849
|
+
const libSrcDir = join22(outputDir, "lib", "src");
|
|
5850
|
+
if (existsSync19(libSrcDir)) {
|
|
5423
5851
|
rmSync(libSrcDir, { recursive: true, force: true });
|
|
5424
5852
|
}
|
|
5425
5853
|
const packageFiles = {
|
|
@@ -5431,16 +5859,16 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
5431
5859
|
};
|
|
5432
5860
|
let totalFiles = 0;
|
|
5433
5861
|
for (const [relPath, content] of Object.entries(packageFiles)) {
|
|
5434
|
-
const absPath =
|
|
5435
|
-
mkdirSync9(
|
|
5862
|
+
const absPath = join22(outputDir, relPath);
|
|
5863
|
+
mkdirSync9(dirname11(absPath), { recursive: true });
|
|
5436
5864
|
writeFileSync13(absPath, content, "utf-8");
|
|
5437
5865
|
totalFiles++;
|
|
5438
5866
|
}
|
|
5439
5867
|
for (const { slug: slug2, tokenFiles } of processed) {
|
|
5440
|
-
const themeBaseDir =
|
|
5868
|
+
const themeBaseDir = join22(outputDir, "lib", "src", slug2);
|
|
5441
5869
|
for (const [relPath, content] of Object.entries(tokenFiles)) {
|
|
5442
|
-
const absPath =
|
|
5443
|
-
mkdirSync9(
|
|
5870
|
+
const absPath = join22(themeBaseDir, relPath);
|
|
5871
|
+
mkdirSync9(dirname11(absPath), { recursive: true });
|
|
5444
5872
|
writeFileSync13(absPath, content, "utf-8");
|
|
5445
5873
|
totalFiles++;
|
|
5446
5874
|
}
|
|
@@ -5462,8 +5890,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
5462
5890
|
}
|
|
5463
5891
|
|
|
5464
5892
|
// src/commands/fonts-add.ts
|
|
5465
|
-
import { existsSync as
|
|
5466
|
-
import { resolve as
|
|
5893
|
+
import { existsSync as existsSync20, statSync as statSync8, readdirSync as readdirSync8, readFileSync as readFileSync23 } from "fs";
|
|
5894
|
+
import { resolve as resolve16, basename as basename6, extname as extname4 } from "path";
|
|
5467
5895
|
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
5468
5896
|
function deriveFamilySlug(filename) {
|
|
5469
5897
|
const name = basename6(filename, extname4(filename));
|
|
@@ -5507,11 +5935,11 @@ function deriveFamilySlug(filename) {
|
|
|
5507
5935
|
return parts.join("-").toLowerCase();
|
|
5508
5936
|
}
|
|
5509
5937
|
function collectWoff2Files(inputPath) {
|
|
5510
|
-
const resolved =
|
|
5511
|
-
if (!
|
|
5938
|
+
const resolved = resolve16(inputPath);
|
|
5939
|
+
if (!existsSync20(resolved)) {
|
|
5512
5940
|
throw new Error(`Path not found: ${resolved}`);
|
|
5513
5941
|
}
|
|
5514
|
-
const stat =
|
|
5942
|
+
const stat = statSync8(resolved);
|
|
5515
5943
|
if (stat.isFile()) {
|
|
5516
5944
|
if (extname4(resolved).toLowerCase() !== ".woff2") {
|
|
5517
5945
|
throw new Error(
|
|
@@ -5521,7 +5949,7 @@ function collectWoff2Files(inputPath) {
|
|
|
5521
5949
|
return [resolved];
|
|
5522
5950
|
}
|
|
5523
5951
|
if (stat.isDirectory()) {
|
|
5524
|
-
const files = readdirSync8(resolved).filter((f) => extname4(f).toLowerCase() === ".woff2").map((f) =>
|
|
5952
|
+
const files = readdirSync8(resolved).filter((f) => extname4(f).toLowerCase() === ".woff2").map((f) => resolve16(resolved, f));
|
|
5525
5953
|
if (files.length === 0) {
|
|
5526
5954
|
throw new Error(
|
|
5527
5955
|
`No .woff2 files found in directory: ${resolved}`
|
|
@@ -5536,8 +5964,8 @@ function collectWoff2Files(inputPath) {
|
|
|
5536
5964
|
throw new Error(`Path is neither a file nor a directory: ${resolved}`);
|
|
5537
5965
|
}
|
|
5538
5966
|
function getNonWoff2Fonts(inputPath) {
|
|
5539
|
-
const resolved =
|
|
5540
|
-
if (!
|
|
5967
|
+
const resolved = resolve16(inputPath);
|
|
5968
|
+
if (!existsSync20(resolved) || !statSync8(resolved).isDirectory()) {
|
|
5541
5969
|
return [];
|
|
5542
5970
|
}
|
|
5543
5971
|
return readdirSync8(resolved).filter((f) => {
|
|
@@ -5574,7 +6002,7 @@ function createR2Client(config) {
|
|
|
5574
6002
|
});
|
|
5575
6003
|
}
|
|
5576
6004
|
async function uploadFile(client, bucket, key, filePath) {
|
|
5577
|
-
const body =
|
|
6005
|
+
const body = readFileSync23(filePath);
|
|
5578
6006
|
await client.send(
|
|
5579
6007
|
new PutObjectCommand({
|
|
5580
6008
|
Bucket: bucket,
|
|
@@ -5590,8 +6018,8 @@ async function fontsAddCommand(inputPath, options) {
|
|
|
5590
6018
|
const r2Config = getR2Config();
|
|
5591
6019
|
const files = collectWoff2Files(inputPath);
|
|
5592
6020
|
const familySlug = options.family ?? deriveFamilySlug(basename6(files[0]));
|
|
5593
|
-
const resolved =
|
|
5594
|
-
const nonWoff2 =
|
|
6021
|
+
const resolved = resolve16(inputPath);
|
|
6022
|
+
const nonWoff2 = statSync8(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
|
|
5595
6023
|
if (!json) {
|
|
5596
6024
|
logger.heading("Visor Font Upload");
|
|
5597
6025
|
logger.info(`Organization: ${org}`);
|
|
@@ -5615,7 +6043,7 @@ async function fontsAddCommand(inputPath, options) {
|
|
|
5615
6043
|
logger.info(`Uploading ${filename}...`);
|
|
5616
6044
|
}
|
|
5617
6045
|
await uploadFile(client, bucket, key, filePath);
|
|
5618
|
-
const size =
|
|
6046
|
+
const size = statSync8(filePath).size;
|
|
5619
6047
|
results.push({ file: filename, key, size });
|
|
5620
6048
|
if (!json) {
|
|
5621
6049
|
logger.success(`Uploaded: ${key} (${formatBytes(size)})`);
|
|
@@ -5878,27 +6306,27 @@ function findCssFiles(dir, maxDepth = 3) {
|
|
|
5878
6306
|
}
|
|
5879
6307
|
|
|
5880
6308
|
// src/utils/patterns.ts
|
|
5881
|
-
import { existsSync as
|
|
5882
|
-
import { join as
|
|
6309
|
+
import { existsSync as existsSync22, readdirSync as readdirSync10, readFileSync as readFileSync25 } from "fs";
|
|
6310
|
+
import { join as join24 } from "path";
|
|
5883
6311
|
import { parse as parseYAML } from "yaml";
|
|
5884
6312
|
function loadPatternsFromYaml(repoRoot) {
|
|
5885
|
-
const patternsDir =
|
|
5886
|
-
if (!
|
|
6313
|
+
const patternsDir = join24(repoRoot, "patterns");
|
|
6314
|
+
if (!existsSync22(patternsDir)) return [];
|
|
5887
6315
|
const files = readdirSync10(patternsDir).filter(
|
|
5888
6316
|
(f) => f.endsWith(".visor-pattern.yaml")
|
|
5889
6317
|
);
|
|
5890
6318
|
return files.map((file) => {
|
|
5891
|
-
const content =
|
|
6319
|
+
const content = readFileSync25(join24(patternsDir, file), "utf-8");
|
|
5892
6320
|
return parseYAML(content);
|
|
5893
6321
|
}).filter(Boolean);
|
|
5894
6322
|
}
|
|
5895
6323
|
function findRepoRoot2(startDir) {
|
|
5896
6324
|
let current = startDir;
|
|
5897
6325
|
while (true) {
|
|
5898
|
-
if (
|
|
6326
|
+
if (existsSync22(join24(current, "patterns"))) {
|
|
5899
6327
|
return current;
|
|
5900
6328
|
}
|
|
5901
|
-
const parent =
|
|
6329
|
+
const parent = join24(current, "..");
|
|
5902
6330
|
if (parent === current) return null;
|
|
5903
6331
|
current = parent;
|
|
5904
6332
|
}
|
|
@@ -6277,8 +6705,8 @@ Visor Tokens (${categoryLabel}) \u2014 ${tokens2.length} tokens
|
|
|
6277
6705
|
}
|
|
6278
6706
|
|
|
6279
6707
|
// src/commands/migrate-token-substitution.ts
|
|
6280
|
-
import { readFileSync as
|
|
6281
|
-
import { resolve as
|
|
6708
|
+
import { readFileSync as readFileSync26, writeFileSync as writeFileSync14, readdirSync as readdirSync11, statSync as statSync9, existsSync as existsSync23 } from "fs";
|
|
6709
|
+
import { resolve as resolve17, join as join25, relative as relative4 } from "path";
|
|
6282
6710
|
import { parse as parseYaml4 } from "yaml";
|
|
6283
6711
|
import pc4 from "picocolors";
|
|
6284
6712
|
var V7_ENTR_SUBSTITUTION_MAP = {
|
|
@@ -6301,14 +6729,14 @@ var BUILT_IN_SUBSTITUTION_MAPS = {
|
|
|
6301
6729
|
var DEFAULT_THEME_ID = "entr";
|
|
6302
6730
|
function readMapFromThemeFile(themeId, cwd) {
|
|
6303
6731
|
const candidates = [
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6732
|
+
join25(cwd, "themes", `${themeId}.visor.yaml`),
|
|
6733
|
+
join25(cwd, "custom-themes", `${themeId}.visor.yaml`),
|
|
6734
|
+
join25(cwd, "packages", "docs", "public", "themes", `${themeId}.visor.yaml`)
|
|
6307
6735
|
];
|
|
6308
6736
|
for (const candidate of candidates) {
|
|
6309
|
-
if (
|
|
6737
|
+
if (existsSync23(candidate)) {
|
|
6310
6738
|
try {
|
|
6311
|
-
const raw =
|
|
6739
|
+
const raw = readFileSync26(candidate, "utf-8");
|
|
6312
6740
|
const parsed = parseYaml4(raw);
|
|
6313
6741
|
if (parsed?.migrate?.["token-substitution"]) {
|
|
6314
6742
|
return parsed.migrate["token-substitution"];
|
|
@@ -6364,7 +6792,7 @@ function collectCssFiles(dirPath) {
|
|
|
6364
6792
|
function walk2(current) {
|
|
6365
6793
|
const entries = readdirSync11(current, { withFileTypes: true });
|
|
6366
6794
|
for (const entry of entries) {
|
|
6367
|
-
const fullPath =
|
|
6795
|
+
const fullPath = join25(current, entry.name);
|
|
6368
6796
|
if (entry.isDirectory()) {
|
|
6369
6797
|
if (["node_modules", ".git", ".next", "dist", "build", ".cache"].includes(entry.name)) {
|
|
6370
6798
|
continue;
|
|
@@ -6383,7 +6811,7 @@ function collectCssFiles(dirPath) {
|
|
|
6383
6811
|
}
|
|
6384
6812
|
function runSubstitutionPass(targetPath, map, themeId) {
|
|
6385
6813
|
let files;
|
|
6386
|
-
const stat =
|
|
6814
|
+
const stat = statSync9(targetPath);
|
|
6387
6815
|
if (stat.isFile()) {
|
|
6388
6816
|
files = [targetPath];
|
|
6389
6817
|
} else {
|
|
@@ -6392,7 +6820,7 @@ function runSubstitutionPass(targetPath, map, themeId) {
|
|
|
6392
6820
|
const fileResults = [];
|
|
6393
6821
|
let totalSubstitutions = 0;
|
|
6394
6822
|
for (const file of files) {
|
|
6395
|
-
const content =
|
|
6823
|
+
const content = readFileSync26(file, "utf-8");
|
|
6396
6824
|
const { newContent, substitutions } = applySubstitutionsToContent(content, map);
|
|
6397
6825
|
if (substitutions.length > 0) {
|
|
6398
6826
|
fileResults.push({ file, substitutions, originalContent: content, newContent });
|
|
@@ -6426,9 +6854,9 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
6426
6854
|
process.exit(1);
|
|
6427
6855
|
return;
|
|
6428
6856
|
}
|
|
6429
|
-
const targetPath =
|
|
6857
|
+
const targetPath = resolve17(cwd, targetArg ?? ".");
|
|
6430
6858
|
try {
|
|
6431
|
-
|
|
6859
|
+
statSync9(targetPath);
|
|
6432
6860
|
} catch {
|
|
6433
6861
|
if (options.json) {
|
|
6434
6862
|
console.log(JSON.stringify({
|
|
@@ -6515,23 +6943,23 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
6515
6943
|
}
|
|
6516
6944
|
|
|
6517
6945
|
// src/commands/sandbox/init.ts
|
|
6518
|
-
import { existsSync as
|
|
6519
|
-
import { isAbsolute as
|
|
6946
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync12, readFileSync as readFileSync29, rmSync as rmSync2 } from "fs";
|
|
6947
|
+
import { isAbsolute as isAbsolute5, join as join28, resolve as resolve19, dirname as dirname13 } from "path";
|
|
6520
6948
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6521
6949
|
import * as childProcess3 from "child_process";
|
|
6522
6950
|
|
|
6523
6951
|
// src/commands/sandbox/parse-handoff.ts
|
|
6524
|
-
import { readFileSync as
|
|
6525
|
-
import { dirname as
|
|
6952
|
+
import { readFileSync as readFileSync27, existsSync as existsSync24 } from "fs";
|
|
6953
|
+
import { dirname as dirname12, resolve as resolve18, isAbsolute as isAbsolute4 } from "path";
|
|
6526
6954
|
function parseHandoff(handoffPath) {
|
|
6527
|
-
const text =
|
|
6955
|
+
const text = readFileSync27(handoffPath, "utf-8");
|
|
6528
6956
|
const lines2 = text.split("\n");
|
|
6529
6957
|
const warnings = [];
|
|
6530
6958
|
const pattern2 = extractPatternSlug(lines2, warnings);
|
|
6531
6959
|
const theme2 = extractMetaField(lines2, "Theme");
|
|
6532
6960
|
const recipePath = extractRecipePath(text, handoffPath, warnings);
|
|
6533
6961
|
const primitives = extractPrimitives(text, warnings);
|
|
6534
|
-
const { screens, mockShapes } = recipePath &&
|
|
6962
|
+
const { screens, mockShapes } = recipePath && existsSync24(recipePath) ? extractRecipeArtifacts(recipePath, warnings) : { screens: [], mockShapes: [] };
|
|
6535
6963
|
return { pattern: pattern2, theme: theme2, recipePath, primitives, screens, mockShapes, warnings };
|
|
6536
6964
|
}
|
|
6537
6965
|
function extractPatternSlug(lines2, warnings) {
|
|
@@ -6562,8 +6990,8 @@ function extractRecipePath(text, handoffPath, warnings) {
|
|
|
6562
6990
|
return void 0;
|
|
6563
6991
|
}
|
|
6564
6992
|
const href = m[2].trim();
|
|
6565
|
-
if (
|
|
6566
|
-
return
|
|
6993
|
+
if (isAbsolute4(href)) return href;
|
|
6994
|
+
return resolve18(dirname12(handoffPath), href);
|
|
6567
6995
|
}
|
|
6568
6996
|
var STATUS_PATTERNS = [
|
|
6569
6997
|
{ re: /NEW\s+gap/i, status: "gap-new" },
|
|
@@ -6610,7 +7038,7 @@ function extractPrimitives(text, warnings) {
|
|
|
6610
7038
|
return primitives;
|
|
6611
7039
|
}
|
|
6612
7040
|
function extractRecipeArtifacts(recipePath, warnings) {
|
|
6613
|
-
const text =
|
|
7041
|
+
const text = readFileSync27(recipePath, "utf-8");
|
|
6614
7042
|
const screens = extractScreens(text);
|
|
6615
7043
|
const mockShapes = extractMockFields(text, warnings);
|
|
6616
7044
|
return { screens, mockShapes };
|
|
@@ -6684,8 +7112,8 @@ function tryPort(port) {
|
|
|
6684
7112
|
}
|
|
6685
7113
|
|
|
6686
7114
|
// src/commands/sandbox/scaffold.ts
|
|
6687
|
-
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync15, existsSync as
|
|
6688
|
-
import { join as
|
|
7115
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync15, existsSync as existsSync25, readdirSync as readdirSync12 } from "fs";
|
|
7116
|
+
import { join as join26 } from "path";
|
|
6689
7117
|
|
|
6690
7118
|
// src/commands/sandbox/templates.ts
|
|
6691
7119
|
var SANDBOX_PACKAGE_VERSION = "16.2.6";
|
|
@@ -7188,7 +7616,7 @@ function writeScaffold(sandboxDir, manifest, port, options = {}) {
|
|
|
7188
7616
|
mkdirSync10(sandboxDir, { recursive: true });
|
|
7189
7617
|
const created = [];
|
|
7190
7618
|
const writeIfNew = (rel, contents) => {
|
|
7191
|
-
const full =
|
|
7619
|
+
const full = join26(sandboxDir, rel);
|
|
7192
7620
|
mkdirSync10(dirOf(full), { recursive: true });
|
|
7193
7621
|
writeFileSync15(full, contents, "utf-8");
|
|
7194
7622
|
created.push(rel);
|
|
@@ -7216,7 +7644,7 @@ function writeScaffold(sandboxDir, manifest, port, options = {}) {
|
|
|
7216
7644
|
}
|
|
7217
7645
|
}
|
|
7218
7646
|
writeIfNew("playwright.capture.mjs", captureScriptTemplate());
|
|
7219
|
-
mkdirSync10(
|
|
7647
|
+
mkdirSync10(join26(sandboxDir, "captures", "approved"), { recursive: true });
|
|
7220
7648
|
return { created };
|
|
7221
7649
|
}
|
|
7222
7650
|
function dirOf(p) {
|
|
@@ -7250,10 +7678,10 @@ function writeSandboxConfig(sandboxDir, manifest, port, options) {
|
|
|
7250
7678
|
stripChromeSelectors: options.prototypeImport.stripChromeSelectors
|
|
7251
7679
|
} : null
|
|
7252
7680
|
};
|
|
7253
|
-
writeFileSync15(
|
|
7681
|
+
writeFileSync15(join26(sandboxDir, "sandbox.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
7254
7682
|
}
|
|
7255
7683
|
function sandboxIsEmpty(sandboxDir) {
|
|
7256
|
-
if (!
|
|
7684
|
+
if (!existsSync25(sandboxDir)) return true;
|
|
7257
7685
|
try {
|
|
7258
7686
|
return readdirSync12(sandboxDir).filter((f) => !f.startsWith(".")).length === 0;
|
|
7259
7687
|
} catch {
|
|
@@ -7262,8 +7690,8 @@ function sandboxIsEmpty(sandboxDir) {
|
|
|
7262
7690
|
}
|
|
7263
7691
|
|
|
7264
7692
|
// src/commands/sandbox/html-prototype.ts
|
|
7265
|
-
import { mkdirSync as mkdirSync11, readdirSync as readdirSync13, readFileSync as
|
|
7266
|
-
import { join as
|
|
7693
|
+
import { mkdirSync as mkdirSync11, readdirSync as readdirSync13, readFileSync as readFileSync28, statSync as statSync10, writeFileSync as writeFileSync16 } from "fs";
|
|
7694
|
+
import { join as join27 } from "path";
|
|
7267
7695
|
|
|
7268
7696
|
// src/commands/sandbox/strip-chrome.ts
|
|
7269
7697
|
var DEFAULT_STRIP_SELECTORS = [
|
|
@@ -7447,7 +7875,7 @@ function stripDocumentaryChrome(html, selectors) {
|
|
|
7447
7875
|
var SCREEN_FILE_PATTERN = /^screen-(\d+)-([^/.]+)\.html$/i;
|
|
7448
7876
|
function copyHtmlPrototype(sourceDir, sandboxDir, manifest, options = {}) {
|
|
7449
7877
|
const warnings = [];
|
|
7450
|
-
const destAbs =
|
|
7878
|
+
const destAbs = join27(sandboxDir, "public", "prototype");
|
|
7451
7879
|
mkdirSync11(destAbs, { recursive: true });
|
|
7452
7880
|
const stripChromeSelectors = options.stripChromeSelectors ?? [];
|
|
7453
7881
|
const copiedFiles = copyTreeRelative(sourceDir, destAbs, "", stripChromeSelectors);
|
|
@@ -7501,24 +7929,24 @@ function deriveStateCoverageScreen(file, existingNames) {
|
|
|
7501
7929
|
}
|
|
7502
7930
|
function copyTreeRelative(srcDir, destDir, relDir = "", stripChromeSelectors = []) {
|
|
7503
7931
|
const out = [];
|
|
7504
|
-
const entries = readdirSync13(
|
|
7932
|
+
const entries = readdirSync13(join27(srcDir, relDir));
|
|
7505
7933
|
for (const name of entries) {
|
|
7506
7934
|
if (name.startsWith(".")) continue;
|
|
7507
|
-
const srcPath =
|
|
7508
|
-
const destPath =
|
|
7509
|
-
const stat =
|
|
7935
|
+
const srcPath = join27(srcDir, relDir, name);
|
|
7936
|
+
const destPath = join27(destDir, relDir, name);
|
|
7937
|
+
const stat = statSync10(srcPath);
|
|
7510
7938
|
if (stat.isDirectory()) {
|
|
7511
7939
|
mkdirSync11(destPath, { recursive: true });
|
|
7512
|
-
out.push(...copyTreeRelative(srcDir, destDir,
|
|
7940
|
+
out.push(...copyTreeRelative(srcDir, destDir, join27(relDir, name), stripChromeSelectors));
|
|
7513
7941
|
} else if (stat.isFile()) {
|
|
7514
7942
|
if (stripChromeSelectors.length > 0 && name.toLowerCase().endsWith(".html")) {
|
|
7515
|
-
const html =
|
|
7943
|
+
const html = readFileSync28(srcPath, "utf-8");
|
|
7516
7944
|
const stripped = stripDocumentaryChrome(html, stripChromeSelectors);
|
|
7517
7945
|
writeFileSync16(destPath, stripped);
|
|
7518
7946
|
} else {
|
|
7519
|
-
writeFileSync16(destPath,
|
|
7947
|
+
writeFileSync16(destPath, readFileSync28(srcPath));
|
|
7520
7948
|
}
|
|
7521
|
-
out.push(
|
|
7949
|
+
out.push(join27("public", "prototype", relDir, name).replace(/\\/g, "/"));
|
|
7522
7950
|
}
|
|
7523
7951
|
}
|
|
7524
7952
|
return out;
|
|
@@ -7570,8 +7998,8 @@ async function runInit(name, cwd, options) {
|
|
|
7570
7998
|
if (!name || !/^[a-z0-9][a-z0-9-_]*$/i.test(name)) {
|
|
7571
7999
|
throw new Error(`Invalid sandbox name '${name}'. Use letters, digits, '-' or '_'.`);
|
|
7572
8000
|
}
|
|
7573
|
-
const handoffPath =
|
|
7574
|
-
if (!
|
|
8001
|
+
const handoffPath = isAbsolute5(options.handoff) ? options.handoff : resolve19(cwd, options.handoff);
|
|
8002
|
+
if (!existsSync26(handoffPath)) {
|
|
7575
8003
|
throw new Error(`Handoff manifest not found: ${handoffPath}`);
|
|
7576
8004
|
}
|
|
7577
8005
|
const manifest = parseHandoff(handoffPath);
|
|
@@ -7580,7 +8008,7 @@ async function runInit(name, cwd, options) {
|
|
|
7580
8008
|
`Handoff has no primitives \u2014 refusing to scaffold an empty sandbox. Check the manifest at ${handoffPath}`
|
|
7581
8009
|
);
|
|
7582
8010
|
}
|
|
7583
|
-
const sandboxDir =
|
|
8011
|
+
const sandboxDir = join28(cwd, ".lo", "sandbox", name);
|
|
7584
8012
|
if (!sandboxIsEmpty(sandboxDir)) {
|
|
7585
8013
|
if (!options.overwrite) {
|
|
7586
8014
|
throw new Error(
|
|
@@ -7593,8 +8021,8 @@ async function runInit(name, cwd, options) {
|
|
|
7593
8021
|
const port = await findOpenPort();
|
|
7594
8022
|
let prototypeImport;
|
|
7595
8023
|
if (options.fromHtmlPrototype) {
|
|
7596
|
-
const prototypeDir =
|
|
7597
|
-
if (!
|
|
8024
|
+
const prototypeDir = isAbsolute5(options.fromHtmlPrototype) ? options.fromHtmlPrototype : resolve19(cwd, options.fromHtmlPrototype);
|
|
8025
|
+
if (!existsSync26(prototypeDir)) {
|
|
7598
8026
|
throw new Error(`HTML prototype directory not found: ${prototypeDir}`);
|
|
7599
8027
|
}
|
|
7600
8028
|
const stripChromeSelectors = resolveStripSelectors(
|
|
@@ -7653,22 +8081,22 @@ async function runInit(name, cwd, options) {
|
|
|
7653
8081
|
function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile) {
|
|
7654
8082
|
const candidates = [];
|
|
7655
8083
|
if (themeFile) {
|
|
7656
|
-
candidates.push(
|
|
8084
|
+
candidates.push(isAbsolute5(themeFile) ? themeFile : resolve19(cwd, themeFile));
|
|
7657
8085
|
}
|
|
7658
|
-
candidates.push(
|
|
8086
|
+
candidates.push(isAbsolute5(theme2) ? theme2 : resolve19(cwd, theme2));
|
|
7659
8087
|
const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
|
|
7660
8088
|
if (privateRoot && privateRoot.length > 0) {
|
|
7661
|
-
candidates.push(
|
|
8089
|
+
candidates.push(join28(privateRoot, "themes", theme2, "theme.visor.yaml"));
|
|
7662
8090
|
}
|
|
7663
8091
|
candidates.push(
|
|
7664
|
-
|
|
7665
|
-
|
|
8092
|
+
join28(cwd, "themes", `${theme2}.visor.yaml`),
|
|
8093
|
+
join28(cwd, "custom-themes", `${theme2}.visor.yaml`)
|
|
7666
8094
|
);
|
|
7667
|
-
const yamlPath = candidates.find((p) =>
|
|
8095
|
+
const yamlPath = candidates.find((p) => existsSync26(p));
|
|
7668
8096
|
if (!yamlPath) {
|
|
7669
8097
|
const searched = candidates.join(", ");
|
|
7670
8098
|
manifest.warnings.push(
|
|
7671
|
-
`Theme '${theme2}' not found (searched: ${searched}) \u2014 sandbox uses placeholder globals.css. Run 'npx visor theme apply <path-to-theme.visor.yaml> --adapter nextjs -o ${
|
|
8099
|
+
`Theme '${theme2}' not found (searched: ${searched}) \u2014 sandbox uses placeholder globals.css. Run 'npx visor theme apply <path-to-theme.visor.yaml> --adapter nextjs -o ${join28(sandboxDir, "app", "globals.css")}' manually, or re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH to a directory containing themes/${theme2}/theme.visor.yaml.`
|
|
7672
8100
|
);
|
|
7673
8101
|
if (!json) {
|
|
7674
8102
|
logger.warn(`Theme '${theme2}' not found \u2014 leaving placeholder globals.css.`);
|
|
@@ -7676,12 +8104,12 @@ function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile
|
|
|
7676
8104
|
`Re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH=<dir-containing-themes/${theme2}/theme.visor.yaml>.`
|
|
7677
8105
|
);
|
|
7678
8106
|
logger.item(
|
|
7679
|
-
`Or apply manually: npx visor theme apply <path> --adapter nextjs -o ${
|
|
8107
|
+
`Or apply manually: npx visor theme apply <path> --adapter nextjs -o ${join28(sandboxDir, "app", "globals.css")}`
|
|
7680
8108
|
);
|
|
7681
8109
|
}
|
|
7682
8110
|
return;
|
|
7683
8111
|
}
|
|
7684
|
-
const globalsOut =
|
|
8112
|
+
const globalsOut = join28(sandboxDir, "app", "globals.css");
|
|
7685
8113
|
try {
|
|
7686
8114
|
themeApplyCommand(yamlPath, sandboxDir, {
|
|
7687
8115
|
output: globalsOut,
|
|
@@ -7734,12 +8162,12 @@ function loadKnownPrimitives() {
|
|
|
7734
8162
|
}
|
|
7735
8163
|
function readCliVersion() {
|
|
7736
8164
|
try {
|
|
7737
|
-
const here =
|
|
8165
|
+
const here = dirname13(fileURLToPath3(import.meta.url));
|
|
7738
8166
|
for (let i = 0; i < 6; i++) {
|
|
7739
8167
|
const segments = new Array(i).fill("..");
|
|
7740
|
-
const candidate =
|
|
8168
|
+
const candidate = join28(here, ...segments, "package.json");
|
|
7741
8169
|
try {
|
|
7742
|
-
const pkg2 = JSON.parse(
|
|
8170
|
+
const pkg2 = JSON.parse(readFileSync29(candidate, "utf-8"));
|
|
7743
8171
|
if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) return pkg2.version;
|
|
7744
8172
|
} catch {
|
|
7745
8173
|
}
|
|
@@ -7750,14 +8178,14 @@ function readCliVersion() {
|
|
|
7750
8178
|
}
|
|
7751
8179
|
|
|
7752
8180
|
// src/commands/sandbox/dev.ts
|
|
7753
|
-
import { existsSync as
|
|
7754
|
-
import { join as
|
|
8181
|
+
import { existsSync as existsSync27, readFileSync as readFileSync30 } from "fs";
|
|
8182
|
+
import { join as join29 } from "path";
|
|
7755
8183
|
import * as childProcess4 from "child_process";
|
|
7756
8184
|
function sandboxDevCommand(cwd, options) {
|
|
7757
8185
|
const json = options.json ?? false;
|
|
7758
|
-
const sandboxDir =
|
|
7759
|
-
const configPath =
|
|
7760
|
-
if (!
|
|
8186
|
+
const sandboxDir = join29(cwd, ".lo", "sandbox", options.name);
|
|
8187
|
+
const configPath = join29(sandboxDir, "sandbox.json");
|
|
8188
|
+
if (!existsSync27(configPath)) {
|
|
7761
8189
|
fail(
|
|
7762
8190
|
json,
|
|
7763
8191
|
`Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init ${options.name} --handoff ... --theme ...' first.`
|
|
@@ -7766,7 +8194,7 @@ function sandboxDevCommand(cwd, options) {
|
|
|
7766
8194
|
}
|
|
7767
8195
|
let config;
|
|
7768
8196
|
try {
|
|
7769
|
-
config = JSON.parse(
|
|
8197
|
+
config = JSON.parse(readFileSync30(configPath, "utf-8"));
|
|
7770
8198
|
} catch (err) {
|
|
7771
8199
|
fail(json, `Invalid sandbox.json at ${configPath}: ${err.message}`);
|
|
7772
8200
|
return;
|
|
@@ -7824,20 +8252,20 @@ function fail(json, message) {
|
|
|
7824
8252
|
// src/commands/sandbox/approve.ts
|
|
7825
8253
|
import {
|
|
7826
8254
|
copyFileSync as copyFileSync2,
|
|
7827
|
-
existsSync as
|
|
8255
|
+
existsSync as existsSync28,
|
|
7828
8256
|
mkdirSync as mkdirSync13,
|
|
7829
|
-
readFileSync as
|
|
8257
|
+
readFileSync as readFileSync31,
|
|
7830
8258
|
readdirSync as readdirSync14,
|
|
7831
8259
|
rmSync as rmSync3,
|
|
7832
8260
|
writeFileSync as writeFileSync17
|
|
7833
8261
|
} from "fs";
|
|
7834
|
-
import { basename as basename7, join as
|
|
8262
|
+
import { basename as basename7, join as join30 } from "path";
|
|
7835
8263
|
import * as childProcess5 from "child_process";
|
|
7836
8264
|
function sandboxApproveCommand(cwd, options) {
|
|
7837
8265
|
const json = options.json ?? false;
|
|
7838
|
-
const sandboxDir =
|
|
7839
|
-
const configPath =
|
|
7840
|
-
if (!
|
|
8266
|
+
const sandboxDir = join30(cwd, ".lo", "sandbox", options.name);
|
|
8267
|
+
const configPath = join30(sandboxDir, "sandbox.json");
|
|
8268
|
+
if (!existsSync28(configPath)) {
|
|
7841
8269
|
fail2(
|
|
7842
8270
|
json,
|
|
7843
8271
|
`Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init' first.`
|
|
@@ -7851,8 +8279,8 @@ function sandboxApproveCommand(cwd, options) {
|
|
|
7851
8279
|
runCapture(sandboxDir, configPath, options.name, json);
|
|
7852
8280
|
}
|
|
7853
8281
|
function runCapture(sandboxDir, configPath, sandboxName, json) {
|
|
7854
|
-
const config = JSON.parse(
|
|
7855
|
-
const captureScriptPath =
|
|
8282
|
+
const config = JSON.parse(readFileSync31(configPath, "utf-8"));
|
|
8283
|
+
const captureScriptPath = join30(sandboxDir, "playwright.capture.mjs");
|
|
7856
8284
|
writeFileSync17(captureScriptPath, captureScriptTemplate(), "utf-8");
|
|
7857
8285
|
ensurePlaywrightInstalled(sandboxDir, json);
|
|
7858
8286
|
const result = childProcess5.spawnSync(
|
|
@@ -7874,9 +8302,9 @@ function runCapture(sandboxDir, configPath, sandboxName, json) {
|
|
|
7874
8302
|
${result.stderr}`);
|
|
7875
8303
|
return;
|
|
7876
8304
|
}
|
|
7877
|
-
const pendingDir =
|
|
7878
|
-
const diffsDir =
|
|
7879
|
-
const approvedDir =
|
|
8305
|
+
const pendingDir = join30(sandboxDir, "captures", "pending");
|
|
8306
|
+
const diffsDir = join30(sandboxDir, "captures", "diffs");
|
|
8307
|
+
const approvedDir = join30(sandboxDir, "captures", "approved");
|
|
7880
8308
|
const pendingFiles = safeListPngs(pendingDir);
|
|
7881
8309
|
const diffFiles = safeListPngs(diffsDir);
|
|
7882
8310
|
const hasBaseline = safeListPngs(approvedDir).length > 0;
|
|
@@ -7913,9 +8341,9 @@ ${result.stderr}`);
|
|
|
7913
8341
|
}
|
|
7914
8342
|
}
|
|
7915
8343
|
function runPromotion(sandboxDir, sandboxName, json) {
|
|
7916
|
-
const pendingDir =
|
|
7917
|
-
const approvedDir =
|
|
7918
|
-
const diffsDir =
|
|
8344
|
+
const pendingDir = join30(sandboxDir, "captures", "pending");
|
|
8345
|
+
const approvedDir = join30(sandboxDir, "captures", "approved");
|
|
8346
|
+
const diffsDir = join30(sandboxDir, "captures", "diffs");
|
|
7919
8347
|
const pendingFiles = safeListPngs(pendingDir);
|
|
7920
8348
|
if (pendingFiles.length === 0) {
|
|
7921
8349
|
fail2(
|
|
@@ -7927,7 +8355,7 @@ function runPromotion(sandboxDir, sandboxName, json) {
|
|
|
7927
8355
|
mkdirSync13(approvedDir, { recursive: true });
|
|
7928
8356
|
const promoted = [];
|
|
7929
8357
|
for (const src of pendingFiles) {
|
|
7930
|
-
const dest =
|
|
8358
|
+
const dest = join30(approvedDir, basename7(src));
|
|
7931
8359
|
copyFileSync2(src, dest);
|
|
7932
8360
|
promoted.push(dest);
|
|
7933
8361
|
}
|
|
@@ -7947,8 +8375,8 @@ function runPromotion(sandboxDir, sandboxName, json) {
|
|
|
7947
8375
|
}
|
|
7948
8376
|
}
|
|
7949
8377
|
function ensurePlaywrightInstalled(sandboxDir, json) {
|
|
7950
|
-
const markerPath =
|
|
7951
|
-
if (
|
|
8378
|
+
const markerPath = join30(sandboxDir, ".playwright-installed");
|
|
8379
|
+
if (existsSync28(markerPath)) return;
|
|
7952
8380
|
if (!json) logger.info("Installing Playwright Chromium (one-time)...");
|
|
7953
8381
|
const result = childProcess5.spawnSync(
|
|
7954
8382
|
"npx",
|
|
@@ -7967,8 +8395,8 @@ function ensurePlaywrightInstalled(sandboxDir, json) {
|
|
|
7967
8395
|
writeFileSync17(markerPath, (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
|
|
7968
8396
|
}
|
|
7969
8397
|
function safeListPngs(dir) {
|
|
7970
|
-
if (!
|
|
7971
|
-
return readdirSync14(dir).filter((f) => f.endsWith(".png")).map((f) =>
|
|
8398
|
+
if (!existsSync28(dir)) return [];
|
|
8399
|
+
return readdirSync14(dir).filter((f) => f.endsWith(".png")).map((f) => join30(dir, f));
|
|
7972
8400
|
}
|
|
7973
8401
|
function tryParseJson(s) {
|
|
7974
8402
|
try {
|
|
@@ -7989,13 +8417,13 @@ function fail2(json, message) {
|
|
|
7989
8417
|
// src/commands/spawn.ts
|
|
7990
8418
|
import {
|
|
7991
8419
|
cpSync,
|
|
7992
|
-
existsSync as
|
|
7993
|
-
readFileSync as
|
|
8420
|
+
existsSync as existsSync30,
|
|
8421
|
+
readFileSync as readFileSync32,
|
|
7994
8422
|
readdirSync as readdirSync16,
|
|
7995
|
-
rmSync as rmSync4
|
|
8423
|
+
rmSync as rmSync4,
|
|
8424
|
+
statSync as statSync12
|
|
7996
8425
|
} from "fs";
|
|
7997
|
-
import { basename as basename8, isAbsolute as
|
|
7998
|
-
import { homedir as homedir3 } from "os";
|
|
8426
|
+
import { basename as basename8, dirname as dirname14, isAbsolute as isAbsolute6, join as join32, resolve as resolve20 } from "path";
|
|
7999
8427
|
import * as childProcess6 from "child_process";
|
|
8000
8428
|
import { parse as parseYaml5 } from "yaml";
|
|
8001
8429
|
import { generateThemeData as generateThemeData7 } from "@loworbitstudio/visor-theme-engine";
|
|
@@ -8003,8 +8431,8 @@ import { nextjsAdapter as nextjsAdapter3 } from "@loworbitstudio/visor-theme-eng
|
|
|
8003
8431
|
import { validate as validate3 } from "@loworbitstudio/visor-theme-engine";
|
|
8004
8432
|
|
|
8005
8433
|
// src/lib/blessed-discovery.ts
|
|
8006
|
-
import { existsSync as
|
|
8007
|
-
import { join as
|
|
8434
|
+
import { existsSync as existsSync29, readdirSync as readdirSync15, statSync as statSync11 } from "fs";
|
|
8435
|
+
import { join as join31 } from "path";
|
|
8008
8436
|
var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
8009
8437
|
"node_modules",
|
|
8010
8438
|
".next",
|
|
@@ -8014,11 +8442,15 @@ var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
|
8014
8442
|
"coverage",
|
|
8015
8443
|
".cache"
|
|
8016
8444
|
]);
|
|
8445
|
+
var REFERENCE_BUILD_DIRNAME = "reference-build";
|
|
8446
|
+
var APPROVED_CAPTURES_SUBPATH = join31("captures", "approved");
|
|
8447
|
+
var MISSING_MANIFEST_REASON = `missing ${BLESSED_MANIFEST_FILENAME}`;
|
|
8017
8448
|
function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
|
|
8018
8449
|
const builds = [];
|
|
8450
|
+
const incomplete = [];
|
|
8019
8451
|
const errors = [];
|
|
8020
|
-
if (!
|
|
8021
|
-
return { builds, errors };
|
|
8452
|
+
if (!existsSync29(blessedDir)) {
|
|
8453
|
+
return { builds, incomplete, errors };
|
|
8022
8454
|
}
|
|
8023
8455
|
walk2(blessedDir, 0);
|
|
8024
8456
|
builds.sort((a, b) => {
|
|
@@ -8027,7 +8459,8 @@ function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
|
|
|
8027
8459
|
}
|
|
8028
8460
|
return a.manifest.pattern.localeCompare(b.manifest.pattern);
|
|
8029
8461
|
});
|
|
8030
|
-
|
|
8462
|
+
incomplete.sort((a, b) => a.dir.localeCompare(b.dir));
|
|
8463
|
+
return { builds, incomplete, errors };
|
|
8031
8464
|
function walk2(dir, depth) {
|
|
8032
8465
|
if (depth > maxDepth) return;
|
|
8033
8466
|
let entries;
|
|
@@ -8048,13 +8481,30 @@ function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
|
|
|
8048
8481
|
}
|
|
8049
8482
|
return;
|
|
8050
8483
|
}
|
|
8484
|
+
if (isNearMissRoot(dir, entries)) {
|
|
8485
|
+
incomplete.push({ dir, reason: MISSING_MANIFEST_REASON });
|
|
8486
|
+
return;
|
|
8487
|
+
}
|
|
8051
8488
|
for (const entry of entries) {
|
|
8052
8489
|
if (!entry.isDirectory()) continue;
|
|
8053
8490
|
if (WALK_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
8054
|
-
walk2(
|
|
8491
|
+
walk2(join31(dir, entry.name), depth + 1);
|
|
8055
8492
|
}
|
|
8056
8493
|
}
|
|
8057
8494
|
}
|
|
8495
|
+
function isNearMissRoot(dir, entries) {
|
|
8496
|
+
const hasReferenceBuild = entries.some(
|
|
8497
|
+
(entry) => entry.isDirectory() && entry.name === REFERENCE_BUILD_DIRNAME
|
|
8498
|
+
);
|
|
8499
|
+
if (!hasReferenceBuild) return false;
|
|
8500
|
+
const approved = join31(dir, APPROVED_CAPTURES_SUBPATH);
|
|
8501
|
+
try {
|
|
8502
|
+
if (!statSync11(approved).isDirectory()) return false;
|
|
8503
|
+
} catch {
|
|
8504
|
+
return false;
|
|
8505
|
+
}
|
|
8506
|
+
return !existsSync29(join31(dir, REFERENCE_BUILD_DIRNAME, BLESSED_MANIFEST_FILENAME));
|
|
8507
|
+
}
|
|
8058
8508
|
function resolveBlessedBuild(blessedDir, shape, pattern2) {
|
|
8059
8509
|
const { builds } = discoverBlessedBuilds(blessedDir);
|
|
8060
8510
|
const build = builds.find(
|
|
@@ -8064,13 +8514,18 @@ function resolveBlessedBuild(blessedDir, shape, pattern2) {
|
|
|
8064
8514
|
}
|
|
8065
8515
|
|
|
8066
8516
|
// src/commands/spawn.ts
|
|
8067
|
-
var
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
);
|
|
8517
|
+
var BLESSED_ROOT_DIRNAME = "design-prototypes";
|
|
8518
|
+
var NO_BLESSED_ROOT_ERROR = `No blessed-build root configured. Pass --blessed-dir <path>, set VISOR_BLESSED_DIR, or run from a checkout that contains a '${BLESSED_ROOT_DIRNAME}/' directory. See docs/blessed-builds.md.`;
|
|
8519
|
+
function discoverBlessedRoot(cwd) {
|
|
8520
|
+
let dir = resolve20(cwd);
|
|
8521
|
+
for (; ; ) {
|
|
8522
|
+
const candidate = join32(dir, BLESSED_ROOT_DIRNAME);
|
|
8523
|
+
if (existsSync30(candidate) && statSync12(candidate).isDirectory()) return candidate;
|
|
8524
|
+
const parent = dirname14(dir);
|
|
8525
|
+
if (parent === dir) return void 0;
|
|
8526
|
+
dir = parent;
|
|
8527
|
+
}
|
|
8528
|
+
}
|
|
8074
8529
|
var FORK_EXCLUDE = /* @__PURE__ */ new Set([
|
|
8075
8530
|
"node_modules",
|
|
8076
8531
|
".next",
|
|
@@ -8099,24 +8554,27 @@ function parseBlessedIdentifier(from) {
|
|
|
8099
8554
|
return { shape: parts[0], pattern: parts[1] };
|
|
8100
8555
|
}
|
|
8101
8556
|
function resolveBlessedDir(cwd, options) {
|
|
8102
|
-
const
|
|
8103
|
-
|
|
8557
|
+
const explicit = options.blessedDir ?? (process.env.VISOR_BLESSED_DIR && process.env.VISOR_BLESSED_DIR.length > 0 ? process.env.VISOR_BLESSED_DIR : void 0);
|
|
8558
|
+
if (explicit !== void 0) {
|
|
8559
|
+
return isAbsolute6(explicit) ? explicit : resolve20(cwd, explicit);
|
|
8560
|
+
}
|
|
8561
|
+
return discoverBlessedRoot(cwd);
|
|
8104
8562
|
}
|
|
8105
8563
|
function resolveThemeFile(theme2, cwd, themeFile) {
|
|
8106
8564
|
const candidates = [];
|
|
8107
8565
|
if (themeFile) {
|
|
8108
|
-
candidates.push(
|
|
8566
|
+
candidates.push(isAbsolute6(themeFile) ? themeFile : resolve20(cwd, themeFile));
|
|
8109
8567
|
}
|
|
8110
|
-
candidates.push(
|
|
8568
|
+
candidates.push(isAbsolute6(theme2) ? theme2 : resolve20(cwd, theme2));
|
|
8111
8569
|
const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
|
|
8112
8570
|
if (privateRoot && privateRoot.length > 0) {
|
|
8113
|
-
candidates.push(
|
|
8571
|
+
candidates.push(join32(privateRoot, "themes", theme2, "theme.visor.yaml"));
|
|
8114
8572
|
}
|
|
8115
8573
|
candidates.push(
|
|
8116
|
-
|
|
8117
|
-
|
|
8574
|
+
join32(cwd, "themes", `${theme2}.visor.yaml`),
|
|
8575
|
+
join32(cwd, "custom-themes", `${theme2}.visor.yaml`)
|
|
8118
8576
|
);
|
|
8119
|
-
const found = candidates.find((candidate) =>
|
|
8577
|
+
const found = candidates.find((candidate) => existsSync30(candidate));
|
|
8120
8578
|
if (!found) {
|
|
8121
8579
|
throw new Error(
|
|
8122
8580
|
`Theme '${theme2}' not found (searched: ${candidates.join(", ")}). Pass --theme-file <path>, use a direct path, or set VISOR_THEMES_PRIVATE_PATH.`
|
|
@@ -8125,7 +8583,7 @@ function resolveThemeFile(theme2, cwd, themeFile) {
|
|
|
8125
8583
|
return { path: found, searched: candidates };
|
|
8126
8584
|
}
|
|
8127
8585
|
function applyThemeToFork(themeFile, outputDir, manifest) {
|
|
8128
|
-
const yaml =
|
|
8586
|
+
const yaml = readFileSync32(themeFile, "utf-8");
|
|
8129
8587
|
const data = generateThemeData7(yaml);
|
|
8130
8588
|
const themeId = data.config.name;
|
|
8131
8589
|
if (!themeId || themeId.length === 0) {
|
|
@@ -8162,6 +8620,7 @@ function runSpawn(cwd, options) {
|
|
|
8162
8620
|
if (!options.output) throw new Error("Missing required --output <path>.");
|
|
8163
8621
|
const { shape, pattern: pattern2 } = parseBlessedIdentifier(options.from);
|
|
8164
8622
|
const blessedDir = resolveBlessedDir(cwd, options);
|
|
8623
|
+
if (blessedDir === void 0) throw new Error(NO_BLESSED_ROOT_ERROR);
|
|
8165
8624
|
const { build, available } = resolveBlessedBuild(blessedDir, shape, pattern2);
|
|
8166
8625
|
if (!build) {
|
|
8167
8626
|
const list = available.length > 0 ? available.map((b) => ` - blessed:${b.manifest.shape}:${b.manifest.pattern}`).join("\n") : " (none found)";
|
|
@@ -8171,8 +8630,8 @@ Available builds:
|
|
|
8171
8630
|
${list}`
|
|
8172
8631
|
);
|
|
8173
8632
|
}
|
|
8174
|
-
const outputDir =
|
|
8175
|
-
if (
|
|
8633
|
+
const outputDir = isAbsolute6(options.output) ? options.output : resolve20(cwd, options.output);
|
|
8634
|
+
if (existsSync30(outputDir) && readdirSync16(outputDir).length > 0) {
|
|
8176
8635
|
throw new Error(`Output directory already exists and is not empty: ${outputDir}`);
|
|
8177
8636
|
}
|
|
8178
8637
|
const { path: themeFile } = resolveThemeFile(options.theme, cwd, options.themeFile);
|
|
@@ -8185,7 +8644,7 @@ ${list}`
|
|
|
8185
8644
|
try {
|
|
8186
8645
|
applyThemeToFork(themeFile, outputDir, build.manifest);
|
|
8187
8646
|
if (options.validate) {
|
|
8188
|
-
const parsed = parseYaml5(
|
|
8647
|
+
const parsed = parseYaml5(readFileSync32(themeFile, "utf-8"));
|
|
8189
8648
|
const result = validate3(parsed);
|
|
8190
8649
|
if (!result.valid) {
|
|
8191
8650
|
const messages = result.errors.map((e) => e.message).join("; ");
|
|
@@ -8218,7 +8677,8 @@ ${list}`
|
|
|
8218
8677
|
}
|
|
8219
8678
|
function listBlessed(cwd, options) {
|
|
8220
8679
|
const blessedDir = resolveBlessedDir(cwd, options);
|
|
8221
|
-
|
|
8680
|
+
if (blessedDir === void 0) throw new Error(NO_BLESSED_ROOT_ERROR);
|
|
8681
|
+
const { builds, incomplete, errors } = discoverBlessedBuilds(blessedDir);
|
|
8222
8682
|
if (options.json) {
|
|
8223
8683
|
console.log(
|
|
8224
8684
|
JSON.stringify({
|
|
@@ -8233,6 +8693,7 @@ function listBlessed(cwd, options) {
|
|
|
8233
8693
|
three_gates_status: b.manifest.three_gates_status,
|
|
8234
8694
|
dir: b.dir
|
|
8235
8695
|
})),
|
|
8696
|
+
incomplete,
|
|
8236
8697
|
errors
|
|
8237
8698
|
})
|
|
8238
8699
|
);
|
|
@@ -8246,6 +8707,14 @@ function listBlessed(cwd, options) {
|
|
|
8246
8707
|
logger.success(`blessed:${b.manifest.shape}:${b.manifest.pattern}`);
|
|
8247
8708
|
logger.item(`base theme: ${b.manifest.base_theme} \xB7 requires visor ${b.manifest.requires_visor} \xB7 gates: ${b.manifest.three_gates_status}`);
|
|
8248
8709
|
}
|
|
8710
|
+
if (incomplete.length > 0) {
|
|
8711
|
+
logger.blank();
|
|
8712
|
+
logger.heading(`Near-miss builds (not spawnable) \u2014 ${incomplete.length}`);
|
|
8713
|
+
for (const i of incomplete) {
|
|
8714
|
+
logger.warn(i.dir);
|
|
8715
|
+
logger.item(i.reason);
|
|
8716
|
+
}
|
|
8717
|
+
}
|
|
8249
8718
|
for (const e of errors) {
|
|
8250
8719
|
logger.warn(`Skipped ${e.dir}: ${e.error}`);
|
|
8251
8720
|
}
|
|
@@ -8287,8 +8756,8 @@ function spawnCommand(cwd, options) {
|
|
|
8287
8756
|
}
|
|
8288
8757
|
|
|
8289
8758
|
// src/commands/render.ts
|
|
8290
|
-
import { existsSync as
|
|
8291
|
-
import { dirname as
|
|
8759
|
+
import { existsSync as existsSync31, mkdirSync as mkdirSync14, readFileSync as readFileSync33 } from "fs";
|
|
8760
|
+
import { dirname as dirname15, isAbsolute as isAbsolute7, resolve as resolve21 } from "path";
|
|
8292
8761
|
var FIXTURES = {
|
|
8293
8762
|
"stat-card": {
|
|
8294
8763
|
default: {
|
|
@@ -8440,28 +8909,28 @@ function pascalCase(kebab) {
|
|
|
8440
8909
|
}
|
|
8441
8910
|
function firstExisting(candidates) {
|
|
8442
8911
|
for (const c of candidates) {
|
|
8443
|
-
if (
|
|
8912
|
+
if (existsSync31(c)) return c;
|
|
8444
8913
|
}
|
|
8445
8914
|
return null;
|
|
8446
8915
|
}
|
|
8447
8916
|
function resolveComponentFile(cwd, name) {
|
|
8448
8917
|
return firstExisting([
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8918
|
+
resolve21(cwd, "components", "ui", name, `${name}.tsx`),
|
|
8919
|
+
resolve21(cwd, "components", "devtools", name, `${name}.tsx`),
|
|
8920
|
+
resolve21(cwd, "components", "ui", name, "index.tsx")
|
|
8452
8921
|
]);
|
|
8453
8922
|
}
|
|
8454
8923
|
function resolveThemeCssFile(cwd, slug2) {
|
|
8455
8924
|
return firstExisting([
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8925
|
+
resolve21(cwd, "packages", "docs", "app", `${slug2}-theme.css`),
|
|
8926
|
+
resolve21(cwd, "app", `${slug2}-theme.css`),
|
|
8927
|
+
resolve21(cwd, `${slug2}-theme.css`)
|
|
8459
8928
|
]);
|
|
8460
8929
|
}
|
|
8461
8930
|
function resolveTokensCssFile(cwd) {
|
|
8462
8931
|
return firstExisting([
|
|
8463
|
-
|
|
8464
|
-
|
|
8932
|
+
resolve21(cwd, "packages", "tokens", "dist", "tokens.css"),
|
|
8933
|
+
resolve21(cwd, "node_modules", "@loworbitstudio", "visor-core", "dist", "tokens.css")
|
|
8465
8934
|
]);
|
|
8466
8935
|
}
|
|
8467
8936
|
async function loadOptional(moduleName) {
|
|
@@ -8640,8 +9109,8 @@ async function renderCommand(component, cwd, options) {
|
|
|
8640
9109
|
}
|
|
8641
9110
|
const themeClass = `${options.theme}-theme`;
|
|
8642
9111
|
const html = buildHtml({
|
|
8643
|
-
tokensCss:
|
|
8644
|
-
themeCss:
|
|
9112
|
+
tokensCss: readFileSync33(tokensCssFile, "utf-8"),
|
|
9113
|
+
themeCss: readFileSync33(themeCssFile, "utf-8"),
|
|
8645
9114
|
componentCss,
|
|
8646
9115
|
bundleJs,
|
|
8647
9116
|
themeClass,
|
|
@@ -8650,7 +9119,7 @@ async function renderCommand(component, cwd, options) {
|
|
|
8650
9119
|
const width = Number(options.width ?? 720);
|
|
8651
9120
|
const height = Number(options.height ?? 640);
|
|
8652
9121
|
const outPath = resolveOutPath(cwd, options.out, component, options.theme, mode, state);
|
|
8653
|
-
mkdirSync14(
|
|
9122
|
+
mkdirSync14(dirname15(outPath), { recursive: true });
|
|
8654
9123
|
const browser = await playwright.chromium.launch();
|
|
8655
9124
|
let probe;
|
|
8656
9125
|
try {
|
|
@@ -8706,7 +9175,7 @@ async function renderCommand(component, cwd, options) {
|
|
|
8706
9175
|
} finally {
|
|
8707
9176
|
await browser.close();
|
|
8708
9177
|
}
|
|
8709
|
-
const fileSize =
|
|
9178
|
+
const fileSize = existsSync31(outPath) ? statBytes(outPath) : 0;
|
|
8710
9179
|
if (json) {
|
|
8711
9180
|
console.log(
|
|
8712
9181
|
JSON.stringify(
|
|
@@ -8742,14 +9211,14 @@ async function renderCommand(component, cwd, options) {
|
|
|
8742
9211
|
}
|
|
8743
9212
|
function resolveOutPath(cwd, out, component, theme2, mode, state) {
|
|
8744
9213
|
if (out) {
|
|
8745
|
-
return
|
|
9214
|
+
return isAbsolute7(out) ? out : resolve21(cwd, out);
|
|
8746
9215
|
}
|
|
8747
9216
|
const suffix = state === "default" ? "" : `__${state}`;
|
|
8748
|
-
return
|
|
9217
|
+
return resolve21(cwd, ".visor", "renders", `${component}__${theme2}__${mode}${suffix}.png`);
|
|
8749
9218
|
}
|
|
8750
9219
|
function statBytes(path2) {
|
|
8751
9220
|
try {
|
|
8752
|
-
return
|
|
9221
|
+
return readFileSync33(path2).length;
|
|
8753
9222
|
} catch {
|
|
8754
9223
|
return 0;
|
|
8755
9224
|
}
|
|
@@ -8762,9 +9231,9 @@ function formatBytes2(bytes) {
|
|
|
8762
9231
|
}
|
|
8763
9232
|
|
|
8764
9233
|
// src/index.ts
|
|
8765
|
-
var __dirname2 =
|
|
9234
|
+
var __dirname2 = dirname16(fileURLToPath4(import.meta.url));
|
|
8766
9235
|
var pkg = JSON.parse(
|
|
8767
|
-
|
|
9236
|
+
readFileSync34(join34(__dirname2, "..", "package.json"), "utf-8")
|
|
8768
9237
|
);
|
|
8769
9238
|
var program = new Command2();
|
|
8770
9239
|
program.name("visor").description("CLI for the Visor design system").version(pkg.version);
|
|
@@ -8959,7 +9428,7 @@ sandbox.command("approve").description(
|
|
|
8959
9428
|
);
|
|
8960
9429
|
program.command("spawn").description(
|
|
8961
9430
|
"Fork a Playbook blessed reference build with atomic theme re-skinning \u2014 one command replaces cp -R + npm install + theme apply"
|
|
8962
|
-
).option("--from <identifier>", "blessed build to spawn: blessed:{shape}:{pattern}").option("--theme <id>", "theme id (or path to a .visor.yaml) to re-skin the fork with").option("--theme-file <path>", "explicit path to a theme.visor.yaml \u2014 bypasses --theme name resolution").option("--output <path>", "destination directory for the forked project").option("--blessed-dir <path>", "override the blessed-build root (default: VISOR_BLESSED_DIR
|
|
9431
|
+
).option("--from <identifier>", "blessed build to spawn: blessed:{shape}:{pattern}").option("--theme <id>", "theme id (or path to a .visor.yaml) to re-skin the fork with").option("--theme-file <path>", "explicit path to a theme.visor.yaml \u2014 bypasses --theme name resolution").option("--output <path>", "destination directory for the forked project").option("--blessed-dir <path>", "override the blessed-build root (default: VISOR_BLESSED_DIR, else the nearest design-prototypes/ directory found by walking up from the cwd)").option("--install", "run npm install in the forked project (default: skip)").option("--validate", "validate the applied theme after forking (default: skip)").option("--list-blessed", "list all discoverable blessed builds and exit").option("--json", "output structured JSON (for AI agents)").action((options) => {
|
|
8963
9432
|
spawnCommand(process.cwd(), options);
|
|
8964
9433
|
});
|
|
8965
9434
|
program.command("render").description(
|