@loworbitstudio/visor 1.13.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/README.md +46 -0
- package/dist/CHANGELOG.json +1 -1
- package/dist/index.js +1137 -381
- package/dist/init-plays.json +22 -0
- package/dist/registry.json +2 -2
- package/dist/visor-manifest.json +1 -1
- package/package.json +6 -4
package/dist/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import { dirname as
|
|
4
|
+
import { readFileSync as 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
|
|
|
9
9
|
// src/commands/check.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
11
|
import { statSync as statSync3 } from "fs";
|
|
12
|
-
import { resolve as
|
|
12
|
+
import { resolve as resolve4, dirname as dirname2 } from "path";
|
|
13
13
|
|
|
14
14
|
// src/registry/resolve.ts
|
|
15
15
|
import { readFileSync } from "fs";
|
|
@@ -867,6 +867,62 @@ function scanDesign(pathArg, options = {}) {
|
|
|
867
867
|
};
|
|
868
868
|
}
|
|
869
869
|
|
|
870
|
+
// src/check/theme-mode.ts
|
|
871
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
872
|
+
import { resolve as resolve3 } from "path";
|
|
873
|
+
import {
|
|
874
|
+
generateThemeData,
|
|
875
|
+
parseColor,
|
|
876
|
+
getLuminance
|
|
877
|
+
} from "@loworbitstudio/visor-theme-engine";
|
|
878
|
+
var LUMINANCE_THRESHOLD = 0.2;
|
|
879
|
+
function checkThemeModeSource(yamlSource, threshold = LUMINANCE_THRESHOLD) {
|
|
880
|
+
const data = generateThemeData(yamlSource);
|
|
881
|
+
const mode = data.config["color-scheme"];
|
|
882
|
+
const theme2 = data.config.name;
|
|
883
|
+
if (mode === "adaptive") {
|
|
884
|
+
return {
|
|
885
|
+
pass: true,
|
|
886
|
+
skipped: true,
|
|
887
|
+
mode,
|
|
888
|
+
theme: theme2,
|
|
889
|
+
computed_bg: null,
|
|
890
|
+
luminance: null,
|
|
891
|
+
threshold,
|
|
892
|
+
reason: "adaptive theme \u2014 supports both modes, no single background to assert"
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
const page = data.tokens.surface.page;
|
|
896
|
+
const computed_bg = mode === "dark-only" ? page.dark : page.light;
|
|
897
|
+
const parsed = parseColor(computed_bg);
|
|
898
|
+
if (!parsed) {
|
|
899
|
+
throw new Error(
|
|
900
|
+
`Could not parse app-root background color "${computed_bg}" for theme "${theme2}".`
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
const luminance = getLuminance(parsed.rgb[0], parsed.rgb[1], parsed.rgb[2]);
|
|
904
|
+
const isDark = luminance < threshold;
|
|
905
|
+
const pass = mode === "dark-only" ? isDark : !isDark;
|
|
906
|
+
const declaredWord = mode === "dark-only" ? "dark" : "light";
|
|
907
|
+
const actualWord = isDark ? "dark" : "light";
|
|
908
|
+
const reason = pass ? `theme declares ${mode}; app-root background renders ${actualWord} (luminance ${luminance.toFixed(4)}) \u2014 matches` : `theme declares ${mode} but app-root background "${computed_bg}" renders ${actualWord} (luminance ${luminance.toFixed(4)}, threshold ${threshold}) \u2014 expected ${declaredWord}`;
|
|
909
|
+
return {
|
|
910
|
+
pass,
|
|
911
|
+
skipped: false,
|
|
912
|
+
mode,
|
|
913
|
+
theme: theme2,
|
|
914
|
+
computed_bg,
|
|
915
|
+
luminance,
|
|
916
|
+
threshold,
|
|
917
|
+
reason
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
function checkThemeModeFile(filePath, threshold = LUMINANCE_THRESHOLD) {
|
|
921
|
+
const abs = resolve3(filePath);
|
|
922
|
+
const source = readFileSync4(abs, "utf-8");
|
|
923
|
+
return checkThemeModeSource(source, threshold);
|
|
924
|
+
}
|
|
925
|
+
|
|
870
926
|
// src/utils/logger.ts
|
|
871
927
|
import pc from "picocolors";
|
|
872
928
|
var logger = {
|
|
@@ -1026,7 +1082,7 @@ async function checkDiffCommand(pathArg, options) {
|
|
|
1026
1082
|
if (options.failOnHits) process.exit(1);
|
|
1027
1083
|
}
|
|
1028
1084
|
function checkDesignCommand(pathArg, options) {
|
|
1029
|
-
const absPath =
|
|
1085
|
+
const absPath = resolve4(pathArg);
|
|
1030
1086
|
const rcDir = statSync3(absPath).isDirectory() ? absPath : dirname2(absPath);
|
|
1031
1087
|
const rc = loadVisorRc(rcDir);
|
|
1032
1088
|
const result = scanDesign(absPath, {
|
|
@@ -1077,6 +1133,43 @@ function checkDesignCommand(pathArg, options) {
|
|
|
1077
1133
|
logger.blank();
|
|
1078
1134
|
if (!options.noFail && summary.errorCount > 0) process.exit(1);
|
|
1079
1135
|
}
|
|
1136
|
+
function checkThemeModeCommand(pathArg, options) {
|
|
1137
|
+
let result;
|
|
1138
|
+
try {
|
|
1139
|
+
result = checkThemeModeFile(pathArg);
|
|
1140
|
+
} catch (err) {
|
|
1141
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1142
|
+
const useJson2 = options.json || options.format === "json";
|
|
1143
|
+
if (useJson2) {
|
|
1144
|
+
console.log(JSON.stringify({ success: false, error: message }, null, 2));
|
|
1145
|
+
} else {
|
|
1146
|
+
logger.error(message);
|
|
1147
|
+
}
|
|
1148
|
+
process.exit(1);
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
const useJson = options.json || options.format === "json";
|
|
1152
|
+
const shouldFail = options.fail !== false;
|
|
1153
|
+
if (useJson) {
|
|
1154
|
+
console.log(JSON.stringify({ success: true, ...result }, null, 2));
|
|
1155
|
+
if (shouldFail && !result.pass) process.exit(1);
|
|
1156
|
+
process.exit(0);
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
if (result.skipped) {
|
|
1160
|
+
logger.info(`\u20DD skipped \u2014 ${result.theme}: ${result.reason}`);
|
|
1161
|
+
process.exit(0);
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
if (result.pass) {
|
|
1165
|
+
logger.success(`${result.theme} \u2014 ${result.reason}`);
|
|
1166
|
+
process.exit(0);
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
logger.error(`${result.theme} \u2014 ${result.reason}`);
|
|
1170
|
+
logger.item(` offending background: ${pc2.cyan(result.computed_bg ?? "unknown")} (luminance ${result.luminance?.toFixed(4)})`);
|
|
1171
|
+
if (shouldFail) process.exit(1);
|
|
1172
|
+
}
|
|
1080
1173
|
function checkCommand() {
|
|
1081
1174
|
const check = new Command("check").description("Check Visor catalog \u2014 list items, test existence, scan JSX for native HTML");
|
|
1082
1175
|
check.command("list").description("List all catalog items (components, blocks, hooks, patterns)").option("--type <type>", "filter by type: ui, blocks, hooks, patterns, all (default: all)").option("--json", "output structured JSON (for AI agents)").action((options) => {
|
|
@@ -1091,17 +1184,20 @@ function checkCommand() {
|
|
|
1091
1184
|
check.command("design").description("Scan frontend code for Borealis design anti-patterns (deterministic, no LLM)").argument("<path>", "file path or directory to scan").option("--format <format>", "output format: json or human (default: human when TTY, json otherwise)").option("--errors-only", "report only error-severity rules (skip warnings)").option("--no-fail", "do not exit 1 on errors (advisory mode)").option("--json", "shorthand for --format json").action((pathArg, options) => {
|
|
1092
1185
|
checkDesignCommand(pathArg, options);
|
|
1093
1186
|
});
|
|
1187
|
+
check.command("theme-mode").description("Assert a theme's rendered app-root background matches its declared color-scheme (dark-only/light-only/adaptive)").argument("<path>", "path to a .visor.yaml theme file").option("--format <format>", "output format: json or human (default: human)").option("--no-fail", "do not exit 1 on a mode mismatch (advisory mode)").option("--json", "shorthand for --format json").action((pathArg, options) => {
|
|
1188
|
+
checkThemeModeCommand(pathArg, options);
|
|
1189
|
+
});
|
|
1094
1190
|
return check;
|
|
1095
1191
|
}
|
|
1096
1192
|
|
|
1097
1193
|
// src/commands/init.ts
|
|
1098
|
-
import { existsSync as
|
|
1099
|
-
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";
|
|
1100
1196
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1101
|
-
import * as
|
|
1197
|
+
import * as childProcess2 from "child_process";
|
|
1102
1198
|
|
|
1103
1199
|
// src/config/config.ts
|
|
1104
|
-
import { readFileSync as
|
|
1200
|
+
import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync2 } from "fs";
|
|
1105
1201
|
import { join as join4 } from "path";
|
|
1106
1202
|
|
|
1107
1203
|
// src/config/defaults.ts
|
|
@@ -1131,7 +1227,7 @@ function loadConfig(cwd) {
|
|
|
1131
1227
|
`No ${CONFIG_FILE} found. Run "visor init" first.`
|
|
1132
1228
|
);
|
|
1133
1229
|
}
|
|
1134
|
-
const raw =
|
|
1230
|
+
const raw = readFileSync5(configPath, "utf-8");
|
|
1135
1231
|
const parsed = JSON.parse(raw);
|
|
1136
1232
|
const knownKeys = /* @__PURE__ */ new Set(["paths"]);
|
|
1137
1233
|
for (const key of Object.keys(parsed)) {
|
|
@@ -1168,12 +1264,12 @@ function writeConfig(cwd, config) {
|
|
|
1168
1264
|
|
|
1169
1265
|
// src/utils/packages.ts
|
|
1170
1266
|
import { execFileSync } from "child_process";
|
|
1171
|
-
import { existsSync as existsSync3, readFileSync as
|
|
1267
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
|
|
1172
1268
|
import { join as join5 } from "path";
|
|
1173
1269
|
function readPackageJson(cwd) {
|
|
1174
1270
|
const pkgPath = join5(cwd, "package.json");
|
|
1175
1271
|
if (!existsSync3(pkgPath)) return null;
|
|
1176
|
-
return JSON.parse(
|
|
1272
|
+
return JSON.parse(readFileSync6(pkgPath, "utf-8"));
|
|
1177
1273
|
}
|
|
1178
1274
|
function isPackageInstalled(packageName, cwd) {
|
|
1179
1275
|
const pkg2 = readPackageJson(cwd);
|
|
@@ -1198,6 +1294,7 @@ function installPackages(packages, cwd, dev = false) {
|
|
|
1198
1294
|
}
|
|
1199
1295
|
|
|
1200
1296
|
// src/commands/templates/nextjs.ts
|
|
1297
|
+
import { parse as parseYaml } from "yaml";
|
|
1201
1298
|
var NEXTJS_PINNED_VERSION = "15.1.6";
|
|
1202
1299
|
var CREATE_NEXT_APP_FLAGS = [
|
|
1203
1300
|
"--ts",
|
|
@@ -1211,11 +1308,24 @@ var CREATE_NEXT_APP_FLAGS = [
|
|
|
1211
1308
|
];
|
|
1212
1309
|
var NEXTJS_STARTER_YAML = `name: my-app
|
|
1213
1310
|
version: 1
|
|
1311
|
+
# color-scheme controls the mode the scaffold applies at the root <html>:
|
|
1312
|
+
# adaptive \u2014 follow the OS via prefers-color-scheme (default)
|
|
1313
|
+
# dark-only \u2014 always dark (adds className="dark" + color-scheme: dark)
|
|
1314
|
+
# light-only \u2014 always light
|
|
1315
|
+
color-scheme: adaptive
|
|
1214
1316
|
colors:
|
|
1215
1317
|
primary: "#2563EB"
|
|
1216
1318
|
`;
|
|
1217
|
-
function
|
|
1218
|
-
|
|
1319
|
+
function extractColorScheme(yamlContent) {
|
|
1320
|
+
const parsed = parseYaml(yamlContent);
|
|
1321
|
+
const v = parsed?.["color-scheme"];
|
|
1322
|
+
if (v === "dark-only" || v === "light-only" || v === "adaptive") return v;
|
|
1323
|
+
return void 0;
|
|
1324
|
+
}
|
|
1325
|
+
function generateNextjsLayout(colorScheme) {
|
|
1326
|
+
const forcedMode = colorScheme === "dark-only" ? "dark" : colorScheme === "light-only" ? "light" : void 0;
|
|
1327
|
+
if (forcedMode === void 0) {
|
|
1328
|
+
return `import "./globals.css";
|
|
1219
1329
|
import { FOWT_SCRIPT } from "@loworbitstudio/visor-theme-engine/fowt";
|
|
1220
1330
|
import type { Metadata } from "next";
|
|
1221
1331
|
import type { ReactNode } from "react";
|
|
@@ -1235,11 +1345,191 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
1235
1345
|
</html>
|
|
1236
1346
|
);
|
|
1237
1347
|
}
|
|
1348
|
+
`;
|
|
1349
|
+
}
|
|
1350
|
+
return `import "./globals.css";
|
|
1351
|
+
import { generateFowtScript } from "@loworbitstudio/visor-theme-engine/fowt";
|
|
1352
|
+
import type { Metadata } from "next";
|
|
1353
|
+
import type { ReactNode } from "react";
|
|
1354
|
+
|
|
1355
|
+
export const metadata: Metadata = {
|
|
1356
|
+
title: "My Visor App",
|
|
1357
|
+
description: "Built with Visor \u2014 Low Orbit Studio's design system.",
|
|
1358
|
+
};
|
|
1359
|
+
|
|
1360
|
+
// Theme declares \`color-scheme: ${colorScheme}\` \u2014 force ${forcedMode} at the root.
|
|
1361
|
+
const FOWT_SCRIPT = generateFowtScript({ defaultTheme: "${forcedMode}" });
|
|
1362
|
+
|
|
1363
|
+
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
1364
|
+
return (
|
|
1365
|
+
<html
|
|
1366
|
+
lang="en"
|
|
1367
|
+
className="${forcedMode}"
|
|
1368
|
+
style={{ colorScheme: "${forcedMode}" }}
|
|
1369
|
+
suppressHydrationWarning
|
|
1370
|
+
>
|
|
1371
|
+
<head>
|
|
1372
|
+
<script>{FOWT_SCRIPT}</script>
|
|
1373
|
+
</head>
|
|
1374
|
+
<body>{children}</body>
|
|
1375
|
+
</html>
|
|
1376
|
+
);
|
|
1377
|
+
}
|
|
1238
1378
|
`;
|
|
1239
1379
|
}
|
|
1240
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
|
+
|
|
1241
1531
|
// src/commands/init.ts
|
|
1242
|
-
import { generateThemeData } from "@loworbitstudio/visor-theme-engine";
|
|
1532
|
+
import { generateThemeData as generateThemeData2 } from "@loworbitstudio/visor-theme-engine";
|
|
1243
1533
|
import { nextjsAdapter } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
1244
1534
|
function initCommand(cwd, options) {
|
|
1245
1535
|
const json = options?.json ?? false;
|
|
@@ -1250,7 +1540,27 @@ function initCommand(cwd, options) {
|
|
|
1250
1540
|
emitError(json, `Unknown template: ${options.template}. Available templates: nextjs`);
|
|
1251
1541
|
process.exit(1);
|
|
1252
1542
|
}
|
|
1253
|
-
|
|
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"))) {
|
|
1254
1564
|
emitError(
|
|
1255
1565
|
json,
|
|
1256
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"
|
|
@@ -1291,6 +1601,10 @@ function initCommand(cwd, options) {
|
|
|
1291
1601
|
}
|
|
1292
1602
|
}
|
|
1293
1603
|
}
|
|
1604
|
+
let playResult;
|
|
1605
|
+
if (playDef) {
|
|
1606
|
+
playResult = runPlayInit(cwd, playDef, playName, options ?? {}, json, warnings);
|
|
1607
|
+
}
|
|
1294
1608
|
if (json) {
|
|
1295
1609
|
const nextSteps = buildNextSteps(options, warnings);
|
|
1296
1610
|
const result = {
|
|
@@ -1298,12 +1612,91 @@ function initCommand(cwd, options) {
|
|
|
1298
1612
|
config: DEFAULT_CONFIG,
|
|
1299
1613
|
files: { created: filesCreated, skipped: filesSkipped },
|
|
1300
1614
|
warnings,
|
|
1301
|
-
nextSteps
|
|
1615
|
+
nextSteps,
|
|
1616
|
+
...playResult ? { play: playResult } : {}
|
|
1302
1617
|
};
|
|
1303
1618
|
console.log(JSON.stringify(result, null, 2));
|
|
1304
1619
|
process.exit(0);
|
|
1305
1620
|
}
|
|
1306
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
|
+
}
|
|
1307
1700
|
function buildNextSteps(options, warnings) {
|
|
1308
1701
|
const steps = [];
|
|
1309
1702
|
if (options?.template === "nextjs") {
|
|
@@ -1333,58 +1726,59 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
|
|
|
1333
1726
|
}
|
|
1334
1727
|
runCreateNextApp(cwd, json);
|
|
1335
1728
|
runInstallVisorDeps(cwd, json);
|
|
1336
|
-
const yamlPath =
|
|
1337
|
-
if (
|
|
1729
|
+
const yamlPath = join8(cwd, ".visor.yaml");
|
|
1730
|
+
if (existsSync6(yamlPath)) {
|
|
1338
1731
|
filesSkipped.push(".visor.yaml");
|
|
1339
1732
|
if (!json) {
|
|
1340
1733
|
logger.warn(".visor.yaml already exists. Skipping.");
|
|
1341
1734
|
}
|
|
1342
1735
|
} else {
|
|
1343
|
-
|
|
1736
|
+
writeFileSync3(yamlPath, NEXTJS_STARTER_YAML, "utf-8");
|
|
1344
1737
|
filesCreated.push(".visor.yaml");
|
|
1345
1738
|
if (!json) {
|
|
1346
1739
|
logger.success("Created .visor.yaml");
|
|
1347
1740
|
}
|
|
1348
1741
|
}
|
|
1349
|
-
const data =
|
|
1742
|
+
const data = generateThemeData2(NEXTJS_STARTER_YAML);
|
|
1350
1743
|
const css = nextjsAdapter({
|
|
1351
1744
|
primitives: data.primitives,
|
|
1352
1745
|
tokens: data.tokens,
|
|
1353
1746
|
config: data.config
|
|
1354
1747
|
});
|
|
1355
|
-
const globalsPath =
|
|
1356
|
-
const globalsDir =
|
|
1357
|
-
|
|
1358
|
-
if (
|
|
1359
|
-
|
|
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");
|
|
1360
1753
|
filesCreated.push("app/globals.css");
|
|
1361
1754
|
} else {
|
|
1362
|
-
|
|
1755
|
+
writeFileSync3(globalsPath, css, "utf-8");
|
|
1363
1756
|
filesCreated.push("app/globals.css");
|
|
1364
1757
|
}
|
|
1365
1758
|
if (!json) {
|
|
1366
1759
|
logger.success("Created app/globals.css with theme tokens");
|
|
1367
1760
|
}
|
|
1368
|
-
const layoutPath =
|
|
1369
|
-
|
|
1761
|
+
const layoutPath = join8(cwd, "app", "layout.tsx");
|
|
1762
|
+
const colorScheme = extractColorScheme(NEXTJS_STARTER_YAML);
|
|
1763
|
+
writeFileSync3(layoutPath, generateNextjsLayout(colorScheme), "utf-8");
|
|
1370
1764
|
filesCreated.push("app/layout.tsx");
|
|
1371
1765
|
if (!json) {
|
|
1372
1766
|
logger.success("Wired app/layout.tsx with FOWT prevention and theme tokens");
|
|
1373
1767
|
}
|
|
1374
|
-
const stampDir =
|
|
1375
|
-
const stampPath =
|
|
1376
|
-
if (
|
|
1768
|
+
const stampDir = join8(cwd, ".lo");
|
|
1769
|
+
const stampPath = join8(stampDir, "borealis.json");
|
|
1770
|
+
if (existsSync6(stampPath)) {
|
|
1377
1771
|
filesSkipped.push(".lo/borealis.json");
|
|
1378
1772
|
if (!json) {
|
|
1379
1773
|
logger.warn(".lo/borealis.json already exists. Skipping.");
|
|
1380
1774
|
}
|
|
1381
1775
|
} else {
|
|
1382
|
-
|
|
1776
|
+
mkdirSync2(stampDir, { recursive: true });
|
|
1383
1777
|
const stamp = {
|
|
1384
1778
|
visorVersion: readVisorCliVersion(),
|
|
1385
1779
|
initializedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1386
1780
|
};
|
|
1387
|
-
|
|
1781
|
+
writeFileSync3(stampPath, JSON.stringify(stamp, null, 2) + "\n", "utf-8");
|
|
1388
1782
|
filesCreated.push(".lo/borealis.json");
|
|
1389
1783
|
if (!json) {
|
|
1390
1784
|
logger.success("Stamped .lo/borealis.json");
|
|
@@ -1405,7 +1799,7 @@ function runCreateNextApp(cwd, json) {
|
|
|
1405
1799
|
if (!json) {
|
|
1406
1800
|
logger.info(`Running create-next-app@${NEXTJS_PINNED_VERSION}...`);
|
|
1407
1801
|
}
|
|
1408
|
-
const result =
|
|
1802
|
+
const result = childProcess2.spawnSync(
|
|
1409
1803
|
"npx",
|
|
1410
1804
|
[`create-next-app@${NEXTJS_PINNED_VERSION}`, ".", ...CREATE_NEXT_APP_FLAGS],
|
|
1411
1805
|
{ cwd, stdio: json ? "ignore" : "inherit" }
|
|
@@ -1416,7 +1810,7 @@ function runInstallVisorDeps(cwd, json) {
|
|
|
1416
1810
|
if (!json) {
|
|
1417
1811
|
logger.info("Installing @loworbitstudio/visor-core and visor-theme-engine...");
|
|
1418
1812
|
}
|
|
1419
|
-
const result =
|
|
1813
|
+
const result = childProcess2.spawnSync(
|
|
1420
1814
|
"npm",
|
|
1421
1815
|
[
|
|
1422
1816
|
"install",
|
|
@@ -1437,12 +1831,12 @@ function assertSpawnSuccess(result, label) {
|
|
|
1437
1831
|
}
|
|
1438
1832
|
function readVisorCliVersion() {
|
|
1439
1833
|
try {
|
|
1440
|
-
const here =
|
|
1834
|
+
const here = dirname4(fileURLToPath2(import.meta.url));
|
|
1441
1835
|
for (let i = 0; i < 5; i++) {
|
|
1442
1836
|
const segments = new Array(i).fill("..");
|
|
1443
|
-
const candidate =
|
|
1837
|
+
const candidate = join8(here, ...segments, "package.json");
|
|
1444
1838
|
try {
|
|
1445
|
-
const pkg2 = JSON.parse(
|
|
1839
|
+
const pkg2 = JSON.parse(readFileSync9(candidate, "utf-8"));
|
|
1446
1840
|
if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) {
|
|
1447
1841
|
return pkg2.version;
|
|
1448
1842
|
}
|
|
@@ -1456,53 +1850,53 @@ function readVisorCliVersion() {
|
|
|
1456
1850
|
|
|
1457
1851
|
// src/utils/fs.ts
|
|
1458
1852
|
import {
|
|
1459
|
-
writeFileSync as
|
|
1460
|
-
readFileSync as
|
|
1461
|
-
existsSync as
|
|
1462
|
-
mkdirSync as
|
|
1853
|
+
writeFileSync as writeFileSync4,
|
|
1854
|
+
readFileSync as readFileSync10,
|
|
1855
|
+
existsSync as existsSync7,
|
|
1856
|
+
mkdirSync as mkdirSync3
|
|
1463
1857
|
} from "fs";
|
|
1464
|
-
import { dirname as
|
|
1858
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
1465
1859
|
function resolveOutputPath(registryPath, type, config, cwd) {
|
|
1466
1860
|
let relativePath;
|
|
1467
1861
|
if (type === "registry:block") {
|
|
1468
1862
|
relativePath = registryPath.replace(/^blocks\//, "");
|
|
1469
|
-
return
|
|
1863
|
+
return join9(cwd, config.paths.blocks, relativePath);
|
|
1470
1864
|
}
|
|
1471
1865
|
if (type === "registry:ui") {
|
|
1472
1866
|
if (registryPath.startsWith("components/deck/")) {
|
|
1473
1867
|
relativePath = registryPath.replace(/^components\/deck\//, "");
|
|
1474
|
-
return
|
|
1868
|
+
return join9(cwd, config.paths.deckComponents, relativePath);
|
|
1475
1869
|
}
|
|
1476
1870
|
if (registryPath.startsWith("components/flutter/")) {
|
|
1477
1871
|
relativePath = registryPath.replace(/^components\/flutter\//, "");
|
|
1478
|
-
return
|
|
1872
|
+
return join9(cwd, config.paths.flutterComponents, relativePath);
|
|
1479
1873
|
}
|
|
1480
1874
|
relativePath = registryPath.replace(/^components\/ui\//, "");
|
|
1481
|
-
return
|
|
1875
|
+
return join9(cwd, config.paths.components, relativePath);
|
|
1482
1876
|
}
|
|
1483
1877
|
if (type === "registry:hook") {
|
|
1484
1878
|
relativePath = registryPath.replace(/^hooks\//, "");
|
|
1485
|
-
return
|
|
1879
|
+
return join9(cwd, config.paths.hooks, relativePath);
|
|
1486
1880
|
}
|
|
1487
1881
|
if (type === "registry:lib") {
|
|
1488
1882
|
relativePath = registryPath.replace(/^lib\//, "");
|
|
1489
|
-
return
|
|
1883
|
+
return join9(cwd, config.paths.lib, relativePath);
|
|
1490
1884
|
}
|
|
1491
|
-
return
|
|
1885
|
+
return join9(cwd, registryPath);
|
|
1492
1886
|
}
|
|
1493
1887
|
function writeFile(filePath, content) {
|
|
1494
|
-
const dir =
|
|
1495
|
-
if (!
|
|
1496
|
-
|
|
1888
|
+
const dir = dirname5(filePath);
|
|
1889
|
+
if (!existsSync7(dir)) {
|
|
1890
|
+
mkdirSync3(dir, { recursive: true });
|
|
1497
1891
|
}
|
|
1498
|
-
|
|
1892
|
+
writeFileSync4(filePath, content, "utf-8");
|
|
1499
1893
|
}
|
|
1500
1894
|
function readFile(filePath) {
|
|
1501
|
-
if (!
|
|
1502
|
-
return
|
|
1895
|
+
if (!existsSync7(filePath)) return null;
|
|
1896
|
+
return readFileSync10(filePath, "utf-8");
|
|
1503
1897
|
}
|
|
1504
1898
|
function fileExists(filePath) {
|
|
1505
|
-
return
|
|
1899
|
+
return existsSync7(filePath);
|
|
1506
1900
|
}
|
|
1507
1901
|
|
|
1508
1902
|
// src/commands/list.ts
|
|
@@ -1643,8 +2037,8 @@ function listCommand(cwd, options = {}) {
|
|
|
1643
2037
|
}
|
|
1644
2038
|
|
|
1645
2039
|
// src/utils/pubspec.ts
|
|
1646
|
-
import { existsSync as
|
|
1647
|
-
import { join as
|
|
2040
|
+
import { existsSync as existsSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
|
|
2041
|
+
import { join as join10 } from "path";
|
|
1648
2042
|
import { parseDocument, YAMLMap } from "yaml";
|
|
1649
2043
|
function mergePubspec(pubspecText, deps) {
|
|
1650
2044
|
const doc = parseDocument(pubspecText);
|
|
@@ -1666,14 +2060,14 @@ function mergePubspec(pubspecText, deps) {
|
|
|
1666
2060
|
return { text: doc.toString(), added, skipped };
|
|
1667
2061
|
}
|
|
1668
2062
|
function pubspecPath(cwd) {
|
|
1669
|
-
return
|
|
2063
|
+
return join10(cwd, "pubspec.yaml");
|
|
1670
2064
|
}
|
|
1671
2065
|
function pubspecExists(cwd) {
|
|
1672
|
-
return
|
|
2066
|
+
return existsSync8(pubspecPath(cwd));
|
|
1673
2067
|
}
|
|
1674
2068
|
function isPubPackageInstalled(packageName, cwd) {
|
|
1675
2069
|
if (!pubspecExists(cwd)) return false;
|
|
1676
|
-
const text =
|
|
2070
|
+
const text = readFileSync11(pubspecPath(cwd), "utf-8");
|
|
1677
2071
|
const doc = parseDocument(text);
|
|
1678
2072
|
const depsNode = doc.get("dependencies");
|
|
1679
2073
|
if (!(depsNode instanceof YAMLMap)) return false;
|
|
@@ -1684,24 +2078,24 @@ function getUninstalledPubDeps(deps, cwd) {
|
|
|
1684
2078
|
}
|
|
1685
2079
|
function addPubDependencies(deps, cwd) {
|
|
1686
2080
|
const path2 = pubspecPath(cwd);
|
|
1687
|
-
if (!
|
|
2081
|
+
if (!existsSync8(path2)) {
|
|
1688
2082
|
throw new Error(
|
|
1689
2083
|
`No pubspec.yaml found at ${path2}. Run this command from a Flutter project root.`
|
|
1690
2084
|
);
|
|
1691
2085
|
}
|
|
1692
|
-
const text =
|
|
2086
|
+
const text = readFileSync11(path2, "utf-8");
|
|
1693
2087
|
const result = mergePubspec(text, deps);
|
|
1694
2088
|
if (result.added.length > 0) {
|
|
1695
|
-
|
|
2089
|
+
writeFileSync5(path2, result.text, "utf-8");
|
|
1696
2090
|
}
|
|
1697
2091
|
return result;
|
|
1698
2092
|
}
|
|
1699
2093
|
|
|
1700
2094
|
// src/utils/flutter.ts
|
|
1701
2095
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1702
|
-
import { existsSync as
|
|
1703
|
-
import { homedir } from "os";
|
|
1704
|
-
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";
|
|
1705
2099
|
function isExecutable(path2) {
|
|
1706
2100
|
try {
|
|
1707
2101
|
const s = statSync4(path2);
|
|
@@ -1716,20 +2110,20 @@ function fromPath(env) {
|
|
|
1716
2110
|
const bin = process.platform === "win32" ? "flutter.bat" : "flutter";
|
|
1717
2111
|
for (const dir of pathVar.split(sep2)) {
|
|
1718
2112
|
if (!dir) continue;
|
|
1719
|
-
const candidate =
|
|
2113
|
+
const candidate = join11(dir, bin);
|
|
1720
2114
|
if (isExecutable(candidate)) return candidate;
|
|
1721
2115
|
}
|
|
1722
2116
|
return null;
|
|
1723
2117
|
}
|
|
1724
2118
|
function fromFvm(home) {
|
|
1725
|
-
const fvmDefault =
|
|
2119
|
+
const fvmDefault = join11(home, "fvm", "default", "bin", "flutter");
|
|
1726
2120
|
if (isExecutable(fvmDefault)) return fvmDefault;
|
|
1727
|
-
const versionsDir =
|
|
1728
|
-
if (!
|
|
2121
|
+
const versionsDir = join11(home, "fvm", "versions");
|
|
2122
|
+
if (!existsSync9(versionsDir)) return null;
|
|
1729
2123
|
let best = null;
|
|
1730
2124
|
try {
|
|
1731
2125
|
for (const name of readdirSync3(versionsDir)) {
|
|
1732
|
-
const candidate =
|
|
2126
|
+
const candidate = join11(versionsDir, name, "bin", "flutter");
|
|
1733
2127
|
if (!isExecutable(candidate)) continue;
|
|
1734
2128
|
if (!best || compareSemver(name, best.version) > 0) {
|
|
1735
2129
|
best = { version: name, path: candidate };
|
|
@@ -1752,10 +2146,10 @@ function compareSemver(a, b) {
|
|
|
1752
2146
|
}
|
|
1753
2147
|
function findFlutterBin(options = {}) {
|
|
1754
2148
|
const env = options.env ?? process.env;
|
|
1755
|
-
const home = options.home ??
|
|
2149
|
+
const home = options.home ?? homedir2();
|
|
1756
2150
|
const envRoot = env.FLUTTER_ROOT;
|
|
1757
2151
|
if (envRoot) {
|
|
1758
|
-
const bin =
|
|
2152
|
+
const bin = join11(envRoot, "bin", "flutter");
|
|
1759
2153
|
if (isExecutable(bin)) return bin;
|
|
1760
2154
|
}
|
|
1761
2155
|
const fromPathBin = fromPath(env);
|
|
@@ -2398,9 +2792,9 @@ function infoCommand(name, cwd, options = {}) {
|
|
|
2398
2792
|
}
|
|
2399
2793
|
|
|
2400
2794
|
// src/commands/theme-apply.ts
|
|
2401
|
-
import { readFileSync as
|
|
2402
|
-
import { resolve as
|
|
2403
|
-
import { generateTheme, generateThemeData 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";
|
|
2797
|
+
import { generateTheme, generateThemeData as generateThemeData3 } from "@loworbitstudio/visor-theme-engine";
|
|
2404
2798
|
import {
|
|
2405
2799
|
nextjsAdapter as nextjsAdapter2,
|
|
2406
2800
|
fumadocsAdapter,
|
|
@@ -2429,10 +2823,10 @@ function defaultOutputPath(adapter, themeName) {
|
|
|
2429
2823
|
}
|
|
2430
2824
|
}
|
|
2431
2825
|
function themeApplyCommand(file, cwd, options) {
|
|
2432
|
-
const filePath =
|
|
2826
|
+
const filePath = resolve5(cwd, file);
|
|
2433
2827
|
let yamlContent;
|
|
2434
2828
|
try {
|
|
2435
|
-
yamlContent =
|
|
2829
|
+
yamlContent = readFileSync12(filePath, "utf-8");
|
|
2436
2830
|
} catch {
|
|
2437
2831
|
if (options.json) {
|
|
2438
2832
|
console.log(
|
|
@@ -2453,7 +2847,7 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
2453
2847
|
let sections;
|
|
2454
2848
|
try {
|
|
2455
2849
|
if (options.adapter) {
|
|
2456
|
-
const data =
|
|
2850
|
+
const data = generateThemeData3(yamlContent);
|
|
2457
2851
|
themeName = data.config.name;
|
|
2458
2852
|
const adapterInput = {
|
|
2459
2853
|
primitives: data.primitives,
|
|
@@ -2511,15 +2905,15 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
2511
2905
|
process.exit(1);
|
|
2512
2906
|
}
|
|
2513
2907
|
const outputTarget = options.output ?? defaultOutputPath(options.adapter, themeName);
|
|
2514
|
-
const outputPath =
|
|
2908
|
+
const outputPath = resolve5(cwd, outputTarget);
|
|
2515
2909
|
if (fileMap) {
|
|
2516
2910
|
try {
|
|
2517
|
-
|
|
2911
|
+
mkdirSync4(outputPath, { recursive: true });
|
|
2518
2912
|
let totalBytes = 0;
|
|
2519
2913
|
for (const [relPath, content] of Object.entries(fileMap.files)) {
|
|
2520
|
-
const filePath2 =
|
|
2521
|
-
|
|
2522
|
-
|
|
2914
|
+
const filePath2 = join12(outputPath, relPath);
|
|
2915
|
+
mkdirSync4(dirname6(filePath2), { recursive: true });
|
|
2916
|
+
writeFileSync6(filePath2, content, "utf-8");
|
|
2523
2917
|
totalBytes += content.length;
|
|
2524
2918
|
}
|
|
2525
2919
|
if (options.json) {
|
|
@@ -2556,10 +2950,10 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
2556
2950
|
if (css === null) {
|
|
2557
2951
|
process.exit(1);
|
|
2558
2952
|
}
|
|
2559
|
-
const outputDir =
|
|
2953
|
+
const outputDir = dirname6(outputPath);
|
|
2560
2954
|
try {
|
|
2561
|
-
|
|
2562
|
-
|
|
2955
|
+
mkdirSync4(outputDir, { recursive: true });
|
|
2956
|
+
writeFileSync6(outputPath, css, "utf-8");
|
|
2563
2957
|
} catch {
|
|
2564
2958
|
if (options.json) {
|
|
2565
2959
|
console.log(
|
|
@@ -2600,8 +2994,8 @@ function formatSize(bytes) {
|
|
|
2600
2994
|
}
|
|
2601
2995
|
|
|
2602
2996
|
// src/commands/theme-export.ts
|
|
2603
|
-
import { readFileSync as
|
|
2604
|
-
import { resolve as
|
|
2997
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
2998
|
+
import { resolve as resolve6 } from "path";
|
|
2605
2999
|
import {
|
|
2606
3000
|
parseConfig,
|
|
2607
3001
|
resolveConfig,
|
|
@@ -2609,10 +3003,10 @@ import {
|
|
|
2609
3003
|
exportTheme
|
|
2610
3004
|
} from "@loworbitstudio/visor-theme-engine";
|
|
2611
3005
|
function themeExportCommand(file, cwd, options) {
|
|
2612
|
-
const filePath =
|
|
3006
|
+
const filePath = resolve6(cwd, file ?? ".visor.yaml");
|
|
2613
3007
|
let yamlContent;
|
|
2614
3008
|
try {
|
|
2615
|
-
yamlContent =
|
|
3009
|
+
yamlContent = readFileSync13(filePath, "utf-8");
|
|
2616
3010
|
} catch {
|
|
2617
3011
|
if (options.json) {
|
|
2618
3012
|
console.log(
|
|
@@ -2664,16 +3058,16 @@ function themeExportCommand(file, cwd, options) {
|
|
|
2664
3058
|
}
|
|
2665
3059
|
|
|
2666
3060
|
// src/commands/theme-validate.ts
|
|
2667
|
-
import { readFileSync as
|
|
2668
|
-
import { resolve as
|
|
2669
|
-
import { parse as
|
|
3061
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
3062
|
+
import { resolve as resolve7 } from "path";
|
|
3063
|
+
import { parse as parseYaml2 } from "yaml";
|
|
2670
3064
|
import { validate } from "@loworbitstudio/visor-theme-engine";
|
|
2671
3065
|
import pc3 from "picocolors";
|
|
2672
3066
|
function themeValidateCommand(file, cwd, options) {
|
|
2673
|
-
const filePath =
|
|
3067
|
+
const filePath = resolve7(cwd, file);
|
|
2674
3068
|
let fileContent;
|
|
2675
3069
|
try {
|
|
2676
|
-
fileContent =
|
|
3070
|
+
fileContent = readFileSync14(filePath, "utf-8");
|
|
2677
3071
|
} catch {
|
|
2678
3072
|
if (options.json) {
|
|
2679
3073
|
console.log(
|
|
@@ -2697,7 +3091,7 @@ function themeValidateCommand(file, cwd, options) {
|
|
|
2697
3091
|
}
|
|
2698
3092
|
let parsed;
|
|
2699
3093
|
try {
|
|
2700
|
-
parsed =
|
|
3094
|
+
parsed = parseYaml2(fileContent);
|
|
2701
3095
|
} catch (err) {
|
|
2702
3096
|
const message = err instanceof Error ? err.message : "Invalid YAML syntax";
|
|
2703
3097
|
if (options.json) {
|
|
@@ -2762,8 +3156,8 @@ function printIssue(issue) {
|
|
|
2762
3156
|
|
|
2763
3157
|
// src/commands/theme-verify.ts
|
|
2764
3158
|
import { spawnSync as _spawnSync } from "child_process";
|
|
2765
|
-
import { existsSync as
|
|
2766
|
-
import { resolve as
|
|
3159
|
+
import { existsSync as existsSync10 } from "fs";
|
|
3160
|
+
import { resolve as resolve8 } from "path";
|
|
2767
3161
|
function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
2768
3162
|
const target = options.target ?? "flutter";
|
|
2769
3163
|
if (target !== "flutter") {
|
|
@@ -2785,8 +3179,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
|
2785
3179
|
}
|
|
2786
3180
|
process.exit(1);
|
|
2787
3181
|
}
|
|
2788
|
-
const dirPath =
|
|
2789
|
-
if (!
|
|
3182
|
+
const dirPath = resolve8(cwd, dir);
|
|
3183
|
+
if (!existsSync10(dirPath)) {
|
|
2790
3184
|
if (options.json) {
|
|
2791
3185
|
console.log(
|
|
2792
3186
|
JSON.stringify({
|
|
@@ -2873,8 +3267,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
|
2873
3267
|
}
|
|
2874
3268
|
|
|
2875
3269
|
// src/commands/theme-extract.ts
|
|
2876
|
-
import { readFileSync as
|
|
2877
|
-
import { resolve 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";
|
|
2878
3272
|
import { stringify as stringifyYaml } from "yaml";
|
|
2879
3273
|
import {
|
|
2880
3274
|
extractFromCSS,
|
|
@@ -2903,8 +3297,8 @@ var CSS_DIRS = [
|
|
|
2903
3297
|
"packages/design-tokens"
|
|
2904
3298
|
];
|
|
2905
3299
|
function themeExtractCommand(cwd, options) {
|
|
2906
|
-
const targetDir =
|
|
2907
|
-
if (!
|
|
3300
|
+
const targetDir = resolve9(cwd, options.from ?? ".");
|
|
3301
|
+
if (!existsSync11(targetDir)) {
|
|
2908
3302
|
if (options.json) {
|
|
2909
3303
|
console.log(JSON.stringify({ success: false, error: `Directory not found: ${targetDir}` }));
|
|
2910
3304
|
} else {
|
|
@@ -2958,16 +3352,16 @@ function collectCSSFiles(targetDir) {
|
|
|
2958
3352
|
const files = [];
|
|
2959
3353
|
const seen = /* @__PURE__ */ new Set();
|
|
2960
3354
|
for (const pattern2 of CSS_FILE_PATTERNS) {
|
|
2961
|
-
const rootPath =
|
|
3355
|
+
const rootPath = join13(targetDir, pattern2);
|
|
2962
3356
|
addFileIfExists(rootPath, files, seen);
|
|
2963
3357
|
for (const dir of CSS_DIRS) {
|
|
2964
|
-
const dirPath =
|
|
3358
|
+
const dirPath = join13(targetDir, dir, pattern2);
|
|
2965
3359
|
addFileIfExists(dirPath, files, seen);
|
|
2966
3360
|
}
|
|
2967
3361
|
}
|
|
2968
3362
|
for (const dir of CSS_DIRS) {
|
|
2969
|
-
const dirPath =
|
|
2970
|
-
if (
|
|
3363
|
+
const dirPath = join13(targetDir, dir);
|
|
3364
|
+
if (existsSync11(dirPath) && statSync5(dirPath).isDirectory()) {
|
|
2971
3365
|
scanDirForCSS(dirPath, files, seen, 2);
|
|
2972
3366
|
}
|
|
2973
3367
|
}
|
|
@@ -2975,11 +3369,11 @@ function collectCSSFiles(targetDir) {
|
|
|
2975
3369
|
return files;
|
|
2976
3370
|
}
|
|
2977
3371
|
function addFileIfExists(filePath, files, seen) {
|
|
2978
|
-
const resolved =
|
|
3372
|
+
const resolved = resolve9(filePath);
|
|
2979
3373
|
if (seen.has(resolved)) return;
|
|
2980
|
-
if (!
|
|
3374
|
+
if (!existsSync11(resolved)) return;
|
|
2981
3375
|
try {
|
|
2982
|
-
const content =
|
|
3376
|
+
const content = readFileSync15(resolved, "utf-8");
|
|
2983
3377
|
if (content.includes("--")) {
|
|
2984
3378
|
files.push({ path: resolved, content });
|
|
2985
3379
|
seen.add(resolved);
|
|
@@ -2988,7 +3382,7 @@ function addFileIfExists(filePath, files, seen) {
|
|
|
2988
3382
|
}
|
|
2989
3383
|
}
|
|
2990
3384
|
function scanDirForCSS(dir, files, seen, maxDepth) {
|
|
2991
|
-
if (!
|
|
3385
|
+
if (!existsSync11(dir)) return;
|
|
2992
3386
|
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2993
3387
|
"node_modules",
|
|
2994
3388
|
".next",
|
|
@@ -3007,10 +3401,10 @@ function scanDirForCSS(dir, files, seen, maxDepth) {
|
|
|
3007
3401
|
if (entry.isDirectory()) {
|
|
3008
3402
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
3009
3403
|
if (maxDepth > 0) {
|
|
3010
|
-
scanDirForCSS(
|
|
3404
|
+
scanDirForCSS(join13(dir, entry.name), files, seen, maxDepth - 1);
|
|
3011
3405
|
}
|
|
3012
3406
|
} else if (entry.isFile() && extname3(entry.name) === ".css") {
|
|
3013
|
-
addFileIfExists(
|
|
3407
|
+
addFileIfExists(join13(dir, entry.name), files, seen);
|
|
3014
3408
|
}
|
|
3015
3409
|
}
|
|
3016
3410
|
} catch {
|
|
@@ -3092,10 +3486,10 @@ function extractVarName(varExpr) {
|
|
|
3092
3486
|
function parseNextFontFromLayouts(targetDir) {
|
|
3093
3487
|
const fontMap = /* @__PURE__ */ new Map();
|
|
3094
3488
|
for (const relPath of LAYOUT_FILE_PATHS) {
|
|
3095
|
-
const fullPath =
|
|
3096
|
-
if (!
|
|
3489
|
+
const fullPath = join13(targetDir, relPath);
|
|
3490
|
+
if (!existsSync11(fullPath)) continue;
|
|
3097
3491
|
try {
|
|
3098
|
-
const content =
|
|
3492
|
+
const content = readFileSync15(fullPath, "utf-8");
|
|
3099
3493
|
parseNextFontDeclarations(content, fontMap);
|
|
3100
3494
|
} catch {
|
|
3101
3495
|
}
|
|
@@ -3142,7 +3536,7 @@ function parseNextFontDeclarations(content, fontMap) {
|
|
|
3142
3536
|
const srcMatch = block.match(/src\s*:\s*["']([^"']+)["']/);
|
|
3143
3537
|
if (srcMatch) {
|
|
3144
3538
|
const srcPath = srcMatch[1];
|
|
3145
|
-
const fileName =
|
|
3539
|
+
const fileName = basename3(srcPath, extname3(srcPath));
|
|
3146
3540
|
const fontBaseName = fileName.replace(/[-_](Variable|Regular|Bold|Light|Medium|SemiBold|ExtraBold|Thin|Black|Italic).*$/i, "").replace(/[-_]/g, " ").trim();
|
|
3147
3541
|
if (fontBaseName) {
|
|
3148
3542
|
fontMap.set(varName, fontBaseName);
|
|
@@ -3180,10 +3574,10 @@ var MONO_FONT_NAMES = /* @__PURE__ */ new Set([
|
|
|
3180
3574
|
"IBM Plex Mono"
|
|
3181
3575
|
]);
|
|
3182
3576
|
function extractFontHints(targetDir) {
|
|
3183
|
-
const pkgPath =
|
|
3184
|
-
if (!
|
|
3577
|
+
const pkgPath = join13(targetDir, "package.json");
|
|
3578
|
+
if (!existsSync11(pkgPath)) return void 0;
|
|
3185
3579
|
try {
|
|
3186
|
-
const pkg2 = JSON.parse(
|
|
3580
|
+
const pkg2 = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
3187
3581
|
const allDeps = { ...pkg2.dependencies, ...pkg2.devDependencies };
|
|
3188
3582
|
const fonts2 = [];
|
|
3189
3583
|
for (const [dep, _] of Object.entries(allDeps)) {
|
|
@@ -3219,10 +3613,10 @@ function extractFontHints(targetDir) {
|
|
|
3219
3613
|
}
|
|
3220
3614
|
}
|
|
3221
3615
|
function inferThemeName(targetDir) {
|
|
3222
|
-
const pkgPath =
|
|
3223
|
-
if (
|
|
3616
|
+
const pkgPath = join13(targetDir, "package.json");
|
|
3617
|
+
if (existsSync11(pkgPath)) {
|
|
3224
3618
|
try {
|
|
3225
|
-
const pkg2 = JSON.parse(
|
|
3619
|
+
const pkg2 = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
3226
3620
|
if (pkg2.name) {
|
|
3227
3621
|
const name = pkg2.name.replace(/^@[\w-]+\//, "");
|
|
3228
3622
|
return `${name}-theme`;
|
|
@@ -3230,7 +3624,7 @@ function inferThemeName(targetDir) {
|
|
|
3230
3624
|
} catch {
|
|
3231
3625
|
}
|
|
3232
3626
|
}
|
|
3233
|
-
return `${
|
|
3627
|
+
return `${basename3(targetDir)}-theme`;
|
|
3234
3628
|
}
|
|
3235
3629
|
function confidenceComment(confidence) {
|
|
3236
3630
|
return `# confidence: ${confidence}`;
|
|
@@ -3259,7 +3653,7 @@ function outputJSON(result, validationResult) {
|
|
|
3259
3653
|
}
|
|
3260
3654
|
function outputYAML(result, outputPath, cwd, validationResult) {
|
|
3261
3655
|
const yamlStr = buildAnnotatedYAML(result);
|
|
3262
|
-
const outFile =
|
|
3656
|
+
const outFile = resolve9(cwd, outputPath ?? ".visor.yaml");
|
|
3263
3657
|
const high = result.tokens.filter((t) => t.confidence === "high").length;
|
|
3264
3658
|
const med = result.tokens.filter((t) => t.confidence === "medium").length;
|
|
3265
3659
|
const low = result.tokens.filter((t) => t.confidence === "low").length;
|
|
@@ -3287,7 +3681,7 @@ function outputYAML(result, outputPath, cwd, validationResult) {
|
|
|
3287
3681
|
}
|
|
3288
3682
|
logger.blank();
|
|
3289
3683
|
}
|
|
3290
|
-
|
|
3684
|
+
writeFileSync7(outFile, yamlStr, "utf-8");
|
|
3291
3685
|
logger.success(`Theme written to ${relative(cwd, outFile)}`);
|
|
3292
3686
|
if (validationResult) {
|
|
3293
3687
|
logger.blank();
|
|
@@ -3345,14 +3739,14 @@ function buildAnnotatedYAML(result) {
|
|
|
3345
3739
|
}
|
|
3346
3740
|
|
|
3347
3741
|
// src/commands/theme-register.ts
|
|
3348
|
-
import { readFileSync as
|
|
3349
|
-
import { resolve as
|
|
3350
|
-
import { generateThemeData 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";
|
|
3744
|
+
import { generateThemeData as generateThemeData4 } from "@loworbitstudio/visor-theme-engine";
|
|
3351
3745
|
import { docsAdapter as docsAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
3352
3746
|
|
|
3353
3747
|
// src/utils/theme-helpers.ts
|
|
3354
|
-
import { existsSync as
|
|
3355
|
-
import { resolve 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";
|
|
3356
3750
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3357
3751
|
function toSlug(name) {
|
|
3358
3752
|
return name.toLowerCase().replace(/\s+/g, "-");
|
|
@@ -3361,12 +3755,12 @@ function toLabel(name) {
|
|
|
3361
3755
|
return name.split(/[\s-]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
3362
3756
|
}
|
|
3363
3757
|
function findRepoRoot(startDir) {
|
|
3364
|
-
let current =
|
|
3758
|
+
let current = resolve10(startDir);
|
|
3365
3759
|
while (true) {
|
|
3366
|
-
if (
|
|
3760
|
+
if (existsSync12(join14(current, "packages", "docs"))) {
|
|
3367
3761
|
return current;
|
|
3368
3762
|
}
|
|
3369
|
-
const parent =
|
|
3763
|
+
const parent = dirname7(current);
|
|
3370
3764
|
if (parent === current) break;
|
|
3371
3765
|
current = parent;
|
|
3372
3766
|
}
|
|
@@ -3380,9 +3774,9 @@ function findMainRepoRoot(startDir) {
|
|
|
3380
3774
|
stdio: ["ignore", "pipe", "ignore"]
|
|
3381
3775
|
}).trim();
|
|
3382
3776
|
if (commonDir) {
|
|
3383
|
-
const absoluteCommonDir =
|
|
3384
|
-
const candidate =
|
|
3385
|
-
if (
|
|
3777
|
+
const absoluteCommonDir = resolve10(startDir, commonDir);
|
|
3778
|
+
const candidate = dirname7(absoluteCommonDir);
|
|
3779
|
+
if (existsSync12(join14(candidate, "packages", "docs"))) {
|
|
3386
3780
|
return candidate;
|
|
3387
3781
|
}
|
|
3388
3782
|
}
|
|
@@ -3401,10 +3795,10 @@ var BrokenSymlinkError = class extends Error {
|
|
|
3401
3795
|
code = "BROKEN_SYMLINK";
|
|
3402
3796
|
};
|
|
3403
3797
|
function assertNoBrokenSymlinks(dir) {
|
|
3404
|
-
if (!
|
|
3798
|
+
if (!existsSync12(dir)) return;
|
|
3405
3799
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
3406
3800
|
for (const entry of entries) {
|
|
3407
|
-
const entryPath =
|
|
3801
|
+
const entryPath = join14(dir, entry.name);
|
|
3408
3802
|
let lst;
|
|
3409
3803
|
try {
|
|
3410
3804
|
lst = lstatSync(entryPath);
|
|
@@ -3422,39 +3816,39 @@ function assertNoBrokenSymlinks(dir) {
|
|
|
3422
3816
|
}
|
|
3423
3817
|
}
|
|
3424
3818
|
function scanParentForPrivateThemes(parentDir) {
|
|
3425
|
-
if (!
|
|
3819
|
+
if (!existsSync12(parentDir)) return [];
|
|
3426
3820
|
const entries = readdirSync5(parentDir, { withFileTypes: true });
|
|
3427
3821
|
const matches = [];
|
|
3428
3822
|
for (const entry of entries) {
|
|
3429
3823
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
3430
|
-
const candidate =
|
|
3431
|
-
if (
|
|
3824
|
+
const candidate = join14(parentDir, entry.name, "visor-themes-private", "themes");
|
|
3825
|
+
if (existsSync12(candidate)) {
|
|
3432
3826
|
matches.push(candidate);
|
|
3433
3827
|
}
|
|
3434
3828
|
}
|
|
3435
3829
|
return matches.sort();
|
|
3436
3830
|
}
|
|
3437
3831
|
function scanNestedThemeDir(dir) {
|
|
3438
|
-
if (!
|
|
3832
|
+
if (!existsSync12(dir)) return [];
|
|
3439
3833
|
assertNoBrokenSymlinks(dir);
|
|
3440
3834
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
3441
3835
|
const out = [];
|
|
3442
3836
|
for (const entry of entries) {
|
|
3443
3837
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
3444
|
-
const themeFile =
|
|
3445
|
-
if (
|
|
3838
|
+
const themeFile = join14(dir, entry.name, "theme.visor.yaml");
|
|
3839
|
+
if (existsSync12(themeFile)) {
|
|
3446
3840
|
out.push({ filePath: themeFile, slug: entry.name });
|
|
3447
3841
|
}
|
|
3448
3842
|
}
|
|
3449
3843
|
return out;
|
|
3450
3844
|
}
|
|
3451
3845
|
function detectVisorWorkspace(cwd) {
|
|
3452
|
-
let current =
|
|
3846
|
+
let current = resolve10(cwd);
|
|
3453
3847
|
while (true) {
|
|
3454
|
-
const pkgPath =
|
|
3455
|
-
if (
|
|
3848
|
+
const pkgPath = join14(current, "package.json");
|
|
3849
|
+
if (existsSync12(pkgPath)) {
|
|
3456
3850
|
try {
|
|
3457
|
-
const pkg2 = JSON.parse(
|
|
3851
|
+
const pkg2 = JSON.parse(readFileSync16(pkgPath, "utf-8"));
|
|
3458
3852
|
const workspaces = pkg2.workspaces ?? [];
|
|
3459
3853
|
const hasCli = workspaces.some((w) => w.includes("packages/cli"));
|
|
3460
3854
|
const hasEngine = workspaces.some((w) => w.includes("packages/theme-engine"));
|
|
@@ -3464,7 +3858,7 @@ function detectVisorWorkspace(cwd) {
|
|
|
3464
3858
|
} catch {
|
|
3465
3859
|
}
|
|
3466
3860
|
}
|
|
3467
|
-
const parent =
|
|
3861
|
+
const parent = dirname7(current);
|
|
3468
3862
|
if (parent === current) break;
|
|
3469
3863
|
current = parent;
|
|
3470
3864
|
}
|
|
@@ -3472,18 +3866,18 @@ function detectVisorWorkspace(cwd) {
|
|
|
3472
3866
|
}
|
|
3473
3867
|
function isLocalVisorBinary(workspaceRoot, scriptPath) {
|
|
3474
3868
|
if (!scriptPath) return false;
|
|
3475
|
-
const expectedPrefix =
|
|
3869
|
+
const expectedPrefix = join14(workspaceRoot, "packages", "cli", "dist");
|
|
3476
3870
|
let resolvedScript;
|
|
3477
3871
|
let resolvedPrefix;
|
|
3478
3872
|
try {
|
|
3479
3873
|
resolvedScript = realpathSync(scriptPath);
|
|
3480
3874
|
} catch {
|
|
3481
|
-
resolvedScript =
|
|
3875
|
+
resolvedScript = resolve10(scriptPath);
|
|
3482
3876
|
}
|
|
3483
3877
|
try {
|
|
3484
3878
|
resolvedPrefix = realpathSync(expectedPrefix);
|
|
3485
3879
|
} catch {
|
|
3486
|
-
resolvedPrefix =
|
|
3880
|
+
resolvedPrefix = resolve10(expectedPrefix);
|
|
3487
3881
|
}
|
|
3488
3882
|
return resolvedScript.startsWith(resolvedPrefix + "/") || resolvedScript === resolvedPrefix;
|
|
3489
3883
|
}
|
|
@@ -3573,10 +3967,10 @@ ${indent}${newEntry},
|
|
|
3573
3967
|
return { updated, changed: true };
|
|
3574
3968
|
}
|
|
3575
3969
|
function themeRegisterCommand(file, cwd, options) {
|
|
3576
|
-
const filePath =
|
|
3970
|
+
const filePath = resolve11(cwd, file);
|
|
3577
3971
|
let yamlContent;
|
|
3578
3972
|
try {
|
|
3579
|
-
yamlContent =
|
|
3973
|
+
yamlContent = readFileSync17(filePath, "utf-8");
|
|
3580
3974
|
} catch {
|
|
3581
3975
|
if (options.json) {
|
|
3582
3976
|
console.log(JSON.stringify({ success: false, error: `Could not read file: ${filePath}` }));
|
|
@@ -3588,7 +3982,7 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3588
3982
|
}
|
|
3589
3983
|
let data;
|
|
3590
3984
|
try {
|
|
3591
|
-
data =
|
|
3985
|
+
data = generateThemeData4(yamlContent);
|
|
3592
3986
|
} catch (err) {
|
|
3593
3987
|
const message = err instanceof Error ? err.message : "Unknown error parsing theme";
|
|
3594
3988
|
if (options.json) {
|
|
@@ -3619,11 +4013,11 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3619
4013
|
process.exit(1);
|
|
3620
4014
|
return;
|
|
3621
4015
|
}
|
|
3622
|
-
const docsAppDir =
|
|
3623
|
-
const cssFilePath =
|
|
3624
|
-
const globalsPath =
|
|
3625
|
-
const themeConfigPath =
|
|
3626
|
-
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)) {
|
|
3627
4021
|
const msg = `Docs app directory not found: ${docsAppDir}`;
|
|
3628
4022
|
if (options.json) {
|
|
3629
4023
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
@@ -3636,8 +4030,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3636
4030
|
let globalsContent = "";
|
|
3637
4031
|
let themeConfigContent = "";
|
|
3638
4032
|
try {
|
|
3639
|
-
globalsContent =
|
|
3640
|
-
themeConfigContent =
|
|
4033
|
+
globalsContent = readFileSync17(globalsPath, "utf-8");
|
|
4034
|
+
themeConfigContent = readFileSync17(themeConfigPath, "utf-8");
|
|
3641
4035
|
} catch (err) {
|
|
3642
4036
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
3643
4037
|
if (options.json) {
|
|
@@ -3648,8 +4042,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3648
4042
|
process.exit(1);
|
|
3649
4043
|
return;
|
|
3650
4044
|
}
|
|
3651
|
-
const cssExists =
|
|
3652
|
-
const cssChanged = !cssExists ||
|
|
4045
|
+
const cssExists = existsSync13(cssFilePath);
|
|
4046
|
+
const cssChanged = !cssExists || readFileSync17(cssFilePath, "utf-8") !== css;
|
|
3653
4047
|
const { updated: newGlobals, changed: globalsChanged } = insertGlobalsImport(globalsContent, slug2);
|
|
3654
4048
|
const { updated: newThemeConfig, changed: themeConfigChanged, error: configError } = insertThemeConfig(
|
|
3655
4049
|
themeConfigContent,
|
|
@@ -3692,14 +4086,14 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3692
4086
|
}
|
|
3693
4087
|
try {
|
|
3694
4088
|
if (cssChanged) {
|
|
3695
|
-
|
|
3696
|
-
|
|
4089
|
+
mkdirSync5(docsAppDir, { recursive: true });
|
|
4090
|
+
writeFileSync8(cssFilePath, css, "utf-8");
|
|
3697
4091
|
}
|
|
3698
4092
|
if (globalsChanged) {
|
|
3699
|
-
|
|
4093
|
+
writeFileSync8(globalsPath, newGlobals, "utf-8");
|
|
3700
4094
|
}
|
|
3701
4095
|
if (themeConfigChanged) {
|
|
3702
|
-
|
|
4096
|
+
writeFileSync8(themeConfigPath, newThemeConfig, "utf-8");
|
|
3703
4097
|
}
|
|
3704
4098
|
} catch (err) {
|
|
3705
4099
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
@@ -3733,8 +4127,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
3733
4127
|
}
|
|
3734
4128
|
|
|
3735
4129
|
// src/commands/theme-unregister.ts
|
|
3736
|
-
import { readFileSync as
|
|
3737
|
-
import { join as
|
|
4130
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync9, existsSync as existsSync14, unlinkSync } from "fs";
|
|
4131
|
+
import { join as join16 } from "path";
|
|
3738
4132
|
function removeGlobalsImport(content, slug2) {
|
|
3739
4133
|
const importLine = `@import './${slug2}-theme.css';`;
|
|
3740
4134
|
if (!content.includes(importLine)) {
|
|
@@ -3766,11 +4160,11 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3766
4160
|
process.exit(1);
|
|
3767
4161
|
return;
|
|
3768
4162
|
}
|
|
3769
|
-
const docsAppDir =
|
|
3770
|
-
const cssFilePath =
|
|
3771
|
-
const globalsPath =
|
|
3772
|
-
const themeConfigPath =
|
|
3773
|
-
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)) {
|
|
3774
4168
|
const msg = `Docs app directory not found: ${docsAppDir}`;
|
|
3775
4169
|
if (options.json) {
|
|
3776
4170
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
@@ -3783,8 +4177,8 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3783
4177
|
let globalsContent = "";
|
|
3784
4178
|
let themeConfigContent = "";
|
|
3785
4179
|
try {
|
|
3786
|
-
globalsContent =
|
|
3787
|
-
themeConfigContent =
|
|
4180
|
+
globalsContent = readFileSync18(globalsPath, "utf-8");
|
|
4181
|
+
themeConfigContent = readFileSync18(themeConfigPath, "utf-8");
|
|
3788
4182
|
} catch (err) {
|
|
3789
4183
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
3790
4184
|
if (options.json) {
|
|
@@ -3795,7 +4189,7 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3795
4189
|
process.exit(1);
|
|
3796
4190
|
return;
|
|
3797
4191
|
}
|
|
3798
|
-
const cssExists =
|
|
4192
|
+
const cssExists = existsSync14(cssFilePath);
|
|
3799
4193
|
const { updated: newGlobals, changed: globalsChanged } = removeGlobalsImport(globalsContent, slug2);
|
|
3800
4194
|
const { updated: newThemeConfig, changed: themeConfigChanged } = removeThemeConfigEntry(themeConfigContent, slug2);
|
|
3801
4195
|
if (!cssExists && !globalsChanged && !themeConfigChanged) {
|
|
@@ -3808,8 +4202,8 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3808
4202
|
}
|
|
3809
4203
|
try {
|
|
3810
4204
|
if (cssExists) unlinkSync(cssFilePath);
|
|
3811
|
-
if (globalsChanged)
|
|
3812
|
-
if (themeConfigChanged)
|
|
4205
|
+
if (globalsChanged) writeFileSync9(globalsPath, newGlobals, "utf-8");
|
|
4206
|
+
if (themeConfigChanged) writeFileSync9(themeConfigPath, newThemeConfig, "utf-8");
|
|
3813
4207
|
} catch (err) {
|
|
3814
4208
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
3815
4209
|
if (options.json) {
|
|
@@ -3836,17 +4230,17 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
3836
4230
|
|
|
3837
4231
|
// src/commands/theme-sync.ts
|
|
3838
4232
|
import {
|
|
3839
|
-
readFileSync as
|
|
3840
|
-
writeFileSync as
|
|
3841
|
-
mkdirSync as
|
|
3842
|
-
existsSync as
|
|
4233
|
+
readFileSync as readFileSync19,
|
|
4234
|
+
writeFileSync as writeFileSync10,
|
|
4235
|
+
mkdirSync as mkdirSync6,
|
|
4236
|
+
existsSync as existsSync15,
|
|
3843
4237
|
readdirSync as readdirSync6,
|
|
3844
4238
|
unlinkSync as unlinkSync2,
|
|
3845
4239
|
copyFileSync
|
|
3846
4240
|
} from "fs";
|
|
3847
|
-
import { join as
|
|
3848
|
-
import { parse as
|
|
3849
|
-
import { generateThemeData as
|
|
4241
|
+
import { join as join17, basename as basename4, resolve as resolve12, sep, relative as relative2 } from "path";
|
|
4242
|
+
import { parse as parseYaml3 } from "yaml";
|
|
4243
|
+
import { generateThemeData as generateThemeData5, validateFontCoverage, formatFontCoverageError } from "@loworbitstudio/visor-theme-engine";
|
|
3850
4244
|
import { docsAdapter as docsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
3851
4245
|
var PRIVATE_THEMES_REPO_URL = "git@github.com:low-orbit-studio/visor-themes-private.git";
|
|
3852
4246
|
var PRIVATE_THEMES_ENV_VAR = "VISOR_THEMES_PRIVATE_PATH";
|
|
@@ -3858,9 +4252,9 @@ var CUSTOM_OVERLAY_CSS_PATH = "packages/docs/app/custom-themes.generated.css";
|
|
|
3858
4252
|
var CUSTOM_OVERLAY_TS_PATH = "packages/docs/lib/theme-config.custom.generated.ts";
|
|
3859
4253
|
var CUSTOM_OVERLAY_IMPORT_LINE = "@import './custom-themes.generated.css';";
|
|
3860
4254
|
function scanThemeDir(dir) {
|
|
3861
|
-
if (!
|
|
4255
|
+
if (!existsSync15(dir)) return [];
|
|
3862
4256
|
assertNoBrokenSymlinks(dir);
|
|
3863
|
-
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));
|
|
3864
4258
|
}
|
|
3865
4259
|
function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
3866
4260
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -3884,20 +4278,20 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
|
3884
4278
|
};
|
|
3885
4279
|
const envPath = process.env[PRIVATE_THEMES_ENV_VAR];
|
|
3886
4280
|
if (envPath && envPath.trim() !== "") {
|
|
3887
|
-
const resolved =
|
|
3888
|
-
if (!
|
|
4281
|
+
const resolved = resolve12(envPath);
|
|
4282
|
+
if (!existsSync15(resolved)) {
|
|
3889
4283
|
throw new Error(
|
|
3890
4284
|
`${PRIVATE_THEMES_ENV_VAR} is set to "${envPath}" but the path does not exist. Expected a directory containing {slug}/theme.visor.yaml entries.`
|
|
3891
4285
|
);
|
|
3892
4286
|
}
|
|
3893
4287
|
addNested(resolved, "env");
|
|
3894
4288
|
}
|
|
3895
|
-
const siblingPath =
|
|
3896
|
-
const siblingExists =
|
|
4289
|
+
const siblingPath = join17(mainRepoRoot, "..", "visor-themes-private", "themes");
|
|
4290
|
+
const siblingExists = existsSync15(siblingPath);
|
|
3897
4291
|
if (siblingExists) {
|
|
3898
4292
|
addNested(siblingPath, "sibling");
|
|
3899
4293
|
}
|
|
3900
|
-
const parentDir =
|
|
4294
|
+
const parentDir = join17(mainRepoRoot, "..");
|
|
3901
4295
|
const parentGlobMatches = scanParentForPrivateThemes(parentDir).filter(
|
|
3902
4296
|
(path2) => !path2.startsWith(mainRepoRoot + sep)
|
|
3903
4297
|
);
|
|
@@ -3918,10 +4312,10 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
|
3918
4312
|
}
|
|
3919
4313
|
}
|
|
3920
4314
|
}
|
|
3921
|
-
const legacyDir =
|
|
4315
|
+
const legacyDir = join17(repoRoot, "custom-themes");
|
|
3922
4316
|
const legacyFiles = scanThemeDir(legacyDir);
|
|
3923
4317
|
for (const legacyFile of legacyFiles) {
|
|
3924
|
-
const slug2 =
|
|
4318
|
+
const slug2 = basename4(legacyFile).replace(/\.visor\.yaml$/, "");
|
|
3925
4319
|
const existing = merged.get(slug2);
|
|
3926
4320
|
if (existing) {
|
|
3927
4321
|
warn(
|
|
@@ -3975,14 +4369,14 @@ function reportBrokenSymlink(err, options) {
|
|
|
3975
4369
|
}
|
|
3976
4370
|
}
|
|
3977
4371
|
function buildEmptySourcesMessage(mainRepoRoot) {
|
|
3978
|
-
const expectedSibling =
|
|
4372
|
+
const expectedSibling = join17(mainRepoRoot, "..", "visor-themes-private");
|
|
3979
4373
|
return [
|
|
3980
4374
|
"No theme sources discovered. Cannot proceed \u2014 refusing to wipe generated CSS.",
|
|
3981
4375
|
"",
|
|
3982
4376
|
"Resolution order checked:",
|
|
3983
4377
|
` 1. Env var ${PRIVATE_THEMES_ENV_VAR} (unset or empty)`,
|
|
3984
4378
|
` 2. Sibling checkout at ${expectedSibling}/themes/ (not found)`,
|
|
3985
|
-
` 3. One-level-deeper at ${
|
|
4379
|
+
` 3. One-level-deeper at ${join17(mainRepoRoot, "..")}/<parent>/visor-themes-private/themes/ (not found)`,
|
|
3986
4380
|
" 4. Legacy custom-themes/ (no .visor.yaml files)",
|
|
3987
4381
|
"",
|
|
3988
4382
|
"To fix, clone the private themes repo as a sibling:",
|
|
@@ -4008,17 +4402,17 @@ function enforceWorkspaceGuard(cwd) {
|
|
|
4008
4402
|
].join("\n");
|
|
4009
4403
|
}
|
|
4010
4404
|
function extractGroup(yamlContent) {
|
|
4011
|
-
const parsed =
|
|
4405
|
+
const parsed = parseYaml3(yamlContent);
|
|
4012
4406
|
if (typeof parsed?.group === "string") return parsed.group;
|
|
4013
4407
|
return void 0;
|
|
4014
4408
|
}
|
|
4015
4409
|
function extractLabel(yamlContent) {
|
|
4016
|
-
const parsed =
|
|
4410
|
+
const parsed = parseYaml3(yamlContent);
|
|
4017
4411
|
if (typeof parsed?.label === "string") return parsed.label;
|
|
4018
4412
|
return void 0;
|
|
4019
4413
|
}
|
|
4020
4414
|
function extractDefaultMode(yamlContent) {
|
|
4021
|
-
const parsed =
|
|
4415
|
+
const parsed = parseYaml3(yamlContent);
|
|
4022
4416
|
const v = parsed?.["default-mode"];
|
|
4023
4417
|
if (v === "dark" || v === "light") return v;
|
|
4024
4418
|
return void 0;
|
|
@@ -4187,14 +4581,14 @@ function themeSyncCommand(cwd, options) {
|
|
|
4187
4581
|
return;
|
|
4188
4582
|
}
|
|
4189
4583
|
const mainRepoRoot = findMainRepoRoot(cwd) ?? repoRoot;
|
|
4190
|
-
const themesDir =
|
|
4191
|
-
const docsAppDir =
|
|
4192
|
-
const docsLibDir =
|
|
4193
|
-
const docsPublicThemesDir =
|
|
4194
|
-
const themeConfigPath =
|
|
4195
|
-
const globalsPath =
|
|
4196
|
-
const customOverlayCssPath =
|
|
4197
|
-
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);
|
|
4198
4592
|
let stockFiles;
|
|
4199
4593
|
try {
|
|
4200
4594
|
stockFiles = scanThemeDir(themesDir);
|
|
@@ -4251,18 +4645,18 @@ function themeSyncCommand(cwd, options) {
|
|
|
4251
4645
|
const processFile = (filePath, isCustom, slugOverride) => {
|
|
4252
4646
|
let yamlContent;
|
|
4253
4647
|
try {
|
|
4254
|
-
yamlContent =
|
|
4648
|
+
yamlContent = readFileSync19(filePath, "utf-8");
|
|
4255
4649
|
} catch {
|
|
4256
4650
|
failures.push({ filePath, error: `Could not read: ${filePath}` });
|
|
4257
4651
|
return;
|
|
4258
4652
|
}
|
|
4259
4653
|
let data;
|
|
4260
4654
|
try {
|
|
4261
|
-
data =
|
|
4655
|
+
data = generateThemeData5(yamlContent);
|
|
4262
4656
|
} catch (err) {
|
|
4263
4657
|
failures.push({
|
|
4264
4658
|
filePath,
|
|
4265
|
-
error: `Failed to parse ${
|
|
4659
|
+
error: `Failed to parse ${basename4(filePath)}: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
4266
4660
|
});
|
|
4267
4661
|
return;
|
|
4268
4662
|
}
|
|
@@ -4276,12 +4670,12 @@ function themeSyncCommand(cwd, options) {
|
|
|
4276
4670
|
for (const e of coverage.errors) {
|
|
4277
4671
|
failures.push({
|
|
4278
4672
|
filePath,
|
|
4279
|
-
error: formatFontCoverageError(
|
|
4673
|
+
error: formatFontCoverageError(basename4(filePath), e.declaredAt, e.family)
|
|
4280
4674
|
});
|
|
4281
4675
|
}
|
|
4282
4676
|
return;
|
|
4283
4677
|
}
|
|
4284
|
-
const yamlFilename = slugOverride ??
|
|
4678
|
+
const yamlFilename = slugOverride ?? basename4(filePath).replace(/\.visor\.yaml$/, "");
|
|
4285
4679
|
manifest.push({ slug: slug2, label, group, defaultMode, css, yamlFilename, isCustom });
|
|
4286
4680
|
};
|
|
4287
4681
|
for (const f of stockFiles) processFile(f, false);
|
|
@@ -4301,8 +4695,8 @@ function themeSyncCommand(cwd, options) {
|
|
|
4301
4695
|
let globalsContent;
|
|
4302
4696
|
let themeConfigContent;
|
|
4303
4697
|
try {
|
|
4304
|
-
globalsContent =
|
|
4305
|
-
themeConfigContent =
|
|
4698
|
+
globalsContent = readFileSync19(globalsPath, "utf-8");
|
|
4699
|
+
themeConfigContent = readFileSync19(themeConfigPath, "utf-8");
|
|
4306
4700
|
} catch (err) {
|
|
4307
4701
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
4308
4702
|
if (options.json) {
|
|
@@ -4317,12 +4711,12 @@ function themeSyncCommand(cwd, options) {
|
|
|
4317
4711
|
const newThemeConfig = updateStockThemeConfigBlock(themeConfigContent, stockManifest);
|
|
4318
4712
|
const newCustomOverlayCss = generateCustomOverlayCss(customManifest);
|
|
4319
4713
|
const newCustomOverlayTs = generateCustomOverlayTs(customManifest);
|
|
4320
|
-
const existingCssFiles =
|
|
4714
|
+
const existingCssFiles = existsSync15(docsAppDir) ? readdirSync6(docsAppDir).filter(
|
|
4321
4715
|
(f) => f.endsWith("-theme.css") && f !== "custom-themes.generated.css"
|
|
4322
4716
|
) : [];
|
|
4323
4717
|
const newCssSet = new Set(allSlugs.map((s) => `${s}-theme.css`));
|
|
4324
4718
|
const staleCssFiles = existingCssFiles.filter((f) => !newCssSet.has(f));
|
|
4325
|
-
const existingPublicYamls =
|
|
4719
|
+
const existingPublicYamls = existsSync15(docsPublicThemesDir) ? readdirSync6(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
|
|
4326
4720
|
const newPublicYamlSet = new Set(manifest.map((e) => `${e.yamlFilename}.visor.yaml`));
|
|
4327
4721
|
const stalePublicYamls = existingPublicYamls.filter((f) => !newPublicYamlSet.has(f));
|
|
4328
4722
|
if (options.dryRun) {
|
|
@@ -4362,28 +4756,28 @@ function themeSyncCommand(cwd, options) {
|
|
|
4362
4756
|
return;
|
|
4363
4757
|
}
|
|
4364
4758
|
try {
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4759
|
+
mkdirSync6(docsAppDir, { recursive: true });
|
|
4760
|
+
mkdirSync6(docsLibDir, { recursive: true });
|
|
4761
|
+
mkdirSync6(docsPublicThemesDir, { recursive: true });
|
|
4368
4762
|
for (const entry of manifest) {
|
|
4369
|
-
|
|
4763
|
+
writeFileSync10(join17(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
|
|
4370
4764
|
}
|
|
4371
4765
|
for (const stale of staleCssFiles) {
|
|
4372
|
-
unlinkSync2(
|
|
4766
|
+
unlinkSync2(join17(docsAppDir, stale));
|
|
4373
4767
|
}
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4768
|
+
writeFileSync10(customOverlayCssPath, newCustomOverlayCss, "utf-8");
|
|
4769
|
+
writeFileSync10(customOverlayTsPath, newCustomOverlayTs, "utf-8");
|
|
4770
|
+
writeFileSync10(themeConfigPath, newThemeConfig, "utf-8");
|
|
4771
|
+
writeFileSync10(globalsPath, newGlobals, "utf-8");
|
|
4378
4772
|
for (const srcFile of stockFiles) {
|
|
4379
|
-
copyFileSync(srcFile,
|
|
4773
|
+
copyFileSync(srcFile, join17(docsPublicThemesDir, basename4(srcFile)));
|
|
4380
4774
|
}
|
|
4381
4775
|
for (const c of customSources) {
|
|
4382
|
-
const targetName = c.origin === "legacy" ?
|
|
4383
|
-
copyFileSync(c.filePath,
|
|
4776
|
+
const targetName = c.origin === "legacy" ? basename4(c.filePath) : `${c.slug}.visor.yaml`;
|
|
4777
|
+
copyFileSync(c.filePath, join17(docsPublicThemesDir, targetName));
|
|
4384
4778
|
}
|
|
4385
4779
|
for (const stale of stalePublicYamls) {
|
|
4386
|
-
unlinkSync2(
|
|
4780
|
+
unlinkSync2(join17(docsPublicThemesDir, stale));
|
|
4387
4781
|
}
|
|
4388
4782
|
} catch (err) {
|
|
4389
4783
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
@@ -4436,19 +4830,19 @@ function themeSyncCommand(cwd, options) {
|
|
|
4436
4830
|
|
|
4437
4831
|
// src/commands/theme-batch-apply-flutter.ts
|
|
4438
4832
|
import {
|
|
4439
|
-
readFileSync as
|
|
4440
|
-
writeFileSync as
|
|
4441
|
-
mkdirSync as
|
|
4442
|
-
existsSync as
|
|
4833
|
+
readFileSync as readFileSync20,
|
|
4834
|
+
writeFileSync as writeFileSync11,
|
|
4835
|
+
mkdirSync as mkdirSync7,
|
|
4836
|
+
existsSync as existsSync16,
|
|
4443
4837
|
readdirSync as readdirSync7,
|
|
4444
4838
|
rmSync
|
|
4445
4839
|
} from "fs";
|
|
4446
|
-
import { join as
|
|
4447
|
-
import { generateThemeData as
|
|
4840
|
+
import { join as join18, basename as basename5, dirname as dirname8 } from "path";
|
|
4841
|
+
import { generateThemeData as generateThemeData6 } from "@loworbitstudio/visor-theme-engine";
|
|
4448
4842
|
import { flutterAdapter as flutterAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
4449
4843
|
function scanThemeDir2(dir) {
|
|
4450
|
-
if (!
|
|
4451
|
-
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();
|
|
4452
4846
|
}
|
|
4453
4847
|
function slugToCamel(slug2) {
|
|
4454
4848
|
return slug2.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
@@ -4581,9 +4975,9 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4581
4975
|
process.exit(1);
|
|
4582
4976
|
return;
|
|
4583
4977
|
}
|
|
4584
|
-
const themesDir =
|
|
4585
|
-
const customThemesDir =
|
|
4586
|
-
const outputDir =
|
|
4978
|
+
const themesDir = join18(repoRoot, "themes");
|
|
4979
|
+
const customThemesDir = join18(repoRoot, "custom-themes");
|
|
4980
|
+
const outputDir = join18(repoRoot, "packages", "visor_themes");
|
|
4587
4981
|
const stockFiles = scanThemeDir2(themesDir);
|
|
4588
4982
|
const customFiles = scanThemeDir2(customThemesDir);
|
|
4589
4983
|
const allFiles = [...stockFiles, ...customFiles];
|
|
@@ -4604,17 +4998,17 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4604
4998
|
for (const filePath of allFiles) {
|
|
4605
4999
|
let yamlContent;
|
|
4606
5000
|
try {
|
|
4607
|
-
yamlContent =
|
|
5001
|
+
yamlContent = readFileSync20(filePath, "utf-8");
|
|
4608
5002
|
} catch {
|
|
4609
5003
|
errors.push(`Could not read: ${filePath}`);
|
|
4610
5004
|
continue;
|
|
4611
5005
|
}
|
|
4612
5006
|
let data;
|
|
4613
5007
|
try {
|
|
4614
|
-
data =
|
|
5008
|
+
data = generateThemeData6(yamlContent);
|
|
4615
5009
|
} catch (err) {
|
|
4616
5010
|
errors.push(
|
|
4617
|
-
`Failed to parse ${
|
|
5011
|
+
`Failed to parse ${basename5(filePath)}: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
4618
5012
|
);
|
|
4619
5013
|
continue;
|
|
4620
5014
|
}
|
|
@@ -4683,8 +5077,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4683
5077
|
return;
|
|
4684
5078
|
}
|
|
4685
5079
|
const slugs = processed.map((p) => p.slug);
|
|
4686
|
-
const libSrcDir =
|
|
4687
|
-
if (
|
|
5080
|
+
const libSrcDir = join18(outputDir, "lib", "src");
|
|
5081
|
+
if (existsSync16(libSrcDir)) {
|
|
4688
5082
|
rmSync(libSrcDir, { recursive: true, force: true });
|
|
4689
5083
|
}
|
|
4690
5084
|
const packageFiles = {
|
|
@@ -4696,17 +5090,17 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4696
5090
|
};
|
|
4697
5091
|
let totalFiles = 0;
|
|
4698
5092
|
for (const [relPath, content] of Object.entries(packageFiles)) {
|
|
4699
|
-
const absPath =
|
|
4700
|
-
|
|
4701
|
-
|
|
5093
|
+
const absPath = join18(outputDir, relPath);
|
|
5094
|
+
mkdirSync7(dirname8(absPath), { recursive: true });
|
|
5095
|
+
writeFileSync11(absPath, content, "utf-8");
|
|
4702
5096
|
totalFiles++;
|
|
4703
5097
|
}
|
|
4704
5098
|
for (const { slug: slug2, tokenFiles } of processed) {
|
|
4705
|
-
const themeBaseDir =
|
|
5099
|
+
const themeBaseDir = join18(outputDir, "lib", "src", slug2);
|
|
4706
5100
|
for (const [relPath, content] of Object.entries(tokenFiles)) {
|
|
4707
|
-
const absPath =
|
|
4708
|
-
|
|
4709
|
-
|
|
5101
|
+
const absPath = join18(themeBaseDir, relPath);
|
|
5102
|
+
mkdirSync7(dirname8(absPath), { recursive: true });
|
|
5103
|
+
writeFileSync11(absPath, content, "utf-8");
|
|
4710
5104
|
totalFiles++;
|
|
4711
5105
|
}
|
|
4712
5106
|
}
|
|
@@ -4727,11 +5121,11 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4727
5121
|
}
|
|
4728
5122
|
|
|
4729
5123
|
// src/commands/fonts-add.ts
|
|
4730
|
-
import { existsSync as
|
|
4731
|
-
import { resolve 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";
|
|
4732
5126
|
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
4733
5127
|
function deriveFamilySlug(filename) {
|
|
4734
|
-
const name =
|
|
5128
|
+
const name = basename6(filename, extname4(filename));
|
|
4735
5129
|
const WEIGHT_STYLE_SUFFIXES = /* @__PURE__ */ new Set([
|
|
4736
5130
|
"thin",
|
|
4737
5131
|
"hairline",
|
|
@@ -4772,21 +5166,21 @@ function deriveFamilySlug(filename) {
|
|
|
4772
5166
|
return parts.join("-").toLowerCase();
|
|
4773
5167
|
}
|
|
4774
5168
|
function collectWoff2Files(inputPath) {
|
|
4775
|
-
const resolved =
|
|
4776
|
-
if (!
|
|
5169
|
+
const resolved = resolve13(inputPath);
|
|
5170
|
+
if (!existsSync17(resolved)) {
|
|
4777
5171
|
throw new Error(`Path not found: ${resolved}`);
|
|
4778
5172
|
}
|
|
4779
5173
|
const stat = statSync7(resolved);
|
|
4780
5174
|
if (stat.isFile()) {
|
|
4781
5175
|
if (extname4(resolved).toLowerCase() !== ".woff2") {
|
|
4782
5176
|
throw new Error(
|
|
4783
|
-
`Invalid file format: ${
|
|
5177
|
+
`Invalid file format: ${basename6(resolved)}. Only .woff2 files are accepted.`
|
|
4784
5178
|
);
|
|
4785
5179
|
}
|
|
4786
5180
|
return [resolved];
|
|
4787
5181
|
}
|
|
4788
5182
|
if (stat.isDirectory()) {
|
|
4789
|
-
const files = readdirSync8(resolved).filter((f) => extname4(f).toLowerCase() === ".woff2").map((f) =>
|
|
5183
|
+
const files = readdirSync8(resolved).filter((f) => extname4(f).toLowerCase() === ".woff2").map((f) => resolve13(resolved, f));
|
|
4790
5184
|
if (files.length === 0) {
|
|
4791
5185
|
throw new Error(
|
|
4792
5186
|
`No .woff2 files found in directory: ${resolved}`
|
|
@@ -4801,8 +5195,8 @@ function collectWoff2Files(inputPath) {
|
|
|
4801
5195
|
throw new Error(`Path is neither a file nor a directory: ${resolved}`);
|
|
4802
5196
|
}
|
|
4803
5197
|
function getNonWoff2Fonts(inputPath) {
|
|
4804
|
-
const resolved =
|
|
4805
|
-
if (!
|
|
5198
|
+
const resolved = resolve13(inputPath);
|
|
5199
|
+
if (!existsSync17(resolved) || !statSync7(resolved).isDirectory()) {
|
|
4806
5200
|
return [];
|
|
4807
5201
|
}
|
|
4808
5202
|
return readdirSync8(resolved).filter((f) => {
|
|
@@ -4839,7 +5233,7 @@ function createR2Client(config) {
|
|
|
4839
5233
|
});
|
|
4840
5234
|
}
|
|
4841
5235
|
async function uploadFile(client, bucket, key, filePath) {
|
|
4842
|
-
const body =
|
|
5236
|
+
const body = readFileSync21(filePath);
|
|
4843
5237
|
await client.send(
|
|
4844
5238
|
new PutObjectCommand({
|
|
4845
5239
|
Bucket: bucket,
|
|
@@ -4854,8 +5248,8 @@ async function fontsAddCommand(inputPath, options) {
|
|
|
4854
5248
|
try {
|
|
4855
5249
|
const r2Config = getR2Config();
|
|
4856
5250
|
const files = collectWoff2Files(inputPath);
|
|
4857
|
-
const familySlug = options.family ?? deriveFamilySlug(
|
|
4858
|
-
const resolved =
|
|
5251
|
+
const familySlug = options.family ?? deriveFamilySlug(basename6(files[0]));
|
|
5252
|
+
const resolved = resolve13(inputPath);
|
|
4859
5253
|
const nonWoff2 = statSync7(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
|
|
4860
5254
|
if (!json) {
|
|
4861
5255
|
logger.heading("Visor Font Upload");
|
|
@@ -4874,7 +5268,7 @@ async function fontsAddCommand(inputPath, options) {
|
|
|
4874
5268
|
const bucket = "visor-fonts";
|
|
4875
5269
|
const results = [];
|
|
4876
5270
|
for (const filePath of files) {
|
|
4877
|
-
const filename =
|
|
5271
|
+
const filename = basename6(filePath);
|
|
4878
5272
|
const key = buildS3Key(org, familySlug, filename);
|
|
4879
5273
|
if (!json) {
|
|
4880
5274
|
logger.info(`Uploading ${filename}...`);
|
|
@@ -5143,27 +5537,27 @@ function findCssFiles(dir, maxDepth = 3) {
|
|
|
5143
5537
|
}
|
|
5144
5538
|
|
|
5145
5539
|
// src/utils/patterns.ts
|
|
5146
|
-
import { existsSync as
|
|
5147
|
-
import { join as
|
|
5540
|
+
import { existsSync as existsSync19, readdirSync as readdirSync10, readFileSync as readFileSync23 } from "fs";
|
|
5541
|
+
import { join as join20 } from "path";
|
|
5148
5542
|
import { parse as parseYAML } from "yaml";
|
|
5149
5543
|
function loadPatternsFromYaml(repoRoot) {
|
|
5150
|
-
const patternsDir =
|
|
5151
|
-
if (!
|
|
5544
|
+
const patternsDir = join20(repoRoot, "patterns");
|
|
5545
|
+
if (!existsSync19(patternsDir)) return [];
|
|
5152
5546
|
const files = readdirSync10(patternsDir).filter(
|
|
5153
5547
|
(f) => f.endsWith(".visor-pattern.yaml")
|
|
5154
5548
|
);
|
|
5155
5549
|
return files.map((file) => {
|
|
5156
|
-
const content =
|
|
5550
|
+
const content = readFileSync23(join20(patternsDir, file), "utf-8");
|
|
5157
5551
|
return parseYAML(content);
|
|
5158
5552
|
}).filter(Boolean);
|
|
5159
5553
|
}
|
|
5160
5554
|
function findRepoRoot2(startDir) {
|
|
5161
5555
|
let current = startDir;
|
|
5162
5556
|
while (true) {
|
|
5163
|
-
if (
|
|
5557
|
+
if (existsSync19(join20(current, "patterns"))) {
|
|
5164
5558
|
return current;
|
|
5165
5559
|
}
|
|
5166
|
-
const parent =
|
|
5560
|
+
const parent = join20(current, "..");
|
|
5167
5561
|
if (parent === current) return null;
|
|
5168
5562
|
current = parent;
|
|
5169
5563
|
}
|
|
@@ -5542,9 +5936,9 @@ Visor Tokens (${categoryLabel}) \u2014 ${tokens2.length} tokens
|
|
|
5542
5936
|
}
|
|
5543
5937
|
|
|
5544
5938
|
// src/commands/migrate-token-substitution.ts
|
|
5545
|
-
import { readFileSync as
|
|
5546
|
-
import { resolve as
|
|
5547
|
-
import { parse 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";
|
|
5941
|
+
import { parse as parseYaml4 } from "yaml";
|
|
5548
5942
|
import pc4 from "picocolors";
|
|
5549
5943
|
var V7_ENTR_SUBSTITUTION_MAP = {
|
|
5550
5944
|
"--panel": "--surface-card",
|
|
@@ -5566,15 +5960,15 @@ var BUILT_IN_SUBSTITUTION_MAPS = {
|
|
|
5566
5960
|
var DEFAULT_THEME_ID = "entr";
|
|
5567
5961
|
function readMapFromThemeFile(themeId, cwd) {
|
|
5568
5962
|
const candidates = [
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
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`)
|
|
5572
5966
|
];
|
|
5573
5967
|
for (const candidate of candidates) {
|
|
5574
|
-
if (
|
|
5968
|
+
if (existsSync20(candidate)) {
|
|
5575
5969
|
try {
|
|
5576
|
-
const raw =
|
|
5577
|
-
const parsed =
|
|
5970
|
+
const raw = readFileSync24(candidate, "utf-8");
|
|
5971
|
+
const parsed = parseYaml4(raw);
|
|
5578
5972
|
if (parsed?.migrate?.["token-substitution"]) {
|
|
5579
5973
|
return parsed.migrate["token-substitution"];
|
|
5580
5974
|
}
|
|
@@ -5629,7 +6023,7 @@ function collectCssFiles(dirPath) {
|
|
|
5629
6023
|
function walk2(current) {
|
|
5630
6024
|
const entries = readdirSync11(current, { withFileTypes: true });
|
|
5631
6025
|
for (const entry of entries) {
|
|
5632
|
-
const fullPath =
|
|
6026
|
+
const fullPath = join21(current, entry.name);
|
|
5633
6027
|
if (entry.isDirectory()) {
|
|
5634
6028
|
if (["node_modules", ".git", ".next", "dist", "build", ".cache"].includes(entry.name)) {
|
|
5635
6029
|
continue;
|
|
@@ -5657,7 +6051,7 @@ function runSubstitutionPass(targetPath, map, themeId) {
|
|
|
5657
6051
|
const fileResults = [];
|
|
5658
6052
|
let totalSubstitutions = 0;
|
|
5659
6053
|
for (const file of files) {
|
|
5660
|
-
const content =
|
|
6054
|
+
const content = readFileSync24(file, "utf-8");
|
|
5661
6055
|
const { newContent, substitutions } = applySubstitutionsToContent(content, map);
|
|
5662
6056
|
if (substitutions.length > 0) {
|
|
5663
6057
|
fileResults.push({ file, substitutions, originalContent: content, newContent });
|
|
@@ -5691,7 +6085,7 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
5691
6085
|
process.exit(1);
|
|
5692
6086
|
return;
|
|
5693
6087
|
}
|
|
5694
|
-
const targetPath =
|
|
6088
|
+
const targetPath = resolve14(cwd, targetArg ?? ".");
|
|
5695
6089
|
try {
|
|
5696
6090
|
statSync8(targetPath);
|
|
5697
6091
|
} catch {
|
|
@@ -5719,7 +6113,7 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
5719
6113
|
if (options.json) {
|
|
5720
6114
|
if (apply) {
|
|
5721
6115
|
for (const f of result.files) {
|
|
5722
|
-
|
|
6116
|
+
writeFileSync12(f.file, f.newContent, "utf-8");
|
|
5723
6117
|
}
|
|
5724
6118
|
console.log(JSON.stringify({
|
|
5725
6119
|
success: true,
|
|
@@ -5759,7 +6153,7 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
5759
6153
|
logger.warn(`Dry run \u2014 no files written. Re-run with ${pc4.bold("--apply")} to commit changes.`);
|
|
5760
6154
|
} else {
|
|
5761
6155
|
for (const f of result.files) {
|
|
5762
|
-
|
|
6156
|
+
writeFileSync12(f.file, f.newContent, "utf-8");
|
|
5763
6157
|
}
|
|
5764
6158
|
logger.heading(`visor migrate token-substitution \u2014 applied`);
|
|
5765
6159
|
logger.blank();
|
|
@@ -5780,23 +6174,23 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
5780
6174
|
}
|
|
5781
6175
|
|
|
5782
6176
|
// src/commands/sandbox/init.ts
|
|
5783
|
-
import { existsSync as
|
|
5784
|
-
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";
|
|
5785
6179
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5786
|
-
import * as
|
|
6180
|
+
import * as childProcess3 from "child_process";
|
|
5787
6181
|
|
|
5788
6182
|
// src/commands/sandbox/parse-handoff.ts
|
|
5789
|
-
import { readFileSync as
|
|
5790
|
-
import { dirname as
|
|
6183
|
+
import { readFileSync as readFileSync25, existsSync as existsSync21 } from "fs";
|
|
6184
|
+
import { dirname as dirname9, resolve as resolve15, isAbsolute } from "path";
|
|
5791
6185
|
function parseHandoff(handoffPath) {
|
|
5792
|
-
const text =
|
|
6186
|
+
const text = readFileSync25(handoffPath, "utf-8");
|
|
5793
6187
|
const lines2 = text.split("\n");
|
|
5794
6188
|
const warnings = [];
|
|
5795
6189
|
const pattern2 = extractPatternSlug(lines2, warnings);
|
|
5796
6190
|
const theme2 = extractMetaField(lines2, "Theme");
|
|
5797
6191
|
const recipePath = extractRecipePath(text, handoffPath, warnings);
|
|
5798
6192
|
const primitives = extractPrimitives(text, warnings);
|
|
5799
|
-
const { screens, mockShapes } = recipePath &&
|
|
6193
|
+
const { screens, mockShapes } = recipePath && existsSync21(recipePath) ? extractRecipeArtifacts(recipePath, warnings) : { screens: [], mockShapes: [] };
|
|
5800
6194
|
return { pattern: pattern2, theme: theme2, recipePath, primitives, screens, mockShapes, warnings };
|
|
5801
6195
|
}
|
|
5802
6196
|
function extractPatternSlug(lines2, warnings) {
|
|
@@ -5828,7 +6222,7 @@ function extractRecipePath(text, handoffPath, warnings) {
|
|
|
5828
6222
|
}
|
|
5829
6223
|
const href = m[2].trim();
|
|
5830
6224
|
if (isAbsolute(href)) return href;
|
|
5831
|
-
return
|
|
6225
|
+
return resolve15(dirname9(handoffPath), href);
|
|
5832
6226
|
}
|
|
5833
6227
|
var STATUS_PATTERNS = [
|
|
5834
6228
|
{ re: /NEW\s+gap/i, status: "gap-new" },
|
|
@@ -5875,7 +6269,7 @@ function extractPrimitives(text, warnings) {
|
|
|
5875
6269
|
return primitives;
|
|
5876
6270
|
}
|
|
5877
6271
|
function extractRecipeArtifacts(recipePath, warnings) {
|
|
5878
|
-
const text =
|
|
6272
|
+
const text = readFileSync25(recipePath, "utf-8");
|
|
5879
6273
|
const screens = extractScreens(text);
|
|
5880
6274
|
const mockShapes = extractMockFields(text, warnings);
|
|
5881
6275
|
return { screens, mockShapes };
|
|
@@ -5949,8 +6343,8 @@ function tryPort(port) {
|
|
|
5949
6343
|
}
|
|
5950
6344
|
|
|
5951
6345
|
// src/commands/sandbox/scaffold.ts
|
|
5952
|
-
import { mkdirSync as
|
|
5953
|
-
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";
|
|
5954
6348
|
|
|
5955
6349
|
// src/commands/sandbox/templates.ts
|
|
5956
6350
|
var SANDBOX_PACKAGE_VERSION = "16.2.6";
|
|
@@ -6450,12 +6844,12 @@ function toPascalCase(s) {
|
|
|
6450
6844
|
|
|
6451
6845
|
// src/commands/sandbox/scaffold.ts
|
|
6452
6846
|
function writeScaffold(sandboxDir, manifest, port, options = {}) {
|
|
6453
|
-
|
|
6847
|
+
mkdirSync8(sandboxDir, { recursive: true });
|
|
6454
6848
|
const created = [];
|
|
6455
6849
|
const writeIfNew = (rel, contents) => {
|
|
6456
|
-
const full =
|
|
6457
|
-
|
|
6458
|
-
|
|
6850
|
+
const full = join22(sandboxDir, rel);
|
|
6851
|
+
mkdirSync8(dirOf(full), { recursive: true });
|
|
6852
|
+
writeFileSync13(full, contents, "utf-8");
|
|
6459
6853
|
created.push(rel);
|
|
6460
6854
|
};
|
|
6461
6855
|
writeIfNew("package.json", packageJsonTemplate(manifest.pattern));
|
|
@@ -6481,7 +6875,7 @@ function writeScaffold(sandboxDir, manifest, port, options = {}) {
|
|
|
6481
6875
|
}
|
|
6482
6876
|
}
|
|
6483
6877
|
writeIfNew("playwright.capture.mjs", captureScriptTemplate());
|
|
6484
|
-
|
|
6878
|
+
mkdirSync8(join22(sandboxDir, "captures", "approved"), { recursive: true });
|
|
6485
6879
|
return { created };
|
|
6486
6880
|
}
|
|
6487
6881
|
function dirOf(p) {
|
|
@@ -6515,10 +6909,10 @@ function writeSandboxConfig(sandboxDir, manifest, port, options) {
|
|
|
6515
6909
|
stripChromeSelectors: options.prototypeImport.stripChromeSelectors
|
|
6516
6910
|
} : null
|
|
6517
6911
|
};
|
|
6518
|
-
|
|
6912
|
+
writeFileSync13(join22(sandboxDir, "sandbox.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
6519
6913
|
}
|
|
6520
6914
|
function sandboxIsEmpty(sandboxDir) {
|
|
6521
|
-
if (!
|
|
6915
|
+
if (!existsSync22(sandboxDir)) return true;
|
|
6522
6916
|
try {
|
|
6523
6917
|
return readdirSync12(sandboxDir).filter((f) => !f.startsWith(".")).length === 0;
|
|
6524
6918
|
} catch {
|
|
@@ -6527,8 +6921,8 @@ function sandboxIsEmpty(sandboxDir) {
|
|
|
6527
6921
|
}
|
|
6528
6922
|
|
|
6529
6923
|
// src/commands/sandbox/html-prototype.ts
|
|
6530
|
-
import { mkdirSync as
|
|
6531
|
-
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";
|
|
6532
6926
|
|
|
6533
6927
|
// src/commands/sandbox/strip-chrome.ts
|
|
6534
6928
|
var DEFAULT_STRIP_SELECTORS = [
|
|
@@ -6712,8 +7106,8 @@ function stripDocumentaryChrome(html, selectors) {
|
|
|
6712
7106
|
var SCREEN_FILE_PATTERN = /^screen-(\d+)-([^/.]+)\.html$/i;
|
|
6713
7107
|
function copyHtmlPrototype(sourceDir, sandboxDir, manifest, options = {}) {
|
|
6714
7108
|
const warnings = [];
|
|
6715
|
-
const destAbs =
|
|
6716
|
-
|
|
7109
|
+
const destAbs = join23(sandboxDir, "public", "prototype");
|
|
7110
|
+
mkdirSync9(destAbs, { recursive: true });
|
|
6717
7111
|
const stripChromeSelectors = options.stripChromeSelectors ?? [];
|
|
6718
7112
|
const copiedFiles = copyTreeRelative(sourceDir, destAbs, "", stripChromeSelectors);
|
|
6719
7113
|
const screenFiles = listOrderedScreenFiles(sourceDir);
|
|
@@ -6766,24 +7160,24 @@ function deriveStateCoverageScreen(file, existingNames) {
|
|
|
6766
7160
|
}
|
|
6767
7161
|
function copyTreeRelative(srcDir, destDir, relDir = "", stripChromeSelectors = []) {
|
|
6768
7162
|
const out = [];
|
|
6769
|
-
const entries = readdirSync13(
|
|
7163
|
+
const entries = readdirSync13(join23(srcDir, relDir));
|
|
6770
7164
|
for (const name of entries) {
|
|
6771
7165
|
if (name.startsWith(".")) continue;
|
|
6772
|
-
const srcPath =
|
|
6773
|
-
const destPath =
|
|
7166
|
+
const srcPath = join23(srcDir, relDir, name);
|
|
7167
|
+
const destPath = join23(destDir, relDir, name);
|
|
6774
7168
|
const stat = statSync9(srcPath);
|
|
6775
7169
|
if (stat.isDirectory()) {
|
|
6776
|
-
|
|
6777
|
-
out.push(...copyTreeRelative(srcDir, destDir,
|
|
7170
|
+
mkdirSync9(destPath, { recursive: true });
|
|
7171
|
+
out.push(...copyTreeRelative(srcDir, destDir, join23(relDir, name), stripChromeSelectors));
|
|
6778
7172
|
} else if (stat.isFile()) {
|
|
6779
7173
|
if (stripChromeSelectors.length > 0 && name.toLowerCase().endsWith(".html")) {
|
|
6780
|
-
const html =
|
|
7174
|
+
const html = readFileSync26(srcPath, "utf-8");
|
|
6781
7175
|
const stripped = stripDocumentaryChrome(html, stripChromeSelectors);
|
|
6782
|
-
|
|
7176
|
+
writeFileSync14(destPath, stripped);
|
|
6783
7177
|
} else {
|
|
6784
|
-
|
|
7178
|
+
writeFileSync14(destPath, readFileSync26(srcPath));
|
|
6785
7179
|
}
|
|
6786
|
-
out.push(
|
|
7180
|
+
out.push(join23("public", "prototype", relDir, name).replace(/\\/g, "/"));
|
|
6787
7181
|
}
|
|
6788
7182
|
}
|
|
6789
7183
|
return out;
|
|
@@ -6835,8 +7229,8 @@ async function runInit(name, cwd, options) {
|
|
|
6835
7229
|
if (!name || !/^[a-z0-9][a-z0-9-_]*$/i.test(name)) {
|
|
6836
7230
|
throw new Error(`Invalid sandbox name '${name}'. Use letters, digits, '-' or '_'.`);
|
|
6837
7231
|
}
|
|
6838
|
-
const handoffPath = isAbsolute2(options.handoff) ? options.handoff :
|
|
6839
|
-
if (!
|
|
7232
|
+
const handoffPath = isAbsolute2(options.handoff) ? options.handoff : resolve16(cwd, options.handoff);
|
|
7233
|
+
if (!existsSync23(handoffPath)) {
|
|
6840
7234
|
throw new Error(`Handoff manifest not found: ${handoffPath}`);
|
|
6841
7235
|
}
|
|
6842
7236
|
const manifest = parseHandoff(handoffPath);
|
|
@@ -6845,7 +7239,7 @@ async function runInit(name, cwd, options) {
|
|
|
6845
7239
|
`Handoff has no primitives \u2014 refusing to scaffold an empty sandbox. Check the manifest at ${handoffPath}`
|
|
6846
7240
|
);
|
|
6847
7241
|
}
|
|
6848
|
-
const sandboxDir =
|
|
7242
|
+
const sandboxDir = join24(cwd, ".lo", "sandbox", name);
|
|
6849
7243
|
if (!sandboxIsEmpty(sandboxDir)) {
|
|
6850
7244
|
if (!options.overwrite) {
|
|
6851
7245
|
throw new Error(
|
|
@@ -6854,12 +7248,12 @@ async function runInit(name, cwd, options) {
|
|
|
6854
7248
|
}
|
|
6855
7249
|
rmSync2(sandboxDir, { recursive: true, force: true });
|
|
6856
7250
|
}
|
|
6857
|
-
|
|
7251
|
+
mkdirSync10(sandboxDir, { recursive: true });
|
|
6858
7252
|
const port = await findOpenPort();
|
|
6859
7253
|
let prototypeImport;
|
|
6860
7254
|
if (options.fromHtmlPrototype) {
|
|
6861
|
-
const prototypeDir = isAbsolute2(options.fromHtmlPrototype) ? options.fromHtmlPrototype :
|
|
6862
|
-
if (!
|
|
7255
|
+
const prototypeDir = isAbsolute2(options.fromHtmlPrototype) ? options.fromHtmlPrototype : resolve16(cwd, options.fromHtmlPrototype);
|
|
7256
|
+
if (!existsSync23(prototypeDir)) {
|
|
6863
7257
|
throw new Error(`HTML prototype directory not found: ${prototypeDir}`);
|
|
6864
7258
|
}
|
|
6865
7259
|
const stripChromeSelectors = resolveStripSelectors(
|
|
@@ -6918,22 +7312,22 @@ async function runInit(name, cwd, options) {
|
|
|
6918
7312
|
function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile) {
|
|
6919
7313
|
const candidates = [];
|
|
6920
7314
|
if (themeFile) {
|
|
6921
|
-
candidates.push(isAbsolute2(themeFile) ? themeFile :
|
|
7315
|
+
candidates.push(isAbsolute2(themeFile) ? themeFile : resolve16(cwd, themeFile));
|
|
6922
7316
|
}
|
|
6923
|
-
candidates.push(isAbsolute2(theme2) ? theme2 :
|
|
7317
|
+
candidates.push(isAbsolute2(theme2) ? theme2 : resolve16(cwd, theme2));
|
|
6924
7318
|
const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
|
|
6925
7319
|
if (privateRoot && privateRoot.length > 0) {
|
|
6926
|
-
candidates.push(
|
|
7320
|
+
candidates.push(join24(privateRoot, "themes", theme2, "theme.visor.yaml"));
|
|
6927
7321
|
}
|
|
6928
7322
|
candidates.push(
|
|
6929
|
-
|
|
6930
|
-
|
|
7323
|
+
join24(cwd, "themes", `${theme2}.visor.yaml`),
|
|
7324
|
+
join24(cwd, "custom-themes", `${theme2}.visor.yaml`)
|
|
6931
7325
|
);
|
|
6932
|
-
const yamlPath = candidates.find((p) =>
|
|
7326
|
+
const yamlPath = candidates.find((p) => existsSync23(p));
|
|
6933
7327
|
if (!yamlPath) {
|
|
6934
7328
|
const searched = candidates.join(", ");
|
|
6935
7329
|
manifest.warnings.push(
|
|
6936
|
-
`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.`
|
|
6937
7331
|
);
|
|
6938
7332
|
if (!json) {
|
|
6939
7333
|
logger.warn(`Theme '${theme2}' not found \u2014 leaving placeholder globals.css.`);
|
|
@@ -6941,12 +7335,12 @@ function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile
|
|
|
6941
7335
|
`Re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH=<dir-containing-themes/${theme2}/theme.visor.yaml>.`
|
|
6942
7336
|
);
|
|
6943
7337
|
logger.item(
|
|
6944
|
-
`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")}`
|
|
6945
7339
|
);
|
|
6946
7340
|
}
|
|
6947
7341
|
return;
|
|
6948
7342
|
}
|
|
6949
|
-
const globalsOut =
|
|
7343
|
+
const globalsOut = join24(sandboxDir, "app", "globals.css");
|
|
6950
7344
|
try {
|
|
6951
7345
|
themeApplyCommand(yamlPath, sandboxDir, {
|
|
6952
7346
|
output: globalsOut,
|
|
@@ -6977,7 +7371,7 @@ function tryAddPrimitive(primitive, sandboxDir, json) {
|
|
|
6977
7371
|
}
|
|
6978
7372
|
function runNpmInstall(sandboxDir, json) {
|
|
6979
7373
|
if (!json) logger.info("Installing sandbox dependencies...");
|
|
6980
|
-
const result =
|
|
7374
|
+
const result = childProcess3.spawnSync("npm", ["install", "--no-audit", "--no-fund"], {
|
|
6981
7375
|
cwd: sandboxDir,
|
|
6982
7376
|
stdio: json ? "ignore" : "inherit"
|
|
6983
7377
|
});
|
|
@@ -6999,12 +7393,12 @@ function loadKnownPrimitives() {
|
|
|
6999
7393
|
}
|
|
7000
7394
|
function readCliVersion() {
|
|
7001
7395
|
try {
|
|
7002
|
-
const here =
|
|
7396
|
+
const here = dirname10(fileURLToPath3(import.meta.url));
|
|
7003
7397
|
for (let i = 0; i < 6; i++) {
|
|
7004
7398
|
const segments = new Array(i).fill("..");
|
|
7005
|
-
const candidate =
|
|
7399
|
+
const candidate = join24(here, ...segments, "package.json");
|
|
7006
7400
|
try {
|
|
7007
|
-
const pkg2 = JSON.parse(
|
|
7401
|
+
const pkg2 = JSON.parse(readFileSync27(candidate, "utf-8"));
|
|
7008
7402
|
if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) return pkg2.version;
|
|
7009
7403
|
} catch {
|
|
7010
7404
|
}
|
|
@@ -7015,14 +7409,14 @@ function readCliVersion() {
|
|
|
7015
7409
|
}
|
|
7016
7410
|
|
|
7017
7411
|
// src/commands/sandbox/dev.ts
|
|
7018
|
-
import { existsSync as
|
|
7019
|
-
import { join as
|
|
7020
|
-
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";
|
|
7021
7415
|
function sandboxDevCommand(cwd, options) {
|
|
7022
7416
|
const json = options.json ?? false;
|
|
7023
|
-
const sandboxDir =
|
|
7024
|
-
const configPath =
|
|
7025
|
-
if (!
|
|
7417
|
+
const sandboxDir = join25(cwd, ".lo", "sandbox", options.name);
|
|
7418
|
+
const configPath = join25(sandboxDir, "sandbox.json");
|
|
7419
|
+
if (!existsSync24(configPath)) {
|
|
7026
7420
|
fail(
|
|
7027
7421
|
json,
|
|
7028
7422
|
`Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init ${options.name} --handoff ... --theme ...' first.`
|
|
@@ -7031,7 +7425,7 @@ function sandboxDevCommand(cwd, options) {
|
|
|
7031
7425
|
}
|
|
7032
7426
|
let config;
|
|
7033
7427
|
try {
|
|
7034
|
-
config = JSON.parse(
|
|
7428
|
+
config = JSON.parse(readFileSync28(configPath, "utf-8"));
|
|
7035
7429
|
} catch (err) {
|
|
7036
7430
|
fail(json, `Invalid sandbox.json at ${configPath}: ${err.message}`);
|
|
7037
7431
|
return;
|
|
@@ -7058,7 +7452,7 @@ function sandboxDevCommand(cwd, options) {
|
|
|
7058
7452
|
spawnNextDev(sandboxDir, config.port, json);
|
|
7059
7453
|
}
|
|
7060
7454
|
function spawnNextDev(sandboxDir, port, json) {
|
|
7061
|
-
const child =
|
|
7455
|
+
const child = childProcess4.spawn(
|
|
7062
7456
|
"npx",
|
|
7063
7457
|
["--no-install", "next", "dev", "--port", String(port)],
|
|
7064
7458
|
{
|
|
@@ -7089,20 +7483,20 @@ function fail(json, message) {
|
|
|
7089
7483
|
// src/commands/sandbox/approve.ts
|
|
7090
7484
|
import {
|
|
7091
7485
|
copyFileSync as copyFileSync2,
|
|
7092
|
-
existsSync as
|
|
7093
|
-
mkdirSync as
|
|
7094
|
-
readFileSync as
|
|
7486
|
+
existsSync as existsSync25,
|
|
7487
|
+
mkdirSync as mkdirSync11,
|
|
7488
|
+
readFileSync as readFileSync29,
|
|
7095
7489
|
readdirSync as readdirSync14,
|
|
7096
7490
|
rmSync as rmSync3,
|
|
7097
|
-
writeFileSync as
|
|
7491
|
+
writeFileSync as writeFileSync15
|
|
7098
7492
|
} from "fs";
|
|
7099
|
-
import { basename as
|
|
7100
|
-
import * as
|
|
7493
|
+
import { basename as basename7, join as join26 } from "path";
|
|
7494
|
+
import * as childProcess5 from "child_process";
|
|
7101
7495
|
function sandboxApproveCommand(cwd, options) {
|
|
7102
7496
|
const json = options.json ?? false;
|
|
7103
|
-
const sandboxDir =
|
|
7104
|
-
const configPath =
|
|
7105
|
-
if (!
|
|
7497
|
+
const sandboxDir = join26(cwd, ".lo", "sandbox", options.name);
|
|
7498
|
+
const configPath = join26(sandboxDir, "sandbox.json");
|
|
7499
|
+
if (!existsSync25(configPath)) {
|
|
7106
7500
|
fail2(
|
|
7107
7501
|
json,
|
|
7108
7502
|
`Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init' first.`
|
|
@@ -7116,11 +7510,11 @@ function sandboxApproveCommand(cwd, options) {
|
|
|
7116
7510
|
runCapture(sandboxDir, configPath, options.name, json);
|
|
7117
7511
|
}
|
|
7118
7512
|
function runCapture(sandboxDir, configPath, sandboxName, json) {
|
|
7119
|
-
const config = JSON.parse(
|
|
7120
|
-
const captureScriptPath =
|
|
7121
|
-
|
|
7513
|
+
const config = JSON.parse(readFileSync29(configPath, "utf-8"));
|
|
7514
|
+
const captureScriptPath = join26(sandboxDir, "playwright.capture.mjs");
|
|
7515
|
+
writeFileSync15(captureScriptPath, captureScriptTemplate(), "utf-8");
|
|
7122
7516
|
ensurePlaywrightInstalled(sandboxDir, json);
|
|
7123
|
-
const result =
|
|
7517
|
+
const result = childProcess5.spawnSync(
|
|
7124
7518
|
"npx",
|
|
7125
7519
|
["--no-install", "node", "playwright.capture.mjs"],
|
|
7126
7520
|
{
|
|
@@ -7139,9 +7533,9 @@ function runCapture(sandboxDir, configPath, sandboxName, json) {
|
|
|
7139
7533
|
${result.stderr}`);
|
|
7140
7534
|
return;
|
|
7141
7535
|
}
|
|
7142
|
-
const pendingDir =
|
|
7143
|
-
const diffsDir =
|
|
7144
|
-
const approvedDir =
|
|
7536
|
+
const pendingDir = join26(sandboxDir, "captures", "pending");
|
|
7537
|
+
const diffsDir = join26(sandboxDir, "captures", "diffs");
|
|
7538
|
+
const approvedDir = join26(sandboxDir, "captures", "approved");
|
|
7145
7539
|
const pendingFiles = safeListPngs(pendingDir);
|
|
7146
7540
|
const diffFiles = safeListPngs(diffsDir);
|
|
7147
7541
|
const hasBaseline = safeListPngs(approvedDir).length > 0;
|
|
@@ -7178,9 +7572,9 @@ ${result.stderr}`);
|
|
|
7178
7572
|
}
|
|
7179
7573
|
}
|
|
7180
7574
|
function runPromotion(sandboxDir, sandboxName, json) {
|
|
7181
|
-
const pendingDir =
|
|
7182
|
-
const approvedDir =
|
|
7183
|
-
const diffsDir =
|
|
7575
|
+
const pendingDir = join26(sandboxDir, "captures", "pending");
|
|
7576
|
+
const approvedDir = join26(sandboxDir, "captures", "approved");
|
|
7577
|
+
const diffsDir = join26(sandboxDir, "captures", "diffs");
|
|
7184
7578
|
const pendingFiles = safeListPngs(pendingDir);
|
|
7185
7579
|
if (pendingFiles.length === 0) {
|
|
7186
7580
|
fail2(
|
|
@@ -7189,10 +7583,10 @@ function runPromotion(sandboxDir, sandboxName, json) {
|
|
|
7189
7583
|
);
|
|
7190
7584
|
return;
|
|
7191
7585
|
}
|
|
7192
|
-
|
|
7586
|
+
mkdirSync11(approvedDir, { recursive: true });
|
|
7193
7587
|
const promoted = [];
|
|
7194
7588
|
for (const src of pendingFiles) {
|
|
7195
|
-
const dest =
|
|
7589
|
+
const dest = join26(approvedDir, basename7(src));
|
|
7196
7590
|
copyFileSync2(src, dest);
|
|
7197
7591
|
promoted.push(dest);
|
|
7198
7592
|
}
|
|
@@ -7212,10 +7606,10 @@ function runPromotion(sandboxDir, sandboxName, json) {
|
|
|
7212
7606
|
}
|
|
7213
7607
|
}
|
|
7214
7608
|
function ensurePlaywrightInstalled(sandboxDir, json) {
|
|
7215
|
-
const markerPath =
|
|
7216
|
-
if (
|
|
7609
|
+
const markerPath = join26(sandboxDir, ".playwright-installed");
|
|
7610
|
+
if (existsSync25(markerPath)) return;
|
|
7217
7611
|
if (!json) logger.info("Installing Playwright Chromium (one-time)...");
|
|
7218
|
-
const result =
|
|
7612
|
+
const result = childProcess5.spawnSync(
|
|
7219
7613
|
"npx",
|
|
7220
7614
|
["--no-install", "playwright", "install", "chromium"],
|
|
7221
7615
|
{
|
|
@@ -7229,11 +7623,11 @@ function ensurePlaywrightInstalled(sandboxDir, json) {
|
|
|
7229
7623
|
if (typeof result.status === "number" && result.status !== 0) {
|
|
7230
7624
|
throw new Error(`playwright install exited with code ${result.status}`);
|
|
7231
7625
|
}
|
|
7232
|
-
|
|
7626
|
+
writeFileSync15(markerPath, (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
|
|
7233
7627
|
}
|
|
7234
7628
|
function safeListPngs(dir) {
|
|
7235
|
-
if (!
|
|
7236
|
-
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));
|
|
7237
7631
|
}
|
|
7238
7632
|
function tryParseJson(s) {
|
|
7239
7633
|
try {
|
|
@@ -7251,15 +7645,372 @@ function fail2(json, message) {
|
|
|
7251
7645
|
process.exit(1);
|
|
7252
7646
|
}
|
|
7253
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
|
+
|
|
7254
8005
|
// src/index.ts
|
|
7255
|
-
var __dirname2 =
|
|
8006
|
+
var __dirname2 = dirname11(fileURLToPath4(import.meta.url));
|
|
7256
8007
|
var pkg = JSON.parse(
|
|
7257
|
-
|
|
8008
|
+
readFileSync32(join30(__dirname2, "..", "package.json"), "utf-8")
|
|
7258
8009
|
);
|
|
7259
8010
|
var program = new Command2();
|
|
7260
8011
|
program.name("visor").description("CLI for the Visor design system").version(pkg.version);
|
|
7261
8012
|
program.addCommand(checkCommand());
|
|
7262
|
-
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) => {
|
|
7263
8014
|
initCommand(process.cwd(), options);
|
|
7264
8015
|
});
|
|
7265
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) => {
|
|
@@ -7444,4 +8195,9 @@ sandbox.command("approve").description(
|
|
|
7444
8195
|
sandboxApproveCommand(process.cwd(), options);
|
|
7445
8196
|
}
|
|
7446
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
|
+
});
|
|
7447
8203
|
program.parse();
|