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