@loworbitstudio/visor 1.14.0 → 1.16.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 +1112 -356
- package/dist/init-plays.json +22 -0
- package/dist/visor-manifest.json +1 -1
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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 readFileSync32 } from "fs";
|
|
5
|
+
import { dirname as dirname12, join as join32 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
7
7
|
import { Command as Command2 } from "commander";
|
|
8
8
|
|
|
@@ -1191,10 +1191,10 @@ function checkCommand() {
|
|
|
1191
1191
|
}
|
|
1192
1192
|
|
|
1193
1193
|
// src/commands/init.ts
|
|
1194
|
-
import { existsSync as
|
|
1195
|
-
import { join as
|
|
1194
|
+
import { existsSync as existsSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync9 } from "fs";
|
|
1195
|
+
import { join as join8, dirname as dirname4, basename as basename2 } from "path";
|
|
1196
1196
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1197
|
-
import * as
|
|
1197
|
+
import * as childProcess2 from "child_process";
|
|
1198
1198
|
|
|
1199
1199
|
// src/config/config.ts
|
|
1200
1200
|
import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync2 } from "fs";
|
|
@@ -1378,6 +1378,156 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
1378
1378
|
`;
|
|
1379
1379
|
}
|
|
1380
1380
|
|
|
1381
|
+
// src/commands/init-plays/registry.ts
|
|
1382
|
+
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
|
|
1383
|
+
import { dirname as dirname3, join as join6 } from "path";
|
|
1384
|
+
|
|
1385
|
+
// src/commands/init-plays/pattern-build.ts
|
|
1386
|
+
var patternBuildPlay = {
|
|
1387
|
+
id: "pattern-build",
|
|
1388
|
+
loSubdir: "pattern-builds",
|
|
1389
|
+
label: "Pattern build",
|
|
1390
|
+
description: "Design + converge a reusable Borealis pattern (admin-ui, forms, ...)."
|
|
1391
|
+
};
|
|
1392
|
+
|
|
1393
|
+
// src/commands/init-plays/new-web-app.ts
|
|
1394
|
+
var newWebAppPlay = {
|
|
1395
|
+
id: "new-web-app",
|
|
1396
|
+
loSubdir: "new-web-apps",
|
|
1397
|
+
label: "New web app",
|
|
1398
|
+
description: "A fresh NextJS app entering the Playbook lifecycle."
|
|
1399
|
+
};
|
|
1400
|
+
|
|
1401
|
+
// src/commands/init-plays/feature-addition.ts
|
|
1402
|
+
var featureAdditionPlay = {
|
|
1403
|
+
id: "feature-addition",
|
|
1404
|
+
loSubdir: "feature-additions",
|
|
1405
|
+
label: "Feature addition",
|
|
1406
|
+
description: "An existing project onboarding into the Playbook mid-stream (safe, idempotent)."
|
|
1407
|
+
};
|
|
1408
|
+
|
|
1409
|
+
// src/commands/init-plays/registry.ts
|
|
1410
|
+
var KNOWN_PLAYS = [
|
|
1411
|
+
patternBuildPlay,
|
|
1412
|
+
newWebAppPlay,
|
|
1413
|
+
featureAdditionPlay
|
|
1414
|
+
];
|
|
1415
|
+
function getPlay(id) {
|
|
1416
|
+
return KNOWN_PLAYS.find((p) => p.id === id);
|
|
1417
|
+
}
|
|
1418
|
+
function knownPlayIds() {
|
|
1419
|
+
return KNOWN_PLAYS.map((p) => p.id);
|
|
1420
|
+
}
|
|
1421
|
+
function playStatePaths(cwd, def, name) {
|
|
1422
|
+
const relDir = join6(".lo", def.loSubdir, name);
|
|
1423
|
+
const dir = join6(cwd, relDir);
|
|
1424
|
+
return {
|
|
1425
|
+
dir,
|
|
1426
|
+
statePath: join6(dir, "state.json"),
|
|
1427
|
+
relStatePath: join6(relDir, "state.json")
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1430
|
+
function readPlayState(statePath) {
|
|
1431
|
+
if (!existsSync4(statePath)) return null;
|
|
1432
|
+
try {
|
|
1433
|
+
return JSON.parse(readFileSync7(statePath, "utf-8"));
|
|
1434
|
+
} catch {
|
|
1435
|
+
return null;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
function writePlayState(statePath, state) {
|
|
1439
|
+
mkdirSync(dirname3(statePath), { recursive: true });
|
|
1440
|
+
writeFileSync2(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// src/lib/lo-ports-bridge.ts
|
|
1444
|
+
import * as childProcess from "child_process";
|
|
1445
|
+
var LO_PORTS_COMMAND = "lo-ports";
|
|
1446
|
+
var LO_PORTS_ARGS = ["next", "--json"];
|
|
1447
|
+
var FALLBACK_RANGE_START = 4200;
|
|
1448
|
+
var FALLBACK_RANGE_SIZE = 100;
|
|
1449
|
+
function allocatePort(name, options = {}) {
|
|
1450
|
+
const runCommand = options.runCommand ?? defaultRunCommand;
|
|
1451
|
+
const stdout = safeRun(runCommand);
|
|
1452
|
+
const fromRegistry = stdout != null ? parsePort(stdout) : null;
|
|
1453
|
+
if (fromRegistry != null) {
|
|
1454
|
+
return { port: fromRegistry, source: "lo-ports" };
|
|
1455
|
+
}
|
|
1456
|
+
const port = heuristicPort(name);
|
|
1457
|
+
return {
|
|
1458
|
+
port,
|
|
1459
|
+
source: "fallback",
|
|
1460
|
+
warning: `Could not allocate a dev port via /lo-ports (no \`${LO_PORTS_COMMAND}\` command found). Used heuristic port ${port} instead \u2014 register it with \`/lo-ports\` when convenient.`
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
function safeRun(runCommand) {
|
|
1464
|
+
try {
|
|
1465
|
+
return runCommand();
|
|
1466
|
+
} catch {
|
|
1467
|
+
return null;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
function defaultRunCommand() {
|
|
1471
|
+
const result = childProcess.spawnSync(LO_PORTS_COMMAND, LO_PORTS_ARGS, {
|
|
1472
|
+
encoding: "utf-8"
|
|
1473
|
+
});
|
|
1474
|
+
if (result.error) return null;
|
|
1475
|
+
if (typeof result.status === "number" && result.status !== 0) return null;
|
|
1476
|
+
return result.stdout ?? null;
|
|
1477
|
+
}
|
|
1478
|
+
function parsePort(stdout) {
|
|
1479
|
+
const trimmed = stdout.trim();
|
|
1480
|
+
if (trimmed.length === 0) return null;
|
|
1481
|
+
try {
|
|
1482
|
+
const parsed = JSON.parse(trimmed);
|
|
1483
|
+
if (typeof parsed === "number" && isValidPort(parsed)) return parsed;
|
|
1484
|
+
if (parsed != null && typeof parsed === "object" && "port" in parsed && isValidPort(parsed.port)) {
|
|
1485
|
+
return parsed.port;
|
|
1486
|
+
}
|
|
1487
|
+
} catch {
|
|
1488
|
+
}
|
|
1489
|
+
const bare = Number(trimmed);
|
|
1490
|
+
return isValidPort(bare) ? bare : null;
|
|
1491
|
+
}
|
|
1492
|
+
function isValidPort(value) {
|
|
1493
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0 && value < 65536;
|
|
1494
|
+
}
|
|
1495
|
+
function heuristicPort(name) {
|
|
1496
|
+
let hash = 0;
|
|
1497
|
+
for (let i = 0; i < name.length; i++) {
|
|
1498
|
+
hash = hash * 31 + name.charCodeAt(i) >>> 0;
|
|
1499
|
+
}
|
|
1500
|
+
return FALLBACK_RANGE_START + hash % FALLBACK_RANGE_SIZE;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
// src/lib/lo-play-checklist.ts
|
|
1504
|
+
import { existsSync as existsSync5, readFileSync as readFileSync8 } from "fs";
|
|
1505
|
+
import { homedir } from "os";
|
|
1506
|
+
import { join as join7 } from "path";
|
|
1507
|
+
function defaultChecklistRoot() {
|
|
1508
|
+
return join7(homedir(), ".claude", "skills", "lo-play");
|
|
1509
|
+
}
|
|
1510
|
+
function readEntryChecklist(playType, options = {}) {
|
|
1511
|
+
const root = options.root ?? defaultChecklistRoot();
|
|
1512
|
+
const path2 = join7(root, playType, "entry-checklist.md");
|
|
1513
|
+
if (!existsSync5(path2)) {
|
|
1514
|
+
return {
|
|
1515
|
+
found: false,
|
|
1516
|
+
path: path2,
|
|
1517
|
+
fallbackMessage: `Entry checklist not found at ${path2}. Run the play manually from \`/lo-play ${playType}\`.`
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
try {
|
|
1521
|
+
return { found: true, path: path2, content: readFileSync8(path2, "utf-8") };
|
|
1522
|
+
} catch {
|
|
1523
|
+
return {
|
|
1524
|
+
found: false,
|
|
1525
|
+
path: path2,
|
|
1526
|
+
fallbackMessage: `Entry checklist at ${path2} could not be read. Run the play manually from \`/lo-play ${playType}\`.`
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1381
1531
|
// src/commands/init.ts
|
|
1382
1532
|
import { generateThemeData as generateThemeData2 } from "@loworbitstudio/visor-theme-engine";
|
|
1383
1533
|
import { nextjsAdapter } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
@@ -1390,7 +1540,27 @@ function initCommand(cwd, options) {
|
|
|
1390
1540
|
emitError(json, `Unknown template: ${options.template}. Available templates: nextjs`);
|
|
1391
1541
|
process.exit(1);
|
|
1392
1542
|
}
|
|
1393
|
-
|
|
1543
|
+
let playDef;
|
|
1544
|
+
let playName = "";
|
|
1545
|
+
if (options?.for) {
|
|
1546
|
+
playDef = getPlay(options.for);
|
|
1547
|
+
if (!playDef) {
|
|
1548
|
+
emitError(
|
|
1549
|
+
json,
|
|
1550
|
+
`Unknown play: ${options.for}. Known plays: ${knownPlayIds().join(", ")}`
|
|
1551
|
+
);
|
|
1552
|
+
process.exit(1);
|
|
1553
|
+
}
|
|
1554
|
+
playName = options.playName ?? basename2(cwd);
|
|
1555
|
+
if (!/^[a-z0-9][a-z0-9-_]*$/i.test(playName)) {
|
|
1556
|
+
emitError(
|
|
1557
|
+
json,
|
|
1558
|
+
`Invalid play name '${playName}'. Use letters, digits, '-' or '_' (e.g. --play-name organization-management).`
|
|
1559
|
+
);
|
|
1560
|
+
process.exit(1);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
if (options?.template === "nextjs" && existsSync6(join8(cwd, "package.json"))) {
|
|
1394
1564
|
emitError(
|
|
1395
1565
|
json,
|
|
1396
1566
|
"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"
|
|
@@ -1431,6 +1601,10 @@ function initCommand(cwd, options) {
|
|
|
1431
1601
|
}
|
|
1432
1602
|
}
|
|
1433
1603
|
}
|
|
1604
|
+
let playResult;
|
|
1605
|
+
if (playDef) {
|
|
1606
|
+
playResult = runPlayInit(cwd, playDef, playName, options ?? {}, json, warnings);
|
|
1607
|
+
}
|
|
1434
1608
|
if (json) {
|
|
1435
1609
|
const nextSteps = buildNextSteps(options, warnings);
|
|
1436
1610
|
const result = {
|
|
@@ -1438,12 +1612,91 @@ function initCommand(cwd, options) {
|
|
|
1438
1612
|
config: DEFAULT_CONFIG,
|
|
1439
1613
|
files: { created: filesCreated, skipped: filesSkipped },
|
|
1440
1614
|
warnings,
|
|
1441
|
-
nextSteps
|
|
1615
|
+
nextSteps,
|
|
1616
|
+
...playResult ? { play: playResult } : {}
|
|
1442
1617
|
};
|
|
1443
1618
|
console.log(JSON.stringify(result, null, 2));
|
|
1444
1619
|
process.exit(0);
|
|
1445
1620
|
}
|
|
1446
1621
|
}
|
|
1622
|
+
function runPlayInit(cwd, def, name, options, json, warnings) {
|
|
1623
|
+
const { statePath, relStatePath } = playStatePaths(cwd, def, name);
|
|
1624
|
+
const existing = readPlayState(statePath);
|
|
1625
|
+
const checklist = readEntryChecklist(def.id);
|
|
1626
|
+
if (existing) {
|
|
1627
|
+
if (!json) {
|
|
1628
|
+
logger.blank();
|
|
1629
|
+
logger.warn(
|
|
1630
|
+
`Play '${def.id}' / '${name}' already initialized at ${relStatePath} (phase ${existing.phase}${existing.devPort ? `, port ${existing.devPort}` : ""}). Nothing to do.`
|
|
1631
|
+
);
|
|
1632
|
+
printChecklist(def.id, checklist);
|
|
1633
|
+
}
|
|
1634
|
+
return {
|
|
1635
|
+
play: def.id,
|
|
1636
|
+
name,
|
|
1637
|
+
statePath: relStatePath,
|
|
1638
|
+
phase: existing.phase,
|
|
1639
|
+
devPort: existing.devPort,
|
|
1640
|
+
portSource: existing.portSource,
|
|
1641
|
+
theme: existing.theme,
|
|
1642
|
+
from: existing.from,
|
|
1643
|
+
alreadyInitialized: true,
|
|
1644
|
+
checklist: { found: checklist.found, path: checklist.path }
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
const port = allocatePort(name);
|
|
1648
|
+
if (port.warning) warnings.push(port.warning);
|
|
1649
|
+
const state = {
|
|
1650
|
+
play: def.id,
|
|
1651
|
+
name,
|
|
1652
|
+
phase: 0,
|
|
1653
|
+
...options.theme ? { theme: options.theme } : {},
|
|
1654
|
+
...options.from ? { from: options.from } : {},
|
|
1655
|
+
devPort: port.port,
|
|
1656
|
+
portSource: port.source,
|
|
1657
|
+
createdWith: `@loworbitstudio/visor@${readVisorCliVersion()}`,
|
|
1658
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1659
|
+
};
|
|
1660
|
+
writePlayState(statePath, state);
|
|
1661
|
+
if (!json) {
|
|
1662
|
+
logger.blank();
|
|
1663
|
+
if (options.template === "nextjs") {
|
|
1664
|
+
logger.success("Visor scaffold: NextJS + tokens + FOWT layout");
|
|
1665
|
+
}
|
|
1666
|
+
logger.success(`Playbook state: ${relStatePath} (phase 0)`);
|
|
1667
|
+
if (port.source === "lo-ports") {
|
|
1668
|
+
logger.success(`Dev port allocated: ${port.port} (via /lo-ports)`);
|
|
1669
|
+
} else {
|
|
1670
|
+
logger.warn(port.warning ?? `Dev port ${port.port} (heuristic fallback)`);
|
|
1671
|
+
logger.success(`Dev port: ${port.port} (heuristic fallback)`);
|
|
1672
|
+
}
|
|
1673
|
+
if (options.theme) logger.success(`Theme recorded: ${options.theme}`);
|
|
1674
|
+
printChecklist(def.id, checklist);
|
|
1675
|
+
}
|
|
1676
|
+
return {
|
|
1677
|
+
play: def.id,
|
|
1678
|
+
name,
|
|
1679
|
+
statePath: relStatePath,
|
|
1680
|
+
phase: 0,
|
|
1681
|
+
devPort: port.port,
|
|
1682
|
+
portSource: port.source,
|
|
1683
|
+
theme: options.theme,
|
|
1684
|
+
from: options.from,
|
|
1685
|
+
alreadyInitialized: false,
|
|
1686
|
+
checklist: { found: checklist.found, path: checklist.path }
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
function printChecklist(playId, checklist) {
|
|
1690
|
+
logger.blank();
|
|
1691
|
+
if (checklist.found) {
|
|
1692
|
+
logger.info(`Next steps (from ${checklist.path}):`);
|
|
1693
|
+
for (const line of checklist.content.split("\n")) {
|
|
1694
|
+
logger.item(line);
|
|
1695
|
+
}
|
|
1696
|
+
} else {
|
|
1697
|
+
logger.warn(checklist.fallbackMessage);
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1447
1700
|
function buildNextSteps(options, warnings) {
|
|
1448
1701
|
const steps = [];
|
|
1449
1702
|
if (options?.template === "nextjs") {
|
|
@@ -1473,14 +1726,14 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
|
|
|
1473
1726
|
}
|
|
1474
1727
|
runCreateNextApp(cwd, json);
|
|
1475
1728
|
runInstallVisorDeps(cwd, json);
|
|
1476
|
-
const yamlPath =
|
|
1477
|
-
if (
|
|
1729
|
+
const yamlPath = join8(cwd, ".visor.yaml");
|
|
1730
|
+
if (existsSync6(yamlPath)) {
|
|
1478
1731
|
filesSkipped.push(".visor.yaml");
|
|
1479
1732
|
if (!json) {
|
|
1480
1733
|
logger.warn(".visor.yaml already exists. Skipping.");
|
|
1481
1734
|
}
|
|
1482
1735
|
} else {
|
|
1483
|
-
|
|
1736
|
+
writeFileSync3(yamlPath, NEXTJS_STARTER_YAML, "utf-8");
|
|
1484
1737
|
filesCreated.push(".visor.yaml");
|
|
1485
1738
|
if (!json) {
|
|
1486
1739
|
logger.success("Created .visor.yaml");
|
|
@@ -1492,40 +1745,40 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
|
|
|
1492
1745
|
tokens: data.tokens,
|
|
1493
1746
|
config: data.config
|
|
1494
1747
|
});
|
|
1495
|
-
const globalsPath =
|
|
1496
|
-
const globalsDir =
|
|
1497
|
-
|
|
1498
|
-
if (
|
|
1499
|
-
|
|
1748
|
+
const globalsPath = join8(cwd, "app", "globals.css");
|
|
1749
|
+
const globalsDir = dirname4(globalsPath);
|
|
1750
|
+
mkdirSync2(globalsDir, { recursive: true });
|
|
1751
|
+
if (existsSync6(globalsPath)) {
|
|
1752
|
+
writeFileSync3(globalsPath, css, "utf-8");
|
|
1500
1753
|
filesCreated.push("app/globals.css");
|
|
1501
1754
|
} else {
|
|
1502
|
-
|
|
1755
|
+
writeFileSync3(globalsPath, css, "utf-8");
|
|
1503
1756
|
filesCreated.push("app/globals.css");
|
|
1504
1757
|
}
|
|
1505
1758
|
if (!json) {
|
|
1506
1759
|
logger.success("Created app/globals.css with theme tokens");
|
|
1507
1760
|
}
|
|
1508
|
-
const layoutPath =
|
|
1761
|
+
const layoutPath = join8(cwd, "app", "layout.tsx");
|
|
1509
1762
|
const colorScheme = extractColorScheme(NEXTJS_STARTER_YAML);
|
|
1510
|
-
|
|
1763
|
+
writeFileSync3(layoutPath, generateNextjsLayout(colorScheme), "utf-8");
|
|
1511
1764
|
filesCreated.push("app/layout.tsx");
|
|
1512
1765
|
if (!json) {
|
|
1513
1766
|
logger.success("Wired app/layout.tsx with FOWT prevention and theme tokens");
|
|
1514
1767
|
}
|
|
1515
|
-
const stampDir =
|
|
1516
|
-
const stampPath =
|
|
1517
|
-
if (
|
|
1768
|
+
const stampDir = join8(cwd, ".lo");
|
|
1769
|
+
const stampPath = join8(stampDir, "borealis.json");
|
|
1770
|
+
if (existsSync6(stampPath)) {
|
|
1518
1771
|
filesSkipped.push(".lo/borealis.json");
|
|
1519
1772
|
if (!json) {
|
|
1520
1773
|
logger.warn(".lo/borealis.json already exists. Skipping.");
|
|
1521
1774
|
}
|
|
1522
1775
|
} else {
|
|
1523
|
-
|
|
1776
|
+
mkdirSync2(stampDir, { recursive: true });
|
|
1524
1777
|
const stamp = {
|
|
1525
1778
|
visorVersion: readVisorCliVersion(),
|
|
1526
1779
|
initializedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1527
1780
|
};
|
|
1528
|
-
|
|
1781
|
+
writeFileSync3(stampPath, JSON.stringify(stamp, null, 2) + "\n", "utf-8");
|
|
1529
1782
|
filesCreated.push(".lo/borealis.json");
|
|
1530
1783
|
if (!json) {
|
|
1531
1784
|
logger.success("Stamped .lo/borealis.json");
|
|
@@ -1546,7 +1799,7 @@ function runCreateNextApp(cwd, json) {
|
|
|
1546
1799
|
if (!json) {
|
|
1547
1800
|
logger.info(`Running create-next-app@${NEXTJS_PINNED_VERSION}...`);
|
|
1548
1801
|
}
|
|
1549
|
-
const result =
|
|
1802
|
+
const result = childProcess2.spawnSync(
|
|
1550
1803
|
"npx",
|
|
1551
1804
|
[`create-next-app@${NEXTJS_PINNED_VERSION}`, ".", ...CREATE_NEXT_APP_FLAGS],
|
|
1552
1805
|
{ cwd, stdio: json ? "ignore" : "inherit" }
|
|
@@ -1557,7 +1810,7 @@ function runInstallVisorDeps(cwd, json) {
|
|
|
1557
1810
|
if (!json) {
|
|
1558
1811
|
logger.info("Installing @loworbitstudio/visor-core and visor-theme-engine...");
|
|
1559
1812
|
}
|
|
1560
|
-
const result =
|
|
1813
|
+
const result = childProcess2.spawnSync(
|
|
1561
1814
|
"npm",
|
|
1562
1815
|
[
|
|
1563
1816
|
"install",
|
|
@@ -1578,12 +1831,12 @@ function assertSpawnSuccess(result, label) {
|
|
|
1578
1831
|
}
|
|
1579
1832
|
function readVisorCliVersion() {
|
|
1580
1833
|
try {
|
|
1581
|
-
const here =
|
|
1834
|
+
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
1582
1835
|
for (let i = 0; i < 5; i++) {
|
|
1583
1836
|
const segments = new Array(i).fill("..");
|
|
1584
|
-
const candidate =
|
|
1837
|
+
const candidate = join8(here, ...segments, "package.json");
|
|
1585
1838
|
try {
|
|
1586
|
-
const pkg2 = JSON.parse(
|
|
1839
|
+
const pkg2 = JSON.parse(readFileSync9(candidate, "utf-8"));
|
|
1587
1840
|
if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) {
|
|
1588
1841
|
return pkg2.version;
|
|
1589
1842
|
}
|
|
@@ -1597,53 +1850,53 @@ function readVisorCliVersion() {
|
|
|
1597
1850
|
|
|
1598
1851
|
// src/utils/fs.ts
|
|
1599
1852
|
import {
|
|
1600
|
-
writeFileSync as
|
|
1601
|
-
readFileSync as
|
|
1602
|
-
existsSync as
|
|
1603
|
-
mkdirSync as
|
|
1853
|
+
writeFileSync as writeFileSync4,
|
|
1854
|
+
readFileSync as readFileSync10,
|
|
1855
|
+
existsSync as existsSync7,
|
|
1856
|
+
mkdirSync as mkdirSync3
|
|
1604
1857
|
} from "fs";
|
|
1605
|
-
import { dirname as
|
|
1858
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
1606
1859
|
function resolveOutputPath(registryPath, type, config, cwd) {
|
|
1607
1860
|
let relativePath;
|
|
1608
1861
|
if (type === "registry:block") {
|
|
1609
1862
|
relativePath = registryPath.replace(/^blocks\//, "");
|
|
1610
|
-
return
|
|
1863
|
+
return join9(cwd, config.paths.blocks, relativePath);
|
|
1611
1864
|
}
|
|
1612
1865
|
if (type === "registry:ui") {
|
|
1613
1866
|
if (registryPath.startsWith("components/deck/")) {
|
|
1614
1867
|
relativePath = registryPath.replace(/^components\/deck\//, "");
|
|
1615
|
-
return
|
|
1868
|
+
return join9(cwd, config.paths.deckComponents, relativePath);
|
|
1616
1869
|
}
|
|
1617
1870
|
if (registryPath.startsWith("components/flutter/")) {
|
|
1618
1871
|
relativePath = registryPath.replace(/^components\/flutter\//, "");
|
|
1619
|
-
return
|
|
1872
|
+
return join9(cwd, config.paths.flutterComponents, relativePath);
|
|
1620
1873
|
}
|
|
1621
1874
|
relativePath = registryPath.replace(/^components\/ui\//, "");
|
|
1622
|
-
return
|
|
1875
|
+
return join9(cwd, config.paths.components, relativePath);
|
|
1623
1876
|
}
|
|
1624
1877
|
if (type === "registry:hook") {
|
|
1625
1878
|
relativePath = registryPath.replace(/^hooks\//, "");
|
|
1626
|
-
return
|
|
1879
|
+
return join9(cwd, config.paths.hooks, relativePath);
|
|
1627
1880
|
}
|
|
1628
1881
|
if (type === "registry:lib") {
|
|
1629
1882
|
relativePath = registryPath.replace(/^lib\//, "");
|
|
1630
|
-
return
|
|
1883
|
+
return join9(cwd, config.paths.lib, relativePath);
|
|
1631
1884
|
}
|
|
1632
|
-
return
|
|
1885
|
+
return join9(cwd, registryPath);
|
|
1633
1886
|
}
|
|
1634
1887
|
function writeFile(filePath, content) {
|
|
1635
|
-
const dir =
|
|
1636
|
-
if (!
|
|
1637
|
-
|
|
1888
|
+
const dir = dirname5(filePath);
|
|
1889
|
+
if (!existsSync7(dir)) {
|
|
1890
|
+
mkdirSync3(dir, { recursive: true });
|
|
1638
1891
|
}
|
|
1639
|
-
|
|
1892
|
+
writeFileSync4(filePath, content, "utf-8");
|
|
1640
1893
|
}
|
|
1641
1894
|
function readFile(filePath) {
|
|
1642
|
-
if (!
|
|
1643
|
-
return
|
|
1895
|
+
if (!existsSync7(filePath)) return null;
|
|
1896
|
+
return readFileSync10(filePath, "utf-8");
|
|
1644
1897
|
}
|
|
1645
1898
|
function fileExists(filePath) {
|
|
1646
|
-
return
|
|
1899
|
+
return existsSync7(filePath);
|
|
1647
1900
|
}
|
|
1648
1901
|
|
|
1649
1902
|
// src/commands/list.ts
|
|
@@ -1784,8 +2037,8 @@ function listCommand(cwd, options = {}) {
|
|
|
1784
2037
|
}
|
|
1785
2038
|
|
|
1786
2039
|
// src/utils/pubspec.ts
|
|
1787
|
-
import { existsSync as
|
|
1788
|
-
import { join as
|
|
2040
|
+
import { existsSync as existsSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
|
|
2041
|
+
import { join as join10 } from "path";
|
|
1789
2042
|
import { parseDocument, YAMLMap } from "yaml";
|
|
1790
2043
|
function mergePubspec(pubspecText, deps) {
|
|
1791
2044
|
const doc = parseDocument(pubspecText);
|
|
@@ -1807,14 +2060,14 @@ function mergePubspec(pubspecText, deps) {
|
|
|
1807
2060
|
return { text: doc.toString(), added, skipped };
|
|
1808
2061
|
}
|
|
1809
2062
|
function pubspecPath(cwd) {
|
|
1810
|
-
return
|
|
2063
|
+
return join10(cwd, "pubspec.yaml");
|
|
1811
2064
|
}
|
|
1812
2065
|
function pubspecExists(cwd) {
|
|
1813
|
-
return
|
|
2066
|
+
return existsSync8(pubspecPath(cwd));
|
|
1814
2067
|
}
|
|
1815
2068
|
function isPubPackageInstalled(packageName, cwd) {
|
|
1816
2069
|
if (!pubspecExists(cwd)) return false;
|
|
1817
|
-
const text =
|
|
2070
|
+
const text = readFileSync11(pubspecPath(cwd), "utf-8");
|
|
1818
2071
|
const doc = parseDocument(text);
|
|
1819
2072
|
const depsNode = doc.get("dependencies");
|
|
1820
2073
|
if (!(depsNode instanceof YAMLMap)) return false;
|
|
@@ -1825,24 +2078,24 @@ function getUninstalledPubDeps(deps, cwd) {
|
|
|
1825
2078
|
}
|
|
1826
2079
|
function addPubDependencies(deps, cwd) {
|
|
1827
2080
|
const path2 = pubspecPath(cwd);
|
|
1828
|
-
if (!
|
|
2081
|
+
if (!existsSync8(path2)) {
|
|
1829
2082
|
throw new Error(
|
|
1830
2083
|
`No pubspec.yaml found at ${path2}. Run this command from a Flutter project root.`
|
|
1831
2084
|
);
|
|
1832
2085
|
}
|
|
1833
|
-
const text =
|
|
2086
|
+
const text = readFileSync11(path2, "utf-8");
|
|
1834
2087
|
const result = mergePubspec(text, deps);
|
|
1835
2088
|
if (result.added.length > 0) {
|
|
1836
|
-
|
|
2089
|
+
writeFileSync5(path2, result.text, "utf-8");
|
|
1837
2090
|
}
|
|
1838
2091
|
return result;
|
|
1839
2092
|
}
|
|
1840
2093
|
|
|
1841
2094
|
// src/utils/flutter.ts
|
|
1842
2095
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1843
|
-
import { existsSync as
|
|
1844
|
-
import { homedir } from "os";
|
|
1845
|
-
import { join as
|
|
2096
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
|
|
2097
|
+
import { homedir as homedir2 } from "os";
|
|
2098
|
+
import { join as join11 } from "path";
|
|
1846
2099
|
function isExecutable(path2) {
|
|
1847
2100
|
try {
|
|
1848
2101
|
const s = statSync4(path2);
|
|
@@ -1857,20 +2110,20 @@ function fromPath(env) {
|
|
|
1857
2110
|
const bin = process.platform === "win32" ? "flutter.bat" : "flutter";
|
|
1858
2111
|
for (const dir of pathVar.split(sep2)) {
|
|
1859
2112
|
if (!dir) continue;
|
|
1860
|
-
const candidate =
|
|
2113
|
+
const candidate = join11(dir, bin);
|
|
1861
2114
|
if (isExecutable(candidate)) return candidate;
|
|
1862
2115
|
}
|
|
1863
2116
|
return null;
|
|
1864
2117
|
}
|
|
1865
2118
|
function fromFvm(home) {
|
|
1866
|
-
const fvmDefault =
|
|
2119
|
+
const fvmDefault = join11(home, "fvm", "default", "bin", "flutter");
|
|
1867
2120
|
if (isExecutable(fvmDefault)) return fvmDefault;
|
|
1868
|
-
const versionsDir =
|
|
1869
|
-
if (!
|
|
2121
|
+
const versionsDir = join11(home, "fvm", "versions");
|
|
2122
|
+
if (!existsSync9(versionsDir)) return null;
|
|
1870
2123
|
let best = null;
|
|
1871
2124
|
try {
|
|
1872
2125
|
for (const name of readdirSync3(versionsDir)) {
|
|
1873
|
-
const candidate =
|
|
2126
|
+
const candidate = join11(versionsDir, name, "bin", "flutter");
|
|
1874
2127
|
if (!isExecutable(candidate)) continue;
|
|
1875
2128
|
if (!best || compareSemver(name, best.version) > 0) {
|
|
1876
2129
|
best = { version: name, path: candidate };
|
|
@@ -1893,10 +2146,10 @@ function compareSemver(a, b) {
|
|
|
1893
2146
|
}
|
|
1894
2147
|
function findFlutterBin(options = {}) {
|
|
1895
2148
|
const env = options.env ?? process.env;
|
|
1896
|
-
const home = options.home ??
|
|
2149
|
+
const home = options.home ?? homedir2();
|
|
1897
2150
|
const envRoot = env.FLUTTER_ROOT;
|
|
1898
2151
|
if (envRoot) {
|
|
1899
|
-
const bin =
|
|
2152
|
+
const bin = join11(envRoot, "bin", "flutter");
|
|
1900
2153
|
if (isExecutable(bin)) return bin;
|
|
1901
2154
|
}
|
|
1902
2155
|
const fromPathBin = fromPath(env);
|
|
@@ -2539,8 +2792,8 @@ function infoCommand(name, cwd, options = {}) {
|
|
|
2539
2792
|
}
|
|
2540
2793
|
|
|
2541
2794
|
// src/commands/theme-apply.ts
|
|
2542
|
-
import { readFileSync as
|
|
2543
|
-
import { resolve as
|
|
2795
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
2796
|
+
import { resolve as resolve7, dirname as dirname7, join as join15 } from "path";
|
|
2544
2797
|
import { generateTheme, generateThemeData as generateThemeData3 } from "@loworbitstudio/visor-theme-engine";
|
|
2545
2798
|
import {
|
|
2546
2799
|
nextjsAdapter as nextjsAdapter2,
|
|
@@ -2549,6 +2802,143 @@ import {
|
|
|
2549
2802
|
docsAdapter,
|
|
2550
2803
|
flutterAdapter
|
|
2551
2804
|
} from "@loworbitstudio/visor-theme-engine/adapters";
|
|
2805
|
+
|
|
2806
|
+
// src/lib/blessed-manifest.ts
|
|
2807
|
+
import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
|
|
2808
|
+
import { join as join12 } from "path";
|
|
2809
|
+
import { z } from "zod";
|
|
2810
|
+
var BLESSED_MANIFEST_FILENAME = "blessed-manifest.json";
|
|
2811
|
+
var themeApplyTargetSchema = z.discriminatedUnion("kind", [
|
|
2812
|
+
z.object({
|
|
2813
|
+
kind: z.literal("globals-css"),
|
|
2814
|
+
/** Build-root-relative destination file. Defaults to `app/globals.css`. */
|
|
2815
|
+
path: z.string().min(1).optional()
|
|
2816
|
+
}).strict(),
|
|
2817
|
+
z.object({
|
|
2818
|
+
kind: z.literal("themes-css-dir"),
|
|
2819
|
+
/** Build-root-relative directory. The adapter output is written to `<path>/<theme-id>.css`. */
|
|
2820
|
+
path: z.string().min(1)
|
|
2821
|
+
}).strict()
|
|
2822
|
+
]);
|
|
2823
|
+
var blessedManifestSchema = z.object({
|
|
2824
|
+
/** Blessed-build shape, e.g. `admin-ui`. Matched against the `{shape}` in `blessed:{shape}:{pattern}`. */
|
|
2825
|
+
shape: z.string().min(1),
|
|
2826
|
+
/** Pattern name within the shape, e.g. `organization-management`. */
|
|
2827
|
+
pattern: z.string().min(1),
|
|
2828
|
+
/** The theme the reference build was authored + captured against. */
|
|
2829
|
+
base_theme: z.string().min(1),
|
|
2830
|
+
/** Minimum Visor version this build is known to work with (semver range). */
|
|
2831
|
+
requires_visor: z.string().min(1),
|
|
2832
|
+
/** Path (relative to the build root) to the approved capture baseline. */
|
|
2833
|
+
captures_baseline: z.string().min(1),
|
|
2834
|
+
/** Three-gates disposition at the time the build was blessed. */
|
|
2835
|
+
three_gates_status: z.string().min(1),
|
|
2836
|
+
/** Optional: where to write the nextjs-adapter CSS. Absent = `globals-css`. */
|
|
2837
|
+
theme_apply_target: themeApplyTargetSchema.optional()
|
|
2838
|
+
}).strict();
|
|
2839
|
+
function parseBlessedManifest(buildDir) {
|
|
2840
|
+
const manifestPath = join12(buildDir, BLESSED_MANIFEST_FILENAME);
|
|
2841
|
+
if (!existsSync10(manifestPath)) {
|
|
2842
|
+
return {
|
|
2843
|
+
ok: false,
|
|
2844
|
+
error: `this directory is not a blessed build (missing ${BLESSED_MANIFEST_FILENAME}); see docs/blessed-builds.md`
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
let raw;
|
|
2848
|
+
try {
|
|
2849
|
+
raw = readFileSync12(manifestPath, "utf-8");
|
|
2850
|
+
} catch {
|
|
2851
|
+
return { ok: false, error: `could not read ${manifestPath}` };
|
|
2852
|
+
}
|
|
2853
|
+
let json;
|
|
2854
|
+
try {
|
|
2855
|
+
json = JSON.parse(raw);
|
|
2856
|
+
} catch (err) {
|
|
2857
|
+
const message = err instanceof Error ? err.message : "invalid JSON";
|
|
2858
|
+
return { ok: false, error: `invalid JSON in ${manifestPath}: ${message}` };
|
|
2859
|
+
}
|
|
2860
|
+
const result = blessedManifestSchema.safeParse(json);
|
|
2861
|
+
if (!result.success) {
|
|
2862
|
+
const issues = result.error.issues.map((issue) => {
|
|
2863
|
+
const path2 = issue.path.join(".") || "(root)";
|
|
2864
|
+
return `${path2}: ${issue.message}`;
|
|
2865
|
+
}).join("; ");
|
|
2866
|
+
return {
|
|
2867
|
+
ok: false,
|
|
2868
|
+
error: `invalid ${BLESSED_MANIFEST_FILENAME} at ${manifestPath}: ${issues}`
|
|
2869
|
+
};
|
|
2870
|
+
}
|
|
2871
|
+
return { ok: true, manifest: result.data };
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
// src/lib/theme-apply-targets/globals-css.ts
|
|
2875
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
2876
|
+
import { dirname as dirname6, isAbsolute, join as join13, resolve as resolve5 } from "path";
|
|
2877
|
+
function applyGlobalsCss(input) {
|
|
2878
|
+
const outFile = input.path !== void 0 ? resolveInsideBuild(input.buildDir, input.path) : resolveDefaultGlobalsCss(input.buildDir);
|
|
2879
|
+
mkdirSync4(dirname6(outFile), { recursive: true });
|
|
2880
|
+
writeFileSync6(outFile, input.adapterCss, "utf-8");
|
|
2881
|
+
return outFile;
|
|
2882
|
+
}
|
|
2883
|
+
function resolveDefaultGlobalsCss(buildDir) {
|
|
2884
|
+
const candidates = [
|
|
2885
|
+
join13(buildDir, "app", "globals.css"),
|
|
2886
|
+
join13(buildDir, "src", "app", "globals.css")
|
|
2887
|
+
];
|
|
2888
|
+
return candidates.find((candidate) => existsSync11(candidate)) ?? candidates[0];
|
|
2889
|
+
}
|
|
2890
|
+
function resolveInsideBuild(buildDir, relativeOrAbsolute) {
|
|
2891
|
+
return isAbsolute(relativeOrAbsolute) ? relativeOrAbsolute : resolve5(buildDir, relativeOrAbsolute);
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
// src/lib/theme-apply-targets/themes-css-dir.ts
|
|
2895
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
2896
|
+
import { isAbsolute as isAbsolute2, join as join14, resolve as resolve6 } from "path";
|
|
2897
|
+
function applyThemesCssDir(input) {
|
|
2898
|
+
const dir = isAbsolute2(input.path) ? input.path : resolve6(input.buildDir, input.path);
|
|
2899
|
+
const outFile = join14(dir, `${input.themeId}.css`);
|
|
2900
|
+
mkdirSync5(dir, { recursive: true });
|
|
2901
|
+
writeFileSync7(outFile, input.adapterCss, "utf-8");
|
|
2902
|
+
return outFile;
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
// src/lib/theme-apply-targets/index.ts
|
|
2906
|
+
var IMPLICIT_TARGET = { kind: "globals-css" };
|
|
2907
|
+
function applyThemeToBuild(input) {
|
|
2908
|
+
const target = input.manifest.theme_apply_target ?? IMPLICIT_TARGET;
|
|
2909
|
+
switch (target.kind) {
|
|
2910
|
+
case "globals-css": {
|
|
2911
|
+
const writtenPath = applyGlobalsCss({
|
|
2912
|
+
buildDir: input.buildDir,
|
|
2913
|
+
adapterCss: input.adapterCss,
|
|
2914
|
+
path: target.path
|
|
2915
|
+
});
|
|
2916
|
+
return { writtenPath, target };
|
|
2917
|
+
}
|
|
2918
|
+
case "themes-css-dir": {
|
|
2919
|
+
if (!input.themeId || input.themeId.length === 0) {
|
|
2920
|
+
throw new Error(
|
|
2921
|
+
`theme_apply_target kind 'themes-css-dir' requires a theme id (used to name the emitted file); see docs/blessed-builds.md`
|
|
2922
|
+
);
|
|
2923
|
+
}
|
|
2924
|
+
const writtenPath = applyThemesCssDir({
|
|
2925
|
+
buildDir: input.buildDir,
|
|
2926
|
+
adapterCss: input.adapterCss,
|
|
2927
|
+
themeId: input.themeId,
|
|
2928
|
+
path: target.path
|
|
2929
|
+
});
|
|
2930
|
+
return { writtenPath, target };
|
|
2931
|
+
}
|
|
2932
|
+
default: {
|
|
2933
|
+
const unknown = target.kind;
|
|
2934
|
+
throw new Error(
|
|
2935
|
+
`Unknown theme_apply_target kind: '${unknown}'. Supported: 'globals-css', 'themes-css-dir'. See docs/blessed-builds.md.`
|
|
2936
|
+
);
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
// src/commands/theme-apply.ts
|
|
2552
2942
|
function defaultOutputPath(adapter, themeName) {
|
|
2553
2943
|
switch (adapter) {
|
|
2554
2944
|
case "nextjs":
|
|
@@ -2570,10 +2960,10 @@ function defaultOutputPath(adapter, themeName) {
|
|
|
2570
2960
|
}
|
|
2571
2961
|
}
|
|
2572
2962
|
function themeApplyCommand(file, cwd, options) {
|
|
2573
|
-
const filePath =
|
|
2963
|
+
const filePath = resolve7(cwd, file);
|
|
2574
2964
|
let yamlContent;
|
|
2575
2965
|
try {
|
|
2576
|
-
yamlContent =
|
|
2966
|
+
yamlContent = readFileSync13(filePath, "utf-8");
|
|
2577
2967
|
} catch {
|
|
2578
2968
|
if (options.json) {
|
|
2579
2969
|
console.log(
|
|
@@ -2651,16 +3041,74 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
2651
3041
|
}
|
|
2652
3042
|
process.exit(1);
|
|
2653
3043
|
}
|
|
3044
|
+
if (options.targetPath) {
|
|
3045
|
+
if (options.adapter !== "nextjs") {
|
|
3046
|
+
const message = "--target-path requires --adapter nextjs; see docs/blessed-builds.md";
|
|
3047
|
+
if (options.json) console.log(JSON.stringify({ success: false, error: message }));
|
|
3048
|
+
else logger.error(message);
|
|
3049
|
+
process.exit(1);
|
|
3050
|
+
}
|
|
3051
|
+
if (css === null) {
|
|
3052
|
+
process.exit(1);
|
|
3053
|
+
}
|
|
3054
|
+
if (!themeName || themeName.length === 0) {
|
|
3055
|
+
const message = "theme config is missing a `name` \u2014 required to derive the theme id for --target-path dispatch; see docs/blessed-builds.md";
|
|
3056
|
+
if (options.json) console.log(JSON.stringify({ success: false, error: message }));
|
|
3057
|
+
else logger.error(message);
|
|
3058
|
+
process.exit(1);
|
|
3059
|
+
}
|
|
3060
|
+
const buildDir = resolve7(cwd, options.targetPath);
|
|
3061
|
+
const manifestResult = parseBlessedManifest(buildDir);
|
|
3062
|
+
if (!manifestResult.ok) {
|
|
3063
|
+
if (options.json) {
|
|
3064
|
+
console.log(JSON.stringify({ success: false, error: manifestResult.error }));
|
|
3065
|
+
} else {
|
|
3066
|
+
logger.error(manifestResult.error);
|
|
3067
|
+
}
|
|
3068
|
+
process.exit(2);
|
|
3069
|
+
}
|
|
3070
|
+
let writtenPath;
|
|
3071
|
+
try {
|
|
3072
|
+
writtenPath = applyThemeToBuild({
|
|
3073
|
+
manifest: manifestResult.manifest,
|
|
3074
|
+
buildDir,
|
|
3075
|
+
themeId: themeName,
|
|
3076
|
+
adapterCss: css
|
|
3077
|
+
}).writtenPath;
|
|
3078
|
+
} catch (err) {
|
|
3079
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3080
|
+
if (options.json) console.log(JSON.stringify({ success: false, error: message }));
|
|
3081
|
+
else logger.error(message);
|
|
3082
|
+
process.exit(1);
|
|
3083
|
+
}
|
|
3084
|
+
if (options.json) {
|
|
3085
|
+
console.log(
|
|
3086
|
+
JSON.stringify({
|
|
3087
|
+
success: true,
|
|
3088
|
+
file: writtenPath,
|
|
3089
|
+
adapter: options.adapter,
|
|
3090
|
+
size: css.length,
|
|
3091
|
+
targetPath: buildDir
|
|
3092
|
+
})
|
|
3093
|
+
);
|
|
3094
|
+
} else {
|
|
3095
|
+
logger.success(`Theme CSS written via blessed-manifest dispatch: ${writtenPath}`);
|
|
3096
|
+
logger.info(`Adapter: ${options.adapter}`);
|
|
3097
|
+
logger.item(`Build root: ${buildDir}`);
|
|
3098
|
+
logger.item(`Size: ${formatSize(css.length)}`);
|
|
3099
|
+
}
|
|
3100
|
+
return;
|
|
3101
|
+
}
|
|
2654
3102
|
const outputTarget = options.output ?? defaultOutputPath(options.adapter, themeName);
|
|
2655
|
-
const outputPath =
|
|
3103
|
+
const outputPath = resolve7(cwd, outputTarget);
|
|
2656
3104
|
if (fileMap) {
|
|
2657
3105
|
try {
|
|
2658
|
-
|
|
3106
|
+
mkdirSync6(outputPath, { recursive: true });
|
|
2659
3107
|
let totalBytes = 0;
|
|
2660
3108
|
for (const [relPath, content] of Object.entries(fileMap.files)) {
|
|
2661
|
-
const filePath2 =
|
|
2662
|
-
|
|
2663
|
-
|
|
3109
|
+
const filePath2 = join15(outputPath, relPath);
|
|
3110
|
+
mkdirSync6(dirname7(filePath2), { recursive: true });
|
|
3111
|
+
writeFileSync8(filePath2, content, "utf-8");
|
|
2664
3112
|
totalBytes += content.length;
|
|
2665
3113
|
}
|
|
2666
3114
|
if (options.json) {
|
|
@@ -2697,10 +3145,10 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
2697
3145
|
if (css === null) {
|
|
2698
3146
|
process.exit(1);
|
|
2699
3147
|
}
|
|
2700
|
-
const outputDir =
|
|
3148
|
+
const outputDir = dirname7(outputPath);
|
|
2701
3149
|
try {
|
|
2702
|
-
|
|
2703
|
-
|
|
3150
|
+
mkdirSync6(outputDir, { recursive: true });
|
|
3151
|
+
writeFileSync8(outputPath, css, "utf-8");
|
|
2704
3152
|
} catch {
|
|
2705
3153
|
if (options.json) {
|
|
2706
3154
|
console.log(
|
|
@@ -2741,8 +3189,8 @@ function formatSize(bytes) {
|
|
|
2741
3189
|
}
|
|
2742
3190
|
|
|
2743
3191
|
// src/commands/theme-export.ts
|
|
2744
|
-
import { readFileSync as
|
|
2745
|
-
import { resolve as
|
|
3192
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
3193
|
+
import { resolve as resolve8 } from "path";
|
|
2746
3194
|
import {
|
|
2747
3195
|
parseConfig,
|
|
2748
3196
|
resolveConfig,
|
|
@@ -2750,10 +3198,10 @@ import {
|
|
|
2750
3198
|
exportTheme
|
|
2751
3199
|
} from "@loworbitstudio/visor-theme-engine";
|
|
2752
3200
|
function themeExportCommand(file, cwd, options) {
|
|
2753
|
-
const filePath =
|
|
3201
|
+
const filePath = resolve8(cwd, file ?? ".visor.yaml");
|
|
2754
3202
|
let yamlContent;
|
|
2755
3203
|
try {
|
|
2756
|
-
yamlContent =
|
|
3204
|
+
yamlContent = readFileSync14(filePath, "utf-8");
|
|
2757
3205
|
} catch {
|
|
2758
3206
|
if (options.json) {
|
|
2759
3207
|
console.log(
|
|
@@ -2805,16 +3253,16 @@ function themeExportCommand(file, cwd, options) {
|
|
|
2805
3253
|
}
|
|
2806
3254
|
|
|
2807
3255
|
// src/commands/theme-validate.ts
|
|
2808
|
-
import { readFileSync as
|
|
2809
|
-
import { resolve as
|
|
3256
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
3257
|
+
import { resolve as resolve9 } from "path";
|
|
2810
3258
|
import { parse as parseYaml2 } from "yaml";
|
|
2811
3259
|
import { validate } from "@loworbitstudio/visor-theme-engine";
|
|
2812
3260
|
import pc3 from "picocolors";
|
|
2813
3261
|
function themeValidateCommand(file, cwd, options) {
|
|
2814
|
-
const filePath =
|
|
3262
|
+
const filePath = resolve9(cwd, file);
|
|
2815
3263
|
let fileContent;
|
|
2816
3264
|
try {
|
|
2817
|
-
fileContent =
|
|
3265
|
+
fileContent = readFileSync15(filePath, "utf-8");
|
|
2818
3266
|
} catch {
|
|
2819
3267
|
if (options.json) {
|
|
2820
3268
|
console.log(
|
|
@@ -2903,8 +3351,8 @@ function printIssue(issue) {
|
|
|
2903
3351
|
|
|
2904
3352
|
// src/commands/theme-verify.ts
|
|
2905
3353
|
import { spawnSync as _spawnSync } from "child_process";
|
|
2906
|
-
import { existsSync as
|
|
2907
|
-
import { resolve as
|
|
3354
|
+
import { existsSync as existsSync12 } from "fs";
|
|
3355
|
+
import { resolve as resolve10 } from "path";
|
|
2908
3356
|
function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
2909
3357
|
const target = options.target ?? "flutter";
|
|
2910
3358
|
if (target !== "flutter") {
|
|
@@ -2926,8 +3374,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
|
2926
3374
|
}
|
|
2927
3375
|
process.exit(1);
|
|
2928
3376
|
}
|
|
2929
|
-
const dirPath =
|
|
2930
|
-
if (!
|
|
3377
|
+
const dirPath = resolve10(cwd, dir);
|
|
3378
|
+
if (!existsSync12(dirPath)) {
|
|
2931
3379
|
if (options.json) {
|
|
2932
3380
|
console.log(
|
|
2933
3381
|
JSON.stringify({
|
|
@@ -3014,8 +3462,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
|
3014
3462
|
}
|
|
3015
3463
|
|
|
3016
3464
|
// src/commands/theme-extract.ts
|
|
3017
|
-
import { readFileSync as
|
|
3018
|
-
import { resolve as
|
|
3465
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync9, existsSync as existsSync13, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
|
|
3466
|
+
import { resolve as resolve11, join as join16, basename as basename3, extname as extname3, relative } from "path";
|
|
3019
3467
|
import { stringify as stringifyYaml } from "yaml";
|
|
3020
3468
|
import {
|
|
3021
3469
|
extractFromCSS,
|
|
@@ -3044,8 +3492,8 @@ var CSS_DIRS = [
|
|
|
3044
3492
|
"packages/design-tokens"
|
|
3045
3493
|
];
|
|
3046
3494
|
function themeExtractCommand(cwd, options) {
|
|
3047
|
-
const targetDir =
|
|
3048
|
-
if (!
|
|
3495
|
+
const targetDir = resolve11(cwd, options.from ?? ".");
|
|
3496
|
+
if (!existsSync13(targetDir)) {
|
|
3049
3497
|
if (options.json) {
|
|
3050
3498
|
console.log(JSON.stringify({ success: false, error: `Directory not found: ${targetDir}` }));
|
|
3051
3499
|
} else {
|
|
@@ -3099,16 +3547,16 @@ function collectCSSFiles(targetDir) {
|
|
|
3099
3547
|
const files = [];
|
|
3100
3548
|
const seen = /* @__PURE__ */ new Set();
|
|
3101
3549
|
for (const pattern2 of CSS_FILE_PATTERNS) {
|
|
3102
|
-
const rootPath =
|
|
3550
|
+
const rootPath = join16(targetDir, pattern2);
|
|
3103
3551
|
addFileIfExists(rootPath, files, seen);
|
|
3104
3552
|
for (const dir of CSS_DIRS) {
|
|
3105
|
-
const dirPath =
|
|
3553
|
+
const dirPath = join16(targetDir, dir, pattern2);
|
|
3106
3554
|
addFileIfExists(dirPath, files, seen);
|
|
3107
3555
|
}
|
|
3108
3556
|
}
|
|
3109
3557
|
for (const dir of CSS_DIRS) {
|
|
3110
|
-
const dirPath =
|
|
3111
|
-
if (
|
|
3558
|
+
const dirPath = join16(targetDir, dir);
|
|
3559
|
+
if (existsSync13(dirPath) && statSync5(dirPath).isDirectory()) {
|
|
3112
3560
|
scanDirForCSS(dirPath, files, seen, 2);
|
|
3113
3561
|
}
|
|
3114
3562
|
}
|
|
@@ -3116,11 +3564,11 @@ function collectCSSFiles(targetDir) {
|
|
|
3116
3564
|
return files;
|
|
3117
3565
|
}
|
|
3118
3566
|
function addFileIfExists(filePath, files, seen) {
|
|
3119
|
-
const resolved =
|
|
3567
|
+
const resolved = resolve11(filePath);
|
|
3120
3568
|
if (seen.has(resolved)) return;
|
|
3121
|
-
if (!
|
|
3569
|
+
if (!existsSync13(resolved)) return;
|
|
3122
3570
|
try {
|
|
3123
|
-
const content =
|
|
3571
|
+
const content = readFileSync16(resolved, "utf-8");
|
|
3124
3572
|
if (content.includes("--")) {
|
|
3125
3573
|
files.push({ path: resolved, content });
|
|
3126
3574
|
seen.add(resolved);
|
|
@@ -3129,7 +3577,7 @@ function addFileIfExists(filePath, files, seen) {
|
|
|
3129
3577
|
}
|
|
3130
3578
|
}
|
|
3131
3579
|
function scanDirForCSS(dir, files, seen, maxDepth) {
|
|
3132
|
-
if (!
|
|
3580
|
+
if (!existsSync13(dir)) return;
|
|
3133
3581
|
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
3134
3582
|
"node_modules",
|
|
3135
3583
|
".next",
|
|
@@ -3148,10 +3596,10 @@ function scanDirForCSS(dir, files, seen, maxDepth) {
|
|
|
3148
3596
|
if (entry.isDirectory()) {
|
|
3149
3597
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
3150
3598
|
if (maxDepth > 0) {
|
|
3151
|
-
scanDirForCSS(
|
|
3599
|
+
scanDirForCSS(join16(dir, entry.name), files, seen, maxDepth - 1);
|
|
3152
3600
|
}
|
|
3153
3601
|
} else if (entry.isFile() && extname3(entry.name) === ".css") {
|
|
3154
|
-
addFileIfExists(
|
|
3602
|
+
addFileIfExists(join16(dir, entry.name), files, seen);
|
|
3155
3603
|
}
|
|
3156
3604
|
}
|
|
3157
3605
|
} catch {
|
|
@@ -3233,10 +3681,10 @@ function extractVarName(varExpr) {
|
|
|
3233
3681
|
function parseNextFontFromLayouts(targetDir) {
|
|
3234
3682
|
const fontMap = /* @__PURE__ */ new Map();
|
|
3235
3683
|
for (const relPath of LAYOUT_FILE_PATHS) {
|
|
3236
|
-
const fullPath =
|
|
3237
|
-
if (!
|
|
3684
|
+
const fullPath = join16(targetDir, relPath);
|
|
3685
|
+
if (!existsSync13(fullPath)) continue;
|
|
3238
3686
|
try {
|
|
3239
|
-
const content =
|
|
3687
|
+
const content = readFileSync16(fullPath, "utf-8");
|
|
3240
3688
|
parseNextFontDeclarations(content, fontMap);
|
|
3241
3689
|
} catch {
|
|
3242
3690
|
}
|
|
@@ -3283,7 +3731,7 @@ function parseNextFontDeclarations(content, fontMap) {
|
|
|
3283
3731
|
const srcMatch = block.match(/src\s*:\s*["']([^"']+)["']/);
|
|
3284
3732
|
if (srcMatch) {
|
|
3285
3733
|
const srcPath = srcMatch[1];
|
|
3286
|
-
const fileName =
|
|
3734
|
+
const fileName = basename3(srcPath, extname3(srcPath));
|
|
3287
3735
|
const fontBaseName = fileName.replace(/[-_](Variable|Regular|Bold|Light|Medium|SemiBold|ExtraBold|Thin|Black|Italic).*$/i, "").replace(/[-_]/g, " ").trim();
|
|
3288
3736
|
if (fontBaseName) {
|
|
3289
3737
|
fontMap.set(varName, fontBaseName);
|
|
@@ -3321,10 +3769,10 @@ var MONO_FONT_NAMES = /* @__PURE__ */ new Set([
|
|
|
3321
3769
|
"IBM Plex Mono"
|
|
3322
3770
|
]);
|
|
3323
3771
|
function extractFontHints(targetDir) {
|
|
3324
|
-
const pkgPath =
|
|
3325
|
-
if (!
|
|
3772
|
+
const pkgPath = join16(targetDir, "package.json");
|
|
3773
|
+
if (!existsSync13(pkgPath)) return void 0;
|
|
3326
3774
|
try {
|
|
3327
|
-
const pkg2 = JSON.parse(
|
|
3775
|
+
const pkg2 = JSON.parse(readFileSync16(pkgPath, "utf-8"));
|
|
3328
3776
|
const allDeps = { ...pkg2.dependencies, ...pkg2.devDependencies };
|
|
3329
3777
|
const fonts2 = [];
|
|
3330
3778
|
for (const [dep, _] of Object.entries(allDeps)) {
|
|
@@ -3360,10 +3808,10 @@ function extractFontHints(targetDir) {
|
|
|
3360
3808
|
}
|
|
3361
3809
|
}
|
|
3362
3810
|
function inferThemeName(targetDir) {
|
|
3363
|
-
const pkgPath =
|
|
3364
|
-
if (
|
|
3811
|
+
const pkgPath = join16(targetDir, "package.json");
|
|
3812
|
+
if (existsSync13(pkgPath)) {
|
|
3365
3813
|
try {
|
|
3366
|
-
const pkg2 = JSON.parse(
|
|
3814
|
+
const pkg2 = JSON.parse(readFileSync16(pkgPath, "utf-8"));
|
|
3367
3815
|
if (pkg2.name) {
|
|
3368
3816
|
const name = pkg2.name.replace(/^@[\w-]+\//, "");
|
|
3369
3817
|
return `${name}-theme`;
|
|
@@ -3371,7 +3819,7 @@ function inferThemeName(targetDir) {
|
|
|
3371
3819
|
} catch {
|
|
3372
3820
|
}
|
|
3373
3821
|
}
|
|
3374
|
-
return `${
|
|
3822
|
+
return `${basename3(targetDir)}-theme`;
|
|
3375
3823
|
}
|
|
3376
3824
|
function confidenceComment(confidence) {
|
|
3377
3825
|
return `# confidence: ${confidence}`;
|
|
@@ -3400,7 +3848,7 @@ function outputJSON(result, validationResult) {
|
|
|
3400
3848
|
}
|
|
3401
3849
|
function outputYAML(result, outputPath, cwd, validationResult) {
|
|
3402
3850
|
const yamlStr = buildAnnotatedYAML(result);
|
|
3403
|
-
const outFile =
|
|
3851
|
+
const outFile = resolve11(cwd, outputPath ?? ".visor.yaml");
|
|
3404
3852
|
const high = result.tokens.filter((t) => t.confidence === "high").length;
|
|
3405
3853
|
const med = result.tokens.filter((t) => t.confidence === "medium").length;
|
|
3406
3854
|
const low = result.tokens.filter((t) => t.confidence === "low").length;
|
|
@@ -3428,7 +3876,7 @@ function outputYAML(result, outputPath, cwd, validationResult) {
|
|
|
3428
3876
|
}
|
|
3429
3877
|
logger.blank();
|
|
3430
3878
|
}
|
|
3431
|
-
|
|
3879
|
+
writeFileSync9(outFile, yamlStr, "utf-8");
|
|
3432
3880
|
logger.success(`Theme written to ${relative(cwd, outFile)}`);
|
|
3433
3881
|
if (validationResult) {
|
|
3434
3882
|
logger.blank();
|
|
@@ -3486,14 +3934,14 @@ function buildAnnotatedYAML(result) {
|
|
|
3486
3934
|
}
|
|
3487
3935
|
|
|
3488
3936
|
// src/commands/theme-register.ts
|
|
3489
|
-
import { readFileSync as
|
|
3490
|
-
import { resolve as
|
|
3937
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync10, mkdirSync as mkdirSync7, existsSync as existsSync15 } from "fs";
|
|
3938
|
+
import { resolve as resolve13, join as join18 } from "path";
|
|
3491
3939
|
import { generateThemeData as generateThemeData4 } from "@loworbitstudio/visor-theme-engine";
|
|
3492
3940
|
import { docsAdapter as docsAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
3493
3941
|
|
|
3494
3942
|
// src/utils/theme-helpers.ts
|
|
3495
|
-
import { existsSync as
|
|
3496
|
-
import { resolve as
|
|
3943
|
+
import { existsSync as existsSync14, lstatSync, readdirSync as readdirSync5, readFileSync as readFileSync17, readlinkSync, realpathSync, statSync as statSync6 } from "fs";
|
|
3944
|
+
import { resolve as resolve12, dirname as dirname8, join as join17 } from "path";
|
|
3497
3945
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3498
3946
|
function toSlug(name) {
|
|
3499
3947
|
return name.toLowerCase().replace(/\s+/g, "-");
|
|
@@ -3502,12 +3950,12 @@ function toLabel(name) {
|
|
|
3502
3950
|
return name.split(/[\s-]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
3503
3951
|
}
|
|
3504
3952
|
function findRepoRoot(startDir) {
|
|
3505
|
-
let current =
|
|
3953
|
+
let current = resolve12(startDir);
|
|
3506
3954
|
while (true) {
|
|
3507
|
-
if (
|
|
3955
|
+
if (existsSync14(join17(current, "packages", "docs"))) {
|
|
3508
3956
|
return current;
|
|
3509
3957
|
}
|
|
3510
|
-
const parent =
|
|
3958
|
+
const parent = dirname8(current);
|
|
3511
3959
|
if (parent === current) break;
|
|
3512
3960
|
current = parent;
|
|
3513
3961
|
}
|
|
@@ -3521,9 +3969,9 @@ function findMainRepoRoot(startDir) {
|
|
|
3521
3969
|
stdio: ["ignore", "pipe", "ignore"]
|
|
3522
3970
|
}).trim();
|
|
3523
3971
|
if (commonDir) {
|
|
3524
|
-
const absoluteCommonDir =
|
|
3525
|
-
const candidate =
|
|
3526
|
-
if (
|
|
3972
|
+
const absoluteCommonDir = resolve12(startDir, commonDir);
|
|
3973
|
+
const candidate = dirname8(absoluteCommonDir);
|
|
3974
|
+
if (existsSync14(join17(candidate, "packages", "docs"))) {
|
|
3527
3975
|
return candidate;
|
|
3528
3976
|
}
|
|
3529
3977
|
}
|
|
@@ -3542,10 +3990,10 @@ var BrokenSymlinkError = class extends Error {
|
|
|
3542
3990
|
code = "BROKEN_SYMLINK";
|
|
3543
3991
|
};
|
|
3544
3992
|
function assertNoBrokenSymlinks(dir) {
|
|
3545
|
-
if (!
|
|
3993
|
+
if (!existsSync14(dir)) return;
|
|
3546
3994
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
3547
3995
|
for (const entry of entries) {
|
|
3548
|
-
const entryPath =
|
|
3996
|
+
const entryPath = join17(dir, entry.name);
|
|
3549
3997
|
let lst;
|
|
3550
3998
|
try {
|
|
3551
3999
|
lst = lstatSync(entryPath);
|
|
@@ -3563,39 +4011,39 @@ function assertNoBrokenSymlinks(dir) {
|
|
|
3563
4011
|
}
|
|
3564
4012
|
}
|
|
3565
4013
|
function scanParentForPrivateThemes(parentDir) {
|
|
3566
|
-
if (!
|
|
4014
|
+
if (!existsSync14(parentDir)) return [];
|
|
3567
4015
|
const entries = readdirSync5(parentDir, { withFileTypes: true });
|
|
3568
4016
|
const matches = [];
|
|
3569
4017
|
for (const entry of entries) {
|
|
3570
4018
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
3571
|
-
const candidate =
|
|
3572
|
-
if (
|
|
4019
|
+
const candidate = join17(parentDir, entry.name, "visor-themes-private", "themes");
|
|
4020
|
+
if (existsSync14(candidate)) {
|
|
3573
4021
|
matches.push(candidate);
|
|
3574
4022
|
}
|
|
3575
4023
|
}
|
|
3576
4024
|
return matches.sort();
|
|
3577
4025
|
}
|
|
3578
4026
|
function scanNestedThemeDir(dir) {
|
|
3579
|
-
if (!
|
|
4027
|
+
if (!existsSync14(dir)) return [];
|
|
3580
4028
|
assertNoBrokenSymlinks(dir);
|
|
3581
4029
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
3582
4030
|
const out = [];
|
|
3583
4031
|
for (const entry of entries) {
|
|
3584
4032
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
3585
|
-
const themeFile =
|
|
3586
|
-
if (
|
|
4033
|
+
const themeFile = join17(dir, entry.name, "theme.visor.yaml");
|
|
4034
|
+
if (existsSync14(themeFile)) {
|
|
3587
4035
|
out.push({ filePath: themeFile, slug: entry.name });
|
|
3588
4036
|
}
|
|
3589
4037
|
}
|
|
3590
4038
|
return out;
|
|
3591
4039
|
}
|
|
3592
4040
|
function detectVisorWorkspace(cwd) {
|
|
3593
|
-
let current =
|
|
4041
|
+
let current = resolve12(cwd);
|
|
3594
4042
|
while (true) {
|
|
3595
|
-
const pkgPath =
|
|
3596
|
-
if (
|
|
4043
|
+
const pkgPath = join17(current, "package.json");
|
|
4044
|
+
if (existsSync14(pkgPath)) {
|
|
3597
4045
|
try {
|
|
3598
|
-
const pkg2 = JSON.parse(
|
|
4046
|
+
const pkg2 = JSON.parse(readFileSync17(pkgPath, "utf-8"));
|
|
3599
4047
|
const workspaces = pkg2.workspaces ?? [];
|
|
3600
4048
|
const hasCli = workspaces.some((w) => w.includes("packages/cli"));
|
|
3601
4049
|
const hasEngine = workspaces.some((w) => w.includes("packages/theme-engine"));
|
|
@@ -3605,7 +4053,7 @@ function detectVisorWorkspace(cwd) {
|
|
|
3605
4053
|
} catch {
|
|
3606
4054
|
}
|
|
3607
4055
|
}
|
|
3608
|
-
const parent =
|
|
4056
|
+
const parent = dirname8(current);
|
|
3609
4057
|
if (parent === current) break;
|
|
3610
4058
|
current = parent;
|
|
3611
4059
|
}
|
|
@@ -3613,18 +4061,18 @@ function detectVisorWorkspace(cwd) {
|
|
|
3613
4061
|
}
|
|
3614
4062
|
function isLocalVisorBinary(workspaceRoot, scriptPath) {
|
|
3615
4063
|
if (!scriptPath) return false;
|
|
3616
|
-
const expectedPrefix =
|
|
4064
|
+
const expectedPrefix = join17(workspaceRoot, "packages", "cli", "dist");
|
|
3617
4065
|
let resolvedScript;
|
|
3618
4066
|
let resolvedPrefix;
|
|
3619
4067
|
try {
|
|
3620
4068
|
resolvedScript = realpathSync(scriptPath);
|
|
3621
4069
|
} catch {
|
|
3622
|
-
resolvedScript =
|
|
4070
|
+
resolvedScript = resolve12(scriptPath);
|
|
3623
4071
|
}
|
|
3624
4072
|
try {
|
|
3625
4073
|
resolvedPrefix = realpathSync(expectedPrefix);
|
|
3626
4074
|
} catch {
|
|
3627
|
-
resolvedPrefix =
|
|
4075
|
+
resolvedPrefix = resolve12(expectedPrefix);
|
|
3628
4076
|
}
|
|
3629
4077
|
return resolvedScript.startsWith(resolvedPrefix + "/") || resolvedScript === resolvedPrefix;
|
|
3630
4078
|
}
|
|
@@ -3714,10 +4162,10 @@ ${indent}${newEntry},
|
|
|
3714
4162
|
return { updated, changed: true };
|
|
3715
4163
|
}
|
|
3716
4164
|
function themeRegisterCommand(file, cwd, options) {
|
|
3717
|
-
const filePath =
|
|
4165
|
+
const filePath = resolve13(cwd, file);
|
|
3718
4166
|
let yamlContent;
|
|
3719
4167
|
try {
|
|
3720
|
-
yamlContent =
|
|
4168
|
+
yamlContent = readFileSync18(filePath, "utf-8");
|
|
3721
4169
|
} catch {
|
|
3722
4170
|
if (options.json) {
|
|
3723
4171
|
console.log(JSON.stringify({ success: false, error: `Could not read file: ${filePath}` }));
|
|
@@ -3760,11 +4208,11 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3760
4208
|
process.exit(1);
|
|
3761
4209
|
return;
|
|
3762
4210
|
}
|
|
3763
|
-
const docsAppDir =
|
|
3764
|
-
const cssFilePath =
|
|
3765
|
-
const globalsPath =
|
|
3766
|
-
const themeConfigPath =
|
|
3767
|
-
if (!
|
|
4211
|
+
const docsAppDir = join18(repoRoot, "packages", "docs", "app");
|
|
4212
|
+
const cssFilePath = join18(docsAppDir, `${slug2}-theme.css`);
|
|
4213
|
+
const globalsPath = join18(docsAppDir, "globals.css");
|
|
4214
|
+
const themeConfigPath = join18(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
4215
|
+
if (!existsSync15(docsAppDir)) {
|
|
3768
4216
|
const msg = `Docs app directory not found: ${docsAppDir}`;
|
|
3769
4217
|
if (options.json) {
|
|
3770
4218
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
@@ -3777,8 +4225,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3777
4225
|
let globalsContent = "";
|
|
3778
4226
|
let themeConfigContent = "";
|
|
3779
4227
|
try {
|
|
3780
|
-
globalsContent =
|
|
3781
|
-
themeConfigContent =
|
|
4228
|
+
globalsContent = readFileSync18(globalsPath, "utf-8");
|
|
4229
|
+
themeConfigContent = readFileSync18(themeConfigPath, "utf-8");
|
|
3782
4230
|
} catch (err) {
|
|
3783
4231
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
3784
4232
|
if (options.json) {
|
|
@@ -3789,8 +4237,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3789
4237
|
process.exit(1);
|
|
3790
4238
|
return;
|
|
3791
4239
|
}
|
|
3792
|
-
const cssExists =
|
|
3793
|
-
const cssChanged = !cssExists ||
|
|
4240
|
+
const cssExists = existsSync15(cssFilePath);
|
|
4241
|
+
const cssChanged = !cssExists || readFileSync18(cssFilePath, "utf-8") !== css;
|
|
3794
4242
|
const { updated: newGlobals, changed: globalsChanged } = insertGlobalsImport(globalsContent, slug2);
|
|
3795
4243
|
const { updated: newThemeConfig, changed: themeConfigChanged, error: configError } = insertThemeConfig(
|
|
3796
4244
|
themeConfigContent,
|
|
@@ -3833,14 +4281,14 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3833
4281
|
}
|
|
3834
4282
|
try {
|
|
3835
4283
|
if (cssChanged) {
|
|
3836
|
-
|
|
3837
|
-
|
|
4284
|
+
mkdirSync7(docsAppDir, { recursive: true });
|
|
4285
|
+
writeFileSync10(cssFilePath, css, "utf-8");
|
|
3838
4286
|
}
|
|
3839
4287
|
if (globalsChanged) {
|
|
3840
|
-
|
|
4288
|
+
writeFileSync10(globalsPath, newGlobals, "utf-8");
|
|
3841
4289
|
}
|
|
3842
4290
|
if (themeConfigChanged) {
|
|
3843
|
-
|
|
4291
|
+
writeFileSync10(themeConfigPath, newThemeConfig, "utf-8");
|
|
3844
4292
|
}
|
|
3845
4293
|
} catch (err) {
|
|
3846
4294
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
@@ -3874,8 +4322,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3874
4322
|
}
|
|
3875
4323
|
|
|
3876
4324
|
// src/commands/theme-unregister.ts
|
|
3877
|
-
import { readFileSync as
|
|
3878
|
-
import { join as
|
|
4325
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync11, existsSync as existsSync16, unlinkSync } from "fs";
|
|
4326
|
+
import { join as join19 } from "path";
|
|
3879
4327
|
function removeGlobalsImport(content, slug2) {
|
|
3880
4328
|
const importLine = `@import './${slug2}-theme.css';`;
|
|
3881
4329
|
if (!content.includes(importLine)) {
|
|
@@ -3907,11 +4355,11 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3907
4355
|
process.exit(1);
|
|
3908
4356
|
return;
|
|
3909
4357
|
}
|
|
3910
|
-
const docsAppDir =
|
|
3911
|
-
const cssFilePath =
|
|
3912
|
-
const globalsPath =
|
|
3913
|
-
const themeConfigPath =
|
|
3914
|
-
if (!
|
|
4358
|
+
const docsAppDir = join19(repoRoot, "packages", "docs", "app");
|
|
4359
|
+
const cssFilePath = join19(docsAppDir, `${slug2}-theme.css`);
|
|
4360
|
+
const globalsPath = join19(docsAppDir, "globals.css");
|
|
4361
|
+
const themeConfigPath = join19(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
4362
|
+
if (!existsSync16(docsAppDir)) {
|
|
3915
4363
|
const msg = `Docs app directory not found: ${docsAppDir}`;
|
|
3916
4364
|
if (options.json) {
|
|
3917
4365
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
@@ -3924,8 +4372,8 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3924
4372
|
let globalsContent = "";
|
|
3925
4373
|
let themeConfigContent = "";
|
|
3926
4374
|
try {
|
|
3927
|
-
globalsContent =
|
|
3928
|
-
themeConfigContent =
|
|
4375
|
+
globalsContent = readFileSync19(globalsPath, "utf-8");
|
|
4376
|
+
themeConfigContent = readFileSync19(themeConfigPath, "utf-8");
|
|
3929
4377
|
} catch (err) {
|
|
3930
4378
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
3931
4379
|
if (options.json) {
|
|
@@ -3936,7 +4384,7 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3936
4384
|
process.exit(1);
|
|
3937
4385
|
return;
|
|
3938
4386
|
}
|
|
3939
|
-
const cssExists =
|
|
4387
|
+
const cssExists = existsSync16(cssFilePath);
|
|
3940
4388
|
const { updated: newGlobals, changed: globalsChanged } = removeGlobalsImport(globalsContent, slug2);
|
|
3941
4389
|
const { updated: newThemeConfig, changed: themeConfigChanged } = removeThemeConfigEntry(themeConfigContent, slug2);
|
|
3942
4390
|
if (!cssExists && !globalsChanged && !themeConfigChanged) {
|
|
@@ -3949,8 +4397,8 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3949
4397
|
}
|
|
3950
4398
|
try {
|
|
3951
4399
|
if (cssExists) unlinkSync(cssFilePath);
|
|
3952
|
-
if (globalsChanged)
|
|
3953
|
-
if (themeConfigChanged)
|
|
4400
|
+
if (globalsChanged) writeFileSync11(globalsPath, newGlobals, "utf-8");
|
|
4401
|
+
if (themeConfigChanged) writeFileSync11(themeConfigPath, newThemeConfig, "utf-8");
|
|
3954
4402
|
} catch (err) {
|
|
3955
4403
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
3956
4404
|
if (options.json) {
|
|
@@ -3977,15 +4425,15 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3977
4425
|
|
|
3978
4426
|
// src/commands/theme-sync.ts
|
|
3979
4427
|
import {
|
|
3980
|
-
readFileSync as
|
|
3981
|
-
writeFileSync as
|
|
3982
|
-
mkdirSync as
|
|
3983
|
-
existsSync as
|
|
4428
|
+
readFileSync as readFileSync20,
|
|
4429
|
+
writeFileSync as writeFileSync12,
|
|
4430
|
+
mkdirSync as mkdirSync8,
|
|
4431
|
+
existsSync as existsSync17,
|
|
3984
4432
|
readdirSync as readdirSync6,
|
|
3985
4433
|
unlinkSync as unlinkSync2,
|
|
3986
4434
|
copyFileSync
|
|
3987
4435
|
} from "fs";
|
|
3988
|
-
import { join as
|
|
4436
|
+
import { join as join20, basename as basename4, resolve as resolve14, sep, relative as relative2 } from "path";
|
|
3989
4437
|
import { parse as parseYaml3 } from "yaml";
|
|
3990
4438
|
import { generateThemeData as generateThemeData5, validateFontCoverage, formatFontCoverageError } from "@loworbitstudio/visor-theme-engine";
|
|
3991
4439
|
import { docsAdapter as docsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
@@ -3999,9 +4447,9 @@ var CUSTOM_OVERLAY_CSS_PATH = "packages/docs/app/custom-themes.generated.css";
|
|
|
3999
4447
|
var CUSTOM_OVERLAY_TS_PATH = "packages/docs/lib/theme-config.custom.generated.ts";
|
|
4000
4448
|
var CUSTOM_OVERLAY_IMPORT_LINE = "@import './custom-themes.generated.css';";
|
|
4001
4449
|
function scanThemeDir(dir) {
|
|
4002
|
-
if (!
|
|
4450
|
+
if (!existsSync17(dir)) return [];
|
|
4003
4451
|
assertNoBrokenSymlinks(dir);
|
|
4004
|
-
return readdirSync6(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) =>
|
|
4452
|
+
return readdirSync6(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join20(dir, f));
|
|
4005
4453
|
}
|
|
4006
4454
|
function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
4007
4455
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -4025,20 +4473,20 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
|
4025
4473
|
};
|
|
4026
4474
|
const envPath = process.env[PRIVATE_THEMES_ENV_VAR];
|
|
4027
4475
|
if (envPath && envPath.trim() !== "") {
|
|
4028
|
-
const resolved =
|
|
4029
|
-
if (!
|
|
4476
|
+
const resolved = resolve14(envPath);
|
|
4477
|
+
if (!existsSync17(resolved)) {
|
|
4030
4478
|
throw new Error(
|
|
4031
4479
|
`${PRIVATE_THEMES_ENV_VAR} is set to "${envPath}" but the path does not exist. Expected a directory containing {slug}/theme.visor.yaml entries.`
|
|
4032
4480
|
);
|
|
4033
4481
|
}
|
|
4034
4482
|
addNested(resolved, "env");
|
|
4035
4483
|
}
|
|
4036
|
-
const siblingPath =
|
|
4037
|
-
const siblingExists =
|
|
4484
|
+
const siblingPath = join20(mainRepoRoot, "..", "visor-themes-private", "themes");
|
|
4485
|
+
const siblingExists = existsSync17(siblingPath);
|
|
4038
4486
|
if (siblingExists) {
|
|
4039
4487
|
addNested(siblingPath, "sibling");
|
|
4040
4488
|
}
|
|
4041
|
-
const parentDir =
|
|
4489
|
+
const parentDir = join20(mainRepoRoot, "..");
|
|
4042
4490
|
const parentGlobMatches = scanParentForPrivateThemes(parentDir).filter(
|
|
4043
4491
|
(path2) => !path2.startsWith(mainRepoRoot + sep)
|
|
4044
4492
|
);
|
|
@@ -4059,10 +4507,10 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
|
4059
4507
|
}
|
|
4060
4508
|
}
|
|
4061
4509
|
}
|
|
4062
|
-
const legacyDir =
|
|
4510
|
+
const legacyDir = join20(repoRoot, "custom-themes");
|
|
4063
4511
|
const legacyFiles = scanThemeDir(legacyDir);
|
|
4064
4512
|
for (const legacyFile of legacyFiles) {
|
|
4065
|
-
const slug2 =
|
|
4513
|
+
const slug2 = basename4(legacyFile).replace(/\.visor\.yaml$/, "");
|
|
4066
4514
|
const existing = merged.get(slug2);
|
|
4067
4515
|
if (existing) {
|
|
4068
4516
|
warn(
|
|
@@ -4116,14 +4564,14 @@ function reportBrokenSymlink(err, options) {
|
|
|
4116
4564
|
}
|
|
4117
4565
|
}
|
|
4118
4566
|
function buildEmptySourcesMessage(mainRepoRoot) {
|
|
4119
|
-
const expectedSibling =
|
|
4567
|
+
const expectedSibling = join20(mainRepoRoot, "..", "visor-themes-private");
|
|
4120
4568
|
return [
|
|
4121
4569
|
"No theme sources discovered. Cannot proceed \u2014 refusing to wipe generated CSS.",
|
|
4122
4570
|
"",
|
|
4123
4571
|
"Resolution order checked:",
|
|
4124
4572
|
` 1. Env var ${PRIVATE_THEMES_ENV_VAR} (unset or empty)`,
|
|
4125
4573
|
` 2. Sibling checkout at ${expectedSibling}/themes/ (not found)`,
|
|
4126
|
-
` 3. One-level-deeper at ${
|
|
4574
|
+
` 3. One-level-deeper at ${join20(mainRepoRoot, "..")}/<parent>/visor-themes-private/themes/ (not found)`,
|
|
4127
4575
|
" 4. Legacy custom-themes/ (no .visor.yaml files)",
|
|
4128
4576
|
"",
|
|
4129
4577
|
"To fix, clone the private themes repo as a sibling:",
|
|
@@ -4328,14 +4776,14 @@ function themeSyncCommand(cwd, options) {
|
|
|
4328
4776
|
return;
|
|
4329
4777
|
}
|
|
4330
4778
|
const mainRepoRoot = findMainRepoRoot(cwd) ?? repoRoot;
|
|
4331
|
-
const themesDir =
|
|
4332
|
-
const docsAppDir =
|
|
4333
|
-
const docsLibDir =
|
|
4334
|
-
const docsPublicThemesDir =
|
|
4335
|
-
const themeConfigPath =
|
|
4336
|
-
const globalsPath =
|
|
4337
|
-
const customOverlayCssPath =
|
|
4338
|
-
const customOverlayTsPath =
|
|
4779
|
+
const themesDir = join20(repoRoot, "themes");
|
|
4780
|
+
const docsAppDir = join20(repoRoot, "packages", "docs", "app");
|
|
4781
|
+
const docsLibDir = join20(repoRoot, "packages", "docs", "lib");
|
|
4782
|
+
const docsPublicThemesDir = join20(repoRoot, "packages", "docs", "public", "themes");
|
|
4783
|
+
const themeConfigPath = join20(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
4784
|
+
const globalsPath = join20(docsAppDir, "globals.css");
|
|
4785
|
+
const customOverlayCssPath = join20(repoRoot, CUSTOM_OVERLAY_CSS_PATH);
|
|
4786
|
+
const customOverlayTsPath = join20(repoRoot, CUSTOM_OVERLAY_TS_PATH);
|
|
4339
4787
|
let stockFiles;
|
|
4340
4788
|
try {
|
|
4341
4789
|
stockFiles = scanThemeDir(themesDir);
|
|
@@ -4392,7 +4840,7 @@ function themeSyncCommand(cwd, options) {
|
|
|
4392
4840
|
const processFile = (filePath, isCustom, slugOverride) => {
|
|
4393
4841
|
let yamlContent;
|
|
4394
4842
|
try {
|
|
4395
|
-
yamlContent =
|
|
4843
|
+
yamlContent = readFileSync20(filePath, "utf-8");
|
|
4396
4844
|
} catch {
|
|
4397
4845
|
failures.push({ filePath, error: `Could not read: ${filePath}` });
|
|
4398
4846
|
return;
|
|
@@ -4403,7 +4851,7 @@ function themeSyncCommand(cwd, options) {
|
|
|
4403
4851
|
} catch (err) {
|
|
4404
4852
|
failures.push({
|
|
4405
4853
|
filePath,
|
|
4406
|
-
error: `Failed to parse ${
|
|
4854
|
+
error: `Failed to parse ${basename4(filePath)}: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
4407
4855
|
});
|
|
4408
4856
|
return;
|
|
4409
4857
|
}
|
|
@@ -4417,12 +4865,12 @@ function themeSyncCommand(cwd, options) {
|
|
|
4417
4865
|
for (const e of coverage.errors) {
|
|
4418
4866
|
failures.push({
|
|
4419
4867
|
filePath,
|
|
4420
|
-
error: formatFontCoverageError(
|
|
4868
|
+
error: formatFontCoverageError(basename4(filePath), e.declaredAt, e.family)
|
|
4421
4869
|
});
|
|
4422
4870
|
}
|
|
4423
4871
|
return;
|
|
4424
4872
|
}
|
|
4425
|
-
const yamlFilename = slugOverride ??
|
|
4873
|
+
const yamlFilename = slugOverride ?? basename4(filePath).replace(/\.visor\.yaml$/, "");
|
|
4426
4874
|
manifest.push({ slug: slug2, label, group, defaultMode, css, yamlFilename, isCustom });
|
|
4427
4875
|
};
|
|
4428
4876
|
for (const f of stockFiles) processFile(f, false);
|
|
@@ -4442,8 +4890,8 @@ function themeSyncCommand(cwd, options) {
|
|
|
4442
4890
|
let globalsContent;
|
|
4443
4891
|
let themeConfigContent;
|
|
4444
4892
|
try {
|
|
4445
|
-
globalsContent =
|
|
4446
|
-
themeConfigContent =
|
|
4893
|
+
globalsContent = readFileSync20(globalsPath, "utf-8");
|
|
4894
|
+
themeConfigContent = readFileSync20(themeConfigPath, "utf-8");
|
|
4447
4895
|
} catch (err) {
|
|
4448
4896
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
4449
4897
|
if (options.json) {
|
|
@@ -4458,12 +4906,12 @@ function themeSyncCommand(cwd, options) {
|
|
|
4458
4906
|
const newThemeConfig = updateStockThemeConfigBlock(themeConfigContent, stockManifest);
|
|
4459
4907
|
const newCustomOverlayCss = generateCustomOverlayCss(customManifest);
|
|
4460
4908
|
const newCustomOverlayTs = generateCustomOverlayTs(customManifest);
|
|
4461
|
-
const existingCssFiles =
|
|
4909
|
+
const existingCssFiles = existsSync17(docsAppDir) ? readdirSync6(docsAppDir).filter(
|
|
4462
4910
|
(f) => f.endsWith("-theme.css") && f !== "custom-themes.generated.css"
|
|
4463
4911
|
) : [];
|
|
4464
4912
|
const newCssSet = new Set(allSlugs.map((s) => `${s}-theme.css`));
|
|
4465
4913
|
const staleCssFiles = existingCssFiles.filter((f) => !newCssSet.has(f));
|
|
4466
|
-
const existingPublicYamls =
|
|
4914
|
+
const existingPublicYamls = existsSync17(docsPublicThemesDir) ? readdirSync6(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
|
|
4467
4915
|
const newPublicYamlSet = new Set(manifest.map((e) => `${e.yamlFilename}.visor.yaml`));
|
|
4468
4916
|
const stalePublicYamls = existingPublicYamls.filter((f) => !newPublicYamlSet.has(f));
|
|
4469
4917
|
if (options.dryRun) {
|
|
@@ -4503,28 +4951,28 @@ function themeSyncCommand(cwd, options) {
|
|
|
4503
4951
|
return;
|
|
4504
4952
|
}
|
|
4505
4953
|
try {
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4954
|
+
mkdirSync8(docsAppDir, { recursive: true });
|
|
4955
|
+
mkdirSync8(docsLibDir, { recursive: true });
|
|
4956
|
+
mkdirSync8(docsPublicThemesDir, { recursive: true });
|
|
4509
4957
|
for (const entry of manifest) {
|
|
4510
|
-
|
|
4958
|
+
writeFileSync12(join20(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
|
|
4511
4959
|
}
|
|
4512
4960
|
for (const stale of staleCssFiles) {
|
|
4513
|
-
unlinkSync2(
|
|
4961
|
+
unlinkSync2(join20(docsAppDir, stale));
|
|
4514
4962
|
}
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4963
|
+
writeFileSync12(customOverlayCssPath, newCustomOverlayCss, "utf-8");
|
|
4964
|
+
writeFileSync12(customOverlayTsPath, newCustomOverlayTs, "utf-8");
|
|
4965
|
+
writeFileSync12(themeConfigPath, newThemeConfig, "utf-8");
|
|
4966
|
+
writeFileSync12(globalsPath, newGlobals, "utf-8");
|
|
4519
4967
|
for (const srcFile of stockFiles) {
|
|
4520
|
-
copyFileSync(srcFile,
|
|
4968
|
+
copyFileSync(srcFile, join20(docsPublicThemesDir, basename4(srcFile)));
|
|
4521
4969
|
}
|
|
4522
4970
|
for (const c of customSources) {
|
|
4523
|
-
const targetName = c.origin === "legacy" ?
|
|
4524
|
-
copyFileSync(c.filePath,
|
|
4971
|
+
const targetName = c.origin === "legacy" ? basename4(c.filePath) : `${c.slug}.visor.yaml`;
|
|
4972
|
+
copyFileSync(c.filePath, join20(docsPublicThemesDir, targetName));
|
|
4525
4973
|
}
|
|
4526
4974
|
for (const stale of stalePublicYamls) {
|
|
4527
|
-
unlinkSync2(
|
|
4975
|
+
unlinkSync2(join20(docsPublicThemesDir, stale));
|
|
4528
4976
|
}
|
|
4529
4977
|
} catch (err) {
|
|
4530
4978
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
@@ -4577,19 +5025,19 @@ function themeSyncCommand(cwd, options) {
|
|
|
4577
5025
|
|
|
4578
5026
|
// src/commands/theme-batch-apply-flutter.ts
|
|
4579
5027
|
import {
|
|
4580
|
-
readFileSync as
|
|
4581
|
-
writeFileSync as
|
|
4582
|
-
mkdirSync as
|
|
4583
|
-
existsSync as
|
|
5028
|
+
readFileSync as readFileSync21,
|
|
5029
|
+
writeFileSync as writeFileSync13,
|
|
5030
|
+
mkdirSync as mkdirSync9,
|
|
5031
|
+
existsSync as existsSync18,
|
|
4584
5032
|
readdirSync as readdirSync7,
|
|
4585
5033
|
rmSync
|
|
4586
5034
|
} from "fs";
|
|
4587
|
-
import { join as
|
|
5035
|
+
import { join as join21, basename as basename5, dirname as dirname9 } from "path";
|
|
4588
5036
|
import { generateThemeData as generateThemeData6 } from "@loworbitstudio/visor-theme-engine";
|
|
4589
5037
|
import { flutterAdapter as flutterAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
4590
5038
|
function scanThemeDir2(dir) {
|
|
4591
|
-
if (!
|
|
4592
|
-
return readdirSync7(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) =>
|
|
5039
|
+
if (!existsSync18(dir)) return [];
|
|
5040
|
+
return readdirSync7(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join21(dir, f)).sort();
|
|
4593
5041
|
}
|
|
4594
5042
|
function slugToCamel(slug2) {
|
|
4595
5043
|
return slug2.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
@@ -4722,9 +5170,9 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4722
5170
|
process.exit(1);
|
|
4723
5171
|
return;
|
|
4724
5172
|
}
|
|
4725
|
-
const themesDir =
|
|
4726
|
-
const customThemesDir =
|
|
4727
|
-
const outputDir =
|
|
5173
|
+
const themesDir = join21(repoRoot, "themes");
|
|
5174
|
+
const customThemesDir = join21(repoRoot, "custom-themes");
|
|
5175
|
+
const outputDir = join21(repoRoot, "packages", "visor_themes");
|
|
4728
5176
|
const stockFiles = scanThemeDir2(themesDir);
|
|
4729
5177
|
const customFiles = scanThemeDir2(customThemesDir);
|
|
4730
5178
|
const allFiles = [...stockFiles, ...customFiles];
|
|
@@ -4745,7 +5193,7 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4745
5193
|
for (const filePath of allFiles) {
|
|
4746
5194
|
let yamlContent;
|
|
4747
5195
|
try {
|
|
4748
|
-
yamlContent =
|
|
5196
|
+
yamlContent = readFileSync21(filePath, "utf-8");
|
|
4749
5197
|
} catch {
|
|
4750
5198
|
errors.push(`Could not read: ${filePath}`);
|
|
4751
5199
|
continue;
|
|
@@ -4755,7 +5203,7 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4755
5203
|
data = generateThemeData6(yamlContent);
|
|
4756
5204
|
} catch (err) {
|
|
4757
5205
|
errors.push(
|
|
4758
|
-
`Failed to parse ${
|
|
5206
|
+
`Failed to parse ${basename5(filePath)}: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
4759
5207
|
);
|
|
4760
5208
|
continue;
|
|
4761
5209
|
}
|
|
@@ -4824,8 +5272,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4824
5272
|
return;
|
|
4825
5273
|
}
|
|
4826
5274
|
const slugs = processed.map((p) => p.slug);
|
|
4827
|
-
const libSrcDir =
|
|
4828
|
-
if (
|
|
5275
|
+
const libSrcDir = join21(outputDir, "lib", "src");
|
|
5276
|
+
if (existsSync18(libSrcDir)) {
|
|
4829
5277
|
rmSync(libSrcDir, { recursive: true, force: true });
|
|
4830
5278
|
}
|
|
4831
5279
|
const packageFiles = {
|
|
@@ -4837,17 +5285,17 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4837
5285
|
};
|
|
4838
5286
|
let totalFiles = 0;
|
|
4839
5287
|
for (const [relPath, content] of Object.entries(packageFiles)) {
|
|
4840
|
-
const absPath =
|
|
4841
|
-
|
|
4842
|
-
|
|
5288
|
+
const absPath = join21(outputDir, relPath);
|
|
5289
|
+
mkdirSync9(dirname9(absPath), { recursive: true });
|
|
5290
|
+
writeFileSync13(absPath, content, "utf-8");
|
|
4843
5291
|
totalFiles++;
|
|
4844
5292
|
}
|
|
4845
5293
|
for (const { slug: slug2, tokenFiles } of processed) {
|
|
4846
|
-
const themeBaseDir =
|
|
5294
|
+
const themeBaseDir = join21(outputDir, "lib", "src", slug2);
|
|
4847
5295
|
for (const [relPath, content] of Object.entries(tokenFiles)) {
|
|
4848
|
-
const absPath =
|
|
4849
|
-
|
|
4850
|
-
|
|
5296
|
+
const absPath = join21(themeBaseDir, relPath);
|
|
5297
|
+
mkdirSync9(dirname9(absPath), { recursive: true });
|
|
5298
|
+
writeFileSync13(absPath, content, "utf-8");
|
|
4851
5299
|
totalFiles++;
|
|
4852
5300
|
}
|
|
4853
5301
|
}
|
|
@@ -4868,11 +5316,11 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4868
5316
|
}
|
|
4869
5317
|
|
|
4870
5318
|
// src/commands/fonts-add.ts
|
|
4871
|
-
import { existsSync as
|
|
4872
|
-
import { resolve as
|
|
5319
|
+
import { existsSync as existsSync19, statSync as statSync7, readdirSync as readdirSync8, readFileSync as readFileSync22 } from "fs";
|
|
5320
|
+
import { resolve as resolve15, basename as basename6, extname as extname4 } from "path";
|
|
4873
5321
|
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
4874
5322
|
function deriveFamilySlug(filename) {
|
|
4875
|
-
const name =
|
|
5323
|
+
const name = basename6(filename, extname4(filename));
|
|
4876
5324
|
const WEIGHT_STYLE_SUFFIXES = /* @__PURE__ */ new Set([
|
|
4877
5325
|
"thin",
|
|
4878
5326
|
"hairline",
|
|
@@ -4913,21 +5361,21 @@ function deriveFamilySlug(filename) {
|
|
|
4913
5361
|
return parts.join("-").toLowerCase();
|
|
4914
5362
|
}
|
|
4915
5363
|
function collectWoff2Files(inputPath) {
|
|
4916
|
-
const resolved =
|
|
4917
|
-
if (!
|
|
5364
|
+
const resolved = resolve15(inputPath);
|
|
5365
|
+
if (!existsSync19(resolved)) {
|
|
4918
5366
|
throw new Error(`Path not found: ${resolved}`);
|
|
4919
5367
|
}
|
|
4920
5368
|
const stat = statSync7(resolved);
|
|
4921
5369
|
if (stat.isFile()) {
|
|
4922
5370
|
if (extname4(resolved).toLowerCase() !== ".woff2") {
|
|
4923
5371
|
throw new Error(
|
|
4924
|
-
`Invalid file format: ${
|
|
5372
|
+
`Invalid file format: ${basename6(resolved)}. Only .woff2 files are accepted.`
|
|
4925
5373
|
);
|
|
4926
5374
|
}
|
|
4927
5375
|
return [resolved];
|
|
4928
5376
|
}
|
|
4929
5377
|
if (stat.isDirectory()) {
|
|
4930
|
-
const files = readdirSync8(resolved).filter((f) => extname4(f).toLowerCase() === ".woff2").map((f) =>
|
|
5378
|
+
const files = readdirSync8(resolved).filter((f) => extname4(f).toLowerCase() === ".woff2").map((f) => resolve15(resolved, f));
|
|
4931
5379
|
if (files.length === 0) {
|
|
4932
5380
|
throw new Error(
|
|
4933
5381
|
`No .woff2 files found in directory: ${resolved}`
|
|
@@ -4942,8 +5390,8 @@ function collectWoff2Files(inputPath) {
|
|
|
4942
5390
|
throw new Error(`Path is neither a file nor a directory: ${resolved}`);
|
|
4943
5391
|
}
|
|
4944
5392
|
function getNonWoff2Fonts(inputPath) {
|
|
4945
|
-
const resolved =
|
|
4946
|
-
if (!
|
|
5393
|
+
const resolved = resolve15(inputPath);
|
|
5394
|
+
if (!existsSync19(resolved) || !statSync7(resolved).isDirectory()) {
|
|
4947
5395
|
return [];
|
|
4948
5396
|
}
|
|
4949
5397
|
return readdirSync8(resolved).filter((f) => {
|
|
@@ -4980,7 +5428,7 @@ function createR2Client(config) {
|
|
|
4980
5428
|
});
|
|
4981
5429
|
}
|
|
4982
5430
|
async function uploadFile(client, bucket, key, filePath) {
|
|
4983
|
-
const body =
|
|
5431
|
+
const body = readFileSync22(filePath);
|
|
4984
5432
|
await client.send(
|
|
4985
5433
|
new PutObjectCommand({
|
|
4986
5434
|
Bucket: bucket,
|
|
@@ -4995,8 +5443,8 @@ async function fontsAddCommand(inputPath, options) {
|
|
|
4995
5443
|
try {
|
|
4996
5444
|
const r2Config = getR2Config();
|
|
4997
5445
|
const files = collectWoff2Files(inputPath);
|
|
4998
|
-
const familySlug = options.family ?? deriveFamilySlug(
|
|
4999
|
-
const resolved =
|
|
5446
|
+
const familySlug = options.family ?? deriveFamilySlug(basename6(files[0]));
|
|
5447
|
+
const resolved = resolve15(inputPath);
|
|
5000
5448
|
const nonWoff2 = statSync7(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
|
|
5001
5449
|
if (!json) {
|
|
5002
5450
|
logger.heading("Visor Font Upload");
|
|
@@ -5015,7 +5463,7 @@ async function fontsAddCommand(inputPath, options) {
|
|
|
5015
5463
|
const bucket = "visor-fonts";
|
|
5016
5464
|
const results = [];
|
|
5017
5465
|
for (const filePath of files) {
|
|
5018
|
-
const filename =
|
|
5466
|
+
const filename = basename6(filePath);
|
|
5019
5467
|
const key = buildS3Key(org, familySlug, filename);
|
|
5020
5468
|
if (!json) {
|
|
5021
5469
|
logger.info(`Uploading ${filename}...`);
|
|
@@ -5284,27 +5732,27 @@ function findCssFiles(dir, maxDepth = 3) {
|
|
|
5284
5732
|
}
|
|
5285
5733
|
|
|
5286
5734
|
// src/utils/patterns.ts
|
|
5287
|
-
import { existsSync as
|
|
5288
|
-
import { join as
|
|
5735
|
+
import { existsSync as existsSync21, readdirSync as readdirSync10, readFileSync as readFileSync24 } from "fs";
|
|
5736
|
+
import { join as join23 } from "path";
|
|
5289
5737
|
import { parse as parseYAML } from "yaml";
|
|
5290
5738
|
function loadPatternsFromYaml(repoRoot) {
|
|
5291
|
-
const patternsDir =
|
|
5292
|
-
if (!
|
|
5739
|
+
const patternsDir = join23(repoRoot, "patterns");
|
|
5740
|
+
if (!existsSync21(patternsDir)) return [];
|
|
5293
5741
|
const files = readdirSync10(patternsDir).filter(
|
|
5294
5742
|
(f) => f.endsWith(".visor-pattern.yaml")
|
|
5295
5743
|
);
|
|
5296
5744
|
return files.map((file) => {
|
|
5297
|
-
const content =
|
|
5745
|
+
const content = readFileSync24(join23(patternsDir, file), "utf-8");
|
|
5298
5746
|
return parseYAML(content);
|
|
5299
5747
|
}).filter(Boolean);
|
|
5300
5748
|
}
|
|
5301
5749
|
function findRepoRoot2(startDir) {
|
|
5302
5750
|
let current = startDir;
|
|
5303
5751
|
while (true) {
|
|
5304
|
-
if (
|
|
5752
|
+
if (existsSync21(join23(current, "patterns"))) {
|
|
5305
5753
|
return current;
|
|
5306
5754
|
}
|
|
5307
|
-
const parent =
|
|
5755
|
+
const parent = join23(current, "..");
|
|
5308
5756
|
if (parent === current) return null;
|
|
5309
5757
|
current = parent;
|
|
5310
5758
|
}
|
|
@@ -5683,8 +6131,8 @@ Visor Tokens (${categoryLabel}) \u2014 ${tokens2.length} tokens
|
|
|
5683
6131
|
}
|
|
5684
6132
|
|
|
5685
6133
|
// src/commands/migrate-token-substitution.ts
|
|
5686
|
-
import { readFileSync as
|
|
5687
|
-
import { resolve as
|
|
6134
|
+
import { readFileSync as readFileSync25, writeFileSync as writeFileSync14, readdirSync as readdirSync11, statSync as statSync8, existsSync as existsSync22 } from "fs";
|
|
6135
|
+
import { resolve as resolve16, join as join24, relative as relative4 } from "path";
|
|
5688
6136
|
import { parse as parseYaml4 } from "yaml";
|
|
5689
6137
|
import pc4 from "picocolors";
|
|
5690
6138
|
var V7_ENTR_SUBSTITUTION_MAP = {
|
|
@@ -5707,14 +6155,14 @@ var BUILT_IN_SUBSTITUTION_MAPS = {
|
|
|
5707
6155
|
var DEFAULT_THEME_ID = "entr";
|
|
5708
6156
|
function readMapFromThemeFile(themeId, cwd) {
|
|
5709
6157
|
const candidates = [
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
6158
|
+
join24(cwd, "themes", `${themeId}.visor.yaml`),
|
|
6159
|
+
join24(cwd, "custom-themes", `${themeId}.visor.yaml`),
|
|
6160
|
+
join24(cwd, "packages", "docs", "public", "themes", `${themeId}.visor.yaml`)
|
|
5713
6161
|
];
|
|
5714
6162
|
for (const candidate of candidates) {
|
|
5715
|
-
if (
|
|
6163
|
+
if (existsSync22(candidate)) {
|
|
5716
6164
|
try {
|
|
5717
|
-
const raw =
|
|
6165
|
+
const raw = readFileSync25(candidate, "utf-8");
|
|
5718
6166
|
const parsed = parseYaml4(raw);
|
|
5719
6167
|
if (parsed?.migrate?.["token-substitution"]) {
|
|
5720
6168
|
return parsed.migrate["token-substitution"];
|
|
@@ -5770,7 +6218,7 @@ function collectCssFiles(dirPath) {
|
|
|
5770
6218
|
function walk2(current) {
|
|
5771
6219
|
const entries = readdirSync11(current, { withFileTypes: true });
|
|
5772
6220
|
for (const entry of entries) {
|
|
5773
|
-
const fullPath =
|
|
6221
|
+
const fullPath = join24(current, entry.name);
|
|
5774
6222
|
if (entry.isDirectory()) {
|
|
5775
6223
|
if (["node_modules", ".git", ".next", "dist", "build", ".cache"].includes(entry.name)) {
|
|
5776
6224
|
continue;
|
|
@@ -5798,7 +6246,7 @@ function runSubstitutionPass(targetPath, map, themeId) {
|
|
|
5798
6246
|
const fileResults = [];
|
|
5799
6247
|
let totalSubstitutions = 0;
|
|
5800
6248
|
for (const file of files) {
|
|
5801
|
-
const content =
|
|
6249
|
+
const content = readFileSync25(file, "utf-8");
|
|
5802
6250
|
const { newContent, substitutions } = applySubstitutionsToContent(content, map);
|
|
5803
6251
|
if (substitutions.length > 0) {
|
|
5804
6252
|
fileResults.push({ file, substitutions, originalContent: content, newContent });
|
|
@@ -5832,7 +6280,7 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
5832
6280
|
process.exit(1);
|
|
5833
6281
|
return;
|
|
5834
6282
|
}
|
|
5835
|
-
const targetPath =
|
|
6283
|
+
const targetPath = resolve16(cwd, targetArg ?? ".");
|
|
5836
6284
|
try {
|
|
5837
6285
|
statSync8(targetPath);
|
|
5838
6286
|
} catch {
|
|
@@ -5860,7 +6308,7 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
5860
6308
|
if (options.json) {
|
|
5861
6309
|
if (apply) {
|
|
5862
6310
|
for (const f of result.files) {
|
|
5863
|
-
|
|
6311
|
+
writeFileSync14(f.file, f.newContent, "utf-8");
|
|
5864
6312
|
}
|
|
5865
6313
|
console.log(JSON.stringify({
|
|
5866
6314
|
success: true,
|
|
@@ -5900,7 +6348,7 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
5900
6348
|
logger.warn(`Dry run \u2014 no files written. Re-run with ${pc4.bold("--apply")} to commit changes.`);
|
|
5901
6349
|
} else {
|
|
5902
6350
|
for (const f of result.files) {
|
|
5903
|
-
|
|
6351
|
+
writeFileSync14(f.file, f.newContent, "utf-8");
|
|
5904
6352
|
}
|
|
5905
6353
|
logger.heading(`visor migrate token-substitution \u2014 applied`);
|
|
5906
6354
|
logger.blank();
|
|
@@ -5921,23 +6369,23 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
5921
6369
|
}
|
|
5922
6370
|
|
|
5923
6371
|
// src/commands/sandbox/init.ts
|
|
5924
|
-
import { existsSync as
|
|
5925
|
-
import { isAbsolute as
|
|
6372
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync12, readFileSync as readFileSync28, rmSync as rmSync2 } from "fs";
|
|
6373
|
+
import { isAbsolute as isAbsolute4, join as join27, resolve as resolve18, dirname as dirname11 } from "path";
|
|
5926
6374
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5927
|
-
import * as
|
|
6375
|
+
import * as childProcess3 from "child_process";
|
|
5928
6376
|
|
|
5929
6377
|
// src/commands/sandbox/parse-handoff.ts
|
|
5930
|
-
import { readFileSync as
|
|
5931
|
-
import { dirname as
|
|
6378
|
+
import { readFileSync as readFileSync26, existsSync as existsSync23 } from "fs";
|
|
6379
|
+
import { dirname as dirname10, resolve as resolve17, isAbsolute as isAbsolute3 } from "path";
|
|
5932
6380
|
function parseHandoff(handoffPath) {
|
|
5933
|
-
const text =
|
|
6381
|
+
const text = readFileSync26(handoffPath, "utf-8");
|
|
5934
6382
|
const lines2 = text.split("\n");
|
|
5935
6383
|
const warnings = [];
|
|
5936
6384
|
const pattern2 = extractPatternSlug(lines2, warnings);
|
|
5937
6385
|
const theme2 = extractMetaField(lines2, "Theme");
|
|
5938
6386
|
const recipePath = extractRecipePath(text, handoffPath, warnings);
|
|
5939
6387
|
const primitives = extractPrimitives(text, warnings);
|
|
5940
|
-
const { screens, mockShapes } = recipePath &&
|
|
6388
|
+
const { screens, mockShapes } = recipePath && existsSync23(recipePath) ? extractRecipeArtifacts(recipePath, warnings) : { screens: [], mockShapes: [] };
|
|
5941
6389
|
return { pattern: pattern2, theme: theme2, recipePath, primitives, screens, mockShapes, warnings };
|
|
5942
6390
|
}
|
|
5943
6391
|
function extractPatternSlug(lines2, warnings) {
|
|
@@ -5968,8 +6416,8 @@ function extractRecipePath(text, handoffPath, warnings) {
|
|
|
5968
6416
|
return void 0;
|
|
5969
6417
|
}
|
|
5970
6418
|
const href = m[2].trim();
|
|
5971
|
-
if (
|
|
5972
|
-
return
|
|
6419
|
+
if (isAbsolute3(href)) return href;
|
|
6420
|
+
return resolve17(dirname10(handoffPath), href);
|
|
5973
6421
|
}
|
|
5974
6422
|
var STATUS_PATTERNS = [
|
|
5975
6423
|
{ re: /NEW\s+gap/i, status: "gap-new" },
|
|
@@ -6016,7 +6464,7 @@ function extractPrimitives(text, warnings) {
|
|
|
6016
6464
|
return primitives;
|
|
6017
6465
|
}
|
|
6018
6466
|
function extractRecipeArtifacts(recipePath, warnings) {
|
|
6019
|
-
const text =
|
|
6467
|
+
const text = readFileSync26(recipePath, "utf-8");
|
|
6020
6468
|
const screens = extractScreens(text);
|
|
6021
6469
|
const mockShapes = extractMockFields(text, warnings);
|
|
6022
6470
|
return { screens, mockShapes };
|
|
@@ -6090,8 +6538,8 @@ function tryPort(port) {
|
|
|
6090
6538
|
}
|
|
6091
6539
|
|
|
6092
6540
|
// src/commands/sandbox/scaffold.ts
|
|
6093
|
-
import { mkdirSync as
|
|
6094
|
-
import { join as
|
|
6541
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync15, existsSync as existsSync24, readdirSync as readdirSync12 } from "fs";
|
|
6542
|
+
import { join as join25 } from "path";
|
|
6095
6543
|
|
|
6096
6544
|
// src/commands/sandbox/templates.ts
|
|
6097
6545
|
var SANDBOX_PACKAGE_VERSION = "16.2.6";
|
|
@@ -6591,12 +7039,12 @@ function toPascalCase(s) {
|
|
|
6591
7039
|
|
|
6592
7040
|
// src/commands/sandbox/scaffold.ts
|
|
6593
7041
|
function writeScaffold(sandboxDir, manifest, port, options = {}) {
|
|
6594
|
-
|
|
7042
|
+
mkdirSync10(sandboxDir, { recursive: true });
|
|
6595
7043
|
const created = [];
|
|
6596
7044
|
const writeIfNew = (rel, contents) => {
|
|
6597
|
-
const full =
|
|
6598
|
-
|
|
6599
|
-
|
|
7045
|
+
const full = join25(sandboxDir, rel);
|
|
7046
|
+
mkdirSync10(dirOf(full), { recursive: true });
|
|
7047
|
+
writeFileSync15(full, contents, "utf-8");
|
|
6600
7048
|
created.push(rel);
|
|
6601
7049
|
};
|
|
6602
7050
|
writeIfNew("package.json", packageJsonTemplate(manifest.pattern));
|
|
@@ -6622,7 +7070,7 @@ function writeScaffold(sandboxDir, manifest, port, options = {}) {
|
|
|
6622
7070
|
}
|
|
6623
7071
|
}
|
|
6624
7072
|
writeIfNew("playwright.capture.mjs", captureScriptTemplate());
|
|
6625
|
-
|
|
7073
|
+
mkdirSync10(join25(sandboxDir, "captures", "approved"), { recursive: true });
|
|
6626
7074
|
return { created };
|
|
6627
7075
|
}
|
|
6628
7076
|
function dirOf(p) {
|
|
@@ -6656,10 +7104,10 @@ function writeSandboxConfig(sandboxDir, manifest, port, options) {
|
|
|
6656
7104
|
stripChromeSelectors: options.prototypeImport.stripChromeSelectors
|
|
6657
7105
|
} : null
|
|
6658
7106
|
};
|
|
6659
|
-
|
|
7107
|
+
writeFileSync15(join25(sandboxDir, "sandbox.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
6660
7108
|
}
|
|
6661
7109
|
function sandboxIsEmpty(sandboxDir) {
|
|
6662
|
-
if (!
|
|
7110
|
+
if (!existsSync24(sandboxDir)) return true;
|
|
6663
7111
|
try {
|
|
6664
7112
|
return readdirSync12(sandboxDir).filter((f) => !f.startsWith(".")).length === 0;
|
|
6665
7113
|
} catch {
|
|
@@ -6668,8 +7116,8 @@ function sandboxIsEmpty(sandboxDir) {
|
|
|
6668
7116
|
}
|
|
6669
7117
|
|
|
6670
7118
|
// src/commands/sandbox/html-prototype.ts
|
|
6671
|
-
import { mkdirSync as
|
|
6672
|
-
import { join as
|
|
7119
|
+
import { mkdirSync as mkdirSync11, readdirSync as readdirSync13, readFileSync as readFileSync27, statSync as statSync9, writeFileSync as writeFileSync16 } from "fs";
|
|
7120
|
+
import { join as join26 } from "path";
|
|
6673
7121
|
|
|
6674
7122
|
// src/commands/sandbox/strip-chrome.ts
|
|
6675
7123
|
var DEFAULT_STRIP_SELECTORS = [
|
|
@@ -6853,8 +7301,8 @@ function stripDocumentaryChrome(html, selectors) {
|
|
|
6853
7301
|
var SCREEN_FILE_PATTERN = /^screen-(\d+)-([^/.]+)\.html$/i;
|
|
6854
7302
|
function copyHtmlPrototype(sourceDir, sandboxDir, manifest, options = {}) {
|
|
6855
7303
|
const warnings = [];
|
|
6856
|
-
const destAbs =
|
|
6857
|
-
|
|
7304
|
+
const destAbs = join26(sandboxDir, "public", "prototype");
|
|
7305
|
+
mkdirSync11(destAbs, { recursive: true });
|
|
6858
7306
|
const stripChromeSelectors = options.stripChromeSelectors ?? [];
|
|
6859
7307
|
const copiedFiles = copyTreeRelative(sourceDir, destAbs, "", stripChromeSelectors);
|
|
6860
7308
|
const screenFiles = listOrderedScreenFiles(sourceDir);
|
|
@@ -6907,24 +7355,24 @@ function deriveStateCoverageScreen(file, existingNames) {
|
|
|
6907
7355
|
}
|
|
6908
7356
|
function copyTreeRelative(srcDir, destDir, relDir = "", stripChromeSelectors = []) {
|
|
6909
7357
|
const out = [];
|
|
6910
|
-
const entries = readdirSync13(
|
|
7358
|
+
const entries = readdirSync13(join26(srcDir, relDir));
|
|
6911
7359
|
for (const name of entries) {
|
|
6912
7360
|
if (name.startsWith(".")) continue;
|
|
6913
|
-
const srcPath =
|
|
6914
|
-
const destPath =
|
|
7361
|
+
const srcPath = join26(srcDir, relDir, name);
|
|
7362
|
+
const destPath = join26(destDir, relDir, name);
|
|
6915
7363
|
const stat = statSync9(srcPath);
|
|
6916
7364
|
if (stat.isDirectory()) {
|
|
6917
|
-
|
|
6918
|
-
out.push(...copyTreeRelative(srcDir, destDir,
|
|
7365
|
+
mkdirSync11(destPath, { recursive: true });
|
|
7366
|
+
out.push(...copyTreeRelative(srcDir, destDir, join26(relDir, name), stripChromeSelectors));
|
|
6919
7367
|
} else if (stat.isFile()) {
|
|
6920
7368
|
if (stripChromeSelectors.length > 0 && name.toLowerCase().endsWith(".html")) {
|
|
6921
|
-
const html =
|
|
7369
|
+
const html = readFileSync27(srcPath, "utf-8");
|
|
6922
7370
|
const stripped = stripDocumentaryChrome(html, stripChromeSelectors);
|
|
6923
|
-
|
|
7371
|
+
writeFileSync16(destPath, stripped);
|
|
6924
7372
|
} else {
|
|
6925
|
-
|
|
7373
|
+
writeFileSync16(destPath, readFileSync27(srcPath));
|
|
6926
7374
|
}
|
|
6927
|
-
out.push(
|
|
7375
|
+
out.push(join26("public", "prototype", relDir, name).replace(/\\/g, "/"));
|
|
6928
7376
|
}
|
|
6929
7377
|
}
|
|
6930
7378
|
return out;
|
|
@@ -6976,8 +7424,8 @@ async function runInit(name, cwd, options) {
|
|
|
6976
7424
|
if (!name || !/^[a-z0-9][a-z0-9-_]*$/i.test(name)) {
|
|
6977
7425
|
throw new Error(`Invalid sandbox name '${name}'. Use letters, digits, '-' or '_'.`);
|
|
6978
7426
|
}
|
|
6979
|
-
const handoffPath =
|
|
6980
|
-
if (!
|
|
7427
|
+
const handoffPath = isAbsolute4(options.handoff) ? options.handoff : resolve18(cwd, options.handoff);
|
|
7428
|
+
if (!existsSync25(handoffPath)) {
|
|
6981
7429
|
throw new Error(`Handoff manifest not found: ${handoffPath}`);
|
|
6982
7430
|
}
|
|
6983
7431
|
const manifest = parseHandoff(handoffPath);
|
|
@@ -6986,7 +7434,7 @@ async function runInit(name, cwd, options) {
|
|
|
6986
7434
|
`Handoff has no primitives \u2014 refusing to scaffold an empty sandbox. Check the manifest at ${handoffPath}`
|
|
6987
7435
|
);
|
|
6988
7436
|
}
|
|
6989
|
-
const sandboxDir =
|
|
7437
|
+
const sandboxDir = join27(cwd, ".lo", "sandbox", name);
|
|
6990
7438
|
if (!sandboxIsEmpty(sandboxDir)) {
|
|
6991
7439
|
if (!options.overwrite) {
|
|
6992
7440
|
throw new Error(
|
|
@@ -6995,12 +7443,12 @@ async function runInit(name, cwd, options) {
|
|
|
6995
7443
|
}
|
|
6996
7444
|
rmSync2(sandboxDir, { recursive: true, force: true });
|
|
6997
7445
|
}
|
|
6998
|
-
|
|
7446
|
+
mkdirSync12(sandboxDir, { recursive: true });
|
|
6999
7447
|
const port = await findOpenPort();
|
|
7000
7448
|
let prototypeImport;
|
|
7001
7449
|
if (options.fromHtmlPrototype) {
|
|
7002
|
-
const prototypeDir =
|
|
7003
|
-
if (!
|
|
7450
|
+
const prototypeDir = isAbsolute4(options.fromHtmlPrototype) ? options.fromHtmlPrototype : resolve18(cwd, options.fromHtmlPrototype);
|
|
7451
|
+
if (!existsSync25(prototypeDir)) {
|
|
7004
7452
|
throw new Error(`HTML prototype directory not found: ${prototypeDir}`);
|
|
7005
7453
|
}
|
|
7006
7454
|
const stripChromeSelectors = resolveStripSelectors(
|
|
@@ -7059,22 +7507,22 @@ async function runInit(name, cwd, options) {
|
|
|
7059
7507
|
function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile) {
|
|
7060
7508
|
const candidates = [];
|
|
7061
7509
|
if (themeFile) {
|
|
7062
|
-
candidates.push(
|
|
7510
|
+
candidates.push(isAbsolute4(themeFile) ? themeFile : resolve18(cwd, themeFile));
|
|
7063
7511
|
}
|
|
7064
|
-
candidates.push(
|
|
7512
|
+
candidates.push(isAbsolute4(theme2) ? theme2 : resolve18(cwd, theme2));
|
|
7065
7513
|
const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
|
|
7066
7514
|
if (privateRoot && privateRoot.length > 0) {
|
|
7067
|
-
candidates.push(
|
|
7515
|
+
candidates.push(join27(privateRoot, "themes", theme2, "theme.visor.yaml"));
|
|
7068
7516
|
}
|
|
7069
7517
|
candidates.push(
|
|
7070
|
-
|
|
7071
|
-
|
|
7518
|
+
join27(cwd, "themes", `${theme2}.visor.yaml`),
|
|
7519
|
+
join27(cwd, "custom-themes", `${theme2}.visor.yaml`)
|
|
7072
7520
|
);
|
|
7073
|
-
const yamlPath = candidates.find((p) =>
|
|
7521
|
+
const yamlPath = candidates.find((p) => existsSync25(p));
|
|
7074
7522
|
if (!yamlPath) {
|
|
7075
7523
|
const searched = candidates.join(", ");
|
|
7076
7524
|
manifest.warnings.push(
|
|
7077
|
-
`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 ${
|
|
7525
|
+
`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 ${join27(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.`
|
|
7078
7526
|
);
|
|
7079
7527
|
if (!json) {
|
|
7080
7528
|
logger.warn(`Theme '${theme2}' not found \u2014 leaving placeholder globals.css.`);
|
|
@@ -7082,12 +7530,12 @@ function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile
|
|
|
7082
7530
|
`Re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH=<dir-containing-themes/${theme2}/theme.visor.yaml>.`
|
|
7083
7531
|
);
|
|
7084
7532
|
logger.item(
|
|
7085
|
-
`Or apply manually: npx visor theme apply <path> --adapter nextjs -o ${
|
|
7533
|
+
`Or apply manually: npx visor theme apply <path> --adapter nextjs -o ${join27(sandboxDir, "app", "globals.css")}`
|
|
7086
7534
|
);
|
|
7087
7535
|
}
|
|
7088
7536
|
return;
|
|
7089
7537
|
}
|
|
7090
|
-
const globalsOut =
|
|
7538
|
+
const globalsOut = join27(sandboxDir, "app", "globals.css");
|
|
7091
7539
|
try {
|
|
7092
7540
|
themeApplyCommand(yamlPath, sandboxDir, {
|
|
7093
7541
|
output: globalsOut,
|
|
@@ -7118,7 +7566,7 @@ function tryAddPrimitive(primitive, sandboxDir, json) {
|
|
|
7118
7566
|
}
|
|
7119
7567
|
function runNpmInstall(sandboxDir, json) {
|
|
7120
7568
|
if (!json) logger.info("Installing sandbox dependencies...");
|
|
7121
|
-
const result =
|
|
7569
|
+
const result = childProcess3.spawnSync("npm", ["install", "--no-audit", "--no-fund"], {
|
|
7122
7570
|
cwd: sandboxDir,
|
|
7123
7571
|
stdio: json ? "ignore" : "inherit"
|
|
7124
7572
|
});
|
|
@@ -7140,12 +7588,12 @@ function loadKnownPrimitives() {
|
|
|
7140
7588
|
}
|
|
7141
7589
|
function readCliVersion() {
|
|
7142
7590
|
try {
|
|
7143
|
-
const here =
|
|
7591
|
+
const here = dirname11(fileURLToPath3(import.meta.url));
|
|
7144
7592
|
for (let i = 0; i < 6; i++) {
|
|
7145
7593
|
const segments = new Array(i).fill("..");
|
|
7146
|
-
const candidate =
|
|
7594
|
+
const candidate = join27(here, ...segments, "package.json");
|
|
7147
7595
|
try {
|
|
7148
|
-
const pkg2 = JSON.parse(
|
|
7596
|
+
const pkg2 = JSON.parse(readFileSync28(candidate, "utf-8"));
|
|
7149
7597
|
if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) return pkg2.version;
|
|
7150
7598
|
} catch {
|
|
7151
7599
|
}
|
|
@@ -7156,14 +7604,14 @@ function readCliVersion() {
|
|
|
7156
7604
|
}
|
|
7157
7605
|
|
|
7158
7606
|
// src/commands/sandbox/dev.ts
|
|
7159
|
-
import { existsSync as
|
|
7160
|
-
import { join as
|
|
7161
|
-
import * as
|
|
7607
|
+
import { existsSync as existsSync26, readFileSync as readFileSync29 } from "fs";
|
|
7608
|
+
import { join as join28 } from "path";
|
|
7609
|
+
import * as childProcess4 from "child_process";
|
|
7162
7610
|
function sandboxDevCommand(cwd, options) {
|
|
7163
7611
|
const json = options.json ?? false;
|
|
7164
|
-
const sandboxDir =
|
|
7165
|
-
const configPath =
|
|
7166
|
-
if (!
|
|
7612
|
+
const sandboxDir = join28(cwd, ".lo", "sandbox", options.name);
|
|
7613
|
+
const configPath = join28(sandboxDir, "sandbox.json");
|
|
7614
|
+
if (!existsSync26(configPath)) {
|
|
7167
7615
|
fail(
|
|
7168
7616
|
json,
|
|
7169
7617
|
`Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init ${options.name} --handoff ... --theme ...' first.`
|
|
@@ -7172,7 +7620,7 @@ function sandboxDevCommand(cwd, options) {
|
|
|
7172
7620
|
}
|
|
7173
7621
|
let config;
|
|
7174
7622
|
try {
|
|
7175
|
-
config = JSON.parse(
|
|
7623
|
+
config = JSON.parse(readFileSync29(configPath, "utf-8"));
|
|
7176
7624
|
} catch (err) {
|
|
7177
7625
|
fail(json, `Invalid sandbox.json at ${configPath}: ${err.message}`);
|
|
7178
7626
|
return;
|
|
@@ -7199,7 +7647,7 @@ function sandboxDevCommand(cwd, options) {
|
|
|
7199
7647
|
spawnNextDev(sandboxDir, config.port, json);
|
|
7200
7648
|
}
|
|
7201
7649
|
function spawnNextDev(sandboxDir, port, json) {
|
|
7202
|
-
const child =
|
|
7650
|
+
const child = childProcess4.spawn(
|
|
7203
7651
|
"npx",
|
|
7204
7652
|
["--no-install", "next", "dev", "--port", String(port)],
|
|
7205
7653
|
{
|
|
@@ -7230,20 +7678,20 @@ function fail(json, message) {
|
|
|
7230
7678
|
// src/commands/sandbox/approve.ts
|
|
7231
7679
|
import {
|
|
7232
7680
|
copyFileSync as copyFileSync2,
|
|
7233
|
-
existsSync as
|
|
7234
|
-
mkdirSync as
|
|
7235
|
-
readFileSync as
|
|
7681
|
+
existsSync as existsSync27,
|
|
7682
|
+
mkdirSync as mkdirSync13,
|
|
7683
|
+
readFileSync as readFileSync30,
|
|
7236
7684
|
readdirSync as readdirSync14,
|
|
7237
7685
|
rmSync as rmSync3,
|
|
7238
|
-
writeFileSync as
|
|
7686
|
+
writeFileSync as writeFileSync17
|
|
7239
7687
|
} from "fs";
|
|
7240
|
-
import { basename as
|
|
7241
|
-
import * as
|
|
7688
|
+
import { basename as basename7, join as join29 } from "path";
|
|
7689
|
+
import * as childProcess5 from "child_process";
|
|
7242
7690
|
function sandboxApproveCommand(cwd, options) {
|
|
7243
7691
|
const json = options.json ?? false;
|
|
7244
|
-
const sandboxDir =
|
|
7245
|
-
const configPath =
|
|
7246
|
-
if (!
|
|
7692
|
+
const sandboxDir = join29(cwd, ".lo", "sandbox", options.name);
|
|
7693
|
+
const configPath = join29(sandboxDir, "sandbox.json");
|
|
7694
|
+
if (!existsSync27(configPath)) {
|
|
7247
7695
|
fail2(
|
|
7248
7696
|
json,
|
|
7249
7697
|
`Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init' first.`
|
|
@@ -7257,11 +7705,11 @@ function sandboxApproveCommand(cwd, options) {
|
|
|
7257
7705
|
runCapture(sandboxDir, configPath, options.name, json);
|
|
7258
7706
|
}
|
|
7259
7707
|
function runCapture(sandboxDir, configPath, sandboxName, json) {
|
|
7260
|
-
const config = JSON.parse(
|
|
7261
|
-
const captureScriptPath =
|
|
7262
|
-
|
|
7708
|
+
const config = JSON.parse(readFileSync30(configPath, "utf-8"));
|
|
7709
|
+
const captureScriptPath = join29(sandboxDir, "playwright.capture.mjs");
|
|
7710
|
+
writeFileSync17(captureScriptPath, captureScriptTemplate(), "utf-8");
|
|
7263
7711
|
ensurePlaywrightInstalled(sandboxDir, json);
|
|
7264
|
-
const result =
|
|
7712
|
+
const result = childProcess5.spawnSync(
|
|
7265
7713
|
"npx",
|
|
7266
7714
|
["--no-install", "node", "playwright.capture.mjs"],
|
|
7267
7715
|
{
|
|
@@ -7280,9 +7728,9 @@ function runCapture(sandboxDir, configPath, sandboxName, json) {
|
|
|
7280
7728
|
${result.stderr}`);
|
|
7281
7729
|
return;
|
|
7282
7730
|
}
|
|
7283
|
-
const pendingDir =
|
|
7284
|
-
const diffsDir =
|
|
7285
|
-
const approvedDir =
|
|
7731
|
+
const pendingDir = join29(sandboxDir, "captures", "pending");
|
|
7732
|
+
const diffsDir = join29(sandboxDir, "captures", "diffs");
|
|
7733
|
+
const approvedDir = join29(sandboxDir, "captures", "approved");
|
|
7286
7734
|
const pendingFiles = safeListPngs(pendingDir);
|
|
7287
7735
|
const diffFiles = safeListPngs(diffsDir);
|
|
7288
7736
|
const hasBaseline = safeListPngs(approvedDir).length > 0;
|
|
@@ -7319,9 +7767,9 @@ ${result.stderr}`);
|
|
|
7319
7767
|
}
|
|
7320
7768
|
}
|
|
7321
7769
|
function runPromotion(sandboxDir, sandboxName, json) {
|
|
7322
|
-
const pendingDir =
|
|
7323
|
-
const approvedDir =
|
|
7324
|
-
const diffsDir =
|
|
7770
|
+
const pendingDir = join29(sandboxDir, "captures", "pending");
|
|
7771
|
+
const approvedDir = join29(sandboxDir, "captures", "approved");
|
|
7772
|
+
const diffsDir = join29(sandboxDir, "captures", "diffs");
|
|
7325
7773
|
const pendingFiles = safeListPngs(pendingDir);
|
|
7326
7774
|
if (pendingFiles.length === 0) {
|
|
7327
7775
|
fail2(
|
|
@@ -7330,10 +7778,10 @@ function runPromotion(sandboxDir, sandboxName, json) {
|
|
|
7330
7778
|
);
|
|
7331
7779
|
return;
|
|
7332
7780
|
}
|
|
7333
|
-
|
|
7781
|
+
mkdirSync13(approvedDir, { recursive: true });
|
|
7334
7782
|
const promoted = [];
|
|
7335
7783
|
for (const src of pendingFiles) {
|
|
7336
|
-
const dest =
|
|
7784
|
+
const dest = join29(approvedDir, basename7(src));
|
|
7337
7785
|
copyFileSync2(src, dest);
|
|
7338
7786
|
promoted.push(dest);
|
|
7339
7787
|
}
|
|
@@ -7353,10 +7801,10 @@ function runPromotion(sandboxDir, sandboxName, json) {
|
|
|
7353
7801
|
}
|
|
7354
7802
|
}
|
|
7355
7803
|
function ensurePlaywrightInstalled(sandboxDir, json) {
|
|
7356
|
-
const markerPath =
|
|
7357
|
-
if (
|
|
7804
|
+
const markerPath = join29(sandboxDir, ".playwright-installed");
|
|
7805
|
+
if (existsSync27(markerPath)) return;
|
|
7358
7806
|
if (!json) logger.info("Installing Playwright Chromium (one-time)...");
|
|
7359
|
-
const result =
|
|
7807
|
+
const result = childProcess5.spawnSync(
|
|
7360
7808
|
"npx",
|
|
7361
7809
|
["--no-install", "playwright", "install", "chromium"],
|
|
7362
7810
|
{
|
|
@@ -7370,11 +7818,11 @@ function ensurePlaywrightInstalled(sandboxDir, json) {
|
|
|
7370
7818
|
if (typeof result.status === "number" && result.status !== 0) {
|
|
7371
7819
|
throw new Error(`playwright install exited with code ${result.status}`);
|
|
7372
7820
|
}
|
|
7373
|
-
|
|
7821
|
+
writeFileSync17(markerPath, (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
|
|
7374
7822
|
}
|
|
7375
7823
|
function safeListPngs(dir) {
|
|
7376
|
-
if (!
|
|
7377
|
-
return readdirSync14(dir).filter((f) => f.endsWith(".png")).map((f) =>
|
|
7824
|
+
if (!existsSync27(dir)) return [];
|
|
7825
|
+
return readdirSync14(dir).filter((f) => f.endsWith(".png")).map((f) => join29(dir, f));
|
|
7378
7826
|
}
|
|
7379
7827
|
function tryParseJson(s) {
|
|
7380
7828
|
try {
|
|
@@ -7392,15 +7840,315 @@ function fail2(json, message) {
|
|
|
7392
7840
|
process.exit(1);
|
|
7393
7841
|
}
|
|
7394
7842
|
|
|
7843
|
+
// src/commands/spawn.ts
|
|
7844
|
+
import {
|
|
7845
|
+
cpSync,
|
|
7846
|
+
existsSync as existsSync29,
|
|
7847
|
+
readFileSync as readFileSync31,
|
|
7848
|
+
readdirSync as readdirSync16,
|
|
7849
|
+
rmSync as rmSync4
|
|
7850
|
+
} from "fs";
|
|
7851
|
+
import { basename as basename8, isAbsolute as isAbsolute5, join as join31, resolve as resolve19 } from "path";
|
|
7852
|
+
import { homedir as homedir3 } from "os";
|
|
7853
|
+
import * as childProcess6 from "child_process";
|
|
7854
|
+
import { parse as parseYaml5 } from "yaml";
|
|
7855
|
+
import { generateThemeData as generateThemeData7 } from "@loworbitstudio/visor-theme-engine";
|
|
7856
|
+
import { nextjsAdapter as nextjsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
7857
|
+
import { validate as validate3 } from "@loworbitstudio/visor-theme-engine";
|
|
7858
|
+
|
|
7859
|
+
// src/lib/blessed-discovery.ts
|
|
7860
|
+
import { existsSync as existsSync28, readdirSync as readdirSync15 } from "fs";
|
|
7861
|
+
import { join as join30 } from "path";
|
|
7862
|
+
var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
7863
|
+
"node_modules",
|
|
7864
|
+
".next",
|
|
7865
|
+
".git",
|
|
7866
|
+
"dist",
|
|
7867
|
+
".turbo",
|
|
7868
|
+
"coverage",
|
|
7869
|
+
".cache"
|
|
7870
|
+
]);
|
|
7871
|
+
function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
|
|
7872
|
+
const builds = [];
|
|
7873
|
+
const errors = [];
|
|
7874
|
+
if (!existsSync28(blessedDir)) {
|
|
7875
|
+
return { builds, errors };
|
|
7876
|
+
}
|
|
7877
|
+
walk2(blessedDir, 0);
|
|
7878
|
+
builds.sort((a, b) => {
|
|
7879
|
+
if (a.manifest.shape !== b.manifest.shape) {
|
|
7880
|
+
return a.manifest.shape.localeCompare(b.manifest.shape);
|
|
7881
|
+
}
|
|
7882
|
+
return a.manifest.pattern.localeCompare(b.manifest.pattern);
|
|
7883
|
+
});
|
|
7884
|
+
return { builds, errors };
|
|
7885
|
+
function walk2(dir, depth) {
|
|
7886
|
+
if (depth > maxDepth) return;
|
|
7887
|
+
let entries;
|
|
7888
|
+
try {
|
|
7889
|
+
entries = readdirSync15(dir, { withFileTypes: true });
|
|
7890
|
+
} catch {
|
|
7891
|
+
return;
|
|
7892
|
+
}
|
|
7893
|
+
const hasManifest = entries.some(
|
|
7894
|
+
(entry) => entry.isFile() && entry.name === BLESSED_MANIFEST_FILENAME
|
|
7895
|
+
);
|
|
7896
|
+
if (hasManifest) {
|
|
7897
|
+
const result = parseBlessedManifest(dir);
|
|
7898
|
+
if (result.ok) {
|
|
7899
|
+
builds.push({ dir, manifest: result.manifest });
|
|
7900
|
+
} else {
|
|
7901
|
+
errors.push({ dir, error: result.error });
|
|
7902
|
+
}
|
|
7903
|
+
return;
|
|
7904
|
+
}
|
|
7905
|
+
for (const entry of entries) {
|
|
7906
|
+
if (!entry.isDirectory()) continue;
|
|
7907
|
+
if (WALK_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
7908
|
+
walk2(join30(dir, entry.name), depth + 1);
|
|
7909
|
+
}
|
|
7910
|
+
}
|
|
7911
|
+
}
|
|
7912
|
+
function resolveBlessedBuild(blessedDir, shape, pattern2) {
|
|
7913
|
+
const { builds } = discoverBlessedBuilds(blessedDir);
|
|
7914
|
+
const build = builds.find(
|
|
7915
|
+
(candidate) => candidate.manifest.shape === shape && candidate.manifest.pattern === pattern2
|
|
7916
|
+
);
|
|
7917
|
+
return { build, available: builds };
|
|
7918
|
+
}
|
|
7919
|
+
|
|
7920
|
+
// src/commands/spawn.ts
|
|
7921
|
+
var DEFAULT_BLESSED_DIR = join31(
|
|
7922
|
+
homedir3(),
|
|
7923
|
+
"Code",
|
|
7924
|
+
"low-orbit",
|
|
7925
|
+
"low-orbit-playbook",
|
|
7926
|
+
"design-prototypes"
|
|
7927
|
+
);
|
|
7928
|
+
var FORK_EXCLUDE = /* @__PURE__ */ new Set([
|
|
7929
|
+
"node_modules",
|
|
7930
|
+
".next",
|
|
7931
|
+
".git",
|
|
7932
|
+
"dist",
|
|
7933
|
+
".turbo",
|
|
7934
|
+
".cache",
|
|
7935
|
+
"coverage",
|
|
7936
|
+
".DS_Store",
|
|
7937
|
+
"tsconfig.tsbuildinfo"
|
|
7938
|
+
]);
|
|
7939
|
+
function parseBlessedIdentifier(from) {
|
|
7940
|
+
const PREFIX = "blessed:";
|
|
7941
|
+
if (!from.startsWith(PREFIX)) {
|
|
7942
|
+
throw new Error(
|
|
7943
|
+
`Invalid --from '${from}'. Expected the form blessed:{shape}:{pattern} (e.g. blessed:admin-ui:organization-management).`
|
|
7944
|
+
);
|
|
7945
|
+
}
|
|
7946
|
+
const rest = from.slice(PREFIX.length);
|
|
7947
|
+
const parts = rest.split(":");
|
|
7948
|
+
if (parts.length !== 2 || parts[0].length === 0 || parts[1].length === 0) {
|
|
7949
|
+
throw new Error(
|
|
7950
|
+
`Invalid --from '${from}'. Expected the form blessed:{shape}:{pattern} (e.g. blessed:admin-ui:organization-management).`
|
|
7951
|
+
);
|
|
7952
|
+
}
|
|
7953
|
+
return { shape: parts[0], pattern: parts[1] };
|
|
7954
|
+
}
|
|
7955
|
+
function resolveBlessedDir(cwd, options) {
|
|
7956
|
+
const raw = options.blessedDir ?? (process.env.VISOR_BLESSED_DIR && process.env.VISOR_BLESSED_DIR.length > 0 ? process.env.VISOR_BLESSED_DIR : void 0) ?? DEFAULT_BLESSED_DIR;
|
|
7957
|
+
return isAbsolute5(raw) ? raw : resolve19(cwd, raw);
|
|
7958
|
+
}
|
|
7959
|
+
function resolveThemeFile(theme2, cwd, themeFile) {
|
|
7960
|
+
const candidates = [];
|
|
7961
|
+
if (themeFile) {
|
|
7962
|
+
candidates.push(isAbsolute5(themeFile) ? themeFile : resolve19(cwd, themeFile));
|
|
7963
|
+
}
|
|
7964
|
+
candidates.push(isAbsolute5(theme2) ? theme2 : resolve19(cwd, theme2));
|
|
7965
|
+
const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
|
|
7966
|
+
if (privateRoot && privateRoot.length > 0) {
|
|
7967
|
+
candidates.push(join31(privateRoot, "themes", theme2, "theme.visor.yaml"));
|
|
7968
|
+
}
|
|
7969
|
+
candidates.push(
|
|
7970
|
+
join31(cwd, "themes", `${theme2}.visor.yaml`),
|
|
7971
|
+
join31(cwd, "custom-themes", `${theme2}.visor.yaml`)
|
|
7972
|
+
);
|
|
7973
|
+
const found = candidates.find((candidate) => existsSync29(candidate));
|
|
7974
|
+
if (!found) {
|
|
7975
|
+
throw new Error(
|
|
7976
|
+
`Theme '${theme2}' not found (searched: ${candidates.join(", ")}). Pass --theme-file <path>, use a direct path, or set VISOR_THEMES_PRIVATE_PATH.`
|
|
7977
|
+
);
|
|
7978
|
+
}
|
|
7979
|
+
return { path: found, searched: candidates };
|
|
7980
|
+
}
|
|
7981
|
+
function applyThemeToFork(themeFile, outputDir, manifest) {
|
|
7982
|
+
const yaml = readFileSync31(themeFile, "utf-8");
|
|
7983
|
+
const data = generateThemeData7(yaml);
|
|
7984
|
+
const themeId = data.config.name;
|
|
7985
|
+
if (!themeId || themeId.length === 0) {
|
|
7986
|
+
throw new Error(
|
|
7987
|
+
`Theme file '${themeFile}' is missing a config.name; required to derive the theme id for spawn's theme-apply dispatch. See docs/blessed-builds.md.`
|
|
7988
|
+
);
|
|
7989
|
+
}
|
|
7990
|
+
const css = nextjsAdapter3(
|
|
7991
|
+
{ primitives: data.primitives, tokens: data.tokens, config: data.config },
|
|
7992
|
+
{}
|
|
7993
|
+
);
|
|
7994
|
+
applyThemeToBuild({
|
|
7995
|
+
manifest,
|
|
7996
|
+
buildDir: outputDir,
|
|
7997
|
+
themeId,
|
|
7998
|
+
adapterCss: css
|
|
7999
|
+
});
|
|
8000
|
+
}
|
|
8001
|
+
function runNpmInstall2(outputDir, json) {
|
|
8002
|
+
const result = childProcess6.spawnSync("npm", ["install", "--no-audit", "--no-fund"], {
|
|
8003
|
+
cwd: outputDir,
|
|
8004
|
+
stdio: json ? "ignore" : "inherit"
|
|
8005
|
+
});
|
|
8006
|
+
if (result.error) {
|
|
8007
|
+
throw new Error(`npm install failed to start: ${result.error.message}`);
|
|
8008
|
+
}
|
|
8009
|
+
if (typeof result.status === "number" && result.status !== 0) {
|
|
8010
|
+
throw new Error(`npm install exited with code ${result.status}`);
|
|
8011
|
+
}
|
|
8012
|
+
}
|
|
8013
|
+
function runSpawn(cwd, options) {
|
|
8014
|
+
if (!options.from) throw new Error("Missing required --from blessed:{shape}:{pattern}.");
|
|
8015
|
+
if (!options.theme) throw new Error("Missing required --theme <id>.");
|
|
8016
|
+
if (!options.output) throw new Error("Missing required --output <path>.");
|
|
8017
|
+
const { shape, pattern: pattern2 } = parseBlessedIdentifier(options.from);
|
|
8018
|
+
const blessedDir = resolveBlessedDir(cwd, options);
|
|
8019
|
+
const { build, available } = resolveBlessedBuild(blessedDir, shape, pattern2);
|
|
8020
|
+
if (!build) {
|
|
8021
|
+
const list = available.length > 0 ? available.map((b) => ` - blessed:${b.manifest.shape}:${b.manifest.pattern}`).join("\n") : " (none found)";
|
|
8022
|
+
throw new Error(
|
|
8023
|
+
`No blessed build found for blessed:${shape}:${pattern2} under ${blessedDir}.
|
|
8024
|
+
Available builds:
|
|
8025
|
+
${list}`
|
|
8026
|
+
);
|
|
8027
|
+
}
|
|
8028
|
+
const outputDir = isAbsolute5(options.output) ? options.output : resolve19(cwd, options.output);
|
|
8029
|
+
if (existsSync29(outputDir) && readdirSync16(outputDir).length > 0) {
|
|
8030
|
+
throw new Error(`Output directory already exists and is not empty: ${outputDir}`);
|
|
8031
|
+
}
|
|
8032
|
+
const { path: themeFile } = resolveThemeFile(options.theme, cwd, options.themeFile);
|
|
8033
|
+
cpSync(build.dir, outputDir, {
|
|
8034
|
+
recursive: true,
|
|
8035
|
+
filter: (src) => !FORK_EXCLUDE.has(basename8(src))
|
|
8036
|
+
});
|
|
8037
|
+
let validated = false;
|
|
8038
|
+
let installed = false;
|
|
8039
|
+
try {
|
|
8040
|
+
applyThemeToFork(themeFile, outputDir, build.manifest);
|
|
8041
|
+
if (options.validate) {
|
|
8042
|
+
const parsed = parseYaml5(readFileSync31(themeFile, "utf-8"));
|
|
8043
|
+
const result = validate3(parsed);
|
|
8044
|
+
if (!result.valid) {
|
|
8045
|
+
const messages = result.errors.map((e) => e.message).join("; ");
|
|
8046
|
+
throw new Error(`Theme validation failed: ${messages}`);
|
|
8047
|
+
}
|
|
8048
|
+
validated = true;
|
|
8049
|
+
}
|
|
8050
|
+
if (options.install) {
|
|
8051
|
+
runNpmInstall2(outputDir, options.json ?? false);
|
|
8052
|
+
installed = true;
|
|
8053
|
+
}
|
|
8054
|
+
} catch (err) {
|
|
8055
|
+
rmSync4(outputDir, { recursive: true, force: true });
|
|
8056
|
+
throw err;
|
|
8057
|
+
}
|
|
8058
|
+
return {
|
|
8059
|
+
success: true,
|
|
8060
|
+
build: {
|
|
8061
|
+
shape: build.manifest.shape,
|
|
8062
|
+
pattern: build.manifest.pattern,
|
|
8063
|
+
requiresVisor: build.manifest.requires_visor,
|
|
8064
|
+
source: build.dir
|
|
8065
|
+
},
|
|
8066
|
+
output: outputDir,
|
|
8067
|
+
themeApplied: options.theme,
|
|
8068
|
+
themeFile,
|
|
8069
|
+
installed,
|
|
8070
|
+
validated
|
|
8071
|
+
};
|
|
8072
|
+
}
|
|
8073
|
+
function listBlessed(cwd, options) {
|
|
8074
|
+
const blessedDir = resolveBlessedDir(cwd, options);
|
|
8075
|
+
const { builds, errors } = discoverBlessedBuilds(blessedDir);
|
|
8076
|
+
if (options.json) {
|
|
8077
|
+
console.log(
|
|
8078
|
+
JSON.stringify({
|
|
8079
|
+
success: true,
|
|
8080
|
+
blessedDir,
|
|
8081
|
+
builds: builds.map((b) => ({
|
|
8082
|
+
from: `blessed:${b.manifest.shape}:${b.manifest.pattern}`,
|
|
8083
|
+
shape: b.manifest.shape,
|
|
8084
|
+
pattern: b.manifest.pattern,
|
|
8085
|
+
base_theme: b.manifest.base_theme,
|
|
8086
|
+
requires_visor: b.manifest.requires_visor,
|
|
8087
|
+
three_gates_status: b.manifest.three_gates_status,
|
|
8088
|
+
dir: b.dir
|
|
8089
|
+
})),
|
|
8090
|
+
errors
|
|
8091
|
+
})
|
|
8092
|
+
);
|
|
8093
|
+
return;
|
|
8094
|
+
}
|
|
8095
|
+
logger.heading(`Blessed builds under ${blessedDir}`);
|
|
8096
|
+
if (builds.length === 0) {
|
|
8097
|
+
logger.info(" (none found)");
|
|
8098
|
+
}
|
|
8099
|
+
for (const b of builds) {
|
|
8100
|
+
logger.success(`blessed:${b.manifest.shape}:${b.manifest.pattern}`);
|
|
8101
|
+
logger.item(`base theme: ${b.manifest.base_theme} \xB7 requires visor ${b.manifest.requires_visor} \xB7 gates: ${b.manifest.three_gates_status}`);
|
|
8102
|
+
}
|
|
8103
|
+
for (const e of errors) {
|
|
8104
|
+
logger.warn(`Skipped ${e.dir}: ${e.error}`);
|
|
8105
|
+
}
|
|
8106
|
+
}
|
|
8107
|
+
function spawnCommand(cwd, options) {
|
|
8108
|
+
const json = options.json ?? false;
|
|
8109
|
+
if (options.listBlessed) {
|
|
8110
|
+
try {
|
|
8111
|
+
listBlessed(cwd, options);
|
|
8112
|
+
} catch (err) {
|
|
8113
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8114
|
+
if (json) console.log(JSON.stringify({ success: false, error: message }));
|
|
8115
|
+
else logger.error(message);
|
|
8116
|
+
process.exit(1);
|
|
8117
|
+
}
|
|
8118
|
+
return;
|
|
8119
|
+
}
|
|
8120
|
+
try {
|
|
8121
|
+
const result = runSpawn(cwd, options);
|
|
8122
|
+
if (json) {
|
|
8123
|
+
console.log(JSON.stringify(result));
|
|
8124
|
+
return;
|
|
8125
|
+
}
|
|
8126
|
+
logger.success(
|
|
8127
|
+
`Discovered blessed build: ${result.build.shape}/${result.build.pattern} (requires visor ${result.build.requiresVisor})`
|
|
8128
|
+
);
|
|
8129
|
+
logger.success(`Forked to ${result.output} (excluded node_modules, .next, .git)`);
|
|
8130
|
+
logger.success(`Theme applied: ${result.themeApplied} \u2192 ${result.output}`);
|
|
8131
|
+
if (result.validated) logger.success("Theme validated");
|
|
8132
|
+
if (result.installed) logger.success("Installed dependencies");
|
|
8133
|
+
logger.blank();
|
|
8134
|
+
logger.info(`Next: cd ${result.output} && npm run dev`);
|
|
8135
|
+
} catch (err) {
|
|
8136
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8137
|
+
if (json) console.log(JSON.stringify({ success: false, error: message }));
|
|
8138
|
+
else logger.error(message);
|
|
8139
|
+
process.exit(1);
|
|
8140
|
+
}
|
|
8141
|
+
}
|
|
8142
|
+
|
|
7395
8143
|
// src/index.ts
|
|
7396
|
-
var __dirname2 =
|
|
8144
|
+
var __dirname2 = dirname12(fileURLToPath4(import.meta.url));
|
|
7397
8145
|
var pkg = JSON.parse(
|
|
7398
|
-
|
|
8146
|
+
readFileSync32(join32(__dirname2, "..", "package.json"), "utf-8")
|
|
7399
8147
|
);
|
|
7400
8148
|
var program = new Command2();
|
|
7401
8149
|
program.name("visor").description("CLI for the Visor design system").version(pkg.version);
|
|
7402
8150
|
program.addCommand(checkCommand());
|
|
7403
|
-
program.command("init").description("Initialize Visor \u2014 with --template nextjs, scaffolds a complete runnable Borealis-native Next.js app in one command").option("--template <name>", "scaffold a complete runnable app (templates: nextjs)").option("--json", "output structured JSON (for AI agents)").action((options) => {
|
|
8151
|
+
program.command("init").description("Initialize Visor \u2014 with --template nextjs, scaffolds a complete runnable Borealis-native Next.js app in one command").option("--template <name>", "scaffold a complete runnable app (templates: nextjs)").option("--for <play>", "bootstrap a Playbook play (pattern-build, new-web-app, feature-addition)").option("--play-name <name>", "name for the play instance (default: current directory name)").option("--theme <id>", "theme id to record in the play's state metadata").option("--from <ref>", "brief source for the play (e.g. a PL-N Linear ticket)").option("--json", "output structured JSON (for AI agents)").action((options) => {
|
|
7404
8152
|
initCommand(process.cwd(), options);
|
|
7405
8153
|
});
|
|
7406
8154
|
program.command("list").description("List all available registry items").option("--json", "output structured JSON (for AI agents)").option("--category <name>", "filter items by category").action((options) => {
|
|
@@ -7448,6 +8196,9 @@ theme.command("apply").description(
|
|
|
7448
8196
|
).option(
|
|
7449
8197
|
"--theme-class-name <name>",
|
|
7450
8198
|
"(flutter) class name for generated theme (default: VisorAppTheme)"
|
|
8199
|
+
).option(
|
|
8200
|
+
"--target-path <path>",
|
|
8201
|
+
"(nextjs) path to a blessed-build root; dispatches CSS through the build's theme_apply_target (VI-601), ignores --output"
|
|
7451
8202
|
).action(
|
|
7452
8203
|
(file, options) => {
|
|
7453
8204
|
themeApplyCommand(file, process.cwd(), {
|
|
@@ -7585,4 +8336,9 @@ sandbox.command("approve").description(
|
|
|
7585
8336
|
sandboxApproveCommand(process.cwd(), options);
|
|
7586
8337
|
}
|
|
7587
8338
|
);
|
|
8339
|
+
program.command("spawn").description(
|
|
8340
|
+
"Fork a Playbook blessed reference build with atomic theme re-skinning \u2014 one command replaces cp -R + npm install + theme apply"
|
|
8341
|
+
).option("--from <identifier>", "blessed build to spawn: blessed:{shape}:{pattern}").option("--theme <id>", "theme id (or path to a .visor.yaml) to re-skin the fork with").option("--theme-file <path>", "explicit path to a theme.visor.yaml \u2014 bypasses --theme name resolution").option("--output <path>", "destination directory for the forked project").option("--blessed-dir <path>", "override the blessed-build root (default: VISOR_BLESSED_DIR or ~/Code/low-orbit/low-orbit-playbook/design-prototypes)").option("--install", "run npm install in the forked project (default: skip)").option("--validate", "validate the applied theme after forking (default: skip)").option("--list-blessed", "list all discoverable blessed builds and exit").option("--json", "output structured JSON (for AI agents)").action((options) => {
|
|
8342
|
+
spawnCommand(process.cwd(), options);
|
|
8343
|
+
});
|
|
7588
8344
|
program.parse();
|