@loworbitstudio/visor 1.14.0 → 1.15.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 +943 -328
- 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 dirname11, join as join30 } 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 resolve5, dirname as
|
|
2795
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
2796
|
+
import { resolve as resolve5, dirname as dirname6, join as join12 } from "path";
|
|
2544
2797
|
import { generateTheme, generateThemeData as generateThemeData3 } from "@loworbitstudio/visor-theme-engine";
|
|
2545
2798
|
import {
|
|
2546
2799
|
nextjsAdapter as nextjsAdapter2,
|
|
@@ -2573,7 +2826,7 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
2573
2826
|
const filePath = resolve5(cwd, file);
|
|
2574
2827
|
let yamlContent;
|
|
2575
2828
|
try {
|
|
2576
|
-
yamlContent =
|
|
2829
|
+
yamlContent = readFileSync12(filePath, "utf-8");
|
|
2577
2830
|
} catch {
|
|
2578
2831
|
if (options.json) {
|
|
2579
2832
|
console.log(
|
|
@@ -2655,12 +2908,12 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
2655
2908
|
const outputPath = resolve5(cwd, outputTarget);
|
|
2656
2909
|
if (fileMap) {
|
|
2657
2910
|
try {
|
|
2658
|
-
|
|
2911
|
+
mkdirSync4(outputPath, { recursive: true });
|
|
2659
2912
|
let totalBytes = 0;
|
|
2660
2913
|
for (const [relPath, content] of Object.entries(fileMap.files)) {
|
|
2661
|
-
const filePath2 =
|
|
2662
|
-
|
|
2663
|
-
|
|
2914
|
+
const filePath2 = join12(outputPath, relPath);
|
|
2915
|
+
mkdirSync4(dirname6(filePath2), { recursive: true });
|
|
2916
|
+
writeFileSync6(filePath2, content, "utf-8");
|
|
2664
2917
|
totalBytes += content.length;
|
|
2665
2918
|
}
|
|
2666
2919
|
if (options.json) {
|
|
@@ -2697,10 +2950,10 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
2697
2950
|
if (css === null) {
|
|
2698
2951
|
process.exit(1);
|
|
2699
2952
|
}
|
|
2700
|
-
const outputDir =
|
|
2953
|
+
const outputDir = dirname6(outputPath);
|
|
2701
2954
|
try {
|
|
2702
|
-
|
|
2703
|
-
|
|
2955
|
+
mkdirSync4(outputDir, { recursive: true });
|
|
2956
|
+
writeFileSync6(outputPath, css, "utf-8");
|
|
2704
2957
|
} catch {
|
|
2705
2958
|
if (options.json) {
|
|
2706
2959
|
console.log(
|
|
@@ -2741,7 +2994,7 @@ function formatSize(bytes) {
|
|
|
2741
2994
|
}
|
|
2742
2995
|
|
|
2743
2996
|
// src/commands/theme-export.ts
|
|
2744
|
-
import { readFileSync as
|
|
2997
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
2745
2998
|
import { resolve as resolve6 } from "path";
|
|
2746
2999
|
import {
|
|
2747
3000
|
parseConfig,
|
|
@@ -2753,7 +3006,7 @@ function themeExportCommand(file, cwd, options) {
|
|
|
2753
3006
|
const filePath = resolve6(cwd, file ?? ".visor.yaml");
|
|
2754
3007
|
let yamlContent;
|
|
2755
3008
|
try {
|
|
2756
|
-
yamlContent =
|
|
3009
|
+
yamlContent = readFileSync13(filePath, "utf-8");
|
|
2757
3010
|
} catch {
|
|
2758
3011
|
if (options.json) {
|
|
2759
3012
|
console.log(
|
|
@@ -2805,7 +3058,7 @@ function themeExportCommand(file, cwd, options) {
|
|
|
2805
3058
|
}
|
|
2806
3059
|
|
|
2807
3060
|
// src/commands/theme-validate.ts
|
|
2808
|
-
import { readFileSync as
|
|
3061
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
2809
3062
|
import { resolve as resolve7 } from "path";
|
|
2810
3063
|
import { parse as parseYaml2 } from "yaml";
|
|
2811
3064
|
import { validate } from "@loworbitstudio/visor-theme-engine";
|
|
@@ -2814,7 +3067,7 @@ function themeValidateCommand(file, cwd, options) {
|
|
|
2814
3067
|
const filePath = resolve7(cwd, file);
|
|
2815
3068
|
let fileContent;
|
|
2816
3069
|
try {
|
|
2817
|
-
fileContent =
|
|
3070
|
+
fileContent = readFileSync14(filePath, "utf-8");
|
|
2818
3071
|
} catch {
|
|
2819
3072
|
if (options.json) {
|
|
2820
3073
|
console.log(
|
|
@@ -2903,7 +3156,7 @@ function printIssue(issue) {
|
|
|
2903
3156
|
|
|
2904
3157
|
// src/commands/theme-verify.ts
|
|
2905
3158
|
import { spawnSync as _spawnSync } from "child_process";
|
|
2906
|
-
import { existsSync as
|
|
3159
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2907
3160
|
import { resolve as resolve8 } from "path";
|
|
2908
3161
|
function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
2909
3162
|
const target = options.target ?? "flutter";
|
|
@@ -2927,7 +3180,7 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
|
2927
3180
|
process.exit(1);
|
|
2928
3181
|
}
|
|
2929
3182
|
const dirPath = resolve8(cwd, dir);
|
|
2930
|
-
if (!
|
|
3183
|
+
if (!existsSync10(dirPath)) {
|
|
2931
3184
|
if (options.json) {
|
|
2932
3185
|
console.log(
|
|
2933
3186
|
JSON.stringify({
|
|
@@ -3014,8 +3267,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
|
3014
3267
|
}
|
|
3015
3268
|
|
|
3016
3269
|
// src/commands/theme-extract.ts
|
|
3017
|
-
import { readFileSync as
|
|
3018
|
-
import { resolve as resolve9, join as
|
|
3270
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync7, existsSync as existsSync11, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
|
|
3271
|
+
import { resolve as resolve9, join as join13, basename as basename3, extname as extname3, relative } from "path";
|
|
3019
3272
|
import { stringify as stringifyYaml } from "yaml";
|
|
3020
3273
|
import {
|
|
3021
3274
|
extractFromCSS,
|
|
@@ -3045,7 +3298,7 @@ var CSS_DIRS = [
|
|
|
3045
3298
|
];
|
|
3046
3299
|
function themeExtractCommand(cwd, options) {
|
|
3047
3300
|
const targetDir = resolve9(cwd, options.from ?? ".");
|
|
3048
|
-
if (!
|
|
3301
|
+
if (!existsSync11(targetDir)) {
|
|
3049
3302
|
if (options.json) {
|
|
3050
3303
|
console.log(JSON.stringify({ success: false, error: `Directory not found: ${targetDir}` }));
|
|
3051
3304
|
} else {
|
|
@@ -3099,16 +3352,16 @@ function collectCSSFiles(targetDir) {
|
|
|
3099
3352
|
const files = [];
|
|
3100
3353
|
const seen = /* @__PURE__ */ new Set();
|
|
3101
3354
|
for (const pattern2 of CSS_FILE_PATTERNS) {
|
|
3102
|
-
const rootPath =
|
|
3355
|
+
const rootPath = join13(targetDir, pattern2);
|
|
3103
3356
|
addFileIfExists(rootPath, files, seen);
|
|
3104
3357
|
for (const dir of CSS_DIRS) {
|
|
3105
|
-
const dirPath =
|
|
3358
|
+
const dirPath = join13(targetDir, dir, pattern2);
|
|
3106
3359
|
addFileIfExists(dirPath, files, seen);
|
|
3107
3360
|
}
|
|
3108
3361
|
}
|
|
3109
3362
|
for (const dir of CSS_DIRS) {
|
|
3110
|
-
const dirPath =
|
|
3111
|
-
if (
|
|
3363
|
+
const dirPath = join13(targetDir, dir);
|
|
3364
|
+
if (existsSync11(dirPath) && statSync5(dirPath).isDirectory()) {
|
|
3112
3365
|
scanDirForCSS(dirPath, files, seen, 2);
|
|
3113
3366
|
}
|
|
3114
3367
|
}
|
|
@@ -3118,9 +3371,9 @@ function collectCSSFiles(targetDir) {
|
|
|
3118
3371
|
function addFileIfExists(filePath, files, seen) {
|
|
3119
3372
|
const resolved = resolve9(filePath);
|
|
3120
3373
|
if (seen.has(resolved)) return;
|
|
3121
|
-
if (!
|
|
3374
|
+
if (!existsSync11(resolved)) return;
|
|
3122
3375
|
try {
|
|
3123
|
-
const content =
|
|
3376
|
+
const content = readFileSync15(resolved, "utf-8");
|
|
3124
3377
|
if (content.includes("--")) {
|
|
3125
3378
|
files.push({ path: resolved, content });
|
|
3126
3379
|
seen.add(resolved);
|
|
@@ -3129,7 +3382,7 @@ function addFileIfExists(filePath, files, seen) {
|
|
|
3129
3382
|
}
|
|
3130
3383
|
}
|
|
3131
3384
|
function scanDirForCSS(dir, files, seen, maxDepth) {
|
|
3132
|
-
if (!
|
|
3385
|
+
if (!existsSync11(dir)) return;
|
|
3133
3386
|
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
3134
3387
|
"node_modules",
|
|
3135
3388
|
".next",
|
|
@@ -3148,10 +3401,10 @@ function scanDirForCSS(dir, files, seen, maxDepth) {
|
|
|
3148
3401
|
if (entry.isDirectory()) {
|
|
3149
3402
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
3150
3403
|
if (maxDepth > 0) {
|
|
3151
|
-
scanDirForCSS(
|
|
3404
|
+
scanDirForCSS(join13(dir, entry.name), files, seen, maxDepth - 1);
|
|
3152
3405
|
}
|
|
3153
3406
|
} else if (entry.isFile() && extname3(entry.name) === ".css") {
|
|
3154
|
-
addFileIfExists(
|
|
3407
|
+
addFileIfExists(join13(dir, entry.name), files, seen);
|
|
3155
3408
|
}
|
|
3156
3409
|
}
|
|
3157
3410
|
} catch {
|
|
@@ -3233,10 +3486,10 @@ function extractVarName(varExpr) {
|
|
|
3233
3486
|
function parseNextFontFromLayouts(targetDir) {
|
|
3234
3487
|
const fontMap = /* @__PURE__ */ new Map();
|
|
3235
3488
|
for (const relPath of LAYOUT_FILE_PATHS) {
|
|
3236
|
-
const fullPath =
|
|
3237
|
-
if (!
|
|
3489
|
+
const fullPath = join13(targetDir, relPath);
|
|
3490
|
+
if (!existsSync11(fullPath)) continue;
|
|
3238
3491
|
try {
|
|
3239
|
-
const content =
|
|
3492
|
+
const content = readFileSync15(fullPath, "utf-8");
|
|
3240
3493
|
parseNextFontDeclarations(content, fontMap);
|
|
3241
3494
|
} catch {
|
|
3242
3495
|
}
|
|
@@ -3283,7 +3536,7 @@ function parseNextFontDeclarations(content, fontMap) {
|
|
|
3283
3536
|
const srcMatch = block.match(/src\s*:\s*["']([^"']+)["']/);
|
|
3284
3537
|
if (srcMatch) {
|
|
3285
3538
|
const srcPath = srcMatch[1];
|
|
3286
|
-
const fileName =
|
|
3539
|
+
const fileName = basename3(srcPath, extname3(srcPath));
|
|
3287
3540
|
const fontBaseName = fileName.replace(/[-_](Variable|Regular|Bold|Light|Medium|SemiBold|ExtraBold|Thin|Black|Italic).*$/i, "").replace(/[-_]/g, " ").trim();
|
|
3288
3541
|
if (fontBaseName) {
|
|
3289
3542
|
fontMap.set(varName, fontBaseName);
|
|
@@ -3321,10 +3574,10 @@ var MONO_FONT_NAMES = /* @__PURE__ */ new Set([
|
|
|
3321
3574
|
"IBM Plex Mono"
|
|
3322
3575
|
]);
|
|
3323
3576
|
function extractFontHints(targetDir) {
|
|
3324
|
-
const pkgPath =
|
|
3325
|
-
if (!
|
|
3577
|
+
const pkgPath = join13(targetDir, "package.json");
|
|
3578
|
+
if (!existsSync11(pkgPath)) return void 0;
|
|
3326
3579
|
try {
|
|
3327
|
-
const pkg2 = JSON.parse(
|
|
3580
|
+
const pkg2 = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
3328
3581
|
const allDeps = { ...pkg2.dependencies, ...pkg2.devDependencies };
|
|
3329
3582
|
const fonts2 = [];
|
|
3330
3583
|
for (const [dep, _] of Object.entries(allDeps)) {
|
|
@@ -3360,10 +3613,10 @@ function extractFontHints(targetDir) {
|
|
|
3360
3613
|
}
|
|
3361
3614
|
}
|
|
3362
3615
|
function inferThemeName(targetDir) {
|
|
3363
|
-
const pkgPath =
|
|
3364
|
-
if (
|
|
3616
|
+
const pkgPath = join13(targetDir, "package.json");
|
|
3617
|
+
if (existsSync11(pkgPath)) {
|
|
3365
3618
|
try {
|
|
3366
|
-
const pkg2 = JSON.parse(
|
|
3619
|
+
const pkg2 = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
3367
3620
|
if (pkg2.name) {
|
|
3368
3621
|
const name = pkg2.name.replace(/^@[\w-]+\//, "");
|
|
3369
3622
|
return `${name}-theme`;
|
|
@@ -3371,7 +3624,7 @@ function inferThemeName(targetDir) {
|
|
|
3371
3624
|
} catch {
|
|
3372
3625
|
}
|
|
3373
3626
|
}
|
|
3374
|
-
return `${
|
|
3627
|
+
return `${basename3(targetDir)}-theme`;
|
|
3375
3628
|
}
|
|
3376
3629
|
function confidenceComment(confidence) {
|
|
3377
3630
|
return `# confidence: ${confidence}`;
|
|
@@ -3428,7 +3681,7 @@ function outputYAML(result, outputPath, cwd, validationResult) {
|
|
|
3428
3681
|
}
|
|
3429
3682
|
logger.blank();
|
|
3430
3683
|
}
|
|
3431
|
-
|
|
3684
|
+
writeFileSync7(outFile, yamlStr, "utf-8");
|
|
3432
3685
|
logger.success(`Theme written to ${relative(cwd, outFile)}`);
|
|
3433
3686
|
if (validationResult) {
|
|
3434
3687
|
logger.blank();
|
|
@@ -3486,14 +3739,14 @@ function buildAnnotatedYAML(result) {
|
|
|
3486
3739
|
}
|
|
3487
3740
|
|
|
3488
3741
|
// src/commands/theme-register.ts
|
|
3489
|
-
import { readFileSync as
|
|
3490
|
-
import { resolve as resolve11, join as
|
|
3742
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5, existsSync as existsSync13 } from "fs";
|
|
3743
|
+
import { resolve as resolve11, join as join15 } from "path";
|
|
3491
3744
|
import { generateThemeData as generateThemeData4 } from "@loworbitstudio/visor-theme-engine";
|
|
3492
3745
|
import { docsAdapter as docsAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
3493
3746
|
|
|
3494
3747
|
// src/utils/theme-helpers.ts
|
|
3495
|
-
import { existsSync as
|
|
3496
|
-
import { resolve as resolve10, dirname as
|
|
3748
|
+
import { existsSync as existsSync12, lstatSync, readdirSync as readdirSync5, readFileSync as readFileSync16, readlinkSync, realpathSync, statSync as statSync6 } from "fs";
|
|
3749
|
+
import { resolve as resolve10, dirname as dirname7, join as join14 } from "path";
|
|
3497
3750
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3498
3751
|
function toSlug(name) {
|
|
3499
3752
|
return name.toLowerCase().replace(/\s+/g, "-");
|
|
@@ -3504,10 +3757,10 @@ function toLabel(name) {
|
|
|
3504
3757
|
function findRepoRoot(startDir) {
|
|
3505
3758
|
let current = resolve10(startDir);
|
|
3506
3759
|
while (true) {
|
|
3507
|
-
if (
|
|
3760
|
+
if (existsSync12(join14(current, "packages", "docs"))) {
|
|
3508
3761
|
return current;
|
|
3509
3762
|
}
|
|
3510
|
-
const parent =
|
|
3763
|
+
const parent = dirname7(current);
|
|
3511
3764
|
if (parent === current) break;
|
|
3512
3765
|
current = parent;
|
|
3513
3766
|
}
|
|
@@ -3522,8 +3775,8 @@ function findMainRepoRoot(startDir) {
|
|
|
3522
3775
|
}).trim();
|
|
3523
3776
|
if (commonDir) {
|
|
3524
3777
|
const absoluteCommonDir = resolve10(startDir, commonDir);
|
|
3525
|
-
const candidate =
|
|
3526
|
-
if (
|
|
3778
|
+
const candidate = dirname7(absoluteCommonDir);
|
|
3779
|
+
if (existsSync12(join14(candidate, "packages", "docs"))) {
|
|
3527
3780
|
return candidate;
|
|
3528
3781
|
}
|
|
3529
3782
|
}
|
|
@@ -3542,10 +3795,10 @@ var BrokenSymlinkError = class extends Error {
|
|
|
3542
3795
|
code = "BROKEN_SYMLINK";
|
|
3543
3796
|
};
|
|
3544
3797
|
function assertNoBrokenSymlinks(dir) {
|
|
3545
|
-
if (!
|
|
3798
|
+
if (!existsSync12(dir)) return;
|
|
3546
3799
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
3547
3800
|
for (const entry of entries) {
|
|
3548
|
-
const entryPath =
|
|
3801
|
+
const entryPath = join14(dir, entry.name);
|
|
3549
3802
|
let lst;
|
|
3550
3803
|
try {
|
|
3551
3804
|
lst = lstatSync(entryPath);
|
|
@@ -3563,27 +3816,27 @@ function assertNoBrokenSymlinks(dir) {
|
|
|
3563
3816
|
}
|
|
3564
3817
|
}
|
|
3565
3818
|
function scanParentForPrivateThemes(parentDir) {
|
|
3566
|
-
if (!
|
|
3819
|
+
if (!existsSync12(parentDir)) return [];
|
|
3567
3820
|
const entries = readdirSync5(parentDir, { withFileTypes: true });
|
|
3568
3821
|
const matches = [];
|
|
3569
3822
|
for (const entry of entries) {
|
|
3570
3823
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
3571
|
-
const candidate =
|
|
3572
|
-
if (
|
|
3824
|
+
const candidate = join14(parentDir, entry.name, "visor-themes-private", "themes");
|
|
3825
|
+
if (existsSync12(candidate)) {
|
|
3573
3826
|
matches.push(candidate);
|
|
3574
3827
|
}
|
|
3575
3828
|
}
|
|
3576
3829
|
return matches.sort();
|
|
3577
3830
|
}
|
|
3578
3831
|
function scanNestedThemeDir(dir) {
|
|
3579
|
-
if (!
|
|
3832
|
+
if (!existsSync12(dir)) return [];
|
|
3580
3833
|
assertNoBrokenSymlinks(dir);
|
|
3581
3834
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
3582
3835
|
const out = [];
|
|
3583
3836
|
for (const entry of entries) {
|
|
3584
3837
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
3585
|
-
const themeFile =
|
|
3586
|
-
if (
|
|
3838
|
+
const themeFile = join14(dir, entry.name, "theme.visor.yaml");
|
|
3839
|
+
if (existsSync12(themeFile)) {
|
|
3587
3840
|
out.push({ filePath: themeFile, slug: entry.name });
|
|
3588
3841
|
}
|
|
3589
3842
|
}
|
|
@@ -3592,10 +3845,10 @@ function scanNestedThemeDir(dir) {
|
|
|
3592
3845
|
function detectVisorWorkspace(cwd) {
|
|
3593
3846
|
let current = resolve10(cwd);
|
|
3594
3847
|
while (true) {
|
|
3595
|
-
const pkgPath =
|
|
3596
|
-
if (
|
|
3848
|
+
const pkgPath = join14(current, "package.json");
|
|
3849
|
+
if (existsSync12(pkgPath)) {
|
|
3597
3850
|
try {
|
|
3598
|
-
const pkg2 = JSON.parse(
|
|
3851
|
+
const pkg2 = JSON.parse(readFileSync16(pkgPath, "utf-8"));
|
|
3599
3852
|
const workspaces = pkg2.workspaces ?? [];
|
|
3600
3853
|
const hasCli = workspaces.some((w) => w.includes("packages/cli"));
|
|
3601
3854
|
const hasEngine = workspaces.some((w) => w.includes("packages/theme-engine"));
|
|
@@ -3605,7 +3858,7 @@ function detectVisorWorkspace(cwd) {
|
|
|
3605
3858
|
} catch {
|
|
3606
3859
|
}
|
|
3607
3860
|
}
|
|
3608
|
-
const parent =
|
|
3861
|
+
const parent = dirname7(current);
|
|
3609
3862
|
if (parent === current) break;
|
|
3610
3863
|
current = parent;
|
|
3611
3864
|
}
|
|
@@ -3613,7 +3866,7 @@ function detectVisorWorkspace(cwd) {
|
|
|
3613
3866
|
}
|
|
3614
3867
|
function isLocalVisorBinary(workspaceRoot, scriptPath) {
|
|
3615
3868
|
if (!scriptPath) return false;
|
|
3616
|
-
const expectedPrefix =
|
|
3869
|
+
const expectedPrefix = join14(workspaceRoot, "packages", "cli", "dist");
|
|
3617
3870
|
let resolvedScript;
|
|
3618
3871
|
let resolvedPrefix;
|
|
3619
3872
|
try {
|
|
@@ -3717,7 +3970,7 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3717
3970
|
const filePath = resolve11(cwd, file);
|
|
3718
3971
|
let yamlContent;
|
|
3719
3972
|
try {
|
|
3720
|
-
yamlContent =
|
|
3973
|
+
yamlContent = readFileSync17(filePath, "utf-8");
|
|
3721
3974
|
} catch {
|
|
3722
3975
|
if (options.json) {
|
|
3723
3976
|
console.log(JSON.stringify({ success: false, error: `Could not read file: ${filePath}` }));
|
|
@@ -3760,11 +4013,11 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3760
4013
|
process.exit(1);
|
|
3761
4014
|
return;
|
|
3762
4015
|
}
|
|
3763
|
-
const docsAppDir =
|
|
3764
|
-
const cssFilePath =
|
|
3765
|
-
const globalsPath =
|
|
3766
|
-
const themeConfigPath =
|
|
3767
|
-
if (!
|
|
4016
|
+
const docsAppDir = join15(repoRoot, "packages", "docs", "app");
|
|
4017
|
+
const cssFilePath = join15(docsAppDir, `${slug2}-theme.css`);
|
|
4018
|
+
const globalsPath = join15(docsAppDir, "globals.css");
|
|
4019
|
+
const themeConfigPath = join15(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
4020
|
+
if (!existsSync13(docsAppDir)) {
|
|
3768
4021
|
const msg = `Docs app directory not found: ${docsAppDir}`;
|
|
3769
4022
|
if (options.json) {
|
|
3770
4023
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
@@ -3777,8 +4030,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3777
4030
|
let globalsContent = "";
|
|
3778
4031
|
let themeConfigContent = "";
|
|
3779
4032
|
try {
|
|
3780
|
-
globalsContent =
|
|
3781
|
-
themeConfigContent =
|
|
4033
|
+
globalsContent = readFileSync17(globalsPath, "utf-8");
|
|
4034
|
+
themeConfigContent = readFileSync17(themeConfigPath, "utf-8");
|
|
3782
4035
|
} catch (err) {
|
|
3783
4036
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
3784
4037
|
if (options.json) {
|
|
@@ -3789,8 +4042,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3789
4042
|
process.exit(1);
|
|
3790
4043
|
return;
|
|
3791
4044
|
}
|
|
3792
|
-
const cssExists =
|
|
3793
|
-
const cssChanged = !cssExists ||
|
|
4045
|
+
const cssExists = existsSync13(cssFilePath);
|
|
4046
|
+
const cssChanged = !cssExists || readFileSync17(cssFilePath, "utf-8") !== css;
|
|
3794
4047
|
const { updated: newGlobals, changed: globalsChanged } = insertGlobalsImport(globalsContent, slug2);
|
|
3795
4048
|
const { updated: newThemeConfig, changed: themeConfigChanged, error: configError } = insertThemeConfig(
|
|
3796
4049
|
themeConfigContent,
|
|
@@ -3833,14 +4086,14 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3833
4086
|
}
|
|
3834
4087
|
try {
|
|
3835
4088
|
if (cssChanged) {
|
|
3836
|
-
|
|
3837
|
-
|
|
4089
|
+
mkdirSync5(docsAppDir, { recursive: true });
|
|
4090
|
+
writeFileSync8(cssFilePath, css, "utf-8");
|
|
3838
4091
|
}
|
|
3839
4092
|
if (globalsChanged) {
|
|
3840
|
-
|
|
4093
|
+
writeFileSync8(globalsPath, newGlobals, "utf-8");
|
|
3841
4094
|
}
|
|
3842
4095
|
if (themeConfigChanged) {
|
|
3843
|
-
|
|
4096
|
+
writeFileSync8(themeConfigPath, newThemeConfig, "utf-8");
|
|
3844
4097
|
}
|
|
3845
4098
|
} catch (err) {
|
|
3846
4099
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
@@ -3874,8 +4127,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3874
4127
|
}
|
|
3875
4128
|
|
|
3876
4129
|
// src/commands/theme-unregister.ts
|
|
3877
|
-
import { readFileSync as
|
|
3878
|
-
import { join as
|
|
4130
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync9, existsSync as existsSync14, unlinkSync } from "fs";
|
|
4131
|
+
import { join as join16 } from "path";
|
|
3879
4132
|
function removeGlobalsImport(content, slug2) {
|
|
3880
4133
|
const importLine = `@import './${slug2}-theme.css';`;
|
|
3881
4134
|
if (!content.includes(importLine)) {
|
|
@@ -3907,11 +4160,11 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3907
4160
|
process.exit(1);
|
|
3908
4161
|
return;
|
|
3909
4162
|
}
|
|
3910
|
-
const docsAppDir =
|
|
3911
|
-
const cssFilePath =
|
|
3912
|
-
const globalsPath =
|
|
3913
|
-
const themeConfigPath =
|
|
3914
|
-
if (!
|
|
4163
|
+
const docsAppDir = join16(repoRoot, "packages", "docs", "app");
|
|
4164
|
+
const cssFilePath = join16(docsAppDir, `${slug2}-theme.css`);
|
|
4165
|
+
const globalsPath = join16(docsAppDir, "globals.css");
|
|
4166
|
+
const themeConfigPath = join16(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
4167
|
+
if (!existsSync14(docsAppDir)) {
|
|
3915
4168
|
const msg = `Docs app directory not found: ${docsAppDir}`;
|
|
3916
4169
|
if (options.json) {
|
|
3917
4170
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
@@ -3924,8 +4177,8 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3924
4177
|
let globalsContent = "";
|
|
3925
4178
|
let themeConfigContent = "";
|
|
3926
4179
|
try {
|
|
3927
|
-
globalsContent =
|
|
3928
|
-
themeConfigContent =
|
|
4180
|
+
globalsContent = readFileSync18(globalsPath, "utf-8");
|
|
4181
|
+
themeConfigContent = readFileSync18(themeConfigPath, "utf-8");
|
|
3929
4182
|
} catch (err) {
|
|
3930
4183
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
3931
4184
|
if (options.json) {
|
|
@@ -3936,7 +4189,7 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3936
4189
|
process.exit(1);
|
|
3937
4190
|
return;
|
|
3938
4191
|
}
|
|
3939
|
-
const cssExists =
|
|
4192
|
+
const cssExists = existsSync14(cssFilePath);
|
|
3940
4193
|
const { updated: newGlobals, changed: globalsChanged } = removeGlobalsImport(globalsContent, slug2);
|
|
3941
4194
|
const { updated: newThemeConfig, changed: themeConfigChanged } = removeThemeConfigEntry(themeConfigContent, slug2);
|
|
3942
4195
|
if (!cssExists && !globalsChanged && !themeConfigChanged) {
|
|
@@ -3949,8 +4202,8 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3949
4202
|
}
|
|
3950
4203
|
try {
|
|
3951
4204
|
if (cssExists) unlinkSync(cssFilePath);
|
|
3952
|
-
if (globalsChanged)
|
|
3953
|
-
if (themeConfigChanged)
|
|
4205
|
+
if (globalsChanged) writeFileSync9(globalsPath, newGlobals, "utf-8");
|
|
4206
|
+
if (themeConfigChanged) writeFileSync9(themeConfigPath, newThemeConfig, "utf-8");
|
|
3954
4207
|
} catch (err) {
|
|
3955
4208
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
3956
4209
|
if (options.json) {
|
|
@@ -3977,15 +4230,15 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3977
4230
|
|
|
3978
4231
|
// src/commands/theme-sync.ts
|
|
3979
4232
|
import {
|
|
3980
|
-
readFileSync as
|
|
3981
|
-
writeFileSync as
|
|
3982
|
-
mkdirSync as
|
|
3983
|
-
existsSync as
|
|
4233
|
+
readFileSync as readFileSync19,
|
|
4234
|
+
writeFileSync as writeFileSync10,
|
|
4235
|
+
mkdirSync as mkdirSync6,
|
|
4236
|
+
existsSync as existsSync15,
|
|
3984
4237
|
readdirSync as readdirSync6,
|
|
3985
4238
|
unlinkSync as unlinkSync2,
|
|
3986
4239
|
copyFileSync
|
|
3987
4240
|
} from "fs";
|
|
3988
|
-
import { join as
|
|
4241
|
+
import { join as join17, basename as basename4, resolve as resolve12, sep, relative as relative2 } from "path";
|
|
3989
4242
|
import { parse as parseYaml3 } from "yaml";
|
|
3990
4243
|
import { generateThemeData as generateThemeData5, validateFontCoverage, formatFontCoverageError } from "@loworbitstudio/visor-theme-engine";
|
|
3991
4244
|
import { docsAdapter as docsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
@@ -3999,9 +4252,9 @@ var CUSTOM_OVERLAY_CSS_PATH = "packages/docs/app/custom-themes.generated.css";
|
|
|
3999
4252
|
var CUSTOM_OVERLAY_TS_PATH = "packages/docs/lib/theme-config.custom.generated.ts";
|
|
4000
4253
|
var CUSTOM_OVERLAY_IMPORT_LINE = "@import './custom-themes.generated.css';";
|
|
4001
4254
|
function scanThemeDir(dir) {
|
|
4002
|
-
if (!
|
|
4255
|
+
if (!existsSync15(dir)) return [];
|
|
4003
4256
|
assertNoBrokenSymlinks(dir);
|
|
4004
|
-
return readdirSync6(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) =>
|
|
4257
|
+
return readdirSync6(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join17(dir, f));
|
|
4005
4258
|
}
|
|
4006
4259
|
function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
4007
4260
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -4026,19 +4279,19 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
|
4026
4279
|
const envPath = process.env[PRIVATE_THEMES_ENV_VAR];
|
|
4027
4280
|
if (envPath && envPath.trim() !== "") {
|
|
4028
4281
|
const resolved = resolve12(envPath);
|
|
4029
|
-
if (!
|
|
4282
|
+
if (!existsSync15(resolved)) {
|
|
4030
4283
|
throw new Error(
|
|
4031
4284
|
`${PRIVATE_THEMES_ENV_VAR} is set to "${envPath}" but the path does not exist. Expected a directory containing {slug}/theme.visor.yaml entries.`
|
|
4032
4285
|
);
|
|
4033
4286
|
}
|
|
4034
4287
|
addNested(resolved, "env");
|
|
4035
4288
|
}
|
|
4036
|
-
const siblingPath =
|
|
4037
|
-
const siblingExists =
|
|
4289
|
+
const siblingPath = join17(mainRepoRoot, "..", "visor-themes-private", "themes");
|
|
4290
|
+
const siblingExists = existsSync15(siblingPath);
|
|
4038
4291
|
if (siblingExists) {
|
|
4039
4292
|
addNested(siblingPath, "sibling");
|
|
4040
4293
|
}
|
|
4041
|
-
const parentDir =
|
|
4294
|
+
const parentDir = join17(mainRepoRoot, "..");
|
|
4042
4295
|
const parentGlobMatches = scanParentForPrivateThemes(parentDir).filter(
|
|
4043
4296
|
(path2) => !path2.startsWith(mainRepoRoot + sep)
|
|
4044
4297
|
);
|
|
@@ -4059,10 +4312,10 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
|
4059
4312
|
}
|
|
4060
4313
|
}
|
|
4061
4314
|
}
|
|
4062
|
-
const legacyDir =
|
|
4315
|
+
const legacyDir = join17(repoRoot, "custom-themes");
|
|
4063
4316
|
const legacyFiles = scanThemeDir(legacyDir);
|
|
4064
4317
|
for (const legacyFile of legacyFiles) {
|
|
4065
|
-
const slug2 =
|
|
4318
|
+
const slug2 = basename4(legacyFile).replace(/\.visor\.yaml$/, "");
|
|
4066
4319
|
const existing = merged.get(slug2);
|
|
4067
4320
|
if (existing) {
|
|
4068
4321
|
warn(
|
|
@@ -4116,14 +4369,14 @@ function reportBrokenSymlink(err, options) {
|
|
|
4116
4369
|
}
|
|
4117
4370
|
}
|
|
4118
4371
|
function buildEmptySourcesMessage(mainRepoRoot) {
|
|
4119
|
-
const expectedSibling =
|
|
4372
|
+
const expectedSibling = join17(mainRepoRoot, "..", "visor-themes-private");
|
|
4120
4373
|
return [
|
|
4121
4374
|
"No theme sources discovered. Cannot proceed \u2014 refusing to wipe generated CSS.",
|
|
4122
4375
|
"",
|
|
4123
4376
|
"Resolution order checked:",
|
|
4124
4377
|
` 1. Env var ${PRIVATE_THEMES_ENV_VAR} (unset or empty)`,
|
|
4125
4378
|
` 2. Sibling checkout at ${expectedSibling}/themes/ (not found)`,
|
|
4126
|
-
` 3. One-level-deeper at ${
|
|
4379
|
+
` 3. One-level-deeper at ${join17(mainRepoRoot, "..")}/<parent>/visor-themes-private/themes/ (not found)`,
|
|
4127
4380
|
" 4. Legacy custom-themes/ (no .visor.yaml files)",
|
|
4128
4381
|
"",
|
|
4129
4382
|
"To fix, clone the private themes repo as a sibling:",
|
|
@@ -4328,14 +4581,14 @@ function themeSyncCommand(cwd, options) {
|
|
|
4328
4581
|
return;
|
|
4329
4582
|
}
|
|
4330
4583
|
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 =
|
|
4584
|
+
const themesDir = join17(repoRoot, "themes");
|
|
4585
|
+
const docsAppDir = join17(repoRoot, "packages", "docs", "app");
|
|
4586
|
+
const docsLibDir = join17(repoRoot, "packages", "docs", "lib");
|
|
4587
|
+
const docsPublicThemesDir = join17(repoRoot, "packages", "docs", "public", "themes");
|
|
4588
|
+
const themeConfigPath = join17(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
4589
|
+
const globalsPath = join17(docsAppDir, "globals.css");
|
|
4590
|
+
const customOverlayCssPath = join17(repoRoot, CUSTOM_OVERLAY_CSS_PATH);
|
|
4591
|
+
const customOverlayTsPath = join17(repoRoot, CUSTOM_OVERLAY_TS_PATH);
|
|
4339
4592
|
let stockFiles;
|
|
4340
4593
|
try {
|
|
4341
4594
|
stockFiles = scanThemeDir(themesDir);
|
|
@@ -4392,7 +4645,7 @@ function themeSyncCommand(cwd, options) {
|
|
|
4392
4645
|
const processFile = (filePath, isCustom, slugOverride) => {
|
|
4393
4646
|
let yamlContent;
|
|
4394
4647
|
try {
|
|
4395
|
-
yamlContent =
|
|
4648
|
+
yamlContent = readFileSync19(filePath, "utf-8");
|
|
4396
4649
|
} catch {
|
|
4397
4650
|
failures.push({ filePath, error: `Could not read: ${filePath}` });
|
|
4398
4651
|
return;
|
|
@@ -4403,7 +4656,7 @@ function themeSyncCommand(cwd, options) {
|
|
|
4403
4656
|
} catch (err) {
|
|
4404
4657
|
failures.push({
|
|
4405
4658
|
filePath,
|
|
4406
|
-
error: `Failed to parse ${
|
|
4659
|
+
error: `Failed to parse ${basename4(filePath)}: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
4407
4660
|
});
|
|
4408
4661
|
return;
|
|
4409
4662
|
}
|
|
@@ -4417,12 +4670,12 @@ function themeSyncCommand(cwd, options) {
|
|
|
4417
4670
|
for (const e of coverage.errors) {
|
|
4418
4671
|
failures.push({
|
|
4419
4672
|
filePath,
|
|
4420
|
-
error: formatFontCoverageError(
|
|
4673
|
+
error: formatFontCoverageError(basename4(filePath), e.declaredAt, e.family)
|
|
4421
4674
|
});
|
|
4422
4675
|
}
|
|
4423
4676
|
return;
|
|
4424
4677
|
}
|
|
4425
|
-
const yamlFilename = slugOverride ??
|
|
4678
|
+
const yamlFilename = slugOverride ?? basename4(filePath).replace(/\.visor\.yaml$/, "");
|
|
4426
4679
|
manifest.push({ slug: slug2, label, group, defaultMode, css, yamlFilename, isCustom });
|
|
4427
4680
|
};
|
|
4428
4681
|
for (const f of stockFiles) processFile(f, false);
|
|
@@ -4442,8 +4695,8 @@ function themeSyncCommand(cwd, options) {
|
|
|
4442
4695
|
let globalsContent;
|
|
4443
4696
|
let themeConfigContent;
|
|
4444
4697
|
try {
|
|
4445
|
-
globalsContent =
|
|
4446
|
-
themeConfigContent =
|
|
4698
|
+
globalsContent = readFileSync19(globalsPath, "utf-8");
|
|
4699
|
+
themeConfigContent = readFileSync19(themeConfigPath, "utf-8");
|
|
4447
4700
|
} catch (err) {
|
|
4448
4701
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
4449
4702
|
if (options.json) {
|
|
@@ -4458,12 +4711,12 @@ function themeSyncCommand(cwd, options) {
|
|
|
4458
4711
|
const newThemeConfig = updateStockThemeConfigBlock(themeConfigContent, stockManifest);
|
|
4459
4712
|
const newCustomOverlayCss = generateCustomOverlayCss(customManifest);
|
|
4460
4713
|
const newCustomOverlayTs = generateCustomOverlayTs(customManifest);
|
|
4461
|
-
const existingCssFiles =
|
|
4714
|
+
const existingCssFiles = existsSync15(docsAppDir) ? readdirSync6(docsAppDir).filter(
|
|
4462
4715
|
(f) => f.endsWith("-theme.css") && f !== "custom-themes.generated.css"
|
|
4463
4716
|
) : [];
|
|
4464
4717
|
const newCssSet = new Set(allSlugs.map((s) => `${s}-theme.css`));
|
|
4465
4718
|
const staleCssFiles = existingCssFiles.filter((f) => !newCssSet.has(f));
|
|
4466
|
-
const existingPublicYamls =
|
|
4719
|
+
const existingPublicYamls = existsSync15(docsPublicThemesDir) ? readdirSync6(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
|
|
4467
4720
|
const newPublicYamlSet = new Set(manifest.map((e) => `${e.yamlFilename}.visor.yaml`));
|
|
4468
4721
|
const stalePublicYamls = existingPublicYamls.filter((f) => !newPublicYamlSet.has(f));
|
|
4469
4722
|
if (options.dryRun) {
|
|
@@ -4503,28 +4756,28 @@ function themeSyncCommand(cwd, options) {
|
|
|
4503
4756
|
return;
|
|
4504
4757
|
}
|
|
4505
4758
|
try {
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4759
|
+
mkdirSync6(docsAppDir, { recursive: true });
|
|
4760
|
+
mkdirSync6(docsLibDir, { recursive: true });
|
|
4761
|
+
mkdirSync6(docsPublicThemesDir, { recursive: true });
|
|
4509
4762
|
for (const entry of manifest) {
|
|
4510
|
-
|
|
4763
|
+
writeFileSync10(join17(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
|
|
4511
4764
|
}
|
|
4512
4765
|
for (const stale of staleCssFiles) {
|
|
4513
|
-
unlinkSync2(
|
|
4766
|
+
unlinkSync2(join17(docsAppDir, stale));
|
|
4514
4767
|
}
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4768
|
+
writeFileSync10(customOverlayCssPath, newCustomOverlayCss, "utf-8");
|
|
4769
|
+
writeFileSync10(customOverlayTsPath, newCustomOverlayTs, "utf-8");
|
|
4770
|
+
writeFileSync10(themeConfigPath, newThemeConfig, "utf-8");
|
|
4771
|
+
writeFileSync10(globalsPath, newGlobals, "utf-8");
|
|
4519
4772
|
for (const srcFile of stockFiles) {
|
|
4520
|
-
copyFileSync(srcFile,
|
|
4773
|
+
copyFileSync(srcFile, join17(docsPublicThemesDir, basename4(srcFile)));
|
|
4521
4774
|
}
|
|
4522
4775
|
for (const c of customSources) {
|
|
4523
|
-
const targetName = c.origin === "legacy" ?
|
|
4524
|
-
copyFileSync(c.filePath,
|
|
4776
|
+
const targetName = c.origin === "legacy" ? basename4(c.filePath) : `${c.slug}.visor.yaml`;
|
|
4777
|
+
copyFileSync(c.filePath, join17(docsPublicThemesDir, targetName));
|
|
4525
4778
|
}
|
|
4526
4779
|
for (const stale of stalePublicYamls) {
|
|
4527
|
-
unlinkSync2(
|
|
4780
|
+
unlinkSync2(join17(docsPublicThemesDir, stale));
|
|
4528
4781
|
}
|
|
4529
4782
|
} catch (err) {
|
|
4530
4783
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
@@ -4577,19 +4830,19 @@ function themeSyncCommand(cwd, options) {
|
|
|
4577
4830
|
|
|
4578
4831
|
// src/commands/theme-batch-apply-flutter.ts
|
|
4579
4832
|
import {
|
|
4580
|
-
readFileSync as
|
|
4581
|
-
writeFileSync as
|
|
4582
|
-
mkdirSync as
|
|
4583
|
-
existsSync as
|
|
4833
|
+
readFileSync as readFileSync20,
|
|
4834
|
+
writeFileSync as writeFileSync11,
|
|
4835
|
+
mkdirSync as mkdirSync7,
|
|
4836
|
+
existsSync as existsSync16,
|
|
4584
4837
|
readdirSync as readdirSync7,
|
|
4585
4838
|
rmSync
|
|
4586
4839
|
} from "fs";
|
|
4587
|
-
import { join as
|
|
4840
|
+
import { join as join18, basename as basename5, dirname as dirname8 } from "path";
|
|
4588
4841
|
import { generateThemeData as generateThemeData6 } from "@loworbitstudio/visor-theme-engine";
|
|
4589
4842
|
import { flutterAdapter as flutterAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
4590
4843
|
function scanThemeDir2(dir) {
|
|
4591
|
-
if (!
|
|
4592
|
-
return readdirSync7(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) =>
|
|
4844
|
+
if (!existsSync16(dir)) return [];
|
|
4845
|
+
return readdirSync7(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join18(dir, f)).sort();
|
|
4593
4846
|
}
|
|
4594
4847
|
function slugToCamel(slug2) {
|
|
4595
4848
|
return slug2.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
@@ -4722,9 +4975,9 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4722
4975
|
process.exit(1);
|
|
4723
4976
|
return;
|
|
4724
4977
|
}
|
|
4725
|
-
const themesDir =
|
|
4726
|
-
const customThemesDir =
|
|
4727
|
-
const outputDir =
|
|
4978
|
+
const themesDir = join18(repoRoot, "themes");
|
|
4979
|
+
const customThemesDir = join18(repoRoot, "custom-themes");
|
|
4980
|
+
const outputDir = join18(repoRoot, "packages", "visor_themes");
|
|
4728
4981
|
const stockFiles = scanThemeDir2(themesDir);
|
|
4729
4982
|
const customFiles = scanThemeDir2(customThemesDir);
|
|
4730
4983
|
const allFiles = [...stockFiles, ...customFiles];
|
|
@@ -4745,7 +4998,7 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4745
4998
|
for (const filePath of allFiles) {
|
|
4746
4999
|
let yamlContent;
|
|
4747
5000
|
try {
|
|
4748
|
-
yamlContent =
|
|
5001
|
+
yamlContent = readFileSync20(filePath, "utf-8");
|
|
4749
5002
|
} catch {
|
|
4750
5003
|
errors.push(`Could not read: ${filePath}`);
|
|
4751
5004
|
continue;
|
|
@@ -4755,7 +5008,7 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4755
5008
|
data = generateThemeData6(yamlContent);
|
|
4756
5009
|
} catch (err) {
|
|
4757
5010
|
errors.push(
|
|
4758
|
-
`Failed to parse ${
|
|
5011
|
+
`Failed to parse ${basename5(filePath)}: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
4759
5012
|
);
|
|
4760
5013
|
continue;
|
|
4761
5014
|
}
|
|
@@ -4824,8 +5077,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4824
5077
|
return;
|
|
4825
5078
|
}
|
|
4826
5079
|
const slugs = processed.map((p) => p.slug);
|
|
4827
|
-
const libSrcDir =
|
|
4828
|
-
if (
|
|
5080
|
+
const libSrcDir = join18(outputDir, "lib", "src");
|
|
5081
|
+
if (existsSync16(libSrcDir)) {
|
|
4829
5082
|
rmSync(libSrcDir, { recursive: true, force: true });
|
|
4830
5083
|
}
|
|
4831
5084
|
const packageFiles = {
|
|
@@ -4837,17 +5090,17 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4837
5090
|
};
|
|
4838
5091
|
let totalFiles = 0;
|
|
4839
5092
|
for (const [relPath, content] of Object.entries(packageFiles)) {
|
|
4840
|
-
const absPath =
|
|
4841
|
-
|
|
4842
|
-
|
|
5093
|
+
const absPath = join18(outputDir, relPath);
|
|
5094
|
+
mkdirSync7(dirname8(absPath), { recursive: true });
|
|
5095
|
+
writeFileSync11(absPath, content, "utf-8");
|
|
4843
5096
|
totalFiles++;
|
|
4844
5097
|
}
|
|
4845
5098
|
for (const { slug: slug2, tokenFiles } of processed) {
|
|
4846
|
-
const themeBaseDir =
|
|
5099
|
+
const themeBaseDir = join18(outputDir, "lib", "src", slug2);
|
|
4847
5100
|
for (const [relPath, content] of Object.entries(tokenFiles)) {
|
|
4848
|
-
const absPath =
|
|
4849
|
-
|
|
4850
|
-
|
|
5101
|
+
const absPath = join18(themeBaseDir, relPath);
|
|
5102
|
+
mkdirSync7(dirname8(absPath), { recursive: true });
|
|
5103
|
+
writeFileSync11(absPath, content, "utf-8");
|
|
4851
5104
|
totalFiles++;
|
|
4852
5105
|
}
|
|
4853
5106
|
}
|
|
@@ -4868,11 +5121,11 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4868
5121
|
}
|
|
4869
5122
|
|
|
4870
5123
|
// src/commands/fonts-add.ts
|
|
4871
|
-
import { existsSync as
|
|
4872
|
-
import { resolve as resolve13, basename as
|
|
5124
|
+
import { existsSync as existsSync17, statSync as statSync7, readdirSync as readdirSync8, readFileSync as readFileSync21 } from "fs";
|
|
5125
|
+
import { resolve as resolve13, basename as basename6, extname as extname4 } from "path";
|
|
4873
5126
|
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
4874
5127
|
function deriveFamilySlug(filename) {
|
|
4875
|
-
const name =
|
|
5128
|
+
const name = basename6(filename, extname4(filename));
|
|
4876
5129
|
const WEIGHT_STYLE_SUFFIXES = /* @__PURE__ */ new Set([
|
|
4877
5130
|
"thin",
|
|
4878
5131
|
"hairline",
|
|
@@ -4914,14 +5167,14 @@ function deriveFamilySlug(filename) {
|
|
|
4914
5167
|
}
|
|
4915
5168
|
function collectWoff2Files(inputPath) {
|
|
4916
5169
|
const resolved = resolve13(inputPath);
|
|
4917
|
-
if (!
|
|
5170
|
+
if (!existsSync17(resolved)) {
|
|
4918
5171
|
throw new Error(`Path not found: ${resolved}`);
|
|
4919
5172
|
}
|
|
4920
5173
|
const stat = statSync7(resolved);
|
|
4921
5174
|
if (stat.isFile()) {
|
|
4922
5175
|
if (extname4(resolved).toLowerCase() !== ".woff2") {
|
|
4923
5176
|
throw new Error(
|
|
4924
|
-
`Invalid file format: ${
|
|
5177
|
+
`Invalid file format: ${basename6(resolved)}. Only .woff2 files are accepted.`
|
|
4925
5178
|
);
|
|
4926
5179
|
}
|
|
4927
5180
|
return [resolved];
|
|
@@ -4943,7 +5196,7 @@ function collectWoff2Files(inputPath) {
|
|
|
4943
5196
|
}
|
|
4944
5197
|
function getNonWoff2Fonts(inputPath) {
|
|
4945
5198
|
const resolved = resolve13(inputPath);
|
|
4946
|
-
if (!
|
|
5199
|
+
if (!existsSync17(resolved) || !statSync7(resolved).isDirectory()) {
|
|
4947
5200
|
return [];
|
|
4948
5201
|
}
|
|
4949
5202
|
return readdirSync8(resolved).filter((f) => {
|
|
@@ -4980,7 +5233,7 @@ function createR2Client(config) {
|
|
|
4980
5233
|
});
|
|
4981
5234
|
}
|
|
4982
5235
|
async function uploadFile(client, bucket, key, filePath) {
|
|
4983
|
-
const body =
|
|
5236
|
+
const body = readFileSync21(filePath);
|
|
4984
5237
|
await client.send(
|
|
4985
5238
|
new PutObjectCommand({
|
|
4986
5239
|
Bucket: bucket,
|
|
@@ -4995,7 +5248,7 @@ async function fontsAddCommand(inputPath, options) {
|
|
|
4995
5248
|
try {
|
|
4996
5249
|
const r2Config = getR2Config();
|
|
4997
5250
|
const files = collectWoff2Files(inputPath);
|
|
4998
|
-
const familySlug = options.family ?? deriveFamilySlug(
|
|
5251
|
+
const familySlug = options.family ?? deriveFamilySlug(basename6(files[0]));
|
|
4999
5252
|
const resolved = resolve13(inputPath);
|
|
5000
5253
|
const nonWoff2 = statSync7(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
|
|
5001
5254
|
if (!json) {
|
|
@@ -5015,7 +5268,7 @@ async function fontsAddCommand(inputPath, options) {
|
|
|
5015
5268
|
const bucket = "visor-fonts";
|
|
5016
5269
|
const results = [];
|
|
5017
5270
|
for (const filePath of files) {
|
|
5018
|
-
const filename =
|
|
5271
|
+
const filename = basename6(filePath);
|
|
5019
5272
|
const key = buildS3Key(org, familySlug, filename);
|
|
5020
5273
|
if (!json) {
|
|
5021
5274
|
logger.info(`Uploading ${filename}...`);
|
|
@@ -5284,27 +5537,27 @@ function findCssFiles(dir, maxDepth = 3) {
|
|
|
5284
5537
|
}
|
|
5285
5538
|
|
|
5286
5539
|
// src/utils/patterns.ts
|
|
5287
|
-
import { existsSync as
|
|
5288
|
-
import { join as
|
|
5540
|
+
import { existsSync as existsSync19, readdirSync as readdirSync10, readFileSync as readFileSync23 } from "fs";
|
|
5541
|
+
import { join as join20 } from "path";
|
|
5289
5542
|
import { parse as parseYAML } from "yaml";
|
|
5290
5543
|
function loadPatternsFromYaml(repoRoot) {
|
|
5291
|
-
const patternsDir =
|
|
5292
|
-
if (!
|
|
5544
|
+
const patternsDir = join20(repoRoot, "patterns");
|
|
5545
|
+
if (!existsSync19(patternsDir)) return [];
|
|
5293
5546
|
const files = readdirSync10(patternsDir).filter(
|
|
5294
5547
|
(f) => f.endsWith(".visor-pattern.yaml")
|
|
5295
5548
|
);
|
|
5296
5549
|
return files.map((file) => {
|
|
5297
|
-
const content =
|
|
5550
|
+
const content = readFileSync23(join20(patternsDir, file), "utf-8");
|
|
5298
5551
|
return parseYAML(content);
|
|
5299
5552
|
}).filter(Boolean);
|
|
5300
5553
|
}
|
|
5301
5554
|
function findRepoRoot2(startDir) {
|
|
5302
5555
|
let current = startDir;
|
|
5303
5556
|
while (true) {
|
|
5304
|
-
if (
|
|
5557
|
+
if (existsSync19(join20(current, "patterns"))) {
|
|
5305
5558
|
return current;
|
|
5306
5559
|
}
|
|
5307
|
-
const parent =
|
|
5560
|
+
const parent = join20(current, "..");
|
|
5308
5561
|
if (parent === current) return null;
|
|
5309
5562
|
current = parent;
|
|
5310
5563
|
}
|
|
@@ -5683,8 +5936,8 @@ Visor Tokens (${categoryLabel}) \u2014 ${tokens2.length} tokens
|
|
|
5683
5936
|
}
|
|
5684
5937
|
|
|
5685
5938
|
// src/commands/migrate-token-substitution.ts
|
|
5686
|
-
import { readFileSync as
|
|
5687
|
-
import { resolve as resolve14, join as
|
|
5939
|
+
import { readFileSync as readFileSync24, writeFileSync as writeFileSync12, readdirSync as readdirSync11, statSync as statSync8, existsSync as existsSync20 } from "fs";
|
|
5940
|
+
import { resolve as resolve14, join as join21, relative as relative4 } from "path";
|
|
5688
5941
|
import { parse as parseYaml4 } from "yaml";
|
|
5689
5942
|
import pc4 from "picocolors";
|
|
5690
5943
|
var V7_ENTR_SUBSTITUTION_MAP = {
|
|
@@ -5707,14 +5960,14 @@ var BUILT_IN_SUBSTITUTION_MAPS = {
|
|
|
5707
5960
|
var DEFAULT_THEME_ID = "entr";
|
|
5708
5961
|
function readMapFromThemeFile(themeId, cwd) {
|
|
5709
5962
|
const candidates = [
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5963
|
+
join21(cwd, "themes", `${themeId}.visor.yaml`),
|
|
5964
|
+
join21(cwd, "custom-themes", `${themeId}.visor.yaml`),
|
|
5965
|
+
join21(cwd, "packages", "docs", "public", "themes", `${themeId}.visor.yaml`)
|
|
5713
5966
|
];
|
|
5714
5967
|
for (const candidate of candidates) {
|
|
5715
|
-
if (
|
|
5968
|
+
if (existsSync20(candidate)) {
|
|
5716
5969
|
try {
|
|
5717
|
-
const raw =
|
|
5970
|
+
const raw = readFileSync24(candidate, "utf-8");
|
|
5718
5971
|
const parsed = parseYaml4(raw);
|
|
5719
5972
|
if (parsed?.migrate?.["token-substitution"]) {
|
|
5720
5973
|
return parsed.migrate["token-substitution"];
|
|
@@ -5770,7 +6023,7 @@ function collectCssFiles(dirPath) {
|
|
|
5770
6023
|
function walk2(current) {
|
|
5771
6024
|
const entries = readdirSync11(current, { withFileTypes: true });
|
|
5772
6025
|
for (const entry of entries) {
|
|
5773
|
-
const fullPath =
|
|
6026
|
+
const fullPath = join21(current, entry.name);
|
|
5774
6027
|
if (entry.isDirectory()) {
|
|
5775
6028
|
if (["node_modules", ".git", ".next", "dist", "build", ".cache"].includes(entry.name)) {
|
|
5776
6029
|
continue;
|
|
@@ -5798,7 +6051,7 @@ function runSubstitutionPass(targetPath, map, themeId) {
|
|
|
5798
6051
|
const fileResults = [];
|
|
5799
6052
|
let totalSubstitutions = 0;
|
|
5800
6053
|
for (const file of files) {
|
|
5801
|
-
const content =
|
|
6054
|
+
const content = readFileSync24(file, "utf-8");
|
|
5802
6055
|
const { newContent, substitutions } = applySubstitutionsToContent(content, map);
|
|
5803
6056
|
if (substitutions.length > 0) {
|
|
5804
6057
|
fileResults.push({ file, substitutions, originalContent: content, newContent });
|
|
@@ -5860,7 +6113,7 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
5860
6113
|
if (options.json) {
|
|
5861
6114
|
if (apply) {
|
|
5862
6115
|
for (const f of result.files) {
|
|
5863
|
-
|
|
6116
|
+
writeFileSync12(f.file, f.newContent, "utf-8");
|
|
5864
6117
|
}
|
|
5865
6118
|
console.log(JSON.stringify({
|
|
5866
6119
|
success: true,
|
|
@@ -5900,7 +6153,7 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
5900
6153
|
logger.warn(`Dry run \u2014 no files written. Re-run with ${pc4.bold("--apply")} to commit changes.`);
|
|
5901
6154
|
} else {
|
|
5902
6155
|
for (const f of result.files) {
|
|
5903
|
-
|
|
6156
|
+
writeFileSync12(f.file, f.newContent, "utf-8");
|
|
5904
6157
|
}
|
|
5905
6158
|
logger.heading(`visor migrate token-substitution \u2014 applied`);
|
|
5906
6159
|
logger.blank();
|
|
@@ -5921,23 +6174,23 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
5921
6174
|
}
|
|
5922
6175
|
|
|
5923
6176
|
// src/commands/sandbox/init.ts
|
|
5924
|
-
import { existsSync as
|
|
5925
|
-
import { isAbsolute as isAbsolute2, join as
|
|
6177
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync10, readFileSync as readFileSync27, rmSync as rmSync2 } from "fs";
|
|
6178
|
+
import { isAbsolute as isAbsolute2, join as join24, resolve as resolve16, dirname as dirname10 } from "path";
|
|
5926
6179
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5927
|
-
import * as
|
|
6180
|
+
import * as childProcess3 from "child_process";
|
|
5928
6181
|
|
|
5929
6182
|
// src/commands/sandbox/parse-handoff.ts
|
|
5930
|
-
import { readFileSync as
|
|
5931
|
-
import { dirname as
|
|
6183
|
+
import { readFileSync as readFileSync25, existsSync as existsSync21 } from "fs";
|
|
6184
|
+
import { dirname as dirname9, resolve as resolve15, isAbsolute } from "path";
|
|
5932
6185
|
function parseHandoff(handoffPath) {
|
|
5933
|
-
const text =
|
|
6186
|
+
const text = readFileSync25(handoffPath, "utf-8");
|
|
5934
6187
|
const lines2 = text.split("\n");
|
|
5935
6188
|
const warnings = [];
|
|
5936
6189
|
const pattern2 = extractPatternSlug(lines2, warnings);
|
|
5937
6190
|
const theme2 = extractMetaField(lines2, "Theme");
|
|
5938
6191
|
const recipePath = extractRecipePath(text, handoffPath, warnings);
|
|
5939
6192
|
const primitives = extractPrimitives(text, warnings);
|
|
5940
|
-
const { screens, mockShapes } = recipePath &&
|
|
6193
|
+
const { screens, mockShapes } = recipePath && existsSync21(recipePath) ? extractRecipeArtifacts(recipePath, warnings) : { screens: [], mockShapes: [] };
|
|
5941
6194
|
return { pattern: pattern2, theme: theme2, recipePath, primitives, screens, mockShapes, warnings };
|
|
5942
6195
|
}
|
|
5943
6196
|
function extractPatternSlug(lines2, warnings) {
|
|
@@ -5969,7 +6222,7 @@ function extractRecipePath(text, handoffPath, warnings) {
|
|
|
5969
6222
|
}
|
|
5970
6223
|
const href = m[2].trim();
|
|
5971
6224
|
if (isAbsolute(href)) return href;
|
|
5972
|
-
return resolve15(
|
|
6225
|
+
return resolve15(dirname9(handoffPath), href);
|
|
5973
6226
|
}
|
|
5974
6227
|
var STATUS_PATTERNS = [
|
|
5975
6228
|
{ re: /NEW\s+gap/i, status: "gap-new" },
|
|
@@ -6016,7 +6269,7 @@ function extractPrimitives(text, warnings) {
|
|
|
6016
6269
|
return primitives;
|
|
6017
6270
|
}
|
|
6018
6271
|
function extractRecipeArtifacts(recipePath, warnings) {
|
|
6019
|
-
const text =
|
|
6272
|
+
const text = readFileSync25(recipePath, "utf-8");
|
|
6020
6273
|
const screens = extractScreens(text);
|
|
6021
6274
|
const mockShapes = extractMockFields(text, warnings);
|
|
6022
6275
|
return { screens, mockShapes };
|
|
@@ -6090,8 +6343,8 @@ function tryPort(port) {
|
|
|
6090
6343
|
}
|
|
6091
6344
|
|
|
6092
6345
|
// src/commands/sandbox/scaffold.ts
|
|
6093
|
-
import { mkdirSync as
|
|
6094
|
-
import { join as
|
|
6346
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync13, existsSync as existsSync22, readdirSync as readdirSync12 } from "fs";
|
|
6347
|
+
import { join as join22 } from "path";
|
|
6095
6348
|
|
|
6096
6349
|
// src/commands/sandbox/templates.ts
|
|
6097
6350
|
var SANDBOX_PACKAGE_VERSION = "16.2.6";
|
|
@@ -6591,12 +6844,12 @@ function toPascalCase(s) {
|
|
|
6591
6844
|
|
|
6592
6845
|
// src/commands/sandbox/scaffold.ts
|
|
6593
6846
|
function writeScaffold(sandboxDir, manifest, port, options = {}) {
|
|
6594
|
-
|
|
6847
|
+
mkdirSync8(sandboxDir, { recursive: true });
|
|
6595
6848
|
const created = [];
|
|
6596
6849
|
const writeIfNew = (rel, contents) => {
|
|
6597
|
-
const full =
|
|
6598
|
-
|
|
6599
|
-
|
|
6850
|
+
const full = join22(sandboxDir, rel);
|
|
6851
|
+
mkdirSync8(dirOf(full), { recursive: true });
|
|
6852
|
+
writeFileSync13(full, contents, "utf-8");
|
|
6600
6853
|
created.push(rel);
|
|
6601
6854
|
};
|
|
6602
6855
|
writeIfNew("package.json", packageJsonTemplate(manifest.pattern));
|
|
@@ -6622,7 +6875,7 @@ function writeScaffold(sandboxDir, manifest, port, options = {}) {
|
|
|
6622
6875
|
}
|
|
6623
6876
|
}
|
|
6624
6877
|
writeIfNew("playwright.capture.mjs", captureScriptTemplate());
|
|
6625
|
-
|
|
6878
|
+
mkdirSync8(join22(sandboxDir, "captures", "approved"), { recursive: true });
|
|
6626
6879
|
return { created };
|
|
6627
6880
|
}
|
|
6628
6881
|
function dirOf(p) {
|
|
@@ -6656,10 +6909,10 @@ function writeSandboxConfig(sandboxDir, manifest, port, options) {
|
|
|
6656
6909
|
stripChromeSelectors: options.prototypeImport.stripChromeSelectors
|
|
6657
6910
|
} : null
|
|
6658
6911
|
};
|
|
6659
|
-
|
|
6912
|
+
writeFileSync13(join22(sandboxDir, "sandbox.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
6660
6913
|
}
|
|
6661
6914
|
function sandboxIsEmpty(sandboxDir) {
|
|
6662
|
-
if (!
|
|
6915
|
+
if (!existsSync22(sandboxDir)) return true;
|
|
6663
6916
|
try {
|
|
6664
6917
|
return readdirSync12(sandboxDir).filter((f) => !f.startsWith(".")).length === 0;
|
|
6665
6918
|
} catch {
|
|
@@ -6668,8 +6921,8 @@ function sandboxIsEmpty(sandboxDir) {
|
|
|
6668
6921
|
}
|
|
6669
6922
|
|
|
6670
6923
|
// src/commands/sandbox/html-prototype.ts
|
|
6671
|
-
import { mkdirSync as
|
|
6672
|
-
import { join as
|
|
6924
|
+
import { mkdirSync as mkdirSync9, readdirSync as readdirSync13, readFileSync as readFileSync26, statSync as statSync9, writeFileSync as writeFileSync14 } from "fs";
|
|
6925
|
+
import { join as join23 } from "path";
|
|
6673
6926
|
|
|
6674
6927
|
// src/commands/sandbox/strip-chrome.ts
|
|
6675
6928
|
var DEFAULT_STRIP_SELECTORS = [
|
|
@@ -6853,8 +7106,8 @@ function stripDocumentaryChrome(html, selectors) {
|
|
|
6853
7106
|
var SCREEN_FILE_PATTERN = /^screen-(\d+)-([^/.]+)\.html$/i;
|
|
6854
7107
|
function copyHtmlPrototype(sourceDir, sandboxDir, manifest, options = {}) {
|
|
6855
7108
|
const warnings = [];
|
|
6856
|
-
const destAbs =
|
|
6857
|
-
|
|
7109
|
+
const destAbs = join23(sandboxDir, "public", "prototype");
|
|
7110
|
+
mkdirSync9(destAbs, { recursive: true });
|
|
6858
7111
|
const stripChromeSelectors = options.stripChromeSelectors ?? [];
|
|
6859
7112
|
const copiedFiles = copyTreeRelative(sourceDir, destAbs, "", stripChromeSelectors);
|
|
6860
7113
|
const screenFiles = listOrderedScreenFiles(sourceDir);
|
|
@@ -6907,24 +7160,24 @@ function deriveStateCoverageScreen(file, existingNames) {
|
|
|
6907
7160
|
}
|
|
6908
7161
|
function copyTreeRelative(srcDir, destDir, relDir = "", stripChromeSelectors = []) {
|
|
6909
7162
|
const out = [];
|
|
6910
|
-
const entries = readdirSync13(
|
|
7163
|
+
const entries = readdirSync13(join23(srcDir, relDir));
|
|
6911
7164
|
for (const name of entries) {
|
|
6912
7165
|
if (name.startsWith(".")) continue;
|
|
6913
|
-
const srcPath =
|
|
6914
|
-
const destPath =
|
|
7166
|
+
const srcPath = join23(srcDir, relDir, name);
|
|
7167
|
+
const destPath = join23(destDir, relDir, name);
|
|
6915
7168
|
const stat = statSync9(srcPath);
|
|
6916
7169
|
if (stat.isDirectory()) {
|
|
6917
|
-
|
|
6918
|
-
out.push(...copyTreeRelative(srcDir, destDir,
|
|
7170
|
+
mkdirSync9(destPath, { recursive: true });
|
|
7171
|
+
out.push(...copyTreeRelative(srcDir, destDir, join23(relDir, name), stripChromeSelectors));
|
|
6919
7172
|
} else if (stat.isFile()) {
|
|
6920
7173
|
if (stripChromeSelectors.length > 0 && name.toLowerCase().endsWith(".html")) {
|
|
6921
|
-
const html =
|
|
7174
|
+
const html = readFileSync26(srcPath, "utf-8");
|
|
6922
7175
|
const stripped = stripDocumentaryChrome(html, stripChromeSelectors);
|
|
6923
|
-
|
|
7176
|
+
writeFileSync14(destPath, stripped);
|
|
6924
7177
|
} else {
|
|
6925
|
-
|
|
7178
|
+
writeFileSync14(destPath, readFileSync26(srcPath));
|
|
6926
7179
|
}
|
|
6927
|
-
out.push(
|
|
7180
|
+
out.push(join23("public", "prototype", relDir, name).replace(/\\/g, "/"));
|
|
6928
7181
|
}
|
|
6929
7182
|
}
|
|
6930
7183
|
return out;
|
|
@@ -6977,7 +7230,7 @@ async function runInit(name, cwd, options) {
|
|
|
6977
7230
|
throw new Error(`Invalid sandbox name '${name}'. Use letters, digits, '-' or '_'.`);
|
|
6978
7231
|
}
|
|
6979
7232
|
const handoffPath = isAbsolute2(options.handoff) ? options.handoff : resolve16(cwd, options.handoff);
|
|
6980
|
-
if (!
|
|
7233
|
+
if (!existsSync23(handoffPath)) {
|
|
6981
7234
|
throw new Error(`Handoff manifest not found: ${handoffPath}`);
|
|
6982
7235
|
}
|
|
6983
7236
|
const manifest = parseHandoff(handoffPath);
|
|
@@ -6986,7 +7239,7 @@ async function runInit(name, cwd, options) {
|
|
|
6986
7239
|
`Handoff has no primitives \u2014 refusing to scaffold an empty sandbox. Check the manifest at ${handoffPath}`
|
|
6987
7240
|
);
|
|
6988
7241
|
}
|
|
6989
|
-
const sandboxDir =
|
|
7242
|
+
const sandboxDir = join24(cwd, ".lo", "sandbox", name);
|
|
6990
7243
|
if (!sandboxIsEmpty(sandboxDir)) {
|
|
6991
7244
|
if (!options.overwrite) {
|
|
6992
7245
|
throw new Error(
|
|
@@ -6995,12 +7248,12 @@ async function runInit(name, cwd, options) {
|
|
|
6995
7248
|
}
|
|
6996
7249
|
rmSync2(sandboxDir, { recursive: true, force: true });
|
|
6997
7250
|
}
|
|
6998
|
-
|
|
7251
|
+
mkdirSync10(sandboxDir, { recursive: true });
|
|
6999
7252
|
const port = await findOpenPort();
|
|
7000
7253
|
let prototypeImport;
|
|
7001
7254
|
if (options.fromHtmlPrototype) {
|
|
7002
7255
|
const prototypeDir = isAbsolute2(options.fromHtmlPrototype) ? options.fromHtmlPrototype : resolve16(cwd, options.fromHtmlPrototype);
|
|
7003
|
-
if (!
|
|
7256
|
+
if (!existsSync23(prototypeDir)) {
|
|
7004
7257
|
throw new Error(`HTML prototype directory not found: ${prototypeDir}`);
|
|
7005
7258
|
}
|
|
7006
7259
|
const stripChromeSelectors = resolveStripSelectors(
|
|
@@ -7064,17 +7317,17 @@ function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile
|
|
|
7064
7317
|
candidates.push(isAbsolute2(theme2) ? theme2 : resolve16(cwd, theme2));
|
|
7065
7318
|
const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
|
|
7066
7319
|
if (privateRoot && privateRoot.length > 0) {
|
|
7067
|
-
candidates.push(
|
|
7320
|
+
candidates.push(join24(privateRoot, "themes", theme2, "theme.visor.yaml"));
|
|
7068
7321
|
}
|
|
7069
7322
|
candidates.push(
|
|
7070
|
-
|
|
7071
|
-
|
|
7323
|
+
join24(cwd, "themes", `${theme2}.visor.yaml`),
|
|
7324
|
+
join24(cwd, "custom-themes", `${theme2}.visor.yaml`)
|
|
7072
7325
|
);
|
|
7073
|
-
const yamlPath = candidates.find((p) =>
|
|
7326
|
+
const yamlPath = candidates.find((p) => existsSync23(p));
|
|
7074
7327
|
if (!yamlPath) {
|
|
7075
7328
|
const searched = candidates.join(", ");
|
|
7076
7329
|
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 ${
|
|
7330
|
+
`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 ${join24(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
7331
|
);
|
|
7079
7332
|
if (!json) {
|
|
7080
7333
|
logger.warn(`Theme '${theme2}' not found \u2014 leaving placeholder globals.css.`);
|
|
@@ -7082,12 +7335,12 @@ function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile
|
|
|
7082
7335
|
`Re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH=<dir-containing-themes/${theme2}/theme.visor.yaml>.`
|
|
7083
7336
|
);
|
|
7084
7337
|
logger.item(
|
|
7085
|
-
`Or apply manually: npx visor theme apply <path> --adapter nextjs -o ${
|
|
7338
|
+
`Or apply manually: npx visor theme apply <path> --adapter nextjs -o ${join24(sandboxDir, "app", "globals.css")}`
|
|
7086
7339
|
);
|
|
7087
7340
|
}
|
|
7088
7341
|
return;
|
|
7089
7342
|
}
|
|
7090
|
-
const globalsOut =
|
|
7343
|
+
const globalsOut = join24(sandboxDir, "app", "globals.css");
|
|
7091
7344
|
try {
|
|
7092
7345
|
themeApplyCommand(yamlPath, sandboxDir, {
|
|
7093
7346
|
output: globalsOut,
|
|
@@ -7118,7 +7371,7 @@ function tryAddPrimitive(primitive, sandboxDir, json) {
|
|
|
7118
7371
|
}
|
|
7119
7372
|
function runNpmInstall(sandboxDir, json) {
|
|
7120
7373
|
if (!json) logger.info("Installing sandbox dependencies...");
|
|
7121
|
-
const result =
|
|
7374
|
+
const result = childProcess3.spawnSync("npm", ["install", "--no-audit", "--no-fund"], {
|
|
7122
7375
|
cwd: sandboxDir,
|
|
7123
7376
|
stdio: json ? "ignore" : "inherit"
|
|
7124
7377
|
});
|
|
@@ -7140,12 +7393,12 @@ function loadKnownPrimitives() {
|
|
|
7140
7393
|
}
|
|
7141
7394
|
function readCliVersion() {
|
|
7142
7395
|
try {
|
|
7143
|
-
const here =
|
|
7396
|
+
const here = dirname10(fileURLToPath3(import.meta.url));
|
|
7144
7397
|
for (let i = 0; i < 6; i++) {
|
|
7145
7398
|
const segments = new Array(i).fill("..");
|
|
7146
|
-
const candidate =
|
|
7399
|
+
const candidate = join24(here, ...segments, "package.json");
|
|
7147
7400
|
try {
|
|
7148
|
-
const pkg2 = JSON.parse(
|
|
7401
|
+
const pkg2 = JSON.parse(readFileSync27(candidate, "utf-8"));
|
|
7149
7402
|
if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) return pkg2.version;
|
|
7150
7403
|
} catch {
|
|
7151
7404
|
}
|
|
@@ -7156,14 +7409,14 @@ function readCliVersion() {
|
|
|
7156
7409
|
}
|
|
7157
7410
|
|
|
7158
7411
|
// src/commands/sandbox/dev.ts
|
|
7159
|
-
import { existsSync as
|
|
7160
|
-
import { join as
|
|
7161
|
-
import * as
|
|
7412
|
+
import { existsSync as existsSync24, readFileSync as readFileSync28 } from "fs";
|
|
7413
|
+
import { join as join25 } from "path";
|
|
7414
|
+
import * as childProcess4 from "child_process";
|
|
7162
7415
|
function sandboxDevCommand(cwd, options) {
|
|
7163
7416
|
const json = options.json ?? false;
|
|
7164
|
-
const sandboxDir =
|
|
7165
|
-
const configPath =
|
|
7166
|
-
if (!
|
|
7417
|
+
const sandboxDir = join25(cwd, ".lo", "sandbox", options.name);
|
|
7418
|
+
const configPath = join25(sandboxDir, "sandbox.json");
|
|
7419
|
+
if (!existsSync24(configPath)) {
|
|
7167
7420
|
fail(
|
|
7168
7421
|
json,
|
|
7169
7422
|
`Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init ${options.name} --handoff ... --theme ...' first.`
|
|
@@ -7172,7 +7425,7 @@ function sandboxDevCommand(cwd, options) {
|
|
|
7172
7425
|
}
|
|
7173
7426
|
let config;
|
|
7174
7427
|
try {
|
|
7175
|
-
config = JSON.parse(
|
|
7428
|
+
config = JSON.parse(readFileSync28(configPath, "utf-8"));
|
|
7176
7429
|
} catch (err) {
|
|
7177
7430
|
fail(json, `Invalid sandbox.json at ${configPath}: ${err.message}`);
|
|
7178
7431
|
return;
|
|
@@ -7199,7 +7452,7 @@ function sandboxDevCommand(cwd, options) {
|
|
|
7199
7452
|
spawnNextDev(sandboxDir, config.port, json);
|
|
7200
7453
|
}
|
|
7201
7454
|
function spawnNextDev(sandboxDir, port, json) {
|
|
7202
|
-
const child =
|
|
7455
|
+
const child = childProcess4.spawn(
|
|
7203
7456
|
"npx",
|
|
7204
7457
|
["--no-install", "next", "dev", "--port", String(port)],
|
|
7205
7458
|
{
|
|
@@ -7230,20 +7483,20 @@ function fail(json, message) {
|
|
|
7230
7483
|
// src/commands/sandbox/approve.ts
|
|
7231
7484
|
import {
|
|
7232
7485
|
copyFileSync as copyFileSync2,
|
|
7233
|
-
existsSync as
|
|
7234
|
-
mkdirSync as
|
|
7235
|
-
readFileSync as
|
|
7486
|
+
existsSync as existsSync25,
|
|
7487
|
+
mkdirSync as mkdirSync11,
|
|
7488
|
+
readFileSync as readFileSync29,
|
|
7236
7489
|
readdirSync as readdirSync14,
|
|
7237
7490
|
rmSync as rmSync3,
|
|
7238
|
-
writeFileSync as
|
|
7491
|
+
writeFileSync as writeFileSync15
|
|
7239
7492
|
} from "fs";
|
|
7240
|
-
import { basename as
|
|
7241
|
-
import * as
|
|
7493
|
+
import { basename as basename7, join as join26 } from "path";
|
|
7494
|
+
import * as childProcess5 from "child_process";
|
|
7242
7495
|
function sandboxApproveCommand(cwd, options) {
|
|
7243
7496
|
const json = options.json ?? false;
|
|
7244
|
-
const sandboxDir =
|
|
7245
|
-
const configPath =
|
|
7246
|
-
if (!
|
|
7497
|
+
const sandboxDir = join26(cwd, ".lo", "sandbox", options.name);
|
|
7498
|
+
const configPath = join26(sandboxDir, "sandbox.json");
|
|
7499
|
+
if (!existsSync25(configPath)) {
|
|
7247
7500
|
fail2(
|
|
7248
7501
|
json,
|
|
7249
7502
|
`Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init' first.`
|
|
@@ -7257,11 +7510,11 @@ function sandboxApproveCommand(cwd, options) {
|
|
|
7257
7510
|
runCapture(sandboxDir, configPath, options.name, json);
|
|
7258
7511
|
}
|
|
7259
7512
|
function runCapture(sandboxDir, configPath, sandboxName, json) {
|
|
7260
|
-
const config = JSON.parse(
|
|
7261
|
-
const captureScriptPath =
|
|
7262
|
-
|
|
7513
|
+
const config = JSON.parse(readFileSync29(configPath, "utf-8"));
|
|
7514
|
+
const captureScriptPath = join26(sandboxDir, "playwright.capture.mjs");
|
|
7515
|
+
writeFileSync15(captureScriptPath, captureScriptTemplate(), "utf-8");
|
|
7263
7516
|
ensurePlaywrightInstalled(sandboxDir, json);
|
|
7264
|
-
const result =
|
|
7517
|
+
const result = childProcess5.spawnSync(
|
|
7265
7518
|
"npx",
|
|
7266
7519
|
["--no-install", "node", "playwright.capture.mjs"],
|
|
7267
7520
|
{
|
|
@@ -7280,9 +7533,9 @@ function runCapture(sandboxDir, configPath, sandboxName, json) {
|
|
|
7280
7533
|
${result.stderr}`);
|
|
7281
7534
|
return;
|
|
7282
7535
|
}
|
|
7283
|
-
const pendingDir =
|
|
7284
|
-
const diffsDir =
|
|
7285
|
-
const approvedDir =
|
|
7536
|
+
const pendingDir = join26(sandboxDir, "captures", "pending");
|
|
7537
|
+
const diffsDir = join26(sandboxDir, "captures", "diffs");
|
|
7538
|
+
const approvedDir = join26(sandboxDir, "captures", "approved");
|
|
7286
7539
|
const pendingFiles = safeListPngs(pendingDir);
|
|
7287
7540
|
const diffFiles = safeListPngs(diffsDir);
|
|
7288
7541
|
const hasBaseline = safeListPngs(approvedDir).length > 0;
|
|
@@ -7319,9 +7572,9 @@ ${result.stderr}`);
|
|
|
7319
7572
|
}
|
|
7320
7573
|
}
|
|
7321
7574
|
function runPromotion(sandboxDir, sandboxName, json) {
|
|
7322
|
-
const pendingDir =
|
|
7323
|
-
const approvedDir =
|
|
7324
|
-
const diffsDir =
|
|
7575
|
+
const pendingDir = join26(sandboxDir, "captures", "pending");
|
|
7576
|
+
const approvedDir = join26(sandboxDir, "captures", "approved");
|
|
7577
|
+
const diffsDir = join26(sandboxDir, "captures", "diffs");
|
|
7325
7578
|
const pendingFiles = safeListPngs(pendingDir);
|
|
7326
7579
|
if (pendingFiles.length === 0) {
|
|
7327
7580
|
fail2(
|
|
@@ -7330,10 +7583,10 @@ function runPromotion(sandboxDir, sandboxName, json) {
|
|
|
7330
7583
|
);
|
|
7331
7584
|
return;
|
|
7332
7585
|
}
|
|
7333
|
-
|
|
7586
|
+
mkdirSync11(approvedDir, { recursive: true });
|
|
7334
7587
|
const promoted = [];
|
|
7335
7588
|
for (const src of pendingFiles) {
|
|
7336
|
-
const dest =
|
|
7589
|
+
const dest = join26(approvedDir, basename7(src));
|
|
7337
7590
|
copyFileSync2(src, dest);
|
|
7338
7591
|
promoted.push(dest);
|
|
7339
7592
|
}
|
|
@@ -7353,10 +7606,10 @@ function runPromotion(sandboxDir, sandboxName, json) {
|
|
|
7353
7606
|
}
|
|
7354
7607
|
}
|
|
7355
7608
|
function ensurePlaywrightInstalled(sandboxDir, json) {
|
|
7356
|
-
const markerPath =
|
|
7357
|
-
if (
|
|
7609
|
+
const markerPath = join26(sandboxDir, ".playwright-installed");
|
|
7610
|
+
if (existsSync25(markerPath)) return;
|
|
7358
7611
|
if (!json) logger.info("Installing Playwright Chromium (one-time)...");
|
|
7359
|
-
const result =
|
|
7612
|
+
const result = childProcess5.spawnSync(
|
|
7360
7613
|
"npx",
|
|
7361
7614
|
["--no-install", "playwright", "install", "chromium"],
|
|
7362
7615
|
{
|
|
@@ -7370,11 +7623,11 @@ function ensurePlaywrightInstalled(sandboxDir, json) {
|
|
|
7370
7623
|
if (typeof result.status === "number" && result.status !== 0) {
|
|
7371
7624
|
throw new Error(`playwright install exited with code ${result.status}`);
|
|
7372
7625
|
}
|
|
7373
|
-
|
|
7626
|
+
writeFileSync15(markerPath, (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
|
|
7374
7627
|
}
|
|
7375
7628
|
function safeListPngs(dir) {
|
|
7376
|
-
if (!
|
|
7377
|
-
return readdirSync14(dir).filter((f) => f.endsWith(".png")).map((f) =>
|
|
7629
|
+
if (!existsSync25(dir)) return [];
|
|
7630
|
+
return readdirSync14(dir).filter((f) => f.endsWith(".png")).map((f) => join26(dir, f));
|
|
7378
7631
|
}
|
|
7379
7632
|
function tryParseJson(s) {
|
|
7380
7633
|
try {
|
|
@@ -7392,15 +7645,372 @@ function fail2(json, message) {
|
|
|
7392
7645
|
process.exit(1);
|
|
7393
7646
|
}
|
|
7394
7647
|
|
|
7648
|
+
// src/commands/spawn.ts
|
|
7649
|
+
import {
|
|
7650
|
+
cpSync,
|
|
7651
|
+
existsSync as existsSync28,
|
|
7652
|
+
mkdirSync as mkdirSync12,
|
|
7653
|
+
readFileSync as readFileSync31,
|
|
7654
|
+
readdirSync as readdirSync16,
|
|
7655
|
+
rmSync as rmSync4,
|
|
7656
|
+
writeFileSync as writeFileSync16
|
|
7657
|
+
} from "fs";
|
|
7658
|
+
import { basename as basename8, isAbsolute as isAbsolute3, join as join29, resolve as resolve17 } from "path";
|
|
7659
|
+
import { homedir as homedir3 } from "os";
|
|
7660
|
+
import * as childProcess6 from "child_process";
|
|
7661
|
+
import { parse as parseYaml5 } from "yaml";
|
|
7662
|
+
import { generateThemeData as generateThemeData7 } from "@loworbitstudio/visor-theme-engine";
|
|
7663
|
+
import { nextjsAdapter as nextjsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
7664
|
+
import { validate as validate3 } from "@loworbitstudio/visor-theme-engine";
|
|
7665
|
+
|
|
7666
|
+
// src/lib/blessed-discovery.ts
|
|
7667
|
+
import { existsSync as existsSync27, readdirSync as readdirSync15 } from "fs";
|
|
7668
|
+
import { join as join28 } from "path";
|
|
7669
|
+
|
|
7670
|
+
// src/lib/blessed-manifest.ts
|
|
7671
|
+
import { existsSync as existsSync26, readFileSync as readFileSync30 } from "fs";
|
|
7672
|
+
import { join as join27 } from "path";
|
|
7673
|
+
import { z } from "zod";
|
|
7674
|
+
var BLESSED_MANIFEST_FILENAME = "blessed-manifest.json";
|
|
7675
|
+
var blessedManifestSchema = z.object({
|
|
7676
|
+
/** Blessed-build shape, e.g. `admin-ui`. Matched against the `{shape}` in `blessed:{shape}:{pattern}`. */
|
|
7677
|
+
shape: z.string().min(1),
|
|
7678
|
+
/** Pattern name within the shape, e.g. `organization-management`. */
|
|
7679
|
+
pattern: z.string().min(1),
|
|
7680
|
+
/** The theme the reference build was authored + captured against. */
|
|
7681
|
+
base_theme: z.string().min(1),
|
|
7682
|
+
/** Minimum Visor version this build is known to work with (semver range). */
|
|
7683
|
+
requires_visor: z.string().min(1),
|
|
7684
|
+
/** Path (relative to the build root) to the approved capture baseline. */
|
|
7685
|
+
captures_baseline: z.string().min(1),
|
|
7686
|
+
/** Three-gates disposition at the time the build was blessed. */
|
|
7687
|
+
three_gates_status: z.string().min(1)
|
|
7688
|
+
}).strict();
|
|
7689
|
+
function parseBlessedManifest(buildDir) {
|
|
7690
|
+
const manifestPath = join27(buildDir, BLESSED_MANIFEST_FILENAME);
|
|
7691
|
+
if (!existsSync26(manifestPath)) {
|
|
7692
|
+
return {
|
|
7693
|
+
ok: false,
|
|
7694
|
+
error: `this directory is not a blessed build (missing ${BLESSED_MANIFEST_FILENAME}); see docs/blessed-builds.md`
|
|
7695
|
+
};
|
|
7696
|
+
}
|
|
7697
|
+
let raw;
|
|
7698
|
+
try {
|
|
7699
|
+
raw = readFileSync30(manifestPath, "utf-8");
|
|
7700
|
+
} catch {
|
|
7701
|
+
return { ok: false, error: `could not read ${manifestPath}` };
|
|
7702
|
+
}
|
|
7703
|
+
let json;
|
|
7704
|
+
try {
|
|
7705
|
+
json = JSON.parse(raw);
|
|
7706
|
+
} catch (err) {
|
|
7707
|
+
const message = err instanceof Error ? err.message : "invalid JSON";
|
|
7708
|
+
return { ok: false, error: `invalid JSON in ${manifestPath}: ${message}` };
|
|
7709
|
+
}
|
|
7710
|
+
const result = blessedManifestSchema.safeParse(json);
|
|
7711
|
+
if (!result.success) {
|
|
7712
|
+
const issues = result.error.issues.map((issue) => {
|
|
7713
|
+
const path2 = issue.path.join(".") || "(root)";
|
|
7714
|
+
return `${path2}: ${issue.message}`;
|
|
7715
|
+
}).join("; ");
|
|
7716
|
+
return {
|
|
7717
|
+
ok: false,
|
|
7718
|
+
error: `invalid ${BLESSED_MANIFEST_FILENAME} at ${manifestPath}: ${issues}`
|
|
7719
|
+
};
|
|
7720
|
+
}
|
|
7721
|
+
return { ok: true, manifest: result.data };
|
|
7722
|
+
}
|
|
7723
|
+
|
|
7724
|
+
// src/lib/blessed-discovery.ts
|
|
7725
|
+
var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
7726
|
+
"node_modules",
|
|
7727
|
+
".next",
|
|
7728
|
+
".git",
|
|
7729
|
+
"dist",
|
|
7730
|
+
".turbo",
|
|
7731
|
+
"coverage",
|
|
7732
|
+
".cache"
|
|
7733
|
+
]);
|
|
7734
|
+
function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
|
|
7735
|
+
const builds = [];
|
|
7736
|
+
const errors = [];
|
|
7737
|
+
if (!existsSync27(blessedDir)) {
|
|
7738
|
+
return { builds, errors };
|
|
7739
|
+
}
|
|
7740
|
+
walk2(blessedDir, 0);
|
|
7741
|
+
builds.sort((a, b) => {
|
|
7742
|
+
if (a.manifest.shape !== b.manifest.shape) {
|
|
7743
|
+
return a.manifest.shape.localeCompare(b.manifest.shape);
|
|
7744
|
+
}
|
|
7745
|
+
return a.manifest.pattern.localeCompare(b.manifest.pattern);
|
|
7746
|
+
});
|
|
7747
|
+
return { builds, errors };
|
|
7748
|
+
function walk2(dir, depth) {
|
|
7749
|
+
if (depth > maxDepth) return;
|
|
7750
|
+
let entries;
|
|
7751
|
+
try {
|
|
7752
|
+
entries = readdirSync15(dir, { withFileTypes: true });
|
|
7753
|
+
} catch {
|
|
7754
|
+
return;
|
|
7755
|
+
}
|
|
7756
|
+
const hasManifest = entries.some(
|
|
7757
|
+
(entry) => entry.isFile() && entry.name === BLESSED_MANIFEST_FILENAME
|
|
7758
|
+
);
|
|
7759
|
+
if (hasManifest) {
|
|
7760
|
+
const result = parseBlessedManifest(dir);
|
|
7761
|
+
if (result.ok) {
|
|
7762
|
+
builds.push({ dir, manifest: result.manifest });
|
|
7763
|
+
} else {
|
|
7764
|
+
errors.push({ dir, error: result.error });
|
|
7765
|
+
}
|
|
7766
|
+
return;
|
|
7767
|
+
}
|
|
7768
|
+
for (const entry of entries) {
|
|
7769
|
+
if (!entry.isDirectory()) continue;
|
|
7770
|
+
if (WALK_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
7771
|
+
walk2(join28(dir, entry.name), depth + 1);
|
|
7772
|
+
}
|
|
7773
|
+
}
|
|
7774
|
+
}
|
|
7775
|
+
function resolveBlessedBuild(blessedDir, shape, pattern2) {
|
|
7776
|
+
const { builds } = discoverBlessedBuilds(blessedDir);
|
|
7777
|
+
const build = builds.find(
|
|
7778
|
+
(candidate) => candidate.manifest.shape === shape && candidate.manifest.pattern === pattern2
|
|
7779
|
+
);
|
|
7780
|
+
return { build, available: builds };
|
|
7781
|
+
}
|
|
7782
|
+
|
|
7783
|
+
// src/commands/spawn.ts
|
|
7784
|
+
var DEFAULT_BLESSED_DIR = join29(
|
|
7785
|
+
homedir3(),
|
|
7786
|
+
"Code",
|
|
7787
|
+
"low-orbit",
|
|
7788
|
+
"low-orbit-playbook",
|
|
7789
|
+
"design-prototypes"
|
|
7790
|
+
);
|
|
7791
|
+
var FORK_EXCLUDE = /* @__PURE__ */ new Set([
|
|
7792
|
+
"node_modules",
|
|
7793
|
+
".next",
|
|
7794
|
+
".git",
|
|
7795
|
+
"dist",
|
|
7796
|
+
".turbo",
|
|
7797
|
+
".cache",
|
|
7798
|
+
"coverage",
|
|
7799
|
+
".DS_Store",
|
|
7800
|
+
"tsconfig.tsbuildinfo"
|
|
7801
|
+
]);
|
|
7802
|
+
function parseBlessedIdentifier(from) {
|
|
7803
|
+
const PREFIX = "blessed:";
|
|
7804
|
+
if (!from.startsWith(PREFIX)) {
|
|
7805
|
+
throw new Error(
|
|
7806
|
+
`Invalid --from '${from}'. Expected the form blessed:{shape}:{pattern} (e.g. blessed:admin-ui:organization-management).`
|
|
7807
|
+
);
|
|
7808
|
+
}
|
|
7809
|
+
const rest = from.slice(PREFIX.length);
|
|
7810
|
+
const parts = rest.split(":");
|
|
7811
|
+
if (parts.length !== 2 || parts[0].length === 0 || parts[1].length === 0) {
|
|
7812
|
+
throw new Error(
|
|
7813
|
+
`Invalid --from '${from}'. Expected the form blessed:{shape}:{pattern} (e.g. blessed:admin-ui:organization-management).`
|
|
7814
|
+
);
|
|
7815
|
+
}
|
|
7816
|
+
return { shape: parts[0], pattern: parts[1] };
|
|
7817
|
+
}
|
|
7818
|
+
function resolveBlessedDir(cwd, options) {
|
|
7819
|
+
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;
|
|
7820
|
+
return isAbsolute3(raw) ? raw : resolve17(cwd, raw);
|
|
7821
|
+
}
|
|
7822
|
+
function resolveThemeFile(theme2, cwd, themeFile) {
|
|
7823
|
+
const candidates = [];
|
|
7824
|
+
if (themeFile) {
|
|
7825
|
+
candidates.push(isAbsolute3(themeFile) ? themeFile : resolve17(cwd, themeFile));
|
|
7826
|
+
}
|
|
7827
|
+
candidates.push(isAbsolute3(theme2) ? theme2 : resolve17(cwd, theme2));
|
|
7828
|
+
const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
|
|
7829
|
+
if (privateRoot && privateRoot.length > 0) {
|
|
7830
|
+
candidates.push(join29(privateRoot, "themes", theme2, "theme.visor.yaml"));
|
|
7831
|
+
}
|
|
7832
|
+
candidates.push(
|
|
7833
|
+
join29(cwd, "themes", `${theme2}.visor.yaml`),
|
|
7834
|
+
join29(cwd, "custom-themes", `${theme2}.visor.yaml`)
|
|
7835
|
+
);
|
|
7836
|
+
const found = candidates.find((candidate) => existsSync28(candidate));
|
|
7837
|
+
if (!found) {
|
|
7838
|
+
throw new Error(
|
|
7839
|
+
`Theme '${theme2}' not found (searched: ${candidates.join(", ")}). Pass --theme-file <path>, use a direct path, or set VISOR_THEMES_PRIVATE_PATH.`
|
|
7840
|
+
);
|
|
7841
|
+
}
|
|
7842
|
+
return { path: found, searched: candidates };
|
|
7843
|
+
}
|
|
7844
|
+
function resolveGlobalsCssPath(outputDir) {
|
|
7845
|
+
const candidates = [
|
|
7846
|
+
join29(outputDir, "app", "globals.css"),
|
|
7847
|
+
join29(outputDir, "src", "app", "globals.css")
|
|
7848
|
+
];
|
|
7849
|
+
const existing = candidates.find((candidate) => existsSync28(candidate));
|
|
7850
|
+
return existing ?? candidates[0];
|
|
7851
|
+
}
|
|
7852
|
+
function applyThemeToFork(themeFile, globalsOut) {
|
|
7853
|
+
const yaml = readFileSync31(themeFile, "utf-8");
|
|
7854
|
+
const data = generateThemeData7(yaml);
|
|
7855
|
+
const css = nextjsAdapter3(
|
|
7856
|
+
{ primitives: data.primitives, tokens: data.tokens, config: data.config },
|
|
7857
|
+
{}
|
|
7858
|
+
);
|
|
7859
|
+
mkdirSync12(join29(globalsOut, ".."), { recursive: true });
|
|
7860
|
+
writeFileSync16(globalsOut, css, "utf-8");
|
|
7861
|
+
}
|
|
7862
|
+
function runNpmInstall2(outputDir, json) {
|
|
7863
|
+
const result = childProcess6.spawnSync("npm", ["install", "--no-audit", "--no-fund"], {
|
|
7864
|
+
cwd: outputDir,
|
|
7865
|
+
stdio: json ? "ignore" : "inherit"
|
|
7866
|
+
});
|
|
7867
|
+
if (result.error) {
|
|
7868
|
+
throw new Error(`npm install failed to start: ${result.error.message}`);
|
|
7869
|
+
}
|
|
7870
|
+
if (typeof result.status === "number" && result.status !== 0) {
|
|
7871
|
+
throw new Error(`npm install exited with code ${result.status}`);
|
|
7872
|
+
}
|
|
7873
|
+
}
|
|
7874
|
+
function runSpawn(cwd, options) {
|
|
7875
|
+
if (!options.from) throw new Error("Missing required --from blessed:{shape}:{pattern}.");
|
|
7876
|
+
if (!options.theme) throw new Error("Missing required --theme <id>.");
|
|
7877
|
+
if (!options.output) throw new Error("Missing required --output <path>.");
|
|
7878
|
+
const { shape, pattern: pattern2 } = parseBlessedIdentifier(options.from);
|
|
7879
|
+
const blessedDir = resolveBlessedDir(cwd, options);
|
|
7880
|
+
const { build, available } = resolveBlessedBuild(blessedDir, shape, pattern2);
|
|
7881
|
+
if (!build) {
|
|
7882
|
+
const list = available.length > 0 ? available.map((b) => ` - blessed:${b.manifest.shape}:${b.manifest.pattern}`).join("\n") : " (none found)";
|
|
7883
|
+
throw new Error(
|
|
7884
|
+
`No blessed build found for blessed:${shape}:${pattern2} under ${blessedDir}.
|
|
7885
|
+
Available builds:
|
|
7886
|
+
${list}`
|
|
7887
|
+
);
|
|
7888
|
+
}
|
|
7889
|
+
const outputDir = isAbsolute3(options.output) ? options.output : resolve17(cwd, options.output);
|
|
7890
|
+
if (existsSync28(outputDir) && readdirSync16(outputDir).length > 0) {
|
|
7891
|
+
throw new Error(`Output directory already exists and is not empty: ${outputDir}`);
|
|
7892
|
+
}
|
|
7893
|
+
const { path: themeFile } = resolveThemeFile(options.theme, cwd, options.themeFile);
|
|
7894
|
+
cpSync(build.dir, outputDir, {
|
|
7895
|
+
recursive: true,
|
|
7896
|
+
filter: (src) => !FORK_EXCLUDE.has(basename8(src))
|
|
7897
|
+
});
|
|
7898
|
+
let validated = false;
|
|
7899
|
+
let installed = false;
|
|
7900
|
+
try {
|
|
7901
|
+
const globalsOut = resolveGlobalsCssPath(outputDir);
|
|
7902
|
+
applyThemeToFork(themeFile, globalsOut);
|
|
7903
|
+
if (options.validate) {
|
|
7904
|
+
const parsed = parseYaml5(readFileSync31(themeFile, "utf-8"));
|
|
7905
|
+
const result = validate3(parsed);
|
|
7906
|
+
if (!result.valid) {
|
|
7907
|
+
const messages = result.errors.map((e) => e.message).join("; ");
|
|
7908
|
+
throw new Error(`Theme validation failed: ${messages}`);
|
|
7909
|
+
}
|
|
7910
|
+
validated = true;
|
|
7911
|
+
}
|
|
7912
|
+
if (options.install) {
|
|
7913
|
+
runNpmInstall2(outputDir, options.json ?? false);
|
|
7914
|
+
installed = true;
|
|
7915
|
+
}
|
|
7916
|
+
} catch (err) {
|
|
7917
|
+
rmSync4(outputDir, { recursive: true, force: true });
|
|
7918
|
+
throw err;
|
|
7919
|
+
}
|
|
7920
|
+
return {
|
|
7921
|
+
success: true,
|
|
7922
|
+
build: {
|
|
7923
|
+
shape: build.manifest.shape,
|
|
7924
|
+
pattern: build.manifest.pattern,
|
|
7925
|
+
requiresVisor: build.manifest.requires_visor,
|
|
7926
|
+
source: build.dir
|
|
7927
|
+
},
|
|
7928
|
+
output: outputDir,
|
|
7929
|
+
themeApplied: options.theme,
|
|
7930
|
+
themeFile,
|
|
7931
|
+
installed,
|
|
7932
|
+
validated
|
|
7933
|
+
};
|
|
7934
|
+
}
|
|
7935
|
+
function listBlessed(cwd, options) {
|
|
7936
|
+
const blessedDir = resolveBlessedDir(cwd, options);
|
|
7937
|
+
const { builds, errors } = discoverBlessedBuilds(blessedDir);
|
|
7938
|
+
if (options.json) {
|
|
7939
|
+
console.log(
|
|
7940
|
+
JSON.stringify({
|
|
7941
|
+
success: true,
|
|
7942
|
+
blessedDir,
|
|
7943
|
+
builds: builds.map((b) => ({
|
|
7944
|
+
from: `blessed:${b.manifest.shape}:${b.manifest.pattern}`,
|
|
7945
|
+
shape: b.manifest.shape,
|
|
7946
|
+
pattern: b.manifest.pattern,
|
|
7947
|
+
base_theme: b.manifest.base_theme,
|
|
7948
|
+
requires_visor: b.manifest.requires_visor,
|
|
7949
|
+
three_gates_status: b.manifest.three_gates_status,
|
|
7950
|
+
dir: b.dir
|
|
7951
|
+
})),
|
|
7952
|
+
errors
|
|
7953
|
+
})
|
|
7954
|
+
);
|
|
7955
|
+
return;
|
|
7956
|
+
}
|
|
7957
|
+
logger.heading(`Blessed builds under ${blessedDir}`);
|
|
7958
|
+
if (builds.length === 0) {
|
|
7959
|
+
logger.info(" (none found)");
|
|
7960
|
+
}
|
|
7961
|
+
for (const b of builds) {
|
|
7962
|
+
logger.success(`blessed:${b.manifest.shape}:${b.manifest.pattern}`);
|
|
7963
|
+
logger.item(`base theme: ${b.manifest.base_theme} \xB7 requires visor ${b.manifest.requires_visor} \xB7 gates: ${b.manifest.three_gates_status}`);
|
|
7964
|
+
}
|
|
7965
|
+
for (const e of errors) {
|
|
7966
|
+
logger.warn(`Skipped ${e.dir}: ${e.error}`);
|
|
7967
|
+
}
|
|
7968
|
+
}
|
|
7969
|
+
function spawnCommand(cwd, options) {
|
|
7970
|
+
const json = options.json ?? false;
|
|
7971
|
+
if (options.listBlessed) {
|
|
7972
|
+
try {
|
|
7973
|
+
listBlessed(cwd, options);
|
|
7974
|
+
} catch (err) {
|
|
7975
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7976
|
+
if (json) console.log(JSON.stringify({ success: false, error: message }));
|
|
7977
|
+
else logger.error(message);
|
|
7978
|
+
process.exit(1);
|
|
7979
|
+
}
|
|
7980
|
+
return;
|
|
7981
|
+
}
|
|
7982
|
+
try {
|
|
7983
|
+
const result = runSpawn(cwd, options);
|
|
7984
|
+
if (json) {
|
|
7985
|
+
console.log(JSON.stringify(result));
|
|
7986
|
+
return;
|
|
7987
|
+
}
|
|
7988
|
+
logger.success(
|
|
7989
|
+
`Discovered blessed build: ${result.build.shape}/${result.build.pattern} (requires visor ${result.build.requiresVisor})`
|
|
7990
|
+
);
|
|
7991
|
+
logger.success(`Forked to ${result.output} (excluded node_modules, .next, .git)`);
|
|
7992
|
+
logger.success(`Theme applied: ${result.themeApplied} \u2192 ${result.output}`);
|
|
7993
|
+
if (result.validated) logger.success("Theme validated");
|
|
7994
|
+
if (result.installed) logger.success("Installed dependencies");
|
|
7995
|
+
logger.blank();
|
|
7996
|
+
logger.info(`Next: cd ${result.output} && npm run dev`);
|
|
7997
|
+
} catch (err) {
|
|
7998
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
7999
|
+
if (json) console.log(JSON.stringify({ success: false, error: message }));
|
|
8000
|
+
else logger.error(message);
|
|
8001
|
+
process.exit(1);
|
|
8002
|
+
}
|
|
8003
|
+
}
|
|
8004
|
+
|
|
7395
8005
|
// src/index.ts
|
|
7396
|
-
var __dirname2 =
|
|
8006
|
+
var __dirname2 = dirname11(fileURLToPath4(import.meta.url));
|
|
7397
8007
|
var pkg = JSON.parse(
|
|
7398
|
-
|
|
8008
|
+
readFileSync32(join30(__dirname2, "..", "package.json"), "utf-8")
|
|
7399
8009
|
);
|
|
7400
8010
|
var program = new Command2();
|
|
7401
8011
|
program.name("visor").description("CLI for the Visor design system").version(pkg.version);
|
|
7402
8012
|
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) => {
|
|
8013
|
+
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
8014
|
initCommand(process.cwd(), options);
|
|
7405
8015
|
});
|
|
7406
8016
|
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) => {
|
|
@@ -7585,4 +8195,9 @@ sandbox.command("approve").description(
|
|
|
7585
8195
|
sandboxApproveCommand(process.cwd(), options);
|
|
7586
8196
|
}
|
|
7587
8197
|
);
|
|
8198
|
+
program.command("spawn").description(
|
|
8199
|
+
"Fork a Playbook blessed reference build with atomic theme re-skinning \u2014 one command replaces cp -R + npm install + theme apply"
|
|
8200
|
+
).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) => {
|
|
8201
|
+
spawnCommand(process.cwd(), options);
|
|
8202
|
+
});
|
|
7588
8203
|
program.parse();
|