@loworbitstudio/visor 1.22.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 +848 -425
- package/dist/visor-manifest.json +1 -1
- package/package.json +1 -1
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."
|
|
758
|
+
));
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return found;
|
|
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.`
|
|
516
823
|
));
|
|
517
824
|
}
|
|
518
825
|
}
|
|
519
826
|
return found;
|
|
520
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,7 +2109,7 @@ 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"
|
|
@@ -1857,8 +2280,8 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
|
|
|
1857
2280
|
runCreateNextApp(cwd, json);
|
|
1858
2281
|
writeVisorConfig(cwd, json, filesCreated, filesSkipped);
|
|
1859
2282
|
runInstallVisorDeps(cwd, json);
|
|
1860
|
-
const yamlPath =
|
|
1861
|
-
if (
|
|
2283
|
+
const yamlPath = join9(cwd, ".visor.yaml");
|
|
2284
|
+
if (existsSync7(yamlPath)) {
|
|
1862
2285
|
filesSkipped.push(".visor.yaml");
|
|
1863
2286
|
if (!json) {
|
|
1864
2287
|
logger.warn(".visor.yaml already exists. Skipping.");
|
|
@@ -1876,10 +2299,10 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
|
|
|
1876
2299
|
tokens: data.tokens,
|
|
1877
2300
|
config: data.config
|
|
1878
2301
|
});
|
|
1879
|
-
const globalsPath =
|
|
1880
|
-
const globalsDir =
|
|
2302
|
+
const globalsPath = join9(cwd, "app", "globals.css");
|
|
2303
|
+
const globalsDir = dirname6(globalsPath);
|
|
1881
2304
|
mkdirSync2(globalsDir, { recursive: true });
|
|
1882
|
-
if (
|
|
2305
|
+
if (existsSync7(globalsPath)) {
|
|
1883
2306
|
writeFileSync3(globalsPath, css, "utf-8");
|
|
1884
2307
|
filesCreated.push("app/globals.css");
|
|
1885
2308
|
} else {
|
|
@@ -1889,16 +2312,16 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
|
|
|
1889
2312
|
if (!json) {
|
|
1890
2313
|
logger.success("Created app/globals.css with theme tokens");
|
|
1891
2314
|
}
|
|
1892
|
-
const layoutPath =
|
|
2315
|
+
const layoutPath = join9(cwd, "app", "layout.tsx");
|
|
1893
2316
|
const colorScheme = extractColorScheme(NEXTJS_STARTER_YAML);
|
|
1894
2317
|
writeFileSync3(layoutPath, generateNextjsLayout(colorScheme), "utf-8");
|
|
1895
2318
|
filesCreated.push("app/layout.tsx");
|
|
1896
2319
|
if (!json) {
|
|
1897
2320
|
logger.success("Wired app/layout.tsx with FOWT prevention and theme tokens");
|
|
1898
2321
|
}
|
|
1899
|
-
const stampDir =
|
|
1900
|
-
const stampPath =
|
|
1901
|
-
if (
|
|
2322
|
+
const stampDir = join9(cwd, ".lo");
|
|
2323
|
+
const stampPath = join9(stampDir, "borealis.json");
|
|
2324
|
+
if (existsSync7(stampPath)) {
|
|
1902
2325
|
filesSkipped.push(".lo/borealis.json");
|
|
1903
2326
|
if (!json) {
|
|
1904
2327
|
logger.warn(".lo/borealis.json already exists. Skipping.");
|
|
@@ -1962,12 +2385,12 @@ function assertSpawnSuccess(result, label) {
|
|
|
1962
2385
|
}
|
|
1963
2386
|
function readVisorCliVersion() {
|
|
1964
2387
|
try {
|
|
1965
|
-
const here =
|
|
2388
|
+
const here = dirname6(fileURLToPath2(import.meta.url));
|
|
1966
2389
|
for (let i = 0; i < 5; i++) {
|
|
1967
2390
|
const segments = new Array(i).fill("..");
|
|
1968
|
-
const candidate =
|
|
2391
|
+
const candidate = join9(here, ...segments, "package.json");
|
|
1969
2392
|
try {
|
|
1970
|
-
const pkg2 = JSON.parse(
|
|
2393
|
+
const pkg2 = JSON.parse(readFileSync10(candidate, "utf-8"));
|
|
1971
2394
|
if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) {
|
|
1972
2395
|
return pkg2.version;
|
|
1973
2396
|
}
|
|
@@ -1982,52 +2405,52 @@ function readVisorCliVersion() {
|
|
|
1982
2405
|
// src/utils/fs.ts
|
|
1983
2406
|
import {
|
|
1984
2407
|
writeFileSync as writeFileSync4,
|
|
1985
|
-
readFileSync as
|
|
1986
|
-
existsSync as
|
|
2408
|
+
readFileSync as readFileSync11,
|
|
2409
|
+
existsSync as existsSync8,
|
|
1987
2410
|
mkdirSync as mkdirSync3
|
|
1988
2411
|
} from "fs";
|
|
1989
|
-
import { dirname as
|
|
2412
|
+
import { dirname as dirname7, join as join10 } from "path";
|
|
1990
2413
|
function resolveOutputPath(registryPath, type, config, cwd) {
|
|
1991
2414
|
let relativePath;
|
|
1992
2415
|
if (type === "registry:block") {
|
|
1993
2416
|
relativePath = registryPath.replace(/^blocks\//, "");
|
|
1994
|
-
return
|
|
2417
|
+
return join10(cwd, config.paths.blocks, relativePath);
|
|
1995
2418
|
}
|
|
1996
2419
|
if (type === "registry:ui") {
|
|
1997
2420
|
if (registryPath.startsWith("components/deck/")) {
|
|
1998
2421
|
relativePath = registryPath.replace(/^components\/deck\//, "");
|
|
1999
|
-
return
|
|
2422
|
+
return join10(cwd, config.paths.deckComponents, relativePath);
|
|
2000
2423
|
}
|
|
2001
2424
|
if (registryPath.startsWith("components/flutter/")) {
|
|
2002
2425
|
relativePath = registryPath.replace(/^components\/flutter\//, "");
|
|
2003
|
-
return
|
|
2426
|
+
return join10(cwd, config.paths.flutterComponents, relativePath);
|
|
2004
2427
|
}
|
|
2005
2428
|
relativePath = registryPath.replace(/^components\/ui\//, "");
|
|
2006
|
-
return
|
|
2429
|
+
return join10(cwd, config.paths.components, relativePath);
|
|
2007
2430
|
}
|
|
2008
2431
|
if (type === "registry:hook") {
|
|
2009
2432
|
relativePath = registryPath.replace(/^hooks\//, "");
|
|
2010
|
-
return
|
|
2433
|
+
return join10(cwd, config.paths.hooks, relativePath);
|
|
2011
2434
|
}
|
|
2012
2435
|
if (type === "registry:lib") {
|
|
2013
2436
|
relativePath = registryPath.replace(/^lib\//, "");
|
|
2014
|
-
return
|
|
2437
|
+
return join10(cwd, config.paths.lib, relativePath);
|
|
2015
2438
|
}
|
|
2016
|
-
return
|
|
2439
|
+
return join10(cwd, registryPath);
|
|
2017
2440
|
}
|
|
2018
2441
|
function writeFile(filePath, content) {
|
|
2019
|
-
const dir =
|
|
2020
|
-
if (!
|
|
2442
|
+
const dir = dirname7(filePath);
|
|
2443
|
+
if (!existsSync8(dir)) {
|
|
2021
2444
|
mkdirSync3(dir, { recursive: true });
|
|
2022
2445
|
}
|
|
2023
2446
|
writeFileSync4(filePath, content, "utf-8");
|
|
2024
2447
|
}
|
|
2025
2448
|
function readFile(filePath) {
|
|
2026
|
-
if (!
|
|
2027
|
-
return
|
|
2449
|
+
if (!existsSync8(filePath)) return null;
|
|
2450
|
+
return readFileSync11(filePath, "utf-8");
|
|
2028
2451
|
}
|
|
2029
2452
|
function fileExists(filePath) {
|
|
2030
|
-
return
|
|
2453
|
+
return existsSync8(filePath);
|
|
2031
2454
|
}
|
|
2032
2455
|
|
|
2033
2456
|
// src/commands/list.ts
|
|
@@ -2168,8 +2591,8 @@ function listCommand(cwd, options = {}) {
|
|
|
2168
2591
|
}
|
|
2169
2592
|
|
|
2170
2593
|
// src/utils/pubspec.ts
|
|
2171
|
-
import { existsSync as
|
|
2172
|
-
import { join as
|
|
2594
|
+
import { existsSync as existsSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
2595
|
+
import { join as join11 } from "path";
|
|
2173
2596
|
import { parseDocument, YAMLMap } from "yaml";
|
|
2174
2597
|
function mergePubspec(pubspecText, deps) {
|
|
2175
2598
|
const doc = parseDocument(pubspecText);
|
|
@@ -2191,14 +2614,14 @@ function mergePubspec(pubspecText, deps) {
|
|
|
2191
2614
|
return { text: doc.toString(), added, skipped };
|
|
2192
2615
|
}
|
|
2193
2616
|
function pubspecPath(cwd) {
|
|
2194
|
-
return
|
|
2617
|
+
return join11(cwd, "pubspec.yaml");
|
|
2195
2618
|
}
|
|
2196
2619
|
function pubspecExists(cwd) {
|
|
2197
|
-
return
|
|
2620
|
+
return existsSync9(pubspecPath(cwd));
|
|
2198
2621
|
}
|
|
2199
2622
|
function isPubPackageInstalled(packageName, cwd) {
|
|
2200
2623
|
if (!pubspecExists(cwd)) return false;
|
|
2201
|
-
const text =
|
|
2624
|
+
const text = readFileSync12(pubspecPath(cwd), "utf-8");
|
|
2202
2625
|
const doc = parseDocument(text);
|
|
2203
2626
|
const depsNode = doc.get("dependencies");
|
|
2204
2627
|
if (!(depsNode instanceof YAMLMap)) return false;
|
|
@@ -2209,12 +2632,12 @@ function getUninstalledPubDeps(deps, cwd) {
|
|
|
2209
2632
|
}
|
|
2210
2633
|
function addPubDependencies(deps, cwd) {
|
|
2211
2634
|
const path2 = pubspecPath(cwd);
|
|
2212
|
-
if (!
|
|
2635
|
+
if (!existsSync9(path2)) {
|
|
2213
2636
|
throw new Error(
|
|
2214
2637
|
`No pubspec.yaml found at ${path2}. Run this command from a Flutter project root.`
|
|
2215
2638
|
);
|
|
2216
2639
|
}
|
|
2217
|
-
const text =
|
|
2640
|
+
const text = readFileSync12(path2, "utf-8");
|
|
2218
2641
|
const result = mergePubspec(text, deps);
|
|
2219
2642
|
if (result.added.length > 0) {
|
|
2220
2643
|
writeFileSync5(path2, result.text, "utf-8");
|
|
@@ -2224,12 +2647,12 @@ function addPubDependencies(deps, cwd) {
|
|
|
2224
2647
|
|
|
2225
2648
|
// src/utils/flutter.ts
|
|
2226
2649
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
2227
|
-
import { existsSync as
|
|
2650
|
+
import { existsSync as existsSync10, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
|
|
2228
2651
|
import { homedir as homedir2 } from "os";
|
|
2229
|
-
import { join as
|
|
2652
|
+
import { join as join12 } from "path";
|
|
2230
2653
|
function isExecutable(path2) {
|
|
2231
2654
|
try {
|
|
2232
|
-
const s =
|
|
2655
|
+
const s = statSync5(path2);
|
|
2233
2656
|
return s.isFile();
|
|
2234
2657
|
} catch {
|
|
2235
2658
|
return false;
|
|
@@ -2241,20 +2664,20 @@ function fromPath(env) {
|
|
|
2241
2664
|
const bin = process.platform === "win32" ? "flutter.bat" : "flutter";
|
|
2242
2665
|
for (const dir of pathVar.split(sep2)) {
|
|
2243
2666
|
if (!dir) continue;
|
|
2244
|
-
const candidate =
|
|
2667
|
+
const candidate = join12(dir, bin);
|
|
2245
2668
|
if (isExecutable(candidate)) return candidate;
|
|
2246
2669
|
}
|
|
2247
2670
|
return null;
|
|
2248
2671
|
}
|
|
2249
2672
|
function fromFvm(home) {
|
|
2250
|
-
const fvmDefault =
|
|
2673
|
+
const fvmDefault = join12(home, "fvm", "default", "bin", "flutter");
|
|
2251
2674
|
if (isExecutable(fvmDefault)) return fvmDefault;
|
|
2252
|
-
const versionsDir =
|
|
2253
|
-
if (!
|
|
2675
|
+
const versionsDir = join12(home, "fvm", "versions");
|
|
2676
|
+
if (!existsSync10(versionsDir)) return null;
|
|
2254
2677
|
let best = null;
|
|
2255
2678
|
try {
|
|
2256
2679
|
for (const name of readdirSync3(versionsDir)) {
|
|
2257
|
-
const candidate =
|
|
2680
|
+
const candidate = join12(versionsDir, name, "bin", "flutter");
|
|
2258
2681
|
if (!isExecutable(candidate)) continue;
|
|
2259
2682
|
if (!best || compareSemver(name, best.version) > 0) {
|
|
2260
2683
|
best = { version: name, path: candidate };
|
|
@@ -2280,7 +2703,7 @@ function findFlutterBin(options = {}) {
|
|
|
2280
2703
|
const home = options.home ?? homedir2();
|
|
2281
2704
|
const envRoot = env.FLUTTER_ROOT;
|
|
2282
2705
|
if (envRoot) {
|
|
2283
|
-
const bin =
|
|
2706
|
+
const bin = join12(envRoot, "bin", "flutter");
|
|
2284
2707
|
if (isExecutable(bin)) return bin;
|
|
2285
2708
|
}
|
|
2286
2709
|
const fromPathBin = fromPath(env);
|
|
@@ -2943,8 +3366,8 @@ function infoCommand(name, cwd, options = {}) {
|
|
|
2943
3366
|
}
|
|
2944
3367
|
|
|
2945
3368
|
// src/commands/theme-apply.ts
|
|
2946
|
-
import { readFileSync as
|
|
2947
|
-
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";
|
|
2948
3371
|
import { generateTheme, generateThemeData as generateThemeData3 } from "@loworbitstudio/visor-theme-engine";
|
|
2949
3372
|
import {
|
|
2950
3373
|
nextjsAdapter as nextjsAdapter2,
|
|
@@ -2955,8 +3378,8 @@ import {
|
|
|
2955
3378
|
} from "@loworbitstudio/visor-theme-engine/adapters";
|
|
2956
3379
|
|
|
2957
3380
|
// src/lib/blessed-manifest.ts
|
|
2958
|
-
import { existsSync as
|
|
2959
|
-
import { join as
|
|
3381
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
|
|
3382
|
+
import { join as join13 } from "path";
|
|
2960
3383
|
import { z } from "zod";
|
|
2961
3384
|
var BLESSED_MANIFEST_FILENAME = "blessed-manifest.json";
|
|
2962
3385
|
var themeApplyTargetSchema = z.discriminatedUnion("kind", [
|
|
@@ -2988,8 +3411,8 @@ var blessedManifestSchema = z.object({
|
|
|
2988
3411
|
theme_apply_target: themeApplyTargetSchema.optional()
|
|
2989
3412
|
}).strict();
|
|
2990
3413
|
function parseBlessedManifest(buildDir) {
|
|
2991
|
-
const manifestPath =
|
|
2992
|
-
if (!
|
|
3414
|
+
const manifestPath = join13(buildDir, BLESSED_MANIFEST_FILENAME);
|
|
3415
|
+
if (!existsSync11(manifestPath)) {
|
|
2993
3416
|
return {
|
|
2994
3417
|
ok: false,
|
|
2995
3418
|
error: `this directory is not a blessed build (missing ${BLESSED_MANIFEST_FILENAME}); see docs/blessed-builds.md`
|
|
@@ -2997,7 +3420,7 @@ function parseBlessedManifest(buildDir) {
|
|
|
2997
3420
|
}
|
|
2998
3421
|
let raw;
|
|
2999
3422
|
try {
|
|
3000
|
-
raw =
|
|
3423
|
+
raw = readFileSync13(manifestPath, "utf-8");
|
|
3001
3424
|
} catch {
|
|
3002
3425
|
return { ok: false, error: `could not read ${manifestPath}` };
|
|
3003
3426
|
}
|
|
@@ -3023,31 +3446,31 @@ function parseBlessedManifest(buildDir) {
|
|
|
3023
3446
|
}
|
|
3024
3447
|
|
|
3025
3448
|
// src/lib/theme-apply-targets/globals-css.ts
|
|
3026
|
-
import { existsSync as
|
|
3027
|
-
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";
|
|
3028
3451
|
function applyGlobalsCss(input) {
|
|
3029
3452
|
const outFile = input.path !== void 0 ? resolveInsideBuild(input.buildDir, input.path) : resolveDefaultGlobalsCss(input.buildDir);
|
|
3030
|
-
mkdirSync4(
|
|
3453
|
+
mkdirSync4(dirname8(outFile), { recursive: true });
|
|
3031
3454
|
writeFileSync6(outFile, input.adapterCss, "utf-8");
|
|
3032
3455
|
return outFile;
|
|
3033
3456
|
}
|
|
3034
3457
|
function resolveDefaultGlobalsCss(buildDir) {
|
|
3035
3458
|
const candidates = [
|
|
3036
|
-
|
|
3037
|
-
|
|
3459
|
+
join14(buildDir, "app", "globals.css"),
|
|
3460
|
+
join14(buildDir, "src", "app", "globals.css")
|
|
3038
3461
|
];
|
|
3039
|
-
return candidates.find((candidate) =>
|
|
3462
|
+
return candidates.find((candidate) => existsSync12(candidate)) ?? candidates[0];
|
|
3040
3463
|
}
|
|
3041
3464
|
function resolveInsideBuild(buildDir, relativeOrAbsolute) {
|
|
3042
|
-
return
|
|
3465
|
+
return isAbsolute2(relativeOrAbsolute) ? relativeOrAbsolute : resolve6(buildDir, relativeOrAbsolute);
|
|
3043
3466
|
}
|
|
3044
3467
|
|
|
3045
3468
|
// src/lib/theme-apply-targets/themes-css-dir.ts
|
|
3046
3469
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
3047
|
-
import { isAbsolute as
|
|
3470
|
+
import { isAbsolute as isAbsolute3, join as join15, resolve as resolve7 } from "path";
|
|
3048
3471
|
function applyThemesCssDir(input) {
|
|
3049
|
-
const dir =
|
|
3050
|
-
const outFile =
|
|
3472
|
+
const dir = isAbsolute3(input.path) ? input.path : resolve7(input.buildDir, input.path);
|
|
3473
|
+
const outFile = join15(dir, `${input.themeId}.css`);
|
|
3051
3474
|
mkdirSync5(dir, { recursive: true });
|
|
3052
3475
|
writeFileSync7(outFile, input.adapterCss, "utf-8");
|
|
3053
3476
|
return outFile;
|
|
@@ -3111,10 +3534,10 @@ function defaultOutputPath(adapter, themeName) {
|
|
|
3111
3534
|
}
|
|
3112
3535
|
}
|
|
3113
3536
|
function themeApplyCommand(file, cwd, options) {
|
|
3114
|
-
const filePath =
|
|
3537
|
+
const filePath = resolve8(cwd, file);
|
|
3115
3538
|
let yamlContent;
|
|
3116
3539
|
try {
|
|
3117
|
-
yamlContent =
|
|
3540
|
+
yamlContent = readFileSync14(filePath, "utf-8");
|
|
3118
3541
|
} catch {
|
|
3119
3542
|
if (options.json) {
|
|
3120
3543
|
console.log(
|
|
@@ -3208,7 +3631,7 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
3208
3631
|
else logger.error(message);
|
|
3209
3632
|
process.exit(1);
|
|
3210
3633
|
}
|
|
3211
|
-
const buildDir =
|
|
3634
|
+
const buildDir = resolve8(cwd, options.targetPath);
|
|
3212
3635
|
const manifestResult = parseBlessedManifest(buildDir);
|
|
3213
3636
|
if (!manifestResult.ok) {
|
|
3214
3637
|
if (options.json) {
|
|
@@ -3251,14 +3674,14 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
3251
3674
|
return;
|
|
3252
3675
|
}
|
|
3253
3676
|
const outputTarget = options.output ?? defaultOutputPath(options.adapter, themeName);
|
|
3254
|
-
const outputPath =
|
|
3677
|
+
const outputPath = resolve8(cwd, outputTarget);
|
|
3255
3678
|
if (fileMap) {
|
|
3256
3679
|
try {
|
|
3257
3680
|
mkdirSync6(outputPath, { recursive: true });
|
|
3258
3681
|
let totalBytes = 0;
|
|
3259
3682
|
for (const [relPath, content] of Object.entries(fileMap.files)) {
|
|
3260
|
-
const filePath2 =
|
|
3261
|
-
mkdirSync6(
|
|
3683
|
+
const filePath2 = join16(outputPath, relPath);
|
|
3684
|
+
mkdirSync6(dirname9(filePath2), { recursive: true });
|
|
3262
3685
|
writeFileSync8(filePath2, content, "utf-8");
|
|
3263
3686
|
totalBytes += content.length;
|
|
3264
3687
|
}
|
|
@@ -3296,7 +3719,7 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
3296
3719
|
if (css === null) {
|
|
3297
3720
|
process.exit(1);
|
|
3298
3721
|
}
|
|
3299
|
-
const outputDir =
|
|
3722
|
+
const outputDir = dirname9(outputPath);
|
|
3300
3723
|
try {
|
|
3301
3724
|
mkdirSync6(outputDir, { recursive: true });
|
|
3302
3725
|
writeFileSync8(outputPath, css, "utf-8");
|
|
@@ -3340,8 +3763,8 @@ function formatSize(bytes) {
|
|
|
3340
3763
|
}
|
|
3341
3764
|
|
|
3342
3765
|
// src/commands/theme-export.ts
|
|
3343
|
-
import { readFileSync as
|
|
3344
|
-
import { resolve as
|
|
3766
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
3767
|
+
import { resolve as resolve9 } from "path";
|
|
3345
3768
|
import {
|
|
3346
3769
|
parseConfig,
|
|
3347
3770
|
resolveConfig,
|
|
@@ -3349,10 +3772,10 @@ import {
|
|
|
3349
3772
|
exportTheme
|
|
3350
3773
|
} from "@loworbitstudio/visor-theme-engine";
|
|
3351
3774
|
function themeExportCommand(file, cwd, options) {
|
|
3352
|
-
const filePath =
|
|
3775
|
+
const filePath = resolve9(cwd, file ?? ".visor.yaml");
|
|
3353
3776
|
let yamlContent;
|
|
3354
3777
|
try {
|
|
3355
|
-
yamlContent =
|
|
3778
|
+
yamlContent = readFileSync15(filePath, "utf-8");
|
|
3356
3779
|
} catch {
|
|
3357
3780
|
if (options.json) {
|
|
3358
3781
|
console.log(
|
|
@@ -3404,16 +3827,16 @@ function themeExportCommand(file, cwd, options) {
|
|
|
3404
3827
|
}
|
|
3405
3828
|
|
|
3406
3829
|
// src/commands/theme-validate.ts
|
|
3407
|
-
import { readFileSync as
|
|
3408
|
-
import { resolve as
|
|
3830
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
3831
|
+
import { resolve as resolve10 } from "path";
|
|
3409
3832
|
import { parse as parseYaml2 } from "yaml";
|
|
3410
3833
|
import { validate } from "@loworbitstudio/visor-theme-engine";
|
|
3411
3834
|
import pc3 from "picocolors";
|
|
3412
3835
|
function themeValidateCommand(file, cwd, options) {
|
|
3413
|
-
const filePath =
|
|
3836
|
+
const filePath = resolve10(cwd, file);
|
|
3414
3837
|
let fileContent;
|
|
3415
3838
|
try {
|
|
3416
|
-
fileContent =
|
|
3839
|
+
fileContent = readFileSync16(filePath, "utf-8");
|
|
3417
3840
|
} catch {
|
|
3418
3841
|
if (options.json) {
|
|
3419
3842
|
console.log(
|
|
@@ -3502,8 +3925,8 @@ function printIssue(issue) {
|
|
|
3502
3925
|
|
|
3503
3926
|
// src/commands/theme-verify.ts
|
|
3504
3927
|
import { spawnSync as _spawnSync } from "child_process";
|
|
3505
|
-
import { existsSync as
|
|
3506
|
-
import { resolve as
|
|
3928
|
+
import { existsSync as existsSync13 } from "fs";
|
|
3929
|
+
import { resolve as resolve11 } from "path";
|
|
3507
3930
|
function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
3508
3931
|
const target = options.target ?? "flutter";
|
|
3509
3932
|
if (target !== "flutter") {
|
|
@@ -3525,8 +3948,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
|
3525
3948
|
}
|
|
3526
3949
|
process.exit(1);
|
|
3527
3950
|
}
|
|
3528
|
-
const dirPath =
|
|
3529
|
-
if (!
|
|
3951
|
+
const dirPath = resolve11(cwd, dir);
|
|
3952
|
+
if (!existsSync13(dirPath)) {
|
|
3530
3953
|
if (options.json) {
|
|
3531
3954
|
console.log(
|
|
3532
3955
|
JSON.stringify({
|
|
@@ -3613,8 +4036,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
|
3613
4036
|
}
|
|
3614
4037
|
|
|
3615
4038
|
// src/commands/theme-extract.ts
|
|
3616
|
-
import { readFileSync as
|
|
3617
|
-
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";
|
|
3618
4041
|
import { stringify as stringifyYaml } from "yaml";
|
|
3619
4042
|
import {
|
|
3620
4043
|
extractFromCSS,
|
|
@@ -3643,8 +4066,8 @@ var CSS_DIRS = [
|
|
|
3643
4066
|
"packages/design-tokens"
|
|
3644
4067
|
];
|
|
3645
4068
|
function themeExtractCommand(cwd, options) {
|
|
3646
|
-
const targetDir =
|
|
3647
|
-
if (!
|
|
4069
|
+
const targetDir = resolve12(cwd, options.from ?? ".");
|
|
4070
|
+
if (!existsSync14(targetDir)) {
|
|
3648
4071
|
if (options.json) {
|
|
3649
4072
|
console.log(JSON.stringify({ success: false, error: `Directory not found: ${targetDir}` }));
|
|
3650
4073
|
} else {
|
|
@@ -3698,16 +4121,16 @@ function collectCSSFiles(targetDir) {
|
|
|
3698
4121
|
const files = [];
|
|
3699
4122
|
const seen = /* @__PURE__ */ new Set();
|
|
3700
4123
|
for (const pattern2 of CSS_FILE_PATTERNS) {
|
|
3701
|
-
const rootPath =
|
|
4124
|
+
const rootPath = join17(targetDir, pattern2);
|
|
3702
4125
|
addFileIfExists(rootPath, files, seen);
|
|
3703
4126
|
for (const dir of CSS_DIRS) {
|
|
3704
|
-
const dirPath =
|
|
4127
|
+
const dirPath = join17(targetDir, dir, pattern2);
|
|
3705
4128
|
addFileIfExists(dirPath, files, seen);
|
|
3706
4129
|
}
|
|
3707
4130
|
}
|
|
3708
4131
|
for (const dir of CSS_DIRS) {
|
|
3709
|
-
const dirPath =
|
|
3710
|
-
if (
|
|
4132
|
+
const dirPath = join17(targetDir, dir);
|
|
4133
|
+
if (existsSync14(dirPath) && statSync6(dirPath).isDirectory()) {
|
|
3711
4134
|
scanDirForCSS(dirPath, files, seen, 2);
|
|
3712
4135
|
}
|
|
3713
4136
|
}
|
|
@@ -3715,11 +4138,11 @@ function collectCSSFiles(targetDir) {
|
|
|
3715
4138
|
return files;
|
|
3716
4139
|
}
|
|
3717
4140
|
function addFileIfExists(filePath, files, seen) {
|
|
3718
|
-
const resolved =
|
|
4141
|
+
const resolved = resolve12(filePath);
|
|
3719
4142
|
if (seen.has(resolved)) return;
|
|
3720
|
-
if (!
|
|
4143
|
+
if (!existsSync14(resolved)) return;
|
|
3721
4144
|
try {
|
|
3722
|
-
const content =
|
|
4145
|
+
const content = readFileSync17(resolved, "utf-8");
|
|
3723
4146
|
if (content.includes("--")) {
|
|
3724
4147
|
files.push({ path: resolved, content });
|
|
3725
4148
|
seen.add(resolved);
|
|
@@ -3728,7 +4151,7 @@ function addFileIfExists(filePath, files, seen) {
|
|
|
3728
4151
|
}
|
|
3729
4152
|
}
|
|
3730
4153
|
function scanDirForCSS(dir, files, seen, maxDepth) {
|
|
3731
|
-
if (!
|
|
4154
|
+
if (!existsSync14(dir)) return;
|
|
3732
4155
|
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
3733
4156
|
"node_modules",
|
|
3734
4157
|
".next",
|
|
@@ -3747,10 +4170,10 @@ function scanDirForCSS(dir, files, seen, maxDepth) {
|
|
|
3747
4170
|
if (entry.isDirectory()) {
|
|
3748
4171
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
3749
4172
|
if (maxDepth > 0) {
|
|
3750
|
-
scanDirForCSS(
|
|
4173
|
+
scanDirForCSS(join17(dir, entry.name), files, seen, maxDepth - 1);
|
|
3751
4174
|
}
|
|
3752
4175
|
} else if (entry.isFile() && extname3(entry.name) === ".css") {
|
|
3753
|
-
addFileIfExists(
|
|
4176
|
+
addFileIfExists(join17(dir, entry.name), files, seen);
|
|
3754
4177
|
}
|
|
3755
4178
|
}
|
|
3756
4179
|
} catch {
|
|
@@ -3832,10 +4255,10 @@ function extractVarName(varExpr) {
|
|
|
3832
4255
|
function parseNextFontFromLayouts(targetDir) {
|
|
3833
4256
|
const fontMap = /* @__PURE__ */ new Map();
|
|
3834
4257
|
for (const relPath of LAYOUT_FILE_PATHS) {
|
|
3835
|
-
const fullPath =
|
|
3836
|
-
if (!
|
|
4258
|
+
const fullPath = join17(targetDir, relPath);
|
|
4259
|
+
if (!existsSync14(fullPath)) continue;
|
|
3837
4260
|
try {
|
|
3838
|
-
const content =
|
|
4261
|
+
const content = readFileSync17(fullPath, "utf-8");
|
|
3839
4262
|
parseNextFontDeclarations(content, fontMap);
|
|
3840
4263
|
} catch {
|
|
3841
4264
|
}
|
|
@@ -3920,10 +4343,10 @@ var MONO_FONT_NAMES = /* @__PURE__ */ new Set([
|
|
|
3920
4343
|
"IBM Plex Mono"
|
|
3921
4344
|
]);
|
|
3922
4345
|
function extractFontHints(targetDir) {
|
|
3923
|
-
const pkgPath =
|
|
3924
|
-
if (!
|
|
4346
|
+
const pkgPath = join17(targetDir, "package.json");
|
|
4347
|
+
if (!existsSync14(pkgPath)) return void 0;
|
|
3925
4348
|
try {
|
|
3926
|
-
const pkg2 = JSON.parse(
|
|
4349
|
+
const pkg2 = JSON.parse(readFileSync17(pkgPath, "utf-8"));
|
|
3927
4350
|
const allDeps = { ...pkg2.dependencies, ...pkg2.devDependencies };
|
|
3928
4351
|
const fonts2 = [];
|
|
3929
4352
|
for (const [dep, _] of Object.entries(allDeps)) {
|
|
@@ -3959,10 +4382,10 @@ function extractFontHints(targetDir) {
|
|
|
3959
4382
|
}
|
|
3960
4383
|
}
|
|
3961
4384
|
function inferThemeName(targetDir) {
|
|
3962
|
-
const pkgPath =
|
|
3963
|
-
if (
|
|
4385
|
+
const pkgPath = join17(targetDir, "package.json");
|
|
4386
|
+
if (existsSync14(pkgPath)) {
|
|
3964
4387
|
try {
|
|
3965
|
-
const pkg2 = JSON.parse(
|
|
4388
|
+
const pkg2 = JSON.parse(readFileSync17(pkgPath, "utf-8"));
|
|
3966
4389
|
if (pkg2.name) {
|
|
3967
4390
|
const name = pkg2.name.replace(/^@[\w-]+\//, "");
|
|
3968
4391
|
return `${name}-theme`;
|
|
@@ -3999,7 +4422,7 @@ function outputJSON(result, validationResult) {
|
|
|
3999
4422
|
}
|
|
4000
4423
|
function outputYAML(result, outputPath, cwd, validationResult) {
|
|
4001
4424
|
const yamlStr = buildAnnotatedYAML(result);
|
|
4002
|
-
const outFile =
|
|
4425
|
+
const outFile = resolve12(cwd, outputPath ?? ".visor.yaml");
|
|
4003
4426
|
const high = result.tokens.filter((t) => t.confidence === "high").length;
|
|
4004
4427
|
const med = result.tokens.filter((t) => t.confidence === "medium").length;
|
|
4005
4428
|
const low = result.tokens.filter((t) => t.confidence === "low").length;
|
|
@@ -4085,14 +4508,14 @@ function buildAnnotatedYAML(result) {
|
|
|
4085
4508
|
}
|
|
4086
4509
|
|
|
4087
4510
|
// src/commands/theme-register.ts
|
|
4088
|
-
import { readFileSync as
|
|
4089
|
-
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";
|
|
4090
4513
|
import { generateThemeData as generateThemeData4 } from "@loworbitstudio/visor-theme-engine";
|
|
4091
4514
|
import { docsAdapter as docsAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
4092
4515
|
|
|
4093
4516
|
// src/utils/theme-helpers.ts
|
|
4094
|
-
import { existsSync as
|
|
4095
|
-
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";
|
|
4096
4519
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
4097
4520
|
function toSlug(name) {
|
|
4098
4521
|
return name.toLowerCase().replace(/\s+/g, "-");
|
|
@@ -4101,12 +4524,12 @@ function toLabel(name) {
|
|
|
4101
4524
|
return name.split(/[\s-]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
4102
4525
|
}
|
|
4103
4526
|
function findRepoRoot(startDir) {
|
|
4104
|
-
let current =
|
|
4527
|
+
let current = resolve13(startDir);
|
|
4105
4528
|
while (true) {
|
|
4106
|
-
if (
|
|
4529
|
+
if (existsSync15(join18(current, "packages", "docs"))) {
|
|
4107
4530
|
return current;
|
|
4108
4531
|
}
|
|
4109
|
-
const parent =
|
|
4532
|
+
const parent = dirname10(current);
|
|
4110
4533
|
if (parent === current) break;
|
|
4111
4534
|
current = parent;
|
|
4112
4535
|
}
|
|
@@ -4120,9 +4543,9 @@ function findMainRepoRoot(startDir) {
|
|
|
4120
4543
|
stdio: ["ignore", "pipe", "ignore"]
|
|
4121
4544
|
}).trim();
|
|
4122
4545
|
if (commonDir) {
|
|
4123
|
-
const absoluteCommonDir =
|
|
4124
|
-
const candidate =
|
|
4125
|
-
if (
|
|
4546
|
+
const absoluteCommonDir = resolve13(startDir, commonDir);
|
|
4547
|
+
const candidate = dirname10(absoluteCommonDir);
|
|
4548
|
+
if (existsSync15(join18(candidate, "packages", "docs"))) {
|
|
4126
4549
|
return candidate;
|
|
4127
4550
|
}
|
|
4128
4551
|
}
|
|
@@ -4141,10 +4564,10 @@ var BrokenSymlinkError = class extends Error {
|
|
|
4141
4564
|
code = "BROKEN_SYMLINK";
|
|
4142
4565
|
};
|
|
4143
4566
|
function assertNoBrokenSymlinks(dir) {
|
|
4144
|
-
if (!
|
|
4567
|
+
if (!existsSync15(dir)) return;
|
|
4145
4568
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
4146
4569
|
for (const entry of entries) {
|
|
4147
|
-
const entryPath =
|
|
4570
|
+
const entryPath = join18(dir, entry.name);
|
|
4148
4571
|
let lst;
|
|
4149
4572
|
try {
|
|
4150
4573
|
lst = lstatSync(entryPath);
|
|
@@ -4154,7 +4577,7 @@ function assertNoBrokenSymlinks(dir) {
|
|
|
4154
4577
|
if (lst.isSymbolicLink()) {
|
|
4155
4578
|
const target = readlinkSync(entryPath);
|
|
4156
4579
|
try {
|
|
4157
|
-
|
|
4580
|
+
statSync7(entryPath);
|
|
4158
4581
|
} catch {
|
|
4159
4582
|
throw new BrokenSymlinkError(entryPath, target);
|
|
4160
4583
|
}
|
|
@@ -4162,39 +4585,39 @@ function assertNoBrokenSymlinks(dir) {
|
|
|
4162
4585
|
}
|
|
4163
4586
|
}
|
|
4164
4587
|
function scanParentForPrivateThemes(parentDir) {
|
|
4165
|
-
if (!
|
|
4588
|
+
if (!existsSync15(parentDir)) return [];
|
|
4166
4589
|
const entries = readdirSync5(parentDir, { withFileTypes: true });
|
|
4167
4590
|
const matches = [];
|
|
4168
4591
|
for (const entry of entries) {
|
|
4169
4592
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
4170
|
-
const candidate =
|
|
4171
|
-
if (
|
|
4593
|
+
const candidate = join18(parentDir, entry.name, "visor-themes-private", "themes");
|
|
4594
|
+
if (existsSync15(candidate)) {
|
|
4172
4595
|
matches.push(candidate);
|
|
4173
4596
|
}
|
|
4174
4597
|
}
|
|
4175
4598
|
return matches.sort();
|
|
4176
4599
|
}
|
|
4177
4600
|
function scanNestedThemeDir(dir) {
|
|
4178
|
-
if (!
|
|
4601
|
+
if (!existsSync15(dir)) return [];
|
|
4179
4602
|
assertNoBrokenSymlinks(dir);
|
|
4180
4603
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
4181
4604
|
const out = [];
|
|
4182
4605
|
for (const entry of entries) {
|
|
4183
4606
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
4184
|
-
const themeFile =
|
|
4185
|
-
if (
|
|
4607
|
+
const themeFile = join18(dir, entry.name, "theme.visor.yaml");
|
|
4608
|
+
if (existsSync15(themeFile)) {
|
|
4186
4609
|
out.push({ filePath: themeFile, slug: entry.name });
|
|
4187
4610
|
}
|
|
4188
4611
|
}
|
|
4189
4612
|
return out;
|
|
4190
4613
|
}
|
|
4191
4614
|
function detectVisorWorkspace(cwd) {
|
|
4192
|
-
let current =
|
|
4615
|
+
let current = resolve13(cwd);
|
|
4193
4616
|
while (true) {
|
|
4194
|
-
const pkgPath =
|
|
4195
|
-
if (
|
|
4617
|
+
const pkgPath = join18(current, "package.json");
|
|
4618
|
+
if (existsSync15(pkgPath)) {
|
|
4196
4619
|
try {
|
|
4197
|
-
const pkg2 = JSON.parse(
|
|
4620
|
+
const pkg2 = JSON.parse(readFileSync18(pkgPath, "utf-8"));
|
|
4198
4621
|
const workspaces = pkg2.workspaces ?? [];
|
|
4199
4622
|
const hasCli = workspaces.some((w) => w.includes("packages/cli"));
|
|
4200
4623
|
const hasEngine = workspaces.some((w) => w.includes("packages/theme-engine"));
|
|
@@ -4204,7 +4627,7 @@ function detectVisorWorkspace(cwd) {
|
|
|
4204
4627
|
} catch {
|
|
4205
4628
|
}
|
|
4206
4629
|
}
|
|
4207
|
-
const parent =
|
|
4630
|
+
const parent = dirname10(current);
|
|
4208
4631
|
if (parent === current) break;
|
|
4209
4632
|
current = parent;
|
|
4210
4633
|
}
|
|
@@ -4212,18 +4635,18 @@ function detectVisorWorkspace(cwd) {
|
|
|
4212
4635
|
}
|
|
4213
4636
|
function isLocalVisorBinary(workspaceRoot, scriptPath) {
|
|
4214
4637
|
if (!scriptPath) return false;
|
|
4215
|
-
const expectedPrefix =
|
|
4638
|
+
const expectedPrefix = join18(workspaceRoot, "packages", "cli", "dist");
|
|
4216
4639
|
let resolvedScript;
|
|
4217
4640
|
let resolvedPrefix;
|
|
4218
4641
|
try {
|
|
4219
4642
|
resolvedScript = realpathSync(scriptPath);
|
|
4220
4643
|
} catch {
|
|
4221
|
-
resolvedScript =
|
|
4644
|
+
resolvedScript = resolve13(scriptPath);
|
|
4222
4645
|
}
|
|
4223
4646
|
try {
|
|
4224
4647
|
resolvedPrefix = realpathSync(expectedPrefix);
|
|
4225
4648
|
} catch {
|
|
4226
|
-
resolvedPrefix =
|
|
4649
|
+
resolvedPrefix = resolve13(expectedPrefix);
|
|
4227
4650
|
}
|
|
4228
4651
|
return resolvedScript.startsWith(resolvedPrefix + "/") || resolvedScript === resolvedPrefix;
|
|
4229
4652
|
}
|
|
@@ -4313,10 +4736,10 @@ ${indent}${newEntry},
|
|
|
4313
4736
|
return { updated, changed: true };
|
|
4314
4737
|
}
|
|
4315
4738
|
function themeRegisterCommand(file, cwd, options) {
|
|
4316
|
-
const filePath =
|
|
4739
|
+
const filePath = resolve14(cwd, file);
|
|
4317
4740
|
let yamlContent;
|
|
4318
4741
|
try {
|
|
4319
|
-
yamlContent =
|
|
4742
|
+
yamlContent = readFileSync19(filePath, "utf-8");
|
|
4320
4743
|
} catch {
|
|
4321
4744
|
if (options.json) {
|
|
4322
4745
|
console.log(JSON.stringify({ success: false, error: `Could not read file: ${filePath}` }));
|
|
@@ -4359,11 +4782,11 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
4359
4782
|
process.exit(1);
|
|
4360
4783
|
return;
|
|
4361
4784
|
}
|
|
4362
|
-
const docsAppDir =
|
|
4363
|
-
const cssFilePath =
|
|
4364
|
-
const globalsPath =
|
|
4365
|
-
const themeConfigPath =
|
|
4366
|
-
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)) {
|
|
4367
4790
|
const msg = `Docs app directory not found: ${docsAppDir}`;
|
|
4368
4791
|
if (options.json) {
|
|
4369
4792
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
@@ -4376,8 +4799,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
4376
4799
|
let globalsContent = "";
|
|
4377
4800
|
let themeConfigContent = "";
|
|
4378
4801
|
try {
|
|
4379
|
-
globalsContent =
|
|
4380
|
-
themeConfigContent =
|
|
4802
|
+
globalsContent = readFileSync19(globalsPath, "utf-8");
|
|
4803
|
+
themeConfigContent = readFileSync19(themeConfigPath, "utf-8");
|
|
4381
4804
|
} catch (err) {
|
|
4382
4805
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
4383
4806
|
if (options.json) {
|
|
@@ -4388,8 +4811,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
4388
4811
|
process.exit(1);
|
|
4389
4812
|
return;
|
|
4390
4813
|
}
|
|
4391
|
-
const cssExists =
|
|
4392
|
-
const cssChanged = !cssExists ||
|
|
4814
|
+
const cssExists = existsSync16(cssFilePath);
|
|
4815
|
+
const cssChanged = !cssExists || readFileSync19(cssFilePath, "utf-8") !== css;
|
|
4393
4816
|
const { updated: newGlobals, changed: globalsChanged } = insertGlobalsImport(globalsContent, slug2);
|
|
4394
4817
|
const { updated: newThemeConfig, changed: themeConfigChanged, error: configError } = insertThemeConfig(
|
|
4395
4818
|
themeConfigContent,
|
|
@@ -4473,8 +4896,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
4473
4896
|
}
|
|
4474
4897
|
|
|
4475
4898
|
// src/commands/theme-unregister.ts
|
|
4476
|
-
import { readFileSync as
|
|
4477
|
-
import { join as
|
|
4899
|
+
import { readFileSync as readFileSync20, writeFileSync as writeFileSync11, existsSync as existsSync17, unlinkSync } from "fs";
|
|
4900
|
+
import { join as join20 } from "path";
|
|
4478
4901
|
function removeGlobalsImport(content, slug2) {
|
|
4479
4902
|
const importLine = `@import './${slug2}-theme.css';`;
|
|
4480
4903
|
if (!content.includes(importLine)) {
|
|
@@ -4506,11 +4929,11 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
4506
4929
|
process.exit(1);
|
|
4507
4930
|
return;
|
|
4508
4931
|
}
|
|
4509
|
-
const docsAppDir =
|
|
4510
|
-
const cssFilePath =
|
|
4511
|
-
const globalsPath =
|
|
4512
|
-
const themeConfigPath =
|
|
4513
|
-
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)) {
|
|
4514
4937
|
const msg = `Docs app directory not found: ${docsAppDir}`;
|
|
4515
4938
|
if (options.json) {
|
|
4516
4939
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
@@ -4523,8 +4946,8 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
4523
4946
|
let globalsContent = "";
|
|
4524
4947
|
let themeConfigContent = "";
|
|
4525
4948
|
try {
|
|
4526
|
-
globalsContent =
|
|
4527
|
-
themeConfigContent =
|
|
4949
|
+
globalsContent = readFileSync20(globalsPath, "utf-8");
|
|
4950
|
+
themeConfigContent = readFileSync20(themeConfigPath, "utf-8");
|
|
4528
4951
|
} catch (err) {
|
|
4529
4952
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
4530
4953
|
if (options.json) {
|
|
@@ -4535,7 +4958,7 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
4535
4958
|
process.exit(1);
|
|
4536
4959
|
return;
|
|
4537
4960
|
}
|
|
4538
|
-
const cssExists =
|
|
4961
|
+
const cssExists = existsSync17(cssFilePath);
|
|
4539
4962
|
const { updated: newGlobals, changed: globalsChanged } = removeGlobalsImport(globalsContent, slug2);
|
|
4540
4963
|
const { updated: newThemeConfig, changed: themeConfigChanged } = removeThemeConfigEntry(themeConfigContent, slug2);
|
|
4541
4964
|
if (!cssExists && !globalsChanged && !themeConfigChanged) {
|
|
@@ -4576,15 +4999,15 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
4576
4999
|
|
|
4577
5000
|
// src/commands/theme-sync.ts
|
|
4578
5001
|
import {
|
|
4579
|
-
readFileSync as
|
|
5002
|
+
readFileSync as readFileSync21,
|
|
4580
5003
|
writeFileSync as writeFileSync12,
|
|
4581
5004
|
mkdirSync as mkdirSync8,
|
|
4582
|
-
existsSync as
|
|
5005
|
+
existsSync as existsSync18,
|
|
4583
5006
|
readdirSync as readdirSync6,
|
|
4584
5007
|
unlinkSync as unlinkSync2,
|
|
4585
5008
|
copyFileSync
|
|
4586
5009
|
} from "fs";
|
|
4587
|
-
import { join as
|
|
5010
|
+
import { join as join21, basename as basename4, resolve as resolve15, sep, relative as relative2 } from "path";
|
|
4588
5011
|
import { parse as parseYaml3 } from "yaml";
|
|
4589
5012
|
import { generateThemeData as generateThemeData5, validateFontCoverage, formatFontCoverageError } from "@loworbitstudio/visor-theme-engine";
|
|
4590
5013
|
import { docsAdapter as docsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
@@ -4598,9 +5021,9 @@ var CUSTOM_OVERLAY_CSS_PATH = "packages/docs/app/custom-themes.generated.css";
|
|
|
4598
5021
|
var CUSTOM_OVERLAY_TS_PATH = "packages/docs/lib/theme-config.custom.generated.ts";
|
|
4599
5022
|
var CUSTOM_OVERLAY_IMPORT_LINE = "@import './custom-themes.generated.css';";
|
|
4600
5023
|
function scanThemeDir(dir) {
|
|
4601
|
-
if (!
|
|
5024
|
+
if (!existsSync18(dir)) return [];
|
|
4602
5025
|
assertNoBrokenSymlinks(dir);
|
|
4603
|
-
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));
|
|
4604
5027
|
}
|
|
4605
5028
|
function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
4606
5029
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -4624,20 +5047,20 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
|
4624
5047
|
};
|
|
4625
5048
|
const envPath = process.env[PRIVATE_THEMES_ENV_VAR];
|
|
4626
5049
|
if (envPath && envPath.trim() !== "") {
|
|
4627
|
-
const resolved =
|
|
4628
|
-
if (!
|
|
5050
|
+
const resolved = resolve15(envPath);
|
|
5051
|
+
if (!existsSync18(resolved)) {
|
|
4629
5052
|
throw new Error(
|
|
4630
5053
|
`${PRIVATE_THEMES_ENV_VAR} is set to "${envPath}" but the path does not exist. Expected a directory containing {slug}/theme.visor.yaml entries.`
|
|
4631
5054
|
);
|
|
4632
5055
|
}
|
|
4633
5056
|
addNested(resolved, "env");
|
|
4634
5057
|
}
|
|
4635
|
-
const siblingPath =
|
|
4636
|
-
const siblingExists =
|
|
5058
|
+
const siblingPath = join21(mainRepoRoot, "..", "visor-themes-private", "themes");
|
|
5059
|
+
const siblingExists = existsSync18(siblingPath);
|
|
4637
5060
|
if (siblingExists) {
|
|
4638
5061
|
addNested(siblingPath, "sibling");
|
|
4639
5062
|
}
|
|
4640
|
-
const parentDir =
|
|
5063
|
+
const parentDir = join21(mainRepoRoot, "..");
|
|
4641
5064
|
const parentGlobMatches = scanParentForPrivateThemes(parentDir).filter(
|
|
4642
5065
|
(path2) => !path2.startsWith(mainRepoRoot + sep)
|
|
4643
5066
|
);
|
|
@@ -4658,7 +5081,7 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
|
4658
5081
|
}
|
|
4659
5082
|
}
|
|
4660
5083
|
}
|
|
4661
|
-
const legacyDir =
|
|
5084
|
+
const legacyDir = join21(repoRoot, "custom-themes");
|
|
4662
5085
|
const legacyFiles = scanThemeDir(legacyDir);
|
|
4663
5086
|
for (const legacyFile of legacyFiles) {
|
|
4664
5087
|
const slug2 = basename4(legacyFile).replace(/\.visor\.yaml$/, "");
|
|
@@ -4715,14 +5138,14 @@ function reportBrokenSymlink(err, options) {
|
|
|
4715
5138
|
}
|
|
4716
5139
|
}
|
|
4717
5140
|
function buildEmptySourcesMessage(mainRepoRoot) {
|
|
4718
|
-
const expectedSibling =
|
|
5141
|
+
const expectedSibling = join21(mainRepoRoot, "..", "visor-themes-private");
|
|
4719
5142
|
return [
|
|
4720
5143
|
"No theme sources discovered. Cannot proceed \u2014 refusing to wipe generated CSS.",
|
|
4721
5144
|
"",
|
|
4722
5145
|
"Resolution order checked:",
|
|
4723
5146
|
` 1. Env var ${PRIVATE_THEMES_ENV_VAR} (unset or empty)`,
|
|
4724
5147
|
` 2. Sibling checkout at ${expectedSibling}/themes/ (not found)`,
|
|
4725
|
-
` 3. One-level-deeper at ${
|
|
5148
|
+
` 3. One-level-deeper at ${join21(mainRepoRoot, "..")}/<parent>/visor-themes-private/themes/ (not found)`,
|
|
4726
5149
|
" 4. Legacy custom-themes/ (no .visor.yaml files)",
|
|
4727
5150
|
"",
|
|
4728
5151
|
"To fix, clone the private themes repo as a sibling:",
|
|
@@ -4927,14 +5350,14 @@ function themeSyncCommand(cwd, options) {
|
|
|
4927
5350
|
return;
|
|
4928
5351
|
}
|
|
4929
5352
|
const mainRepoRoot = findMainRepoRoot(cwd) ?? repoRoot;
|
|
4930
|
-
const themesDir =
|
|
4931
|
-
const docsAppDir =
|
|
4932
|
-
const docsLibDir =
|
|
4933
|
-
const docsPublicThemesDir =
|
|
4934
|
-
const themeConfigPath =
|
|
4935
|
-
const globalsPath =
|
|
4936
|
-
const customOverlayCssPath =
|
|
4937
|
-
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);
|
|
4938
5361
|
let stockFiles;
|
|
4939
5362
|
try {
|
|
4940
5363
|
stockFiles = scanThemeDir(themesDir);
|
|
@@ -4991,7 +5414,7 @@ function themeSyncCommand(cwd, options) {
|
|
|
4991
5414
|
const processFile = (filePath, isCustom, slugOverride) => {
|
|
4992
5415
|
let yamlContent;
|
|
4993
5416
|
try {
|
|
4994
|
-
yamlContent =
|
|
5417
|
+
yamlContent = readFileSync21(filePath, "utf-8");
|
|
4995
5418
|
} catch {
|
|
4996
5419
|
failures.push({ filePath, error: `Could not read: ${filePath}` });
|
|
4997
5420
|
return;
|
|
@@ -5041,8 +5464,8 @@ function themeSyncCommand(cwd, options) {
|
|
|
5041
5464
|
let globalsContent;
|
|
5042
5465
|
let themeConfigContent;
|
|
5043
5466
|
try {
|
|
5044
|
-
globalsContent =
|
|
5045
|
-
themeConfigContent =
|
|
5467
|
+
globalsContent = readFileSync21(globalsPath, "utf-8");
|
|
5468
|
+
themeConfigContent = readFileSync21(themeConfigPath, "utf-8");
|
|
5046
5469
|
} catch (err) {
|
|
5047
5470
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
5048
5471
|
if (options.json) {
|
|
@@ -5057,12 +5480,12 @@ function themeSyncCommand(cwd, options) {
|
|
|
5057
5480
|
const newThemeConfig = updateStockThemeConfigBlock(themeConfigContent, stockManifest);
|
|
5058
5481
|
const newCustomOverlayCss = generateCustomOverlayCss(customManifest);
|
|
5059
5482
|
const newCustomOverlayTs = generateCustomOverlayTs(customManifest);
|
|
5060
|
-
const existingCssFiles =
|
|
5483
|
+
const existingCssFiles = existsSync18(docsAppDir) ? readdirSync6(docsAppDir).filter(
|
|
5061
5484
|
(f) => f.endsWith("-theme.css") && f !== "custom-themes.generated.css"
|
|
5062
5485
|
) : [];
|
|
5063
5486
|
const newCssSet = new Set(allSlugs.map((s) => `${s}-theme.css`));
|
|
5064
5487
|
const staleCssFiles = existingCssFiles.filter((f) => !newCssSet.has(f));
|
|
5065
|
-
const existingPublicYamls =
|
|
5488
|
+
const existingPublicYamls = existsSync18(docsPublicThemesDir) ? readdirSync6(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
|
|
5066
5489
|
const newPublicYamlSet = new Set(manifest.map((e) => `${e.yamlFilename}.visor.yaml`));
|
|
5067
5490
|
const stalePublicYamls = existingPublicYamls.filter((f) => !newPublicYamlSet.has(f));
|
|
5068
5491
|
if (options.dryRun) {
|
|
@@ -5106,24 +5529,24 @@ function themeSyncCommand(cwd, options) {
|
|
|
5106
5529
|
mkdirSync8(docsLibDir, { recursive: true });
|
|
5107
5530
|
mkdirSync8(docsPublicThemesDir, { recursive: true });
|
|
5108
5531
|
for (const entry of manifest) {
|
|
5109
|
-
writeFileSync12(
|
|
5532
|
+
writeFileSync12(join21(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
|
|
5110
5533
|
}
|
|
5111
5534
|
for (const stale of staleCssFiles) {
|
|
5112
|
-
unlinkSync2(
|
|
5535
|
+
unlinkSync2(join21(docsAppDir, stale));
|
|
5113
5536
|
}
|
|
5114
5537
|
writeFileSync12(customOverlayCssPath, newCustomOverlayCss, "utf-8");
|
|
5115
5538
|
writeFileSync12(customOverlayTsPath, newCustomOverlayTs, "utf-8");
|
|
5116
5539
|
writeFileSync12(themeConfigPath, newThemeConfig, "utf-8");
|
|
5117
5540
|
writeFileSync12(globalsPath, newGlobals, "utf-8");
|
|
5118
5541
|
for (const srcFile of stockFiles) {
|
|
5119
|
-
copyFileSync(srcFile,
|
|
5542
|
+
copyFileSync(srcFile, join21(docsPublicThemesDir, basename4(srcFile)));
|
|
5120
5543
|
}
|
|
5121
5544
|
for (const c of customSources) {
|
|
5122
5545
|
const targetName = c.origin === "legacy" ? basename4(c.filePath) : `${c.slug}.visor.yaml`;
|
|
5123
|
-
copyFileSync(c.filePath,
|
|
5546
|
+
copyFileSync(c.filePath, join21(docsPublicThemesDir, targetName));
|
|
5124
5547
|
}
|
|
5125
5548
|
for (const stale of stalePublicYamls) {
|
|
5126
|
-
unlinkSync2(
|
|
5549
|
+
unlinkSync2(join21(docsPublicThemesDir, stale));
|
|
5127
5550
|
}
|
|
5128
5551
|
} catch (err) {
|
|
5129
5552
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
@@ -5176,19 +5599,19 @@ function themeSyncCommand(cwd, options) {
|
|
|
5176
5599
|
|
|
5177
5600
|
// src/commands/theme-batch-apply-flutter.ts
|
|
5178
5601
|
import {
|
|
5179
|
-
readFileSync as
|
|
5602
|
+
readFileSync as readFileSync22,
|
|
5180
5603
|
writeFileSync as writeFileSync13,
|
|
5181
5604
|
mkdirSync as mkdirSync9,
|
|
5182
|
-
existsSync as
|
|
5605
|
+
existsSync as existsSync19,
|
|
5183
5606
|
readdirSync as readdirSync7,
|
|
5184
5607
|
rmSync
|
|
5185
5608
|
} from "fs";
|
|
5186
|
-
import { join as
|
|
5609
|
+
import { join as join22, basename as basename5, dirname as dirname11 } from "path";
|
|
5187
5610
|
import { generateThemeData as generateThemeData6 } from "@loworbitstudio/visor-theme-engine";
|
|
5188
5611
|
import { flutterAdapter as flutterAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
5189
5612
|
function scanThemeDir2(dir) {
|
|
5190
|
-
if (!
|
|
5191
|
-
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();
|
|
5192
5615
|
}
|
|
5193
5616
|
function slugToCamel(slug2) {
|
|
5194
5617
|
return slug2.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
@@ -5321,9 +5744,9 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
5321
5744
|
process.exit(1);
|
|
5322
5745
|
return;
|
|
5323
5746
|
}
|
|
5324
|
-
const themesDir =
|
|
5325
|
-
const customThemesDir =
|
|
5326
|
-
const outputDir =
|
|
5747
|
+
const themesDir = join22(repoRoot, "themes");
|
|
5748
|
+
const customThemesDir = join22(repoRoot, "custom-themes");
|
|
5749
|
+
const outputDir = join22(repoRoot, "packages", "visor_themes");
|
|
5327
5750
|
const stockFiles = scanThemeDir2(themesDir);
|
|
5328
5751
|
const customFiles = scanThemeDir2(customThemesDir);
|
|
5329
5752
|
const allFiles = [...stockFiles, ...customFiles];
|
|
@@ -5344,7 +5767,7 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
5344
5767
|
for (const filePath of allFiles) {
|
|
5345
5768
|
let yamlContent;
|
|
5346
5769
|
try {
|
|
5347
|
-
yamlContent =
|
|
5770
|
+
yamlContent = readFileSync22(filePath, "utf-8");
|
|
5348
5771
|
} catch {
|
|
5349
5772
|
errors.push(`Could not read: ${filePath}`);
|
|
5350
5773
|
continue;
|
|
@@ -5423,8 +5846,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
5423
5846
|
return;
|
|
5424
5847
|
}
|
|
5425
5848
|
const slugs = processed.map((p) => p.slug);
|
|
5426
|
-
const libSrcDir =
|
|
5427
|
-
if (
|
|
5849
|
+
const libSrcDir = join22(outputDir, "lib", "src");
|
|
5850
|
+
if (existsSync19(libSrcDir)) {
|
|
5428
5851
|
rmSync(libSrcDir, { recursive: true, force: true });
|
|
5429
5852
|
}
|
|
5430
5853
|
const packageFiles = {
|
|
@@ -5436,16 +5859,16 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
5436
5859
|
};
|
|
5437
5860
|
let totalFiles = 0;
|
|
5438
5861
|
for (const [relPath, content] of Object.entries(packageFiles)) {
|
|
5439
|
-
const absPath =
|
|
5440
|
-
mkdirSync9(
|
|
5862
|
+
const absPath = join22(outputDir, relPath);
|
|
5863
|
+
mkdirSync9(dirname11(absPath), { recursive: true });
|
|
5441
5864
|
writeFileSync13(absPath, content, "utf-8");
|
|
5442
5865
|
totalFiles++;
|
|
5443
5866
|
}
|
|
5444
5867
|
for (const { slug: slug2, tokenFiles } of processed) {
|
|
5445
|
-
const themeBaseDir =
|
|
5868
|
+
const themeBaseDir = join22(outputDir, "lib", "src", slug2);
|
|
5446
5869
|
for (const [relPath, content] of Object.entries(tokenFiles)) {
|
|
5447
|
-
const absPath =
|
|
5448
|
-
mkdirSync9(
|
|
5870
|
+
const absPath = join22(themeBaseDir, relPath);
|
|
5871
|
+
mkdirSync9(dirname11(absPath), { recursive: true });
|
|
5449
5872
|
writeFileSync13(absPath, content, "utf-8");
|
|
5450
5873
|
totalFiles++;
|
|
5451
5874
|
}
|
|
@@ -5467,8 +5890,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
5467
5890
|
}
|
|
5468
5891
|
|
|
5469
5892
|
// src/commands/fonts-add.ts
|
|
5470
|
-
import { existsSync as
|
|
5471
|
-
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";
|
|
5472
5895
|
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
5473
5896
|
function deriveFamilySlug(filename) {
|
|
5474
5897
|
const name = basename6(filename, extname4(filename));
|
|
@@ -5512,11 +5935,11 @@ function deriveFamilySlug(filename) {
|
|
|
5512
5935
|
return parts.join("-").toLowerCase();
|
|
5513
5936
|
}
|
|
5514
5937
|
function collectWoff2Files(inputPath) {
|
|
5515
|
-
const resolved =
|
|
5516
|
-
if (!
|
|
5938
|
+
const resolved = resolve16(inputPath);
|
|
5939
|
+
if (!existsSync20(resolved)) {
|
|
5517
5940
|
throw new Error(`Path not found: ${resolved}`);
|
|
5518
5941
|
}
|
|
5519
|
-
const stat =
|
|
5942
|
+
const stat = statSync8(resolved);
|
|
5520
5943
|
if (stat.isFile()) {
|
|
5521
5944
|
if (extname4(resolved).toLowerCase() !== ".woff2") {
|
|
5522
5945
|
throw new Error(
|
|
@@ -5526,7 +5949,7 @@ function collectWoff2Files(inputPath) {
|
|
|
5526
5949
|
return [resolved];
|
|
5527
5950
|
}
|
|
5528
5951
|
if (stat.isDirectory()) {
|
|
5529
|
-
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));
|
|
5530
5953
|
if (files.length === 0) {
|
|
5531
5954
|
throw new Error(
|
|
5532
5955
|
`No .woff2 files found in directory: ${resolved}`
|
|
@@ -5541,8 +5964,8 @@ function collectWoff2Files(inputPath) {
|
|
|
5541
5964
|
throw new Error(`Path is neither a file nor a directory: ${resolved}`);
|
|
5542
5965
|
}
|
|
5543
5966
|
function getNonWoff2Fonts(inputPath) {
|
|
5544
|
-
const resolved =
|
|
5545
|
-
if (!
|
|
5967
|
+
const resolved = resolve16(inputPath);
|
|
5968
|
+
if (!existsSync20(resolved) || !statSync8(resolved).isDirectory()) {
|
|
5546
5969
|
return [];
|
|
5547
5970
|
}
|
|
5548
5971
|
return readdirSync8(resolved).filter((f) => {
|
|
@@ -5579,7 +6002,7 @@ function createR2Client(config) {
|
|
|
5579
6002
|
});
|
|
5580
6003
|
}
|
|
5581
6004
|
async function uploadFile(client, bucket, key, filePath) {
|
|
5582
|
-
const body =
|
|
6005
|
+
const body = readFileSync23(filePath);
|
|
5583
6006
|
await client.send(
|
|
5584
6007
|
new PutObjectCommand({
|
|
5585
6008
|
Bucket: bucket,
|
|
@@ -5595,8 +6018,8 @@ async function fontsAddCommand(inputPath, options) {
|
|
|
5595
6018
|
const r2Config = getR2Config();
|
|
5596
6019
|
const files = collectWoff2Files(inputPath);
|
|
5597
6020
|
const familySlug = options.family ?? deriveFamilySlug(basename6(files[0]));
|
|
5598
|
-
const resolved =
|
|
5599
|
-
const nonWoff2 =
|
|
6021
|
+
const resolved = resolve16(inputPath);
|
|
6022
|
+
const nonWoff2 = statSync8(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
|
|
5600
6023
|
if (!json) {
|
|
5601
6024
|
logger.heading("Visor Font Upload");
|
|
5602
6025
|
logger.info(`Organization: ${org}`);
|
|
@@ -5620,7 +6043,7 @@ async function fontsAddCommand(inputPath, options) {
|
|
|
5620
6043
|
logger.info(`Uploading ${filename}...`);
|
|
5621
6044
|
}
|
|
5622
6045
|
await uploadFile(client, bucket, key, filePath);
|
|
5623
|
-
const size =
|
|
6046
|
+
const size = statSync8(filePath).size;
|
|
5624
6047
|
results.push({ file: filename, key, size });
|
|
5625
6048
|
if (!json) {
|
|
5626
6049
|
logger.success(`Uploaded: ${key} (${formatBytes(size)})`);
|
|
@@ -5883,27 +6306,27 @@ function findCssFiles(dir, maxDepth = 3) {
|
|
|
5883
6306
|
}
|
|
5884
6307
|
|
|
5885
6308
|
// src/utils/patterns.ts
|
|
5886
|
-
import { existsSync as
|
|
5887
|
-
import { join as
|
|
6309
|
+
import { existsSync as existsSync22, readdirSync as readdirSync10, readFileSync as readFileSync25 } from "fs";
|
|
6310
|
+
import { join as join24 } from "path";
|
|
5888
6311
|
import { parse as parseYAML } from "yaml";
|
|
5889
6312
|
function loadPatternsFromYaml(repoRoot) {
|
|
5890
|
-
const patternsDir =
|
|
5891
|
-
if (!
|
|
6313
|
+
const patternsDir = join24(repoRoot, "patterns");
|
|
6314
|
+
if (!existsSync22(patternsDir)) return [];
|
|
5892
6315
|
const files = readdirSync10(patternsDir).filter(
|
|
5893
6316
|
(f) => f.endsWith(".visor-pattern.yaml")
|
|
5894
6317
|
);
|
|
5895
6318
|
return files.map((file) => {
|
|
5896
|
-
const content =
|
|
6319
|
+
const content = readFileSync25(join24(patternsDir, file), "utf-8");
|
|
5897
6320
|
return parseYAML(content);
|
|
5898
6321
|
}).filter(Boolean);
|
|
5899
6322
|
}
|
|
5900
6323
|
function findRepoRoot2(startDir) {
|
|
5901
6324
|
let current = startDir;
|
|
5902
6325
|
while (true) {
|
|
5903
|
-
if (
|
|
6326
|
+
if (existsSync22(join24(current, "patterns"))) {
|
|
5904
6327
|
return current;
|
|
5905
6328
|
}
|
|
5906
|
-
const parent =
|
|
6329
|
+
const parent = join24(current, "..");
|
|
5907
6330
|
if (parent === current) return null;
|
|
5908
6331
|
current = parent;
|
|
5909
6332
|
}
|
|
@@ -6282,8 +6705,8 @@ Visor Tokens (${categoryLabel}) \u2014 ${tokens2.length} tokens
|
|
|
6282
6705
|
}
|
|
6283
6706
|
|
|
6284
6707
|
// src/commands/migrate-token-substitution.ts
|
|
6285
|
-
import { readFileSync as
|
|
6286
|
-
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";
|
|
6287
6710
|
import { parse as parseYaml4 } from "yaml";
|
|
6288
6711
|
import pc4 from "picocolors";
|
|
6289
6712
|
var V7_ENTR_SUBSTITUTION_MAP = {
|
|
@@ -6306,14 +6729,14 @@ var BUILT_IN_SUBSTITUTION_MAPS = {
|
|
|
6306
6729
|
var DEFAULT_THEME_ID = "entr";
|
|
6307
6730
|
function readMapFromThemeFile(themeId, cwd) {
|
|
6308
6731
|
const candidates = [
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
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`)
|
|
6312
6735
|
];
|
|
6313
6736
|
for (const candidate of candidates) {
|
|
6314
|
-
if (
|
|
6737
|
+
if (existsSync23(candidate)) {
|
|
6315
6738
|
try {
|
|
6316
|
-
const raw =
|
|
6739
|
+
const raw = readFileSync26(candidate, "utf-8");
|
|
6317
6740
|
const parsed = parseYaml4(raw);
|
|
6318
6741
|
if (parsed?.migrate?.["token-substitution"]) {
|
|
6319
6742
|
return parsed.migrate["token-substitution"];
|
|
@@ -6369,7 +6792,7 @@ function collectCssFiles(dirPath) {
|
|
|
6369
6792
|
function walk2(current) {
|
|
6370
6793
|
const entries = readdirSync11(current, { withFileTypes: true });
|
|
6371
6794
|
for (const entry of entries) {
|
|
6372
|
-
const fullPath =
|
|
6795
|
+
const fullPath = join25(current, entry.name);
|
|
6373
6796
|
if (entry.isDirectory()) {
|
|
6374
6797
|
if (["node_modules", ".git", ".next", "dist", "build", ".cache"].includes(entry.name)) {
|
|
6375
6798
|
continue;
|
|
@@ -6388,7 +6811,7 @@ function collectCssFiles(dirPath) {
|
|
|
6388
6811
|
}
|
|
6389
6812
|
function runSubstitutionPass(targetPath, map, themeId) {
|
|
6390
6813
|
let files;
|
|
6391
|
-
const stat =
|
|
6814
|
+
const stat = statSync9(targetPath);
|
|
6392
6815
|
if (stat.isFile()) {
|
|
6393
6816
|
files = [targetPath];
|
|
6394
6817
|
} else {
|
|
@@ -6397,7 +6820,7 @@ function runSubstitutionPass(targetPath, map, themeId) {
|
|
|
6397
6820
|
const fileResults = [];
|
|
6398
6821
|
let totalSubstitutions = 0;
|
|
6399
6822
|
for (const file of files) {
|
|
6400
|
-
const content =
|
|
6823
|
+
const content = readFileSync26(file, "utf-8");
|
|
6401
6824
|
const { newContent, substitutions } = applySubstitutionsToContent(content, map);
|
|
6402
6825
|
if (substitutions.length > 0) {
|
|
6403
6826
|
fileResults.push({ file, substitutions, originalContent: content, newContent });
|
|
@@ -6431,9 +6854,9 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
6431
6854
|
process.exit(1);
|
|
6432
6855
|
return;
|
|
6433
6856
|
}
|
|
6434
|
-
const targetPath =
|
|
6857
|
+
const targetPath = resolve17(cwd, targetArg ?? ".");
|
|
6435
6858
|
try {
|
|
6436
|
-
|
|
6859
|
+
statSync9(targetPath);
|
|
6437
6860
|
} catch {
|
|
6438
6861
|
if (options.json) {
|
|
6439
6862
|
console.log(JSON.stringify({
|
|
@@ -6520,23 +6943,23 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
6520
6943
|
}
|
|
6521
6944
|
|
|
6522
6945
|
// src/commands/sandbox/init.ts
|
|
6523
|
-
import { existsSync as
|
|
6524
|
-
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";
|
|
6525
6948
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6526
6949
|
import * as childProcess3 from "child_process";
|
|
6527
6950
|
|
|
6528
6951
|
// src/commands/sandbox/parse-handoff.ts
|
|
6529
|
-
import { readFileSync as
|
|
6530
|
-
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";
|
|
6531
6954
|
function parseHandoff(handoffPath) {
|
|
6532
|
-
const text =
|
|
6955
|
+
const text = readFileSync27(handoffPath, "utf-8");
|
|
6533
6956
|
const lines2 = text.split("\n");
|
|
6534
6957
|
const warnings = [];
|
|
6535
6958
|
const pattern2 = extractPatternSlug(lines2, warnings);
|
|
6536
6959
|
const theme2 = extractMetaField(lines2, "Theme");
|
|
6537
6960
|
const recipePath = extractRecipePath(text, handoffPath, warnings);
|
|
6538
6961
|
const primitives = extractPrimitives(text, warnings);
|
|
6539
|
-
const { screens, mockShapes } = recipePath &&
|
|
6962
|
+
const { screens, mockShapes } = recipePath && existsSync24(recipePath) ? extractRecipeArtifacts(recipePath, warnings) : { screens: [], mockShapes: [] };
|
|
6540
6963
|
return { pattern: pattern2, theme: theme2, recipePath, primitives, screens, mockShapes, warnings };
|
|
6541
6964
|
}
|
|
6542
6965
|
function extractPatternSlug(lines2, warnings) {
|
|
@@ -6567,8 +6990,8 @@ function extractRecipePath(text, handoffPath, warnings) {
|
|
|
6567
6990
|
return void 0;
|
|
6568
6991
|
}
|
|
6569
6992
|
const href = m[2].trim();
|
|
6570
|
-
if (
|
|
6571
|
-
return
|
|
6993
|
+
if (isAbsolute4(href)) return href;
|
|
6994
|
+
return resolve18(dirname12(handoffPath), href);
|
|
6572
6995
|
}
|
|
6573
6996
|
var STATUS_PATTERNS = [
|
|
6574
6997
|
{ re: /NEW\s+gap/i, status: "gap-new" },
|
|
@@ -6615,7 +7038,7 @@ function extractPrimitives(text, warnings) {
|
|
|
6615
7038
|
return primitives;
|
|
6616
7039
|
}
|
|
6617
7040
|
function extractRecipeArtifacts(recipePath, warnings) {
|
|
6618
|
-
const text =
|
|
7041
|
+
const text = readFileSync27(recipePath, "utf-8");
|
|
6619
7042
|
const screens = extractScreens(text);
|
|
6620
7043
|
const mockShapes = extractMockFields(text, warnings);
|
|
6621
7044
|
return { screens, mockShapes };
|
|
@@ -6689,8 +7112,8 @@ function tryPort(port) {
|
|
|
6689
7112
|
}
|
|
6690
7113
|
|
|
6691
7114
|
// src/commands/sandbox/scaffold.ts
|
|
6692
|
-
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync15, existsSync as
|
|
6693
|
-
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";
|
|
6694
7117
|
|
|
6695
7118
|
// src/commands/sandbox/templates.ts
|
|
6696
7119
|
var SANDBOX_PACKAGE_VERSION = "16.2.6";
|
|
@@ -7193,7 +7616,7 @@ function writeScaffold(sandboxDir, manifest, port, options = {}) {
|
|
|
7193
7616
|
mkdirSync10(sandboxDir, { recursive: true });
|
|
7194
7617
|
const created = [];
|
|
7195
7618
|
const writeIfNew = (rel, contents) => {
|
|
7196
|
-
const full =
|
|
7619
|
+
const full = join26(sandboxDir, rel);
|
|
7197
7620
|
mkdirSync10(dirOf(full), { recursive: true });
|
|
7198
7621
|
writeFileSync15(full, contents, "utf-8");
|
|
7199
7622
|
created.push(rel);
|
|
@@ -7221,7 +7644,7 @@ function writeScaffold(sandboxDir, manifest, port, options = {}) {
|
|
|
7221
7644
|
}
|
|
7222
7645
|
}
|
|
7223
7646
|
writeIfNew("playwright.capture.mjs", captureScriptTemplate());
|
|
7224
|
-
mkdirSync10(
|
|
7647
|
+
mkdirSync10(join26(sandboxDir, "captures", "approved"), { recursive: true });
|
|
7225
7648
|
return { created };
|
|
7226
7649
|
}
|
|
7227
7650
|
function dirOf(p) {
|
|
@@ -7255,10 +7678,10 @@ function writeSandboxConfig(sandboxDir, manifest, port, options) {
|
|
|
7255
7678
|
stripChromeSelectors: options.prototypeImport.stripChromeSelectors
|
|
7256
7679
|
} : null
|
|
7257
7680
|
};
|
|
7258
|
-
writeFileSync15(
|
|
7681
|
+
writeFileSync15(join26(sandboxDir, "sandbox.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
7259
7682
|
}
|
|
7260
7683
|
function sandboxIsEmpty(sandboxDir) {
|
|
7261
|
-
if (!
|
|
7684
|
+
if (!existsSync25(sandboxDir)) return true;
|
|
7262
7685
|
try {
|
|
7263
7686
|
return readdirSync12(sandboxDir).filter((f) => !f.startsWith(".")).length === 0;
|
|
7264
7687
|
} catch {
|
|
@@ -7267,8 +7690,8 @@ function sandboxIsEmpty(sandboxDir) {
|
|
|
7267
7690
|
}
|
|
7268
7691
|
|
|
7269
7692
|
// src/commands/sandbox/html-prototype.ts
|
|
7270
|
-
import { mkdirSync as mkdirSync11, readdirSync as readdirSync13, readFileSync as
|
|
7271
|
-
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";
|
|
7272
7695
|
|
|
7273
7696
|
// src/commands/sandbox/strip-chrome.ts
|
|
7274
7697
|
var DEFAULT_STRIP_SELECTORS = [
|
|
@@ -7452,7 +7875,7 @@ function stripDocumentaryChrome(html, selectors) {
|
|
|
7452
7875
|
var SCREEN_FILE_PATTERN = /^screen-(\d+)-([^/.]+)\.html$/i;
|
|
7453
7876
|
function copyHtmlPrototype(sourceDir, sandboxDir, manifest, options = {}) {
|
|
7454
7877
|
const warnings = [];
|
|
7455
|
-
const destAbs =
|
|
7878
|
+
const destAbs = join27(sandboxDir, "public", "prototype");
|
|
7456
7879
|
mkdirSync11(destAbs, { recursive: true });
|
|
7457
7880
|
const stripChromeSelectors = options.stripChromeSelectors ?? [];
|
|
7458
7881
|
const copiedFiles = copyTreeRelative(sourceDir, destAbs, "", stripChromeSelectors);
|
|
@@ -7506,24 +7929,24 @@ function deriveStateCoverageScreen(file, existingNames) {
|
|
|
7506
7929
|
}
|
|
7507
7930
|
function copyTreeRelative(srcDir, destDir, relDir = "", stripChromeSelectors = []) {
|
|
7508
7931
|
const out = [];
|
|
7509
|
-
const entries = readdirSync13(
|
|
7932
|
+
const entries = readdirSync13(join27(srcDir, relDir));
|
|
7510
7933
|
for (const name of entries) {
|
|
7511
7934
|
if (name.startsWith(".")) continue;
|
|
7512
|
-
const srcPath =
|
|
7513
|
-
const destPath =
|
|
7514
|
-
const stat =
|
|
7935
|
+
const srcPath = join27(srcDir, relDir, name);
|
|
7936
|
+
const destPath = join27(destDir, relDir, name);
|
|
7937
|
+
const stat = statSync10(srcPath);
|
|
7515
7938
|
if (stat.isDirectory()) {
|
|
7516
7939
|
mkdirSync11(destPath, { recursive: true });
|
|
7517
|
-
out.push(...copyTreeRelative(srcDir, destDir,
|
|
7940
|
+
out.push(...copyTreeRelative(srcDir, destDir, join27(relDir, name), stripChromeSelectors));
|
|
7518
7941
|
} else if (stat.isFile()) {
|
|
7519
7942
|
if (stripChromeSelectors.length > 0 && name.toLowerCase().endsWith(".html")) {
|
|
7520
|
-
const html =
|
|
7943
|
+
const html = readFileSync28(srcPath, "utf-8");
|
|
7521
7944
|
const stripped = stripDocumentaryChrome(html, stripChromeSelectors);
|
|
7522
7945
|
writeFileSync16(destPath, stripped);
|
|
7523
7946
|
} else {
|
|
7524
|
-
writeFileSync16(destPath,
|
|
7947
|
+
writeFileSync16(destPath, readFileSync28(srcPath));
|
|
7525
7948
|
}
|
|
7526
|
-
out.push(
|
|
7949
|
+
out.push(join27("public", "prototype", relDir, name).replace(/\\/g, "/"));
|
|
7527
7950
|
}
|
|
7528
7951
|
}
|
|
7529
7952
|
return out;
|
|
@@ -7575,8 +7998,8 @@ async function runInit(name, cwd, options) {
|
|
|
7575
7998
|
if (!name || !/^[a-z0-9][a-z0-9-_]*$/i.test(name)) {
|
|
7576
7999
|
throw new Error(`Invalid sandbox name '${name}'. Use letters, digits, '-' or '_'.`);
|
|
7577
8000
|
}
|
|
7578
|
-
const handoffPath =
|
|
7579
|
-
if (!
|
|
8001
|
+
const handoffPath = isAbsolute5(options.handoff) ? options.handoff : resolve19(cwd, options.handoff);
|
|
8002
|
+
if (!existsSync26(handoffPath)) {
|
|
7580
8003
|
throw new Error(`Handoff manifest not found: ${handoffPath}`);
|
|
7581
8004
|
}
|
|
7582
8005
|
const manifest = parseHandoff(handoffPath);
|
|
@@ -7585,7 +8008,7 @@ async function runInit(name, cwd, options) {
|
|
|
7585
8008
|
`Handoff has no primitives \u2014 refusing to scaffold an empty sandbox. Check the manifest at ${handoffPath}`
|
|
7586
8009
|
);
|
|
7587
8010
|
}
|
|
7588
|
-
const sandboxDir =
|
|
8011
|
+
const sandboxDir = join28(cwd, ".lo", "sandbox", name);
|
|
7589
8012
|
if (!sandboxIsEmpty(sandboxDir)) {
|
|
7590
8013
|
if (!options.overwrite) {
|
|
7591
8014
|
throw new Error(
|
|
@@ -7598,8 +8021,8 @@ async function runInit(name, cwd, options) {
|
|
|
7598
8021
|
const port = await findOpenPort();
|
|
7599
8022
|
let prototypeImport;
|
|
7600
8023
|
if (options.fromHtmlPrototype) {
|
|
7601
|
-
const prototypeDir =
|
|
7602
|
-
if (!
|
|
8024
|
+
const prototypeDir = isAbsolute5(options.fromHtmlPrototype) ? options.fromHtmlPrototype : resolve19(cwd, options.fromHtmlPrototype);
|
|
8025
|
+
if (!existsSync26(prototypeDir)) {
|
|
7603
8026
|
throw new Error(`HTML prototype directory not found: ${prototypeDir}`);
|
|
7604
8027
|
}
|
|
7605
8028
|
const stripChromeSelectors = resolveStripSelectors(
|
|
@@ -7658,22 +8081,22 @@ async function runInit(name, cwd, options) {
|
|
|
7658
8081
|
function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile) {
|
|
7659
8082
|
const candidates = [];
|
|
7660
8083
|
if (themeFile) {
|
|
7661
|
-
candidates.push(
|
|
8084
|
+
candidates.push(isAbsolute5(themeFile) ? themeFile : resolve19(cwd, themeFile));
|
|
7662
8085
|
}
|
|
7663
|
-
candidates.push(
|
|
8086
|
+
candidates.push(isAbsolute5(theme2) ? theme2 : resolve19(cwd, theme2));
|
|
7664
8087
|
const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
|
|
7665
8088
|
if (privateRoot && privateRoot.length > 0) {
|
|
7666
|
-
candidates.push(
|
|
8089
|
+
candidates.push(join28(privateRoot, "themes", theme2, "theme.visor.yaml"));
|
|
7667
8090
|
}
|
|
7668
8091
|
candidates.push(
|
|
7669
|
-
|
|
7670
|
-
|
|
8092
|
+
join28(cwd, "themes", `${theme2}.visor.yaml`),
|
|
8093
|
+
join28(cwd, "custom-themes", `${theme2}.visor.yaml`)
|
|
7671
8094
|
);
|
|
7672
|
-
const yamlPath = candidates.find((p) =>
|
|
8095
|
+
const yamlPath = candidates.find((p) => existsSync26(p));
|
|
7673
8096
|
if (!yamlPath) {
|
|
7674
8097
|
const searched = candidates.join(", ");
|
|
7675
8098
|
manifest.warnings.push(
|
|
7676
|
-
`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.`
|
|
7677
8100
|
);
|
|
7678
8101
|
if (!json) {
|
|
7679
8102
|
logger.warn(`Theme '${theme2}' not found \u2014 leaving placeholder globals.css.`);
|
|
@@ -7681,12 +8104,12 @@ function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile
|
|
|
7681
8104
|
`Re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH=<dir-containing-themes/${theme2}/theme.visor.yaml>.`
|
|
7682
8105
|
);
|
|
7683
8106
|
logger.item(
|
|
7684
|
-
`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")}`
|
|
7685
8108
|
);
|
|
7686
8109
|
}
|
|
7687
8110
|
return;
|
|
7688
8111
|
}
|
|
7689
|
-
const globalsOut =
|
|
8112
|
+
const globalsOut = join28(sandboxDir, "app", "globals.css");
|
|
7690
8113
|
try {
|
|
7691
8114
|
themeApplyCommand(yamlPath, sandboxDir, {
|
|
7692
8115
|
output: globalsOut,
|
|
@@ -7739,12 +8162,12 @@ function loadKnownPrimitives() {
|
|
|
7739
8162
|
}
|
|
7740
8163
|
function readCliVersion() {
|
|
7741
8164
|
try {
|
|
7742
|
-
const here =
|
|
8165
|
+
const here = dirname13(fileURLToPath3(import.meta.url));
|
|
7743
8166
|
for (let i = 0; i < 6; i++) {
|
|
7744
8167
|
const segments = new Array(i).fill("..");
|
|
7745
|
-
const candidate =
|
|
8168
|
+
const candidate = join28(here, ...segments, "package.json");
|
|
7746
8169
|
try {
|
|
7747
|
-
const pkg2 = JSON.parse(
|
|
8170
|
+
const pkg2 = JSON.parse(readFileSync29(candidate, "utf-8"));
|
|
7748
8171
|
if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) return pkg2.version;
|
|
7749
8172
|
} catch {
|
|
7750
8173
|
}
|
|
@@ -7755,14 +8178,14 @@ function readCliVersion() {
|
|
|
7755
8178
|
}
|
|
7756
8179
|
|
|
7757
8180
|
// src/commands/sandbox/dev.ts
|
|
7758
|
-
import { existsSync as
|
|
7759
|
-
import { join as
|
|
8181
|
+
import { existsSync as existsSync27, readFileSync as readFileSync30 } from "fs";
|
|
8182
|
+
import { join as join29 } from "path";
|
|
7760
8183
|
import * as childProcess4 from "child_process";
|
|
7761
8184
|
function sandboxDevCommand(cwd, options) {
|
|
7762
8185
|
const json = options.json ?? false;
|
|
7763
|
-
const sandboxDir =
|
|
7764
|
-
const configPath =
|
|
7765
|
-
if (!
|
|
8186
|
+
const sandboxDir = join29(cwd, ".lo", "sandbox", options.name);
|
|
8187
|
+
const configPath = join29(sandboxDir, "sandbox.json");
|
|
8188
|
+
if (!existsSync27(configPath)) {
|
|
7766
8189
|
fail(
|
|
7767
8190
|
json,
|
|
7768
8191
|
`Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init ${options.name} --handoff ... --theme ...' first.`
|
|
@@ -7771,7 +8194,7 @@ function sandboxDevCommand(cwd, options) {
|
|
|
7771
8194
|
}
|
|
7772
8195
|
let config;
|
|
7773
8196
|
try {
|
|
7774
|
-
config = JSON.parse(
|
|
8197
|
+
config = JSON.parse(readFileSync30(configPath, "utf-8"));
|
|
7775
8198
|
} catch (err) {
|
|
7776
8199
|
fail(json, `Invalid sandbox.json at ${configPath}: ${err.message}`);
|
|
7777
8200
|
return;
|
|
@@ -7829,20 +8252,20 @@ function fail(json, message) {
|
|
|
7829
8252
|
// src/commands/sandbox/approve.ts
|
|
7830
8253
|
import {
|
|
7831
8254
|
copyFileSync as copyFileSync2,
|
|
7832
|
-
existsSync as
|
|
8255
|
+
existsSync as existsSync28,
|
|
7833
8256
|
mkdirSync as mkdirSync13,
|
|
7834
|
-
readFileSync as
|
|
8257
|
+
readFileSync as readFileSync31,
|
|
7835
8258
|
readdirSync as readdirSync14,
|
|
7836
8259
|
rmSync as rmSync3,
|
|
7837
8260
|
writeFileSync as writeFileSync17
|
|
7838
8261
|
} from "fs";
|
|
7839
|
-
import { basename as basename7, join as
|
|
8262
|
+
import { basename as basename7, join as join30 } from "path";
|
|
7840
8263
|
import * as childProcess5 from "child_process";
|
|
7841
8264
|
function sandboxApproveCommand(cwd, options) {
|
|
7842
8265
|
const json = options.json ?? false;
|
|
7843
|
-
const sandboxDir =
|
|
7844
|
-
const configPath =
|
|
7845
|
-
if (!
|
|
8266
|
+
const sandboxDir = join30(cwd, ".lo", "sandbox", options.name);
|
|
8267
|
+
const configPath = join30(sandboxDir, "sandbox.json");
|
|
8268
|
+
if (!existsSync28(configPath)) {
|
|
7846
8269
|
fail2(
|
|
7847
8270
|
json,
|
|
7848
8271
|
`Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init' first.`
|
|
@@ -7856,8 +8279,8 @@ function sandboxApproveCommand(cwd, options) {
|
|
|
7856
8279
|
runCapture(sandboxDir, configPath, options.name, json);
|
|
7857
8280
|
}
|
|
7858
8281
|
function runCapture(sandboxDir, configPath, sandboxName, json) {
|
|
7859
|
-
const config = JSON.parse(
|
|
7860
|
-
const captureScriptPath =
|
|
8282
|
+
const config = JSON.parse(readFileSync31(configPath, "utf-8"));
|
|
8283
|
+
const captureScriptPath = join30(sandboxDir, "playwright.capture.mjs");
|
|
7861
8284
|
writeFileSync17(captureScriptPath, captureScriptTemplate(), "utf-8");
|
|
7862
8285
|
ensurePlaywrightInstalled(sandboxDir, json);
|
|
7863
8286
|
const result = childProcess5.spawnSync(
|
|
@@ -7879,9 +8302,9 @@ function runCapture(sandboxDir, configPath, sandboxName, json) {
|
|
|
7879
8302
|
${result.stderr}`);
|
|
7880
8303
|
return;
|
|
7881
8304
|
}
|
|
7882
|
-
const pendingDir =
|
|
7883
|
-
const diffsDir =
|
|
7884
|
-
const approvedDir =
|
|
8305
|
+
const pendingDir = join30(sandboxDir, "captures", "pending");
|
|
8306
|
+
const diffsDir = join30(sandboxDir, "captures", "diffs");
|
|
8307
|
+
const approvedDir = join30(sandboxDir, "captures", "approved");
|
|
7885
8308
|
const pendingFiles = safeListPngs(pendingDir);
|
|
7886
8309
|
const diffFiles = safeListPngs(diffsDir);
|
|
7887
8310
|
const hasBaseline = safeListPngs(approvedDir).length > 0;
|
|
@@ -7918,9 +8341,9 @@ ${result.stderr}`);
|
|
|
7918
8341
|
}
|
|
7919
8342
|
}
|
|
7920
8343
|
function runPromotion(sandboxDir, sandboxName, json) {
|
|
7921
|
-
const pendingDir =
|
|
7922
|
-
const approvedDir =
|
|
7923
|
-
const diffsDir =
|
|
8344
|
+
const pendingDir = join30(sandboxDir, "captures", "pending");
|
|
8345
|
+
const approvedDir = join30(sandboxDir, "captures", "approved");
|
|
8346
|
+
const diffsDir = join30(sandboxDir, "captures", "diffs");
|
|
7924
8347
|
const pendingFiles = safeListPngs(pendingDir);
|
|
7925
8348
|
if (pendingFiles.length === 0) {
|
|
7926
8349
|
fail2(
|
|
@@ -7932,7 +8355,7 @@ function runPromotion(sandboxDir, sandboxName, json) {
|
|
|
7932
8355
|
mkdirSync13(approvedDir, { recursive: true });
|
|
7933
8356
|
const promoted = [];
|
|
7934
8357
|
for (const src of pendingFiles) {
|
|
7935
|
-
const dest =
|
|
8358
|
+
const dest = join30(approvedDir, basename7(src));
|
|
7936
8359
|
copyFileSync2(src, dest);
|
|
7937
8360
|
promoted.push(dest);
|
|
7938
8361
|
}
|
|
@@ -7952,8 +8375,8 @@ function runPromotion(sandboxDir, sandboxName, json) {
|
|
|
7952
8375
|
}
|
|
7953
8376
|
}
|
|
7954
8377
|
function ensurePlaywrightInstalled(sandboxDir, json) {
|
|
7955
|
-
const markerPath =
|
|
7956
|
-
if (
|
|
8378
|
+
const markerPath = join30(sandboxDir, ".playwright-installed");
|
|
8379
|
+
if (existsSync28(markerPath)) return;
|
|
7957
8380
|
if (!json) logger.info("Installing Playwright Chromium (one-time)...");
|
|
7958
8381
|
const result = childProcess5.spawnSync(
|
|
7959
8382
|
"npx",
|
|
@@ -7972,8 +8395,8 @@ function ensurePlaywrightInstalled(sandboxDir, json) {
|
|
|
7972
8395
|
writeFileSync17(markerPath, (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
|
|
7973
8396
|
}
|
|
7974
8397
|
function safeListPngs(dir) {
|
|
7975
|
-
if (!
|
|
7976
|
-
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));
|
|
7977
8400
|
}
|
|
7978
8401
|
function tryParseJson(s) {
|
|
7979
8402
|
try {
|
|
@@ -7994,13 +8417,13 @@ function fail2(json, message) {
|
|
|
7994
8417
|
// src/commands/spawn.ts
|
|
7995
8418
|
import {
|
|
7996
8419
|
cpSync,
|
|
7997
|
-
existsSync as
|
|
7998
|
-
readFileSync as
|
|
8420
|
+
existsSync as existsSync30,
|
|
8421
|
+
readFileSync as readFileSync32,
|
|
7999
8422
|
readdirSync as readdirSync16,
|
|
8000
8423
|
rmSync as rmSync4,
|
|
8001
|
-
statSync as
|
|
8424
|
+
statSync as statSync12
|
|
8002
8425
|
} from "fs";
|
|
8003
|
-
import { basename as basename8, dirname as
|
|
8426
|
+
import { basename as basename8, dirname as dirname14, isAbsolute as isAbsolute6, join as join32, resolve as resolve20 } from "path";
|
|
8004
8427
|
import * as childProcess6 from "child_process";
|
|
8005
8428
|
import { parse as parseYaml5 } from "yaml";
|
|
8006
8429
|
import { generateThemeData as generateThemeData7 } from "@loworbitstudio/visor-theme-engine";
|
|
@@ -8008,8 +8431,8 @@ import { nextjsAdapter as nextjsAdapter3 } from "@loworbitstudio/visor-theme-eng
|
|
|
8008
8431
|
import { validate as validate3 } from "@loworbitstudio/visor-theme-engine";
|
|
8009
8432
|
|
|
8010
8433
|
// src/lib/blessed-discovery.ts
|
|
8011
|
-
import { existsSync as
|
|
8012
|
-
import { join as
|
|
8434
|
+
import { existsSync as existsSync29, readdirSync as readdirSync15, statSync as statSync11 } from "fs";
|
|
8435
|
+
import { join as join31 } from "path";
|
|
8013
8436
|
var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
8014
8437
|
"node_modules",
|
|
8015
8438
|
".next",
|
|
@@ -8020,13 +8443,13 @@ var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
|
8020
8443
|
".cache"
|
|
8021
8444
|
]);
|
|
8022
8445
|
var REFERENCE_BUILD_DIRNAME = "reference-build";
|
|
8023
|
-
var APPROVED_CAPTURES_SUBPATH =
|
|
8446
|
+
var APPROVED_CAPTURES_SUBPATH = join31("captures", "approved");
|
|
8024
8447
|
var MISSING_MANIFEST_REASON = `missing ${BLESSED_MANIFEST_FILENAME}`;
|
|
8025
8448
|
function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
|
|
8026
8449
|
const builds = [];
|
|
8027
8450
|
const incomplete = [];
|
|
8028
8451
|
const errors = [];
|
|
8029
|
-
if (!
|
|
8452
|
+
if (!existsSync29(blessedDir)) {
|
|
8030
8453
|
return { builds, incomplete, errors };
|
|
8031
8454
|
}
|
|
8032
8455
|
walk2(blessedDir, 0);
|
|
@@ -8065,7 +8488,7 @@ function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
|
|
|
8065
8488
|
for (const entry of entries) {
|
|
8066
8489
|
if (!entry.isDirectory()) continue;
|
|
8067
8490
|
if (WALK_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
8068
|
-
walk2(
|
|
8491
|
+
walk2(join31(dir, entry.name), depth + 1);
|
|
8069
8492
|
}
|
|
8070
8493
|
}
|
|
8071
8494
|
}
|
|
@@ -8074,13 +8497,13 @@ function isNearMissRoot(dir, entries) {
|
|
|
8074
8497
|
(entry) => entry.isDirectory() && entry.name === REFERENCE_BUILD_DIRNAME
|
|
8075
8498
|
);
|
|
8076
8499
|
if (!hasReferenceBuild) return false;
|
|
8077
|
-
const approved =
|
|
8500
|
+
const approved = join31(dir, APPROVED_CAPTURES_SUBPATH);
|
|
8078
8501
|
try {
|
|
8079
|
-
if (!
|
|
8502
|
+
if (!statSync11(approved).isDirectory()) return false;
|
|
8080
8503
|
} catch {
|
|
8081
8504
|
return false;
|
|
8082
8505
|
}
|
|
8083
|
-
return !
|
|
8506
|
+
return !existsSync29(join31(dir, REFERENCE_BUILD_DIRNAME, BLESSED_MANIFEST_FILENAME));
|
|
8084
8507
|
}
|
|
8085
8508
|
function resolveBlessedBuild(blessedDir, shape, pattern2) {
|
|
8086
8509
|
const { builds } = discoverBlessedBuilds(blessedDir);
|
|
@@ -8094,11 +8517,11 @@ function resolveBlessedBuild(blessedDir, shape, pattern2) {
|
|
|
8094
8517
|
var BLESSED_ROOT_DIRNAME = "design-prototypes";
|
|
8095
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.`;
|
|
8096
8519
|
function discoverBlessedRoot(cwd) {
|
|
8097
|
-
let dir =
|
|
8520
|
+
let dir = resolve20(cwd);
|
|
8098
8521
|
for (; ; ) {
|
|
8099
|
-
const candidate =
|
|
8100
|
-
if (
|
|
8101
|
-
const parent =
|
|
8522
|
+
const candidate = join32(dir, BLESSED_ROOT_DIRNAME);
|
|
8523
|
+
if (existsSync30(candidate) && statSync12(candidate).isDirectory()) return candidate;
|
|
8524
|
+
const parent = dirname14(dir);
|
|
8102
8525
|
if (parent === dir) return void 0;
|
|
8103
8526
|
dir = parent;
|
|
8104
8527
|
}
|
|
@@ -8133,25 +8556,25 @@ function parseBlessedIdentifier(from) {
|
|
|
8133
8556
|
function resolveBlessedDir(cwd, options) {
|
|
8134
8557
|
const explicit = options.blessedDir ?? (process.env.VISOR_BLESSED_DIR && process.env.VISOR_BLESSED_DIR.length > 0 ? process.env.VISOR_BLESSED_DIR : void 0);
|
|
8135
8558
|
if (explicit !== void 0) {
|
|
8136
|
-
return
|
|
8559
|
+
return isAbsolute6(explicit) ? explicit : resolve20(cwd, explicit);
|
|
8137
8560
|
}
|
|
8138
8561
|
return discoverBlessedRoot(cwd);
|
|
8139
8562
|
}
|
|
8140
8563
|
function resolveThemeFile(theme2, cwd, themeFile) {
|
|
8141
8564
|
const candidates = [];
|
|
8142
8565
|
if (themeFile) {
|
|
8143
|
-
candidates.push(
|
|
8566
|
+
candidates.push(isAbsolute6(themeFile) ? themeFile : resolve20(cwd, themeFile));
|
|
8144
8567
|
}
|
|
8145
|
-
candidates.push(
|
|
8568
|
+
candidates.push(isAbsolute6(theme2) ? theme2 : resolve20(cwd, theme2));
|
|
8146
8569
|
const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
|
|
8147
8570
|
if (privateRoot && privateRoot.length > 0) {
|
|
8148
|
-
candidates.push(
|
|
8571
|
+
candidates.push(join32(privateRoot, "themes", theme2, "theme.visor.yaml"));
|
|
8149
8572
|
}
|
|
8150
8573
|
candidates.push(
|
|
8151
|
-
|
|
8152
|
-
|
|
8574
|
+
join32(cwd, "themes", `${theme2}.visor.yaml`),
|
|
8575
|
+
join32(cwd, "custom-themes", `${theme2}.visor.yaml`)
|
|
8153
8576
|
);
|
|
8154
|
-
const found = candidates.find((candidate) =>
|
|
8577
|
+
const found = candidates.find((candidate) => existsSync30(candidate));
|
|
8155
8578
|
if (!found) {
|
|
8156
8579
|
throw new Error(
|
|
8157
8580
|
`Theme '${theme2}' not found (searched: ${candidates.join(", ")}). Pass --theme-file <path>, use a direct path, or set VISOR_THEMES_PRIVATE_PATH.`
|
|
@@ -8160,7 +8583,7 @@ function resolveThemeFile(theme2, cwd, themeFile) {
|
|
|
8160
8583
|
return { path: found, searched: candidates };
|
|
8161
8584
|
}
|
|
8162
8585
|
function applyThemeToFork(themeFile, outputDir, manifest) {
|
|
8163
|
-
const yaml =
|
|
8586
|
+
const yaml = readFileSync32(themeFile, "utf-8");
|
|
8164
8587
|
const data = generateThemeData7(yaml);
|
|
8165
8588
|
const themeId = data.config.name;
|
|
8166
8589
|
if (!themeId || themeId.length === 0) {
|
|
@@ -8207,8 +8630,8 @@ Available builds:
|
|
|
8207
8630
|
${list}`
|
|
8208
8631
|
);
|
|
8209
8632
|
}
|
|
8210
|
-
const outputDir =
|
|
8211
|
-
if (
|
|
8633
|
+
const outputDir = isAbsolute6(options.output) ? options.output : resolve20(cwd, options.output);
|
|
8634
|
+
if (existsSync30(outputDir) && readdirSync16(outputDir).length > 0) {
|
|
8212
8635
|
throw new Error(`Output directory already exists and is not empty: ${outputDir}`);
|
|
8213
8636
|
}
|
|
8214
8637
|
const { path: themeFile } = resolveThemeFile(options.theme, cwd, options.themeFile);
|
|
@@ -8221,7 +8644,7 @@ ${list}`
|
|
|
8221
8644
|
try {
|
|
8222
8645
|
applyThemeToFork(themeFile, outputDir, build.manifest);
|
|
8223
8646
|
if (options.validate) {
|
|
8224
|
-
const parsed = parseYaml5(
|
|
8647
|
+
const parsed = parseYaml5(readFileSync32(themeFile, "utf-8"));
|
|
8225
8648
|
const result = validate3(parsed);
|
|
8226
8649
|
if (!result.valid) {
|
|
8227
8650
|
const messages = result.errors.map((e) => e.message).join("; ");
|
|
@@ -8333,8 +8756,8 @@ function spawnCommand(cwd, options) {
|
|
|
8333
8756
|
}
|
|
8334
8757
|
|
|
8335
8758
|
// src/commands/render.ts
|
|
8336
|
-
import { existsSync as
|
|
8337
|
-
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";
|
|
8338
8761
|
var FIXTURES = {
|
|
8339
8762
|
"stat-card": {
|
|
8340
8763
|
default: {
|
|
@@ -8486,28 +8909,28 @@ function pascalCase(kebab) {
|
|
|
8486
8909
|
}
|
|
8487
8910
|
function firstExisting(candidates) {
|
|
8488
8911
|
for (const c of candidates) {
|
|
8489
|
-
if (
|
|
8912
|
+
if (existsSync31(c)) return c;
|
|
8490
8913
|
}
|
|
8491
8914
|
return null;
|
|
8492
8915
|
}
|
|
8493
8916
|
function resolveComponentFile(cwd, name) {
|
|
8494
8917
|
return firstExisting([
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8918
|
+
resolve21(cwd, "components", "ui", name, `${name}.tsx`),
|
|
8919
|
+
resolve21(cwd, "components", "devtools", name, `${name}.tsx`),
|
|
8920
|
+
resolve21(cwd, "components", "ui", name, "index.tsx")
|
|
8498
8921
|
]);
|
|
8499
8922
|
}
|
|
8500
8923
|
function resolveThemeCssFile(cwd, slug2) {
|
|
8501
8924
|
return firstExisting([
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8925
|
+
resolve21(cwd, "packages", "docs", "app", `${slug2}-theme.css`),
|
|
8926
|
+
resolve21(cwd, "app", `${slug2}-theme.css`),
|
|
8927
|
+
resolve21(cwd, `${slug2}-theme.css`)
|
|
8505
8928
|
]);
|
|
8506
8929
|
}
|
|
8507
8930
|
function resolveTokensCssFile(cwd) {
|
|
8508
8931
|
return firstExisting([
|
|
8509
|
-
|
|
8510
|
-
|
|
8932
|
+
resolve21(cwd, "packages", "tokens", "dist", "tokens.css"),
|
|
8933
|
+
resolve21(cwd, "node_modules", "@loworbitstudio", "visor-core", "dist", "tokens.css")
|
|
8511
8934
|
]);
|
|
8512
8935
|
}
|
|
8513
8936
|
async function loadOptional(moduleName) {
|
|
@@ -8686,8 +9109,8 @@ async function renderCommand(component, cwd, options) {
|
|
|
8686
9109
|
}
|
|
8687
9110
|
const themeClass = `${options.theme}-theme`;
|
|
8688
9111
|
const html = buildHtml({
|
|
8689
|
-
tokensCss:
|
|
8690
|
-
themeCss:
|
|
9112
|
+
tokensCss: readFileSync33(tokensCssFile, "utf-8"),
|
|
9113
|
+
themeCss: readFileSync33(themeCssFile, "utf-8"),
|
|
8691
9114
|
componentCss,
|
|
8692
9115
|
bundleJs,
|
|
8693
9116
|
themeClass,
|
|
@@ -8696,7 +9119,7 @@ async function renderCommand(component, cwd, options) {
|
|
|
8696
9119
|
const width = Number(options.width ?? 720);
|
|
8697
9120
|
const height = Number(options.height ?? 640);
|
|
8698
9121
|
const outPath = resolveOutPath(cwd, options.out, component, options.theme, mode, state);
|
|
8699
|
-
mkdirSync14(
|
|
9122
|
+
mkdirSync14(dirname15(outPath), { recursive: true });
|
|
8700
9123
|
const browser = await playwright.chromium.launch();
|
|
8701
9124
|
let probe;
|
|
8702
9125
|
try {
|
|
@@ -8752,7 +9175,7 @@ async function renderCommand(component, cwd, options) {
|
|
|
8752
9175
|
} finally {
|
|
8753
9176
|
await browser.close();
|
|
8754
9177
|
}
|
|
8755
|
-
const fileSize =
|
|
9178
|
+
const fileSize = existsSync31(outPath) ? statBytes(outPath) : 0;
|
|
8756
9179
|
if (json) {
|
|
8757
9180
|
console.log(
|
|
8758
9181
|
JSON.stringify(
|
|
@@ -8788,14 +9211,14 @@ async function renderCommand(component, cwd, options) {
|
|
|
8788
9211
|
}
|
|
8789
9212
|
function resolveOutPath(cwd, out, component, theme2, mode, state) {
|
|
8790
9213
|
if (out) {
|
|
8791
|
-
return
|
|
9214
|
+
return isAbsolute7(out) ? out : resolve21(cwd, out);
|
|
8792
9215
|
}
|
|
8793
9216
|
const suffix = state === "default" ? "" : `__${state}`;
|
|
8794
|
-
return
|
|
9217
|
+
return resolve21(cwd, ".visor", "renders", `${component}__${theme2}__${mode}${suffix}.png`);
|
|
8795
9218
|
}
|
|
8796
9219
|
function statBytes(path2) {
|
|
8797
9220
|
try {
|
|
8798
|
-
return
|
|
9221
|
+
return readFileSync33(path2).length;
|
|
8799
9222
|
} catch {
|
|
8800
9223
|
return 0;
|
|
8801
9224
|
}
|
|
@@ -8808,9 +9231,9 @@ function formatBytes2(bytes) {
|
|
|
8808
9231
|
}
|
|
8809
9232
|
|
|
8810
9233
|
// src/index.ts
|
|
8811
|
-
var __dirname2 =
|
|
9234
|
+
var __dirname2 = dirname16(fileURLToPath4(import.meta.url));
|
|
8812
9235
|
var pkg = JSON.parse(
|
|
8813
|
-
|
|
9236
|
+
readFileSync34(join34(__dirname2, "..", "package.json"), "utf-8")
|
|
8814
9237
|
);
|
|
8815
9238
|
var program = new Command2();
|
|
8816
9239
|
program.name("visor").description("CLI for the Visor design system").version(pkg.version);
|