@loworbitstudio/visor 1.15.0 → 1.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CHANGELOG.json +1 -1
- package/dist/index.js +501 -360
- package/dist/registry.json +3 -3
- package/dist/visor-manifest.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { readFileSync as readFileSync32 } from "fs";
|
|
5
|
-
import { dirname as
|
|
5
|
+
import { dirname as dirname12, join as join32 } from "path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
7
7
|
import { Command as Command2 } from "commander";
|
|
8
8
|
|
|
@@ -2792,8 +2792,8 @@ function infoCommand(name, cwd, options = {}) {
|
|
|
2792
2792
|
}
|
|
2793
2793
|
|
|
2794
2794
|
// src/commands/theme-apply.ts
|
|
2795
|
-
import { readFileSync as
|
|
2796
|
-
import { resolve as
|
|
2795
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
|
|
2796
|
+
import { resolve as resolve7, dirname as dirname7, join as join15 } from "path";
|
|
2797
2797
|
import { generateTheme, generateThemeData as generateThemeData3 } from "@loworbitstudio/visor-theme-engine";
|
|
2798
2798
|
import {
|
|
2799
2799
|
nextjsAdapter as nextjsAdapter2,
|
|
@@ -2802,6 +2802,143 @@ import {
|
|
|
2802
2802
|
docsAdapter,
|
|
2803
2803
|
flutterAdapter
|
|
2804
2804
|
} from "@loworbitstudio/visor-theme-engine/adapters";
|
|
2805
|
+
|
|
2806
|
+
// src/lib/blessed-manifest.ts
|
|
2807
|
+
import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
|
|
2808
|
+
import { join as join12 } from "path";
|
|
2809
|
+
import { z } from "zod";
|
|
2810
|
+
var BLESSED_MANIFEST_FILENAME = "blessed-manifest.json";
|
|
2811
|
+
var themeApplyTargetSchema = z.discriminatedUnion("kind", [
|
|
2812
|
+
z.object({
|
|
2813
|
+
kind: z.literal("globals-css"),
|
|
2814
|
+
/** Build-root-relative destination file. Defaults to `app/globals.css`. */
|
|
2815
|
+
path: z.string().min(1).optional()
|
|
2816
|
+
}).strict(),
|
|
2817
|
+
z.object({
|
|
2818
|
+
kind: z.literal("themes-css-dir"),
|
|
2819
|
+
/** Build-root-relative directory. The adapter output is written to `<path>/<theme-id>.css`. */
|
|
2820
|
+
path: z.string().min(1)
|
|
2821
|
+
}).strict()
|
|
2822
|
+
]);
|
|
2823
|
+
var blessedManifestSchema = z.object({
|
|
2824
|
+
/** Blessed-build shape, e.g. `admin-ui`. Matched against the `{shape}` in `blessed:{shape}:{pattern}`. */
|
|
2825
|
+
shape: z.string().min(1),
|
|
2826
|
+
/** Pattern name within the shape, e.g. `organization-management`. */
|
|
2827
|
+
pattern: z.string().min(1),
|
|
2828
|
+
/** The theme the reference build was authored + captured against. */
|
|
2829
|
+
base_theme: z.string().min(1),
|
|
2830
|
+
/** Minimum Visor version this build is known to work with (semver range). */
|
|
2831
|
+
requires_visor: z.string().min(1),
|
|
2832
|
+
/** Path (relative to the build root) to the approved capture baseline. */
|
|
2833
|
+
captures_baseline: z.string().min(1),
|
|
2834
|
+
/** Three-gates disposition at the time the build was blessed. */
|
|
2835
|
+
three_gates_status: z.string().min(1),
|
|
2836
|
+
/** Optional: where to write the nextjs-adapter CSS. Absent = `globals-css`. */
|
|
2837
|
+
theme_apply_target: themeApplyTargetSchema.optional()
|
|
2838
|
+
}).strict();
|
|
2839
|
+
function parseBlessedManifest(buildDir) {
|
|
2840
|
+
const manifestPath = join12(buildDir, BLESSED_MANIFEST_FILENAME);
|
|
2841
|
+
if (!existsSync10(manifestPath)) {
|
|
2842
|
+
return {
|
|
2843
|
+
ok: false,
|
|
2844
|
+
error: `this directory is not a blessed build (missing ${BLESSED_MANIFEST_FILENAME}); see docs/blessed-builds.md`
|
|
2845
|
+
};
|
|
2846
|
+
}
|
|
2847
|
+
let raw;
|
|
2848
|
+
try {
|
|
2849
|
+
raw = readFileSync12(manifestPath, "utf-8");
|
|
2850
|
+
} catch {
|
|
2851
|
+
return { ok: false, error: `could not read ${manifestPath}` };
|
|
2852
|
+
}
|
|
2853
|
+
let json;
|
|
2854
|
+
try {
|
|
2855
|
+
json = JSON.parse(raw);
|
|
2856
|
+
} catch (err) {
|
|
2857
|
+
const message = err instanceof Error ? err.message : "invalid JSON";
|
|
2858
|
+
return { ok: false, error: `invalid JSON in ${manifestPath}: ${message}` };
|
|
2859
|
+
}
|
|
2860
|
+
const result = blessedManifestSchema.safeParse(json);
|
|
2861
|
+
if (!result.success) {
|
|
2862
|
+
const issues = result.error.issues.map((issue) => {
|
|
2863
|
+
const path2 = issue.path.join(".") || "(root)";
|
|
2864
|
+
return `${path2}: ${issue.message}`;
|
|
2865
|
+
}).join("; ");
|
|
2866
|
+
return {
|
|
2867
|
+
ok: false,
|
|
2868
|
+
error: `invalid ${BLESSED_MANIFEST_FILENAME} at ${manifestPath}: ${issues}`
|
|
2869
|
+
};
|
|
2870
|
+
}
|
|
2871
|
+
return { ok: true, manifest: result.data };
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
// src/lib/theme-apply-targets/globals-css.ts
|
|
2875
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
2876
|
+
import { dirname as dirname6, isAbsolute, join as join13, resolve as resolve5 } from "path";
|
|
2877
|
+
function applyGlobalsCss(input) {
|
|
2878
|
+
const outFile = input.path !== void 0 ? resolveInsideBuild(input.buildDir, input.path) : resolveDefaultGlobalsCss(input.buildDir);
|
|
2879
|
+
mkdirSync4(dirname6(outFile), { recursive: true });
|
|
2880
|
+
writeFileSync6(outFile, input.adapterCss, "utf-8");
|
|
2881
|
+
return outFile;
|
|
2882
|
+
}
|
|
2883
|
+
function resolveDefaultGlobalsCss(buildDir) {
|
|
2884
|
+
const candidates = [
|
|
2885
|
+
join13(buildDir, "app", "globals.css"),
|
|
2886
|
+
join13(buildDir, "src", "app", "globals.css")
|
|
2887
|
+
];
|
|
2888
|
+
return candidates.find((candidate) => existsSync11(candidate)) ?? candidates[0];
|
|
2889
|
+
}
|
|
2890
|
+
function resolveInsideBuild(buildDir, relativeOrAbsolute) {
|
|
2891
|
+
return isAbsolute(relativeOrAbsolute) ? relativeOrAbsolute : resolve5(buildDir, relativeOrAbsolute);
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
// src/lib/theme-apply-targets/themes-css-dir.ts
|
|
2895
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
2896
|
+
import { isAbsolute as isAbsolute2, join as join14, resolve as resolve6 } from "path";
|
|
2897
|
+
function applyThemesCssDir(input) {
|
|
2898
|
+
const dir = isAbsolute2(input.path) ? input.path : resolve6(input.buildDir, input.path);
|
|
2899
|
+
const outFile = join14(dir, `${input.themeId}.css`);
|
|
2900
|
+
mkdirSync5(dir, { recursive: true });
|
|
2901
|
+
writeFileSync7(outFile, input.adapterCss, "utf-8");
|
|
2902
|
+
return outFile;
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
// src/lib/theme-apply-targets/index.ts
|
|
2906
|
+
var IMPLICIT_TARGET = { kind: "globals-css" };
|
|
2907
|
+
function applyThemeToBuild(input) {
|
|
2908
|
+
const target = input.manifest.theme_apply_target ?? IMPLICIT_TARGET;
|
|
2909
|
+
switch (target.kind) {
|
|
2910
|
+
case "globals-css": {
|
|
2911
|
+
const writtenPath = applyGlobalsCss({
|
|
2912
|
+
buildDir: input.buildDir,
|
|
2913
|
+
adapterCss: input.adapterCss,
|
|
2914
|
+
path: target.path
|
|
2915
|
+
});
|
|
2916
|
+
return { writtenPath, target };
|
|
2917
|
+
}
|
|
2918
|
+
case "themes-css-dir": {
|
|
2919
|
+
if (!input.themeId || input.themeId.length === 0) {
|
|
2920
|
+
throw new Error(
|
|
2921
|
+
`theme_apply_target kind 'themes-css-dir' requires a theme id (used to name the emitted file); see docs/blessed-builds.md`
|
|
2922
|
+
);
|
|
2923
|
+
}
|
|
2924
|
+
const writtenPath = applyThemesCssDir({
|
|
2925
|
+
buildDir: input.buildDir,
|
|
2926
|
+
adapterCss: input.adapterCss,
|
|
2927
|
+
themeId: input.themeId,
|
|
2928
|
+
path: target.path
|
|
2929
|
+
});
|
|
2930
|
+
return { writtenPath, target };
|
|
2931
|
+
}
|
|
2932
|
+
default: {
|
|
2933
|
+
const unknown = target.kind;
|
|
2934
|
+
throw new Error(
|
|
2935
|
+
`Unknown theme_apply_target kind: '${unknown}'. Supported: 'globals-css', 'themes-css-dir'. See docs/blessed-builds.md.`
|
|
2936
|
+
);
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
// src/commands/theme-apply.ts
|
|
2805
2942
|
function defaultOutputPath(adapter, themeName) {
|
|
2806
2943
|
switch (adapter) {
|
|
2807
2944
|
case "nextjs":
|
|
@@ -2823,10 +2960,10 @@ function defaultOutputPath(adapter, themeName) {
|
|
|
2823
2960
|
}
|
|
2824
2961
|
}
|
|
2825
2962
|
function themeApplyCommand(file, cwd, options) {
|
|
2826
|
-
const filePath =
|
|
2963
|
+
const filePath = resolve7(cwd, file);
|
|
2827
2964
|
let yamlContent;
|
|
2828
2965
|
try {
|
|
2829
|
-
yamlContent =
|
|
2966
|
+
yamlContent = readFileSync13(filePath, "utf-8");
|
|
2830
2967
|
} catch {
|
|
2831
2968
|
if (options.json) {
|
|
2832
2969
|
console.log(
|
|
@@ -2904,16 +3041,74 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
2904
3041
|
}
|
|
2905
3042
|
process.exit(1);
|
|
2906
3043
|
}
|
|
3044
|
+
if (options.targetPath) {
|
|
3045
|
+
if (options.adapter !== "nextjs") {
|
|
3046
|
+
const message = "--target-path requires --adapter nextjs; see docs/blessed-builds.md";
|
|
3047
|
+
if (options.json) console.log(JSON.stringify({ success: false, error: message }));
|
|
3048
|
+
else logger.error(message);
|
|
3049
|
+
process.exit(1);
|
|
3050
|
+
}
|
|
3051
|
+
if (css === null) {
|
|
3052
|
+
process.exit(1);
|
|
3053
|
+
}
|
|
3054
|
+
if (!themeName || themeName.length === 0) {
|
|
3055
|
+
const message = "theme config is missing a `name` \u2014 required to derive the theme id for --target-path dispatch; see docs/blessed-builds.md";
|
|
3056
|
+
if (options.json) console.log(JSON.stringify({ success: false, error: message }));
|
|
3057
|
+
else logger.error(message);
|
|
3058
|
+
process.exit(1);
|
|
3059
|
+
}
|
|
3060
|
+
const buildDir = resolve7(cwd, options.targetPath);
|
|
3061
|
+
const manifestResult = parseBlessedManifest(buildDir);
|
|
3062
|
+
if (!manifestResult.ok) {
|
|
3063
|
+
if (options.json) {
|
|
3064
|
+
console.log(JSON.stringify({ success: false, error: manifestResult.error }));
|
|
3065
|
+
} else {
|
|
3066
|
+
logger.error(manifestResult.error);
|
|
3067
|
+
}
|
|
3068
|
+
process.exit(2);
|
|
3069
|
+
}
|
|
3070
|
+
let writtenPath;
|
|
3071
|
+
try {
|
|
3072
|
+
writtenPath = applyThemeToBuild({
|
|
3073
|
+
manifest: manifestResult.manifest,
|
|
3074
|
+
buildDir,
|
|
3075
|
+
themeId: themeName,
|
|
3076
|
+
adapterCss: css
|
|
3077
|
+
}).writtenPath;
|
|
3078
|
+
} catch (err) {
|
|
3079
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3080
|
+
if (options.json) console.log(JSON.stringify({ success: false, error: message }));
|
|
3081
|
+
else logger.error(message);
|
|
3082
|
+
process.exit(1);
|
|
3083
|
+
}
|
|
3084
|
+
if (options.json) {
|
|
3085
|
+
console.log(
|
|
3086
|
+
JSON.stringify({
|
|
3087
|
+
success: true,
|
|
3088
|
+
file: writtenPath,
|
|
3089
|
+
adapter: options.adapter,
|
|
3090
|
+
size: css.length,
|
|
3091
|
+
targetPath: buildDir
|
|
3092
|
+
})
|
|
3093
|
+
);
|
|
3094
|
+
} else {
|
|
3095
|
+
logger.success(`Theme CSS written via blessed-manifest dispatch: ${writtenPath}`);
|
|
3096
|
+
logger.info(`Adapter: ${options.adapter}`);
|
|
3097
|
+
logger.item(`Build root: ${buildDir}`);
|
|
3098
|
+
logger.item(`Size: ${formatSize(css.length)}`);
|
|
3099
|
+
}
|
|
3100
|
+
return;
|
|
3101
|
+
}
|
|
2907
3102
|
const outputTarget = options.output ?? defaultOutputPath(options.adapter, themeName);
|
|
2908
|
-
const outputPath =
|
|
3103
|
+
const outputPath = resolve7(cwd, outputTarget);
|
|
2909
3104
|
if (fileMap) {
|
|
2910
3105
|
try {
|
|
2911
|
-
|
|
3106
|
+
mkdirSync6(outputPath, { recursive: true });
|
|
2912
3107
|
let totalBytes = 0;
|
|
2913
3108
|
for (const [relPath, content] of Object.entries(fileMap.files)) {
|
|
2914
|
-
const filePath2 =
|
|
2915
|
-
|
|
2916
|
-
|
|
3109
|
+
const filePath2 = join15(outputPath, relPath);
|
|
3110
|
+
mkdirSync6(dirname7(filePath2), { recursive: true });
|
|
3111
|
+
writeFileSync8(filePath2, content, "utf-8");
|
|
2917
3112
|
totalBytes += content.length;
|
|
2918
3113
|
}
|
|
2919
3114
|
if (options.json) {
|
|
@@ -2950,10 +3145,10 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
2950
3145
|
if (css === null) {
|
|
2951
3146
|
process.exit(1);
|
|
2952
3147
|
}
|
|
2953
|
-
const outputDir =
|
|
3148
|
+
const outputDir = dirname7(outputPath);
|
|
2954
3149
|
try {
|
|
2955
|
-
|
|
2956
|
-
|
|
3150
|
+
mkdirSync6(outputDir, { recursive: true });
|
|
3151
|
+
writeFileSync8(outputPath, css, "utf-8");
|
|
2957
3152
|
} catch {
|
|
2958
3153
|
if (options.json) {
|
|
2959
3154
|
console.log(
|
|
@@ -2994,8 +3189,8 @@ function formatSize(bytes) {
|
|
|
2994
3189
|
}
|
|
2995
3190
|
|
|
2996
3191
|
// src/commands/theme-export.ts
|
|
2997
|
-
import { readFileSync as
|
|
2998
|
-
import { resolve as
|
|
3192
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
3193
|
+
import { resolve as resolve8 } from "path";
|
|
2999
3194
|
import {
|
|
3000
3195
|
parseConfig,
|
|
3001
3196
|
resolveConfig,
|
|
@@ -3003,10 +3198,10 @@ import {
|
|
|
3003
3198
|
exportTheme
|
|
3004
3199
|
} from "@loworbitstudio/visor-theme-engine";
|
|
3005
3200
|
function themeExportCommand(file, cwd, options) {
|
|
3006
|
-
const filePath =
|
|
3201
|
+
const filePath = resolve8(cwd, file ?? ".visor.yaml");
|
|
3007
3202
|
let yamlContent;
|
|
3008
3203
|
try {
|
|
3009
|
-
yamlContent =
|
|
3204
|
+
yamlContent = readFileSync14(filePath, "utf-8");
|
|
3010
3205
|
} catch {
|
|
3011
3206
|
if (options.json) {
|
|
3012
3207
|
console.log(
|
|
@@ -3058,16 +3253,16 @@ function themeExportCommand(file, cwd, options) {
|
|
|
3058
3253
|
}
|
|
3059
3254
|
|
|
3060
3255
|
// src/commands/theme-validate.ts
|
|
3061
|
-
import { readFileSync as
|
|
3062
|
-
import { resolve as
|
|
3256
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
3257
|
+
import { resolve as resolve9 } from "path";
|
|
3063
3258
|
import { parse as parseYaml2 } from "yaml";
|
|
3064
3259
|
import { validate } from "@loworbitstudio/visor-theme-engine";
|
|
3065
3260
|
import pc3 from "picocolors";
|
|
3066
3261
|
function themeValidateCommand(file, cwd, options) {
|
|
3067
|
-
const filePath =
|
|
3262
|
+
const filePath = resolve9(cwd, file);
|
|
3068
3263
|
let fileContent;
|
|
3069
3264
|
try {
|
|
3070
|
-
fileContent =
|
|
3265
|
+
fileContent = readFileSync15(filePath, "utf-8");
|
|
3071
3266
|
} catch {
|
|
3072
3267
|
if (options.json) {
|
|
3073
3268
|
console.log(
|
|
@@ -3156,8 +3351,8 @@ function printIssue(issue) {
|
|
|
3156
3351
|
|
|
3157
3352
|
// src/commands/theme-verify.ts
|
|
3158
3353
|
import { spawnSync as _spawnSync } from "child_process";
|
|
3159
|
-
import { existsSync as
|
|
3160
|
-
import { resolve as
|
|
3354
|
+
import { existsSync as existsSync12 } from "fs";
|
|
3355
|
+
import { resolve as resolve10 } from "path";
|
|
3161
3356
|
function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
3162
3357
|
const target = options.target ?? "flutter";
|
|
3163
3358
|
if (target !== "flutter") {
|
|
@@ -3179,8 +3374,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
|
3179
3374
|
}
|
|
3180
3375
|
process.exit(1);
|
|
3181
3376
|
}
|
|
3182
|
-
const dirPath =
|
|
3183
|
-
if (!
|
|
3377
|
+
const dirPath = resolve10(cwd, dir);
|
|
3378
|
+
if (!existsSync12(dirPath)) {
|
|
3184
3379
|
if (options.json) {
|
|
3185
3380
|
console.log(
|
|
3186
3381
|
JSON.stringify({
|
|
@@ -3267,8 +3462,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
|
|
|
3267
3462
|
}
|
|
3268
3463
|
|
|
3269
3464
|
// src/commands/theme-extract.ts
|
|
3270
|
-
import { readFileSync as
|
|
3271
|
-
import { resolve as
|
|
3465
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync9, existsSync as existsSync13, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
|
|
3466
|
+
import { resolve as resolve11, join as join16, basename as basename3, extname as extname3, relative } from "path";
|
|
3272
3467
|
import { stringify as stringifyYaml } from "yaml";
|
|
3273
3468
|
import {
|
|
3274
3469
|
extractFromCSS,
|
|
@@ -3297,8 +3492,8 @@ var CSS_DIRS = [
|
|
|
3297
3492
|
"packages/design-tokens"
|
|
3298
3493
|
];
|
|
3299
3494
|
function themeExtractCommand(cwd, options) {
|
|
3300
|
-
const targetDir =
|
|
3301
|
-
if (!
|
|
3495
|
+
const targetDir = resolve11(cwd, options.from ?? ".");
|
|
3496
|
+
if (!existsSync13(targetDir)) {
|
|
3302
3497
|
if (options.json) {
|
|
3303
3498
|
console.log(JSON.stringify({ success: false, error: `Directory not found: ${targetDir}` }));
|
|
3304
3499
|
} else {
|
|
@@ -3352,16 +3547,16 @@ function collectCSSFiles(targetDir) {
|
|
|
3352
3547
|
const files = [];
|
|
3353
3548
|
const seen = /* @__PURE__ */ new Set();
|
|
3354
3549
|
for (const pattern2 of CSS_FILE_PATTERNS) {
|
|
3355
|
-
const rootPath =
|
|
3550
|
+
const rootPath = join16(targetDir, pattern2);
|
|
3356
3551
|
addFileIfExists(rootPath, files, seen);
|
|
3357
3552
|
for (const dir of CSS_DIRS) {
|
|
3358
|
-
const dirPath =
|
|
3553
|
+
const dirPath = join16(targetDir, dir, pattern2);
|
|
3359
3554
|
addFileIfExists(dirPath, files, seen);
|
|
3360
3555
|
}
|
|
3361
3556
|
}
|
|
3362
3557
|
for (const dir of CSS_DIRS) {
|
|
3363
|
-
const dirPath =
|
|
3364
|
-
if (
|
|
3558
|
+
const dirPath = join16(targetDir, dir);
|
|
3559
|
+
if (existsSync13(dirPath) && statSync5(dirPath).isDirectory()) {
|
|
3365
3560
|
scanDirForCSS(dirPath, files, seen, 2);
|
|
3366
3561
|
}
|
|
3367
3562
|
}
|
|
@@ -3369,11 +3564,11 @@ function collectCSSFiles(targetDir) {
|
|
|
3369
3564
|
return files;
|
|
3370
3565
|
}
|
|
3371
3566
|
function addFileIfExists(filePath, files, seen) {
|
|
3372
|
-
const resolved =
|
|
3567
|
+
const resolved = resolve11(filePath);
|
|
3373
3568
|
if (seen.has(resolved)) return;
|
|
3374
|
-
if (!
|
|
3569
|
+
if (!existsSync13(resolved)) return;
|
|
3375
3570
|
try {
|
|
3376
|
-
const content =
|
|
3571
|
+
const content = readFileSync16(resolved, "utf-8");
|
|
3377
3572
|
if (content.includes("--")) {
|
|
3378
3573
|
files.push({ path: resolved, content });
|
|
3379
3574
|
seen.add(resolved);
|
|
@@ -3382,7 +3577,7 @@ function addFileIfExists(filePath, files, seen) {
|
|
|
3382
3577
|
}
|
|
3383
3578
|
}
|
|
3384
3579
|
function scanDirForCSS(dir, files, seen, maxDepth) {
|
|
3385
|
-
if (!
|
|
3580
|
+
if (!existsSync13(dir)) return;
|
|
3386
3581
|
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
3387
3582
|
"node_modules",
|
|
3388
3583
|
".next",
|
|
@@ -3401,10 +3596,10 @@ function scanDirForCSS(dir, files, seen, maxDepth) {
|
|
|
3401
3596
|
if (entry.isDirectory()) {
|
|
3402
3597
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
3403
3598
|
if (maxDepth > 0) {
|
|
3404
|
-
scanDirForCSS(
|
|
3599
|
+
scanDirForCSS(join16(dir, entry.name), files, seen, maxDepth - 1);
|
|
3405
3600
|
}
|
|
3406
3601
|
} else if (entry.isFile() && extname3(entry.name) === ".css") {
|
|
3407
|
-
addFileIfExists(
|
|
3602
|
+
addFileIfExists(join16(dir, entry.name), files, seen);
|
|
3408
3603
|
}
|
|
3409
3604
|
}
|
|
3410
3605
|
} catch {
|
|
@@ -3486,10 +3681,10 @@ function extractVarName(varExpr) {
|
|
|
3486
3681
|
function parseNextFontFromLayouts(targetDir) {
|
|
3487
3682
|
const fontMap = /* @__PURE__ */ new Map();
|
|
3488
3683
|
for (const relPath of LAYOUT_FILE_PATHS) {
|
|
3489
|
-
const fullPath =
|
|
3490
|
-
if (!
|
|
3684
|
+
const fullPath = join16(targetDir, relPath);
|
|
3685
|
+
if (!existsSync13(fullPath)) continue;
|
|
3491
3686
|
try {
|
|
3492
|
-
const content =
|
|
3687
|
+
const content = readFileSync16(fullPath, "utf-8");
|
|
3493
3688
|
parseNextFontDeclarations(content, fontMap);
|
|
3494
3689
|
} catch {
|
|
3495
3690
|
}
|
|
@@ -3574,10 +3769,10 @@ var MONO_FONT_NAMES = /* @__PURE__ */ new Set([
|
|
|
3574
3769
|
"IBM Plex Mono"
|
|
3575
3770
|
]);
|
|
3576
3771
|
function extractFontHints(targetDir) {
|
|
3577
|
-
const pkgPath =
|
|
3578
|
-
if (!
|
|
3772
|
+
const pkgPath = join16(targetDir, "package.json");
|
|
3773
|
+
if (!existsSync13(pkgPath)) return void 0;
|
|
3579
3774
|
try {
|
|
3580
|
-
const pkg2 = JSON.parse(
|
|
3775
|
+
const pkg2 = JSON.parse(readFileSync16(pkgPath, "utf-8"));
|
|
3581
3776
|
const allDeps = { ...pkg2.dependencies, ...pkg2.devDependencies };
|
|
3582
3777
|
const fonts2 = [];
|
|
3583
3778
|
for (const [dep, _] of Object.entries(allDeps)) {
|
|
@@ -3613,10 +3808,10 @@ function extractFontHints(targetDir) {
|
|
|
3613
3808
|
}
|
|
3614
3809
|
}
|
|
3615
3810
|
function inferThemeName(targetDir) {
|
|
3616
|
-
const pkgPath =
|
|
3617
|
-
if (
|
|
3811
|
+
const pkgPath = join16(targetDir, "package.json");
|
|
3812
|
+
if (existsSync13(pkgPath)) {
|
|
3618
3813
|
try {
|
|
3619
|
-
const pkg2 = JSON.parse(
|
|
3814
|
+
const pkg2 = JSON.parse(readFileSync16(pkgPath, "utf-8"));
|
|
3620
3815
|
if (pkg2.name) {
|
|
3621
3816
|
const name = pkg2.name.replace(/^@[\w-]+\//, "");
|
|
3622
3817
|
return `${name}-theme`;
|
|
@@ -3653,7 +3848,7 @@ function outputJSON(result, validationResult) {
|
|
|
3653
3848
|
}
|
|
3654
3849
|
function outputYAML(result, outputPath, cwd, validationResult) {
|
|
3655
3850
|
const yamlStr = buildAnnotatedYAML(result);
|
|
3656
|
-
const outFile =
|
|
3851
|
+
const outFile = resolve11(cwd, outputPath ?? ".visor.yaml");
|
|
3657
3852
|
const high = result.tokens.filter((t) => t.confidence === "high").length;
|
|
3658
3853
|
const med = result.tokens.filter((t) => t.confidence === "medium").length;
|
|
3659
3854
|
const low = result.tokens.filter((t) => t.confidence === "low").length;
|
|
@@ -3681,7 +3876,7 @@ function outputYAML(result, outputPath, cwd, validationResult) {
|
|
|
3681
3876
|
}
|
|
3682
3877
|
logger.blank();
|
|
3683
3878
|
}
|
|
3684
|
-
|
|
3879
|
+
writeFileSync9(outFile, yamlStr, "utf-8");
|
|
3685
3880
|
logger.success(`Theme written to ${relative(cwd, outFile)}`);
|
|
3686
3881
|
if (validationResult) {
|
|
3687
3882
|
logger.blank();
|
|
@@ -3739,14 +3934,14 @@ function buildAnnotatedYAML(result) {
|
|
|
3739
3934
|
}
|
|
3740
3935
|
|
|
3741
3936
|
// src/commands/theme-register.ts
|
|
3742
|
-
import { readFileSync as
|
|
3743
|
-
import { resolve as
|
|
3937
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync10, mkdirSync as mkdirSync7, existsSync as existsSync15 } from "fs";
|
|
3938
|
+
import { resolve as resolve13, join as join18 } from "path";
|
|
3744
3939
|
import { generateThemeData as generateThemeData4 } from "@loworbitstudio/visor-theme-engine";
|
|
3745
3940
|
import { docsAdapter as docsAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
3746
3941
|
|
|
3747
3942
|
// src/utils/theme-helpers.ts
|
|
3748
|
-
import { existsSync as
|
|
3749
|
-
import { resolve as
|
|
3943
|
+
import { existsSync as existsSync14, lstatSync, readdirSync as readdirSync5, readFileSync as readFileSync17, readlinkSync, realpathSync, statSync as statSync6 } from "fs";
|
|
3944
|
+
import { resolve as resolve12, dirname as dirname8, join as join17 } from "path";
|
|
3750
3945
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
3751
3946
|
function toSlug(name) {
|
|
3752
3947
|
return name.toLowerCase().replace(/\s+/g, "-");
|
|
@@ -3755,12 +3950,12 @@ function toLabel(name) {
|
|
|
3755
3950
|
return name.split(/[\s-]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
3756
3951
|
}
|
|
3757
3952
|
function findRepoRoot(startDir) {
|
|
3758
|
-
let current =
|
|
3953
|
+
let current = resolve12(startDir);
|
|
3759
3954
|
while (true) {
|
|
3760
|
-
if (
|
|
3955
|
+
if (existsSync14(join17(current, "packages", "docs"))) {
|
|
3761
3956
|
return current;
|
|
3762
3957
|
}
|
|
3763
|
-
const parent =
|
|
3958
|
+
const parent = dirname8(current);
|
|
3764
3959
|
if (parent === current) break;
|
|
3765
3960
|
current = parent;
|
|
3766
3961
|
}
|
|
@@ -3774,9 +3969,9 @@ function findMainRepoRoot(startDir) {
|
|
|
3774
3969
|
stdio: ["ignore", "pipe", "ignore"]
|
|
3775
3970
|
}).trim();
|
|
3776
3971
|
if (commonDir) {
|
|
3777
|
-
const absoluteCommonDir =
|
|
3778
|
-
const candidate =
|
|
3779
|
-
if (
|
|
3972
|
+
const absoluteCommonDir = resolve12(startDir, commonDir);
|
|
3973
|
+
const candidate = dirname8(absoluteCommonDir);
|
|
3974
|
+
if (existsSync14(join17(candidate, "packages", "docs"))) {
|
|
3780
3975
|
return candidate;
|
|
3781
3976
|
}
|
|
3782
3977
|
}
|
|
@@ -3795,10 +3990,10 @@ var BrokenSymlinkError = class extends Error {
|
|
|
3795
3990
|
code = "BROKEN_SYMLINK";
|
|
3796
3991
|
};
|
|
3797
3992
|
function assertNoBrokenSymlinks(dir) {
|
|
3798
|
-
if (!
|
|
3993
|
+
if (!existsSync14(dir)) return;
|
|
3799
3994
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
3800
3995
|
for (const entry of entries) {
|
|
3801
|
-
const entryPath =
|
|
3996
|
+
const entryPath = join17(dir, entry.name);
|
|
3802
3997
|
let lst;
|
|
3803
3998
|
try {
|
|
3804
3999
|
lst = lstatSync(entryPath);
|
|
@@ -3816,39 +4011,39 @@ function assertNoBrokenSymlinks(dir) {
|
|
|
3816
4011
|
}
|
|
3817
4012
|
}
|
|
3818
4013
|
function scanParentForPrivateThemes(parentDir) {
|
|
3819
|
-
if (!
|
|
4014
|
+
if (!existsSync14(parentDir)) return [];
|
|
3820
4015
|
const entries = readdirSync5(parentDir, { withFileTypes: true });
|
|
3821
4016
|
const matches = [];
|
|
3822
4017
|
for (const entry of entries) {
|
|
3823
4018
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
3824
|
-
const candidate =
|
|
3825
|
-
if (
|
|
4019
|
+
const candidate = join17(parentDir, entry.name, "visor-themes-private", "themes");
|
|
4020
|
+
if (existsSync14(candidate)) {
|
|
3826
4021
|
matches.push(candidate);
|
|
3827
4022
|
}
|
|
3828
4023
|
}
|
|
3829
4024
|
return matches.sort();
|
|
3830
4025
|
}
|
|
3831
4026
|
function scanNestedThemeDir(dir) {
|
|
3832
|
-
if (!
|
|
4027
|
+
if (!existsSync14(dir)) return [];
|
|
3833
4028
|
assertNoBrokenSymlinks(dir);
|
|
3834
4029
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
3835
4030
|
const out = [];
|
|
3836
4031
|
for (const entry of entries) {
|
|
3837
4032
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
3838
|
-
const themeFile =
|
|
3839
|
-
if (
|
|
4033
|
+
const themeFile = join17(dir, entry.name, "theme.visor.yaml");
|
|
4034
|
+
if (existsSync14(themeFile)) {
|
|
3840
4035
|
out.push({ filePath: themeFile, slug: entry.name });
|
|
3841
4036
|
}
|
|
3842
4037
|
}
|
|
3843
4038
|
return out;
|
|
3844
4039
|
}
|
|
3845
4040
|
function detectVisorWorkspace(cwd) {
|
|
3846
|
-
let current =
|
|
4041
|
+
let current = resolve12(cwd);
|
|
3847
4042
|
while (true) {
|
|
3848
|
-
const pkgPath =
|
|
3849
|
-
if (
|
|
4043
|
+
const pkgPath = join17(current, "package.json");
|
|
4044
|
+
if (existsSync14(pkgPath)) {
|
|
3850
4045
|
try {
|
|
3851
|
-
const pkg2 = JSON.parse(
|
|
4046
|
+
const pkg2 = JSON.parse(readFileSync17(pkgPath, "utf-8"));
|
|
3852
4047
|
const workspaces = pkg2.workspaces ?? [];
|
|
3853
4048
|
const hasCli = workspaces.some((w) => w.includes("packages/cli"));
|
|
3854
4049
|
const hasEngine = workspaces.some((w) => w.includes("packages/theme-engine"));
|
|
@@ -3858,7 +4053,7 @@ function detectVisorWorkspace(cwd) {
|
|
|
3858
4053
|
} catch {
|
|
3859
4054
|
}
|
|
3860
4055
|
}
|
|
3861
|
-
const parent =
|
|
4056
|
+
const parent = dirname8(current);
|
|
3862
4057
|
if (parent === current) break;
|
|
3863
4058
|
current = parent;
|
|
3864
4059
|
}
|
|
@@ -3866,18 +4061,18 @@ function detectVisorWorkspace(cwd) {
|
|
|
3866
4061
|
}
|
|
3867
4062
|
function isLocalVisorBinary(workspaceRoot, scriptPath) {
|
|
3868
4063
|
if (!scriptPath) return false;
|
|
3869
|
-
const expectedPrefix =
|
|
4064
|
+
const expectedPrefix = join17(workspaceRoot, "packages", "cli", "dist");
|
|
3870
4065
|
let resolvedScript;
|
|
3871
4066
|
let resolvedPrefix;
|
|
3872
4067
|
try {
|
|
3873
4068
|
resolvedScript = realpathSync(scriptPath);
|
|
3874
4069
|
} catch {
|
|
3875
|
-
resolvedScript =
|
|
4070
|
+
resolvedScript = resolve12(scriptPath);
|
|
3876
4071
|
}
|
|
3877
4072
|
try {
|
|
3878
4073
|
resolvedPrefix = realpathSync(expectedPrefix);
|
|
3879
4074
|
} catch {
|
|
3880
|
-
resolvedPrefix =
|
|
4075
|
+
resolvedPrefix = resolve12(expectedPrefix);
|
|
3881
4076
|
}
|
|
3882
4077
|
return resolvedScript.startsWith(resolvedPrefix + "/") || resolvedScript === resolvedPrefix;
|
|
3883
4078
|
}
|
|
@@ -3967,10 +4162,10 @@ ${indent}${newEntry},
|
|
|
3967
4162
|
return { updated, changed: true };
|
|
3968
4163
|
}
|
|
3969
4164
|
function themeRegisterCommand(file, cwd, options) {
|
|
3970
|
-
const filePath =
|
|
4165
|
+
const filePath = resolve13(cwd, file);
|
|
3971
4166
|
let yamlContent;
|
|
3972
4167
|
try {
|
|
3973
|
-
yamlContent =
|
|
4168
|
+
yamlContent = readFileSync18(filePath, "utf-8");
|
|
3974
4169
|
} catch {
|
|
3975
4170
|
if (options.json) {
|
|
3976
4171
|
console.log(JSON.stringify({ success: false, error: `Could not read file: ${filePath}` }));
|
|
@@ -4013,11 +4208,11 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
4013
4208
|
process.exit(1);
|
|
4014
4209
|
return;
|
|
4015
4210
|
}
|
|
4016
|
-
const docsAppDir =
|
|
4017
|
-
const cssFilePath =
|
|
4018
|
-
const globalsPath =
|
|
4019
|
-
const themeConfigPath =
|
|
4020
|
-
if (!
|
|
4211
|
+
const docsAppDir = join18(repoRoot, "packages", "docs", "app");
|
|
4212
|
+
const cssFilePath = join18(docsAppDir, `${slug2}-theme.css`);
|
|
4213
|
+
const globalsPath = join18(docsAppDir, "globals.css");
|
|
4214
|
+
const themeConfigPath = join18(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
4215
|
+
if (!existsSync15(docsAppDir)) {
|
|
4021
4216
|
const msg = `Docs app directory not found: ${docsAppDir}`;
|
|
4022
4217
|
if (options.json) {
|
|
4023
4218
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
@@ -4030,8 +4225,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
4030
4225
|
let globalsContent = "";
|
|
4031
4226
|
let themeConfigContent = "";
|
|
4032
4227
|
try {
|
|
4033
|
-
globalsContent =
|
|
4034
|
-
themeConfigContent =
|
|
4228
|
+
globalsContent = readFileSync18(globalsPath, "utf-8");
|
|
4229
|
+
themeConfigContent = readFileSync18(themeConfigPath, "utf-8");
|
|
4035
4230
|
} catch (err) {
|
|
4036
4231
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
4037
4232
|
if (options.json) {
|
|
@@ -4042,8 +4237,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
4042
4237
|
process.exit(1);
|
|
4043
4238
|
return;
|
|
4044
4239
|
}
|
|
4045
|
-
const cssExists =
|
|
4046
|
-
const cssChanged = !cssExists ||
|
|
4240
|
+
const cssExists = existsSync15(cssFilePath);
|
|
4241
|
+
const cssChanged = !cssExists || readFileSync18(cssFilePath, "utf-8") !== css;
|
|
4047
4242
|
const { updated: newGlobals, changed: globalsChanged } = insertGlobalsImport(globalsContent, slug2);
|
|
4048
4243
|
const { updated: newThemeConfig, changed: themeConfigChanged, error: configError } = insertThemeConfig(
|
|
4049
4244
|
themeConfigContent,
|
|
@@ -4086,14 +4281,14 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
4086
4281
|
}
|
|
4087
4282
|
try {
|
|
4088
4283
|
if (cssChanged) {
|
|
4089
|
-
|
|
4090
|
-
|
|
4284
|
+
mkdirSync7(docsAppDir, { recursive: true });
|
|
4285
|
+
writeFileSync10(cssFilePath, css, "utf-8");
|
|
4091
4286
|
}
|
|
4092
4287
|
if (globalsChanged) {
|
|
4093
|
-
|
|
4288
|
+
writeFileSync10(globalsPath, newGlobals, "utf-8");
|
|
4094
4289
|
}
|
|
4095
4290
|
if (themeConfigChanged) {
|
|
4096
|
-
|
|
4291
|
+
writeFileSync10(themeConfigPath, newThemeConfig, "utf-8");
|
|
4097
4292
|
}
|
|
4098
4293
|
} catch (err) {
|
|
4099
4294
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
@@ -4127,8 +4322,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
4127
4322
|
}
|
|
4128
4323
|
|
|
4129
4324
|
// src/commands/theme-unregister.ts
|
|
4130
|
-
import { readFileSync as
|
|
4131
|
-
import { join as
|
|
4325
|
+
import { readFileSync as readFileSync19, writeFileSync as writeFileSync11, existsSync as existsSync16, unlinkSync } from "fs";
|
|
4326
|
+
import { join as join19 } from "path";
|
|
4132
4327
|
function removeGlobalsImport(content, slug2) {
|
|
4133
4328
|
const importLine = `@import './${slug2}-theme.css';`;
|
|
4134
4329
|
if (!content.includes(importLine)) {
|
|
@@ -4160,11 +4355,11 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
4160
4355
|
process.exit(1);
|
|
4161
4356
|
return;
|
|
4162
4357
|
}
|
|
4163
|
-
const docsAppDir =
|
|
4164
|
-
const cssFilePath =
|
|
4165
|
-
const globalsPath =
|
|
4166
|
-
const themeConfigPath =
|
|
4167
|
-
if (!
|
|
4358
|
+
const docsAppDir = join19(repoRoot, "packages", "docs", "app");
|
|
4359
|
+
const cssFilePath = join19(docsAppDir, `${slug2}-theme.css`);
|
|
4360
|
+
const globalsPath = join19(docsAppDir, "globals.css");
|
|
4361
|
+
const themeConfigPath = join19(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
4362
|
+
if (!existsSync16(docsAppDir)) {
|
|
4168
4363
|
const msg = `Docs app directory not found: ${docsAppDir}`;
|
|
4169
4364
|
if (options.json) {
|
|
4170
4365
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
@@ -4177,8 +4372,8 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
4177
4372
|
let globalsContent = "";
|
|
4178
4373
|
let themeConfigContent = "";
|
|
4179
4374
|
try {
|
|
4180
|
-
globalsContent =
|
|
4181
|
-
themeConfigContent =
|
|
4375
|
+
globalsContent = readFileSync19(globalsPath, "utf-8");
|
|
4376
|
+
themeConfigContent = readFileSync19(themeConfigPath, "utf-8");
|
|
4182
4377
|
} catch (err) {
|
|
4183
4378
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
4184
4379
|
if (options.json) {
|
|
@@ -4189,7 +4384,7 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
4189
4384
|
process.exit(1);
|
|
4190
4385
|
return;
|
|
4191
4386
|
}
|
|
4192
|
-
const cssExists =
|
|
4387
|
+
const cssExists = existsSync16(cssFilePath);
|
|
4193
4388
|
const { updated: newGlobals, changed: globalsChanged } = removeGlobalsImport(globalsContent, slug2);
|
|
4194
4389
|
const { updated: newThemeConfig, changed: themeConfigChanged } = removeThemeConfigEntry(themeConfigContent, slug2);
|
|
4195
4390
|
if (!cssExists && !globalsChanged && !themeConfigChanged) {
|
|
@@ -4202,8 +4397,8 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
4202
4397
|
}
|
|
4203
4398
|
try {
|
|
4204
4399
|
if (cssExists) unlinkSync(cssFilePath);
|
|
4205
|
-
if (globalsChanged)
|
|
4206
|
-
if (themeConfigChanged)
|
|
4400
|
+
if (globalsChanged) writeFileSync11(globalsPath, newGlobals, "utf-8");
|
|
4401
|
+
if (themeConfigChanged) writeFileSync11(themeConfigPath, newThemeConfig, "utf-8");
|
|
4207
4402
|
} catch (err) {
|
|
4208
4403
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
4209
4404
|
if (options.json) {
|
|
@@ -4230,15 +4425,15 @@ function themeUnregisterCommand(slug2, cwd, options) {
|
|
|
4230
4425
|
|
|
4231
4426
|
// src/commands/theme-sync.ts
|
|
4232
4427
|
import {
|
|
4233
|
-
readFileSync as
|
|
4234
|
-
writeFileSync as
|
|
4235
|
-
mkdirSync as
|
|
4236
|
-
existsSync as
|
|
4428
|
+
readFileSync as readFileSync20,
|
|
4429
|
+
writeFileSync as writeFileSync12,
|
|
4430
|
+
mkdirSync as mkdirSync8,
|
|
4431
|
+
existsSync as existsSync17,
|
|
4237
4432
|
readdirSync as readdirSync6,
|
|
4238
4433
|
unlinkSync as unlinkSync2,
|
|
4239
4434
|
copyFileSync
|
|
4240
4435
|
} from "fs";
|
|
4241
|
-
import { join as
|
|
4436
|
+
import { join as join20, basename as basename4, resolve as resolve14, sep, relative as relative2 } from "path";
|
|
4242
4437
|
import { parse as parseYaml3 } from "yaml";
|
|
4243
4438
|
import { generateThemeData as generateThemeData5, validateFontCoverage, formatFontCoverageError } from "@loworbitstudio/visor-theme-engine";
|
|
4244
4439
|
import { docsAdapter as docsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
@@ -4252,9 +4447,9 @@ var CUSTOM_OVERLAY_CSS_PATH = "packages/docs/app/custom-themes.generated.css";
|
|
|
4252
4447
|
var CUSTOM_OVERLAY_TS_PATH = "packages/docs/lib/theme-config.custom.generated.ts";
|
|
4253
4448
|
var CUSTOM_OVERLAY_IMPORT_LINE = "@import './custom-themes.generated.css';";
|
|
4254
4449
|
function scanThemeDir(dir) {
|
|
4255
|
-
if (!
|
|
4450
|
+
if (!existsSync17(dir)) return [];
|
|
4256
4451
|
assertNoBrokenSymlinks(dir);
|
|
4257
|
-
return readdirSync6(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) =>
|
|
4452
|
+
return readdirSync6(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join20(dir, f));
|
|
4258
4453
|
}
|
|
4259
4454
|
function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
4260
4455
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -4278,20 +4473,20 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
|
4278
4473
|
};
|
|
4279
4474
|
const envPath = process.env[PRIVATE_THEMES_ENV_VAR];
|
|
4280
4475
|
if (envPath && envPath.trim() !== "") {
|
|
4281
|
-
const resolved =
|
|
4282
|
-
if (!
|
|
4476
|
+
const resolved = resolve14(envPath);
|
|
4477
|
+
if (!existsSync17(resolved)) {
|
|
4283
4478
|
throw new Error(
|
|
4284
4479
|
`${PRIVATE_THEMES_ENV_VAR} is set to "${envPath}" but the path does not exist. Expected a directory containing {slug}/theme.visor.yaml entries.`
|
|
4285
4480
|
);
|
|
4286
4481
|
}
|
|
4287
4482
|
addNested(resolved, "env");
|
|
4288
4483
|
}
|
|
4289
|
-
const siblingPath =
|
|
4290
|
-
const siblingExists =
|
|
4484
|
+
const siblingPath = join20(mainRepoRoot, "..", "visor-themes-private", "themes");
|
|
4485
|
+
const siblingExists = existsSync17(siblingPath);
|
|
4291
4486
|
if (siblingExists) {
|
|
4292
4487
|
addNested(siblingPath, "sibling");
|
|
4293
4488
|
}
|
|
4294
|
-
const parentDir =
|
|
4489
|
+
const parentDir = join20(mainRepoRoot, "..");
|
|
4295
4490
|
const parentGlobMatches = scanParentForPrivateThemes(parentDir).filter(
|
|
4296
4491
|
(path2) => !path2.startsWith(mainRepoRoot + sep)
|
|
4297
4492
|
);
|
|
@@ -4312,7 +4507,7 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
|
|
|
4312
4507
|
}
|
|
4313
4508
|
}
|
|
4314
4509
|
}
|
|
4315
|
-
const legacyDir =
|
|
4510
|
+
const legacyDir = join20(repoRoot, "custom-themes");
|
|
4316
4511
|
const legacyFiles = scanThemeDir(legacyDir);
|
|
4317
4512
|
for (const legacyFile of legacyFiles) {
|
|
4318
4513
|
const slug2 = basename4(legacyFile).replace(/\.visor\.yaml$/, "");
|
|
@@ -4369,14 +4564,14 @@ function reportBrokenSymlink(err, options) {
|
|
|
4369
4564
|
}
|
|
4370
4565
|
}
|
|
4371
4566
|
function buildEmptySourcesMessage(mainRepoRoot) {
|
|
4372
|
-
const expectedSibling =
|
|
4567
|
+
const expectedSibling = join20(mainRepoRoot, "..", "visor-themes-private");
|
|
4373
4568
|
return [
|
|
4374
4569
|
"No theme sources discovered. Cannot proceed \u2014 refusing to wipe generated CSS.",
|
|
4375
4570
|
"",
|
|
4376
4571
|
"Resolution order checked:",
|
|
4377
4572
|
` 1. Env var ${PRIVATE_THEMES_ENV_VAR} (unset or empty)`,
|
|
4378
4573
|
` 2. Sibling checkout at ${expectedSibling}/themes/ (not found)`,
|
|
4379
|
-
` 3. One-level-deeper at ${
|
|
4574
|
+
` 3. One-level-deeper at ${join20(mainRepoRoot, "..")}/<parent>/visor-themes-private/themes/ (not found)`,
|
|
4380
4575
|
" 4. Legacy custom-themes/ (no .visor.yaml files)",
|
|
4381
4576
|
"",
|
|
4382
4577
|
"To fix, clone the private themes repo as a sibling:",
|
|
@@ -4581,14 +4776,14 @@ function themeSyncCommand(cwd, options) {
|
|
|
4581
4776
|
return;
|
|
4582
4777
|
}
|
|
4583
4778
|
const mainRepoRoot = findMainRepoRoot(cwd) ?? repoRoot;
|
|
4584
|
-
const themesDir =
|
|
4585
|
-
const docsAppDir =
|
|
4586
|
-
const docsLibDir =
|
|
4587
|
-
const docsPublicThemesDir =
|
|
4588
|
-
const themeConfigPath =
|
|
4589
|
-
const globalsPath =
|
|
4590
|
-
const customOverlayCssPath =
|
|
4591
|
-
const customOverlayTsPath =
|
|
4779
|
+
const themesDir = join20(repoRoot, "themes");
|
|
4780
|
+
const docsAppDir = join20(repoRoot, "packages", "docs", "app");
|
|
4781
|
+
const docsLibDir = join20(repoRoot, "packages", "docs", "lib");
|
|
4782
|
+
const docsPublicThemesDir = join20(repoRoot, "packages", "docs", "public", "themes");
|
|
4783
|
+
const themeConfigPath = join20(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
4784
|
+
const globalsPath = join20(docsAppDir, "globals.css");
|
|
4785
|
+
const customOverlayCssPath = join20(repoRoot, CUSTOM_OVERLAY_CSS_PATH);
|
|
4786
|
+
const customOverlayTsPath = join20(repoRoot, CUSTOM_OVERLAY_TS_PATH);
|
|
4592
4787
|
let stockFiles;
|
|
4593
4788
|
try {
|
|
4594
4789
|
stockFiles = scanThemeDir(themesDir);
|
|
@@ -4645,7 +4840,7 @@ function themeSyncCommand(cwd, options) {
|
|
|
4645
4840
|
const processFile = (filePath, isCustom, slugOverride) => {
|
|
4646
4841
|
let yamlContent;
|
|
4647
4842
|
try {
|
|
4648
|
-
yamlContent =
|
|
4843
|
+
yamlContent = readFileSync20(filePath, "utf-8");
|
|
4649
4844
|
} catch {
|
|
4650
4845
|
failures.push({ filePath, error: `Could not read: ${filePath}` });
|
|
4651
4846
|
return;
|
|
@@ -4695,8 +4890,8 @@ function themeSyncCommand(cwd, options) {
|
|
|
4695
4890
|
let globalsContent;
|
|
4696
4891
|
let themeConfigContent;
|
|
4697
4892
|
try {
|
|
4698
|
-
globalsContent =
|
|
4699
|
-
themeConfigContent =
|
|
4893
|
+
globalsContent = readFileSync20(globalsPath, "utf-8");
|
|
4894
|
+
themeConfigContent = readFileSync20(themeConfigPath, "utf-8");
|
|
4700
4895
|
} catch (err) {
|
|
4701
4896
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
4702
4897
|
if (options.json) {
|
|
@@ -4711,12 +4906,12 @@ function themeSyncCommand(cwd, options) {
|
|
|
4711
4906
|
const newThemeConfig = updateStockThemeConfigBlock(themeConfigContent, stockManifest);
|
|
4712
4907
|
const newCustomOverlayCss = generateCustomOverlayCss(customManifest);
|
|
4713
4908
|
const newCustomOverlayTs = generateCustomOverlayTs(customManifest);
|
|
4714
|
-
const existingCssFiles =
|
|
4909
|
+
const existingCssFiles = existsSync17(docsAppDir) ? readdirSync6(docsAppDir).filter(
|
|
4715
4910
|
(f) => f.endsWith("-theme.css") && f !== "custom-themes.generated.css"
|
|
4716
4911
|
) : [];
|
|
4717
4912
|
const newCssSet = new Set(allSlugs.map((s) => `${s}-theme.css`));
|
|
4718
4913
|
const staleCssFiles = existingCssFiles.filter((f) => !newCssSet.has(f));
|
|
4719
|
-
const existingPublicYamls =
|
|
4914
|
+
const existingPublicYamls = existsSync17(docsPublicThemesDir) ? readdirSync6(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
|
|
4720
4915
|
const newPublicYamlSet = new Set(manifest.map((e) => `${e.yamlFilename}.visor.yaml`));
|
|
4721
4916
|
const stalePublicYamls = existingPublicYamls.filter((f) => !newPublicYamlSet.has(f));
|
|
4722
4917
|
if (options.dryRun) {
|
|
@@ -4756,28 +4951,28 @@ function themeSyncCommand(cwd, options) {
|
|
|
4756
4951
|
return;
|
|
4757
4952
|
}
|
|
4758
4953
|
try {
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4954
|
+
mkdirSync8(docsAppDir, { recursive: true });
|
|
4955
|
+
mkdirSync8(docsLibDir, { recursive: true });
|
|
4956
|
+
mkdirSync8(docsPublicThemesDir, { recursive: true });
|
|
4762
4957
|
for (const entry of manifest) {
|
|
4763
|
-
|
|
4958
|
+
writeFileSync12(join20(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
|
|
4764
4959
|
}
|
|
4765
4960
|
for (const stale of staleCssFiles) {
|
|
4766
|
-
unlinkSync2(
|
|
4961
|
+
unlinkSync2(join20(docsAppDir, stale));
|
|
4767
4962
|
}
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4963
|
+
writeFileSync12(customOverlayCssPath, newCustomOverlayCss, "utf-8");
|
|
4964
|
+
writeFileSync12(customOverlayTsPath, newCustomOverlayTs, "utf-8");
|
|
4965
|
+
writeFileSync12(themeConfigPath, newThemeConfig, "utf-8");
|
|
4966
|
+
writeFileSync12(globalsPath, newGlobals, "utf-8");
|
|
4772
4967
|
for (const srcFile of stockFiles) {
|
|
4773
|
-
copyFileSync(srcFile,
|
|
4968
|
+
copyFileSync(srcFile, join20(docsPublicThemesDir, basename4(srcFile)));
|
|
4774
4969
|
}
|
|
4775
4970
|
for (const c of customSources) {
|
|
4776
4971
|
const targetName = c.origin === "legacy" ? basename4(c.filePath) : `${c.slug}.visor.yaml`;
|
|
4777
|
-
copyFileSync(c.filePath,
|
|
4972
|
+
copyFileSync(c.filePath, join20(docsPublicThemesDir, targetName));
|
|
4778
4973
|
}
|
|
4779
4974
|
for (const stale of stalePublicYamls) {
|
|
4780
|
-
unlinkSync2(
|
|
4975
|
+
unlinkSync2(join20(docsPublicThemesDir, stale));
|
|
4781
4976
|
}
|
|
4782
4977
|
} catch (err) {
|
|
4783
4978
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
@@ -4830,19 +5025,19 @@ function themeSyncCommand(cwd, options) {
|
|
|
4830
5025
|
|
|
4831
5026
|
// src/commands/theme-batch-apply-flutter.ts
|
|
4832
5027
|
import {
|
|
4833
|
-
readFileSync as
|
|
4834
|
-
writeFileSync as
|
|
4835
|
-
mkdirSync as
|
|
4836
|
-
existsSync as
|
|
5028
|
+
readFileSync as readFileSync21,
|
|
5029
|
+
writeFileSync as writeFileSync13,
|
|
5030
|
+
mkdirSync as mkdirSync9,
|
|
5031
|
+
existsSync as existsSync18,
|
|
4837
5032
|
readdirSync as readdirSync7,
|
|
4838
5033
|
rmSync
|
|
4839
5034
|
} from "fs";
|
|
4840
|
-
import { join as
|
|
5035
|
+
import { join as join21, basename as basename5, dirname as dirname9 } from "path";
|
|
4841
5036
|
import { generateThemeData as generateThemeData6 } from "@loworbitstudio/visor-theme-engine";
|
|
4842
5037
|
import { flutterAdapter as flutterAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
4843
5038
|
function scanThemeDir2(dir) {
|
|
4844
|
-
if (!
|
|
4845
|
-
return readdirSync7(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) =>
|
|
5039
|
+
if (!existsSync18(dir)) return [];
|
|
5040
|
+
return readdirSync7(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join21(dir, f)).sort();
|
|
4846
5041
|
}
|
|
4847
5042
|
function slugToCamel(slug2) {
|
|
4848
5043
|
return slug2.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
@@ -4975,9 +5170,9 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4975
5170
|
process.exit(1);
|
|
4976
5171
|
return;
|
|
4977
5172
|
}
|
|
4978
|
-
const themesDir =
|
|
4979
|
-
const customThemesDir =
|
|
4980
|
-
const outputDir =
|
|
5173
|
+
const themesDir = join21(repoRoot, "themes");
|
|
5174
|
+
const customThemesDir = join21(repoRoot, "custom-themes");
|
|
5175
|
+
const outputDir = join21(repoRoot, "packages", "visor_themes");
|
|
4981
5176
|
const stockFiles = scanThemeDir2(themesDir);
|
|
4982
5177
|
const customFiles = scanThemeDir2(customThemesDir);
|
|
4983
5178
|
const allFiles = [...stockFiles, ...customFiles];
|
|
@@ -4998,7 +5193,7 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
4998
5193
|
for (const filePath of allFiles) {
|
|
4999
5194
|
let yamlContent;
|
|
5000
5195
|
try {
|
|
5001
|
-
yamlContent =
|
|
5196
|
+
yamlContent = readFileSync21(filePath, "utf-8");
|
|
5002
5197
|
} catch {
|
|
5003
5198
|
errors.push(`Could not read: ${filePath}`);
|
|
5004
5199
|
continue;
|
|
@@ -5077,8 +5272,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
5077
5272
|
return;
|
|
5078
5273
|
}
|
|
5079
5274
|
const slugs = processed.map((p) => p.slug);
|
|
5080
|
-
const libSrcDir =
|
|
5081
|
-
if (
|
|
5275
|
+
const libSrcDir = join21(outputDir, "lib", "src");
|
|
5276
|
+
if (existsSync18(libSrcDir)) {
|
|
5082
5277
|
rmSync(libSrcDir, { recursive: true, force: true });
|
|
5083
5278
|
}
|
|
5084
5279
|
const packageFiles = {
|
|
@@ -5090,17 +5285,17 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
5090
5285
|
};
|
|
5091
5286
|
let totalFiles = 0;
|
|
5092
5287
|
for (const [relPath, content] of Object.entries(packageFiles)) {
|
|
5093
|
-
const absPath =
|
|
5094
|
-
|
|
5095
|
-
|
|
5288
|
+
const absPath = join21(outputDir, relPath);
|
|
5289
|
+
mkdirSync9(dirname9(absPath), { recursive: true });
|
|
5290
|
+
writeFileSync13(absPath, content, "utf-8");
|
|
5096
5291
|
totalFiles++;
|
|
5097
5292
|
}
|
|
5098
5293
|
for (const { slug: slug2, tokenFiles } of processed) {
|
|
5099
|
-
const themeBaseDir =
|
|
5294
|
+
const themeBaseDir = join21(outputDir, "lib", "src", slug2);
|
|
5100
5295
|
for (const [relPath, content] of Object.entries(tokenFiles)) {
|
|
5101
|
-
const absPath =
|
|
5102
|
-
|
|
5103
|
-
|
|
5296
|
+
const absPath = join21(themeBaseDir, relPath);
|
|
5297
|
+
mkdirSync9(dirname9(absPath), { recursive: true });
|
|
5298
|
+
writeFileSync13(absPath, content, "utf-8");
|
|
5104
5299
|
totalFiles++;
|
|
5105
5300
|
}
|
|
5106
5301
|
}
|
|
@@ -5121,8 +5316,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
|
|
|
5121
5316
|
}
|
|
5122
5317
|
|
|
5123
5318
|
// src/commands/fonts-add.ts
|
|
5124
|
-
import { existsSync as
|
|
5125
|
-
import { resolve as
|
|
5319
|
+
import { existsSync as existsSync19, statSync as statSync7, readdirSync as readdirSync8, readFileSync as readFileSync22 } from "fs";
|
|
5320
|
+
import { resolve as resolve15, basename as basename6, extname as extname4 } from "path";
|
|
5126
5321
|
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
5127
5322
|
function deriveFamilySlug(filename) {
|
|
5128
5323
|
const name = basename6(filename, extname4(filename));
|
|
@@ -5166,8 +5361,8 @@ function deriveFamilySlug(filename) {
|
|
|
5166
5361
|
return parts.join("-").toLowerCase();
|
|
5167
5362
|
}
|
|
5168
5363
|
function collectWoff2Files(inputPath) {
|
|
5169
|
-
const resolved =
|
|
5170
|
-
if (!
|
|
5364
|
+
const resolved = resolve15(inputPath);
|
|
5365
|
+
if (!existsSync19(resolved)) {
|
|
5171
5366
|
throw new Error(`Path not found: ${resolved}`);
|
|
5172
5367
|
}
|
|
5173
5368
|
const stat = statSync7(resolved);
|
|
@@ -5180,7 +5375,7 @@ function collectWoff2Files(inputPath) {
|
|
|
5180
5375
|
return [resolved];
|
|
5181
5376
|
}
|
|
5182
5377
|
if (stat.isDirectory()) {
|
|
5183
|
-
const files = readdirSync8(resolved).filter((f) => extname4(f).toLowerCase() === ".woff2").map((f) =>
|
|
5378
|
+
const files = readdirSync8(resolved).filter((f) => extname4(f).toLowerCase() === ".woff2").map((f) => resolve15(resolved, f));
|
|
5184
5379
|
if (files.length === 0) {
|
|
5185
5380
|
throw new Error(
|
|
5186
5381
|
`No .woff2 files found in directory: ${resolved}`
|
|
@@ -5195,8 +5390,8 @@ function collectWoff2Files(inputPath) {
|
|
|
5195
5390
|
throw new Error(`Path is neither a file nor a directory: ${resolved}`);
|
|
5196
5391
|
}
|
|
5197
5392
|
function getNonWoff2Fonts(inputPath) {
|
|
5198
|
-
const resolved =
|
|
5199
|
-
if (!
|
|
5393
|
+
const resolved = resolve15(inputPath);
|
|
5394
|
+
if (!existsSync19(resolved) || !statSync7(resolved).isDirectory()) {
|
|
5200
5395
|
return [];
|
|
5201
5396
|
}
|
|
5202
5397
|
return readdirSync8(resolved).filter((f) => {
|
|
@@ -5233,7 +5428,7 @@ function createR2Client(config) {
|
|
|
5233
5428
|
});
|
|
5234
5429
|
}
|
|
5235
5430
|
async function uploadFile(client, bucket, key, filePath) {
|
|
5236
|
-
const body =
|
|
5431
|
+
const body = readFileSync22(filePath);
|
|
5237
5432
|
await client.send(
|
|
5238
5433
|
new PutObjectCommand({
|
|
5239
5434
|
Bucket: bucket,
|
|
@@ -5249,7 +5444,7 @@ async function fontsAddCommand(inputPath, options) {
|
|
|
5249
5444
|
const r2Config = getR2Config();
|
|
5250
5445
|
const files = collectWoff2Files(inputPath);
|
|
5251
5446
|
const familySlug = options.family ?? deriveFamilySlug(basename6(files[0]));
|
|
5252
|
-
const resolved =
|
|
5447
|
+
const resolved = resolve15(inputPath);
|
|
5253
5448
|
const nonWoff2 = statSync7(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
|
|
5254
5449
|
if (!json) {
|
|
5255
5450
|
logger.heading("Visor Font Upload");
|
|
@@ -5537,27 +5732,27 @@ function findCssFiles(dir, maxDepth = 3) {
|
|
|
5537
5732
|
}
|
|
5538
5733
|
|
|
5539
5734
|
// src/utils/patterns.ts
|
|
5540
|
-
import { existsSync as
|
|
5541
|
-
import { join as
|
|
5735
|
+
import { existsSync as existsSync21, readdirSync as readdirSync10, readFileSync as readFileSync24 } from "fs";
|
|
5736
|
+
import { join as join23 } from "path";
|
|
5542
5737
|
import { parse as parseYAML } from "yaml";
|
|
5543
5738
|
function loadPatternsFromYaml(repoRoot) {
|
|
5544
|
-
const patternsDir =
|
|
5545
|
-
if (!
|
|
5739
|
+
const patternsDir = join23(repoRoot, "patterns");
|
|
5740
|
+
if (!existsSync21(patternsDir)) return [];
|
|
5546
5741
|
const files = readdirSync10(patternsDir).filter(
|
|
5547
5742
|
(f) => f.endsWith(".visor-pattern.yaml")
|
|
5548
5743
|
);
|
|
5549
5744
|
return files.map((file) => {
|
|
5550
|
-
const content =
|
|
5745
|
+
const content = readFileSync24(join23(patternsDir, file), "utf-8");
|
|
5551
5746
|
return parseYAML(content);
|
|
5552
5747
|
}).filter(Boolean);
|
|
5553
5748
|
}
|
|
5554
5749
|
function findRepoRoot2(startDir) {
|
|
5555
5750
|
let current = startDir;
|
|
5556
5751
|
while (true) {
|
|
5557
|
-
if (
|
|
5752
|
+
if (existsSync21(join23(current, "patterns"))) {
|
|
5558
5753
|
return current;
|
|
5559
5754
|
}
|
|
5560
|
-
const parent =
|
|
5755
|
+
const parent = join23(current, "..");
|
|
5561
5756
|
if (parent === current) return null;
|
|
5562
5757
|
current = parent;
|
|
5563
5758
|
}
|
|
@@ -5936,8 +6131,8 @@ Visor Tokens (${categoryLabel}) \u2014 ${tokens2.length} tokens
|
|
|
5936
6131
|
}
|
|
5937
6132
|
|
|
5938
6133
|
// src/commands/migrate-token-substitution.ts
|
|
5939
|
-
import { readFileSync as
|
|
5940
|
-
import { resolve as
|
|
6134
|
+
import { readFileSync as readFileSync25, writeFileSync as writeFileSync14, readdirSync as readdirSync11, statSync as statSync8, existsSync as existsSync22 } from "fs";
|
|
6135
|
+
import { resolve as resolve16, join as join24, relative as relative4 } from "path";
|
|
5941
6136
|
import { parse as parseYaml4 } from "yaml";
|
|
5942
6137
|
import pc4 from "picocolors";
|
|
5943
6138
|
var V7_ENTR_SUBSTITUTION_MAP = {
|
|
@@ -5960,14 +6155,14 @@ var BUILT_IN_SUBSTITUTION_MAPS = {
|
|
|
5960
6155
|
var DEFAULT_THEME_ID = "entr";
|
|
5961
6156
|
function readMapFromThemeFile(themeId, cwd) {
|
|
5962
6157
|
const candidates = [
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
6158
|
+
join24(cwd, "themes", `${themeId}.visor.yaml`),
|
|
6159
|
+
join24(cwd, "custom-themes", `${themeId}.visor.yaml`),
|
|
6160
|
+
join24(cwd, "packages", "docs", "public", "themes", `${themeId}.visor.yaml`)
|
|
5966
6161
|
];
|
|
5967
6162
|
for (const candidate of candidates) {
|
|
5968
|
-
if (
|
|
6163
|
+
if (existsSync22(candidate)) {
|
|
5969
6164
|
try {
|
|
5970
|
-
const raw =
|
|
6165
|
+
const raw = readFileSync25(candidate, "utf-8");
|
|
5971
6166
|
const parsed = parseYaml4(raw);
|
|
5972
6167
|
if (parsed?.migrate?.["token-substitution"]) {
|
|
5973
6168
|
return parsed.migrate["token-substitution"];
|
|
@@ -6023,7 +6218,7 @@ function collectCssFiles(dirPath) {
|
|
|
6023
6218
|
function walk2(current) {
|
|
6024
6219
|
const entries = readdirSync11(current, { withFileTypes: true });
|
|
6025
6220
|
for (const entry of entries) {
|
|
6026
|
-
const fullPath =
|
|
6221
|
+
const fullPath = join24(current, entry.name);
|
|
6027
6222
|
if (entry.isDirectory()) {
|
|
6028
6223
|
if (["node_modules", ".git", ".next", "dist", "build", ".cache"].includes(entry.name)) {
|
|
6029
6224
|
continue;
|
|
@@ -6051,7 +6246,7 @@ function runSubstitutionPass(targetPath, map, themeId) {
|
|
|
6051
6246
|
const fileResults = [];
|
|
6052
6247
|
let totalSubstitutions = 0;
|
|
6053
6248
|
for (const file of files) {
|
|
6054
|
-
const content =
|
|
6249
|
+
const content = readFileSync25(file, "utf-8");
|
|
6055
6250
|
const { newContent, substitutions } = applySubstitutionsToContent(content, map);
|
|
6056
6251
|
if (substitutions.length > 0) {
|
|
6057
6252
|
fileResults.push({ file, substitutions, originalContent: content, newContent });
|
|
@@ -6085,7 +6280,7 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
6085
6280
|
process.exit(1);
|
|
6086
6281
|
return;
|
|
6087
6282
|
}
|
|
6088
|
-
const targetPath =
|
|
6283
|
+
const targetPath = resolve16(cwd, targetArg ?? ".");
|
|
6089
6284
|
try {
|
|
6090
6285
|
statSync8(targetPath);
|
|
6091
6286
|
} catch {
|
|
@@ -6113,7 +6308,7 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
6113
6308
|
if (options.json) {
|
|
6114
6309
|
if (apply) {
|
|
6115
6310
|
for (const f of result.files) {
|
|
6116
|
-
|
|
6311
|
+
writeFileSync14(f.file, f.newContent, "utf-8");
|
|
6117
6312
|
}
|
|
6118
6313
|
console.log(JSON.stringify({
|
|
6119
6314
|
success: true,
|
|
@@ -6153,7 +6348,7 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
6153
6348
|
logger.warn(`Dry run \u2014 no files written. Re-run with ${pc4.bold("--apply")} to commit changes.`);
|
|
6154
6349
|
} else {
|
|
6155
6350
|
for (const f of result.files) {
|
|
6156
|
-
|
|
6351
|
+
writeFileSync14(f.file, f.newContent, "utf-8");
|
|
6157
6352
|
}
|
|
6158
6353
|
logger.heading(`visor migrate token-substitution \u2014 applied`);
|
|
6159
6354
|
logger.blank();
|
|
@@ -6174,23 +6369,23 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
|
|
|
6174
6369
|
}
|
|
6175
6370
|
|
|
6176
6371
|
// src/commands/sandbox/init.ts
|
|
6177
|
-
import { existsSync as
|
|
6178
|
-
import { isAbsolute as
|
|
6372
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync12, readFileSync as readFileSync28, rmSync as rmSync2 } from "fs";
|
|
6373
|
+
import { isAbsolute as isAbsolute4, join as join27, resolve as resolve18, dirname as dirname11 } from "path";
|
|
6179
6374
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6180
6375
|
import * as childProcess3 from "child_process";
|
|
6181
6376
|
|
|
6182
6377
|
// src/commands/sandbox/parse-handoff.ts
|
|
6183
|
-
import { readFileSync as
|
|
6184
|
-
import { dirname as
|
|
6378
|
+
import { readFileSync as readFileSync26, existsSync as existsSync23 } from "fs";
|
|
6379
|
+
import { dirname as dirname10, resolve as resolve17, isAbsolute as isAbsolute3 } from "path";
|
|
6185
6380
|
function parseHandoff(handoffPath) {
|
|
6186
|
-
const text =
|
|
6381
|
+
const text = readFileSync26(handoffPath, "utf-8");
|
|
6187
6382
|
const lines2 = text.split("\n");
|
|
6188
6383
|
const warnings = [];
|
|
6189
6384
|
const pattern2 = extractPatternSlug(lines2, warnings);
|
|
6190
6385
|
const theme2 = extractMetaField(lines2, "Theme");
|
|
6191
6386
|
const recipePath = extractRecipePath(text, handoffPath, warnings);
|
|
6192
6387
|
const primitives = extractPrimitives(text, warnings);
|
|
6193
|
-
const { screens, mockShapes } = recipePath &&
|
|
6388
|
+
const { screens, mockShapes } = recipePath && existsSync23(recipePath) ? extractRecipeArtifacts(recipePath, warnings) : { screens: [], mockShapes: [] };
|
|
6194
6389
|
return { pattern: pattern2, theme: theme2, recipePath, primitives, screens, mockShapes, warnings };
|
|
6195
6390
|
}
|
|
6196
6391
|
function extractPatternSlug(lines2, warnings) {
|
|
@@ -6221,8 +6416,8 @@ function extractRecipePath(text, handoffPath, warnings) {
|
|
|
6221
6416
|
return void 0;
|
|
6222
6417
|
}
|
|
6223
6418
|
const href = m[2].trim();
|
|
6224
|
-
if (
|
|
6225
|
-
return
|
|
6419
|
+
if (isAbsolute3(href)) return href;
|
|
6420
|
+
return resolve17(dirname10(handoffPath), href);
|
|
6226
6421
|
}
|
|
6227
6422
|
var STATUS_PATTERNS = [
|
|
6228
6423
|
{ re: /NEW\s+gap/i, status: "gap-new" },
|
|
@@ -6269,7 +6464,7 @@ function extractPrimitives(text, warnings) {
|
|
|
6269
6464
|
return primitives;
|
|
6270
6465
|
}
|
|
6271
6466
|
function extractRecipeArtifacts(recipePath, warnings) {
|
|
6272
|
-
const text =
|
|
6467
|
+
const text = readFileSync26(recipePath, "utf-8");
|
|
6273
6468
|
const screens = extractScreens(text);
|
|
6274
6469
|
const mockShapes = extractMockFields(text, warnings);
|
|
6275
6470
|
return { screens, mockShapes };
|
|
@@ -6343,8 +6538,8 @@ function tryPort(port) {
|
|
|
6343
6538
|
}
|
|
6344
6539
|
|
|
6345
6540
|
// src/commands/sandbox/scaffold.ts
|
|
6346
|
-
import { mkdirSync as
|
|
6347
|
-
import { join as
|
|
6541
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync15, existsSync as existsSync24, readdirSync as readdirSync12 } from "fs";
|
|
6542
|
+
import { join as join25 } from "path";
|
|
6348
6543
|
|
|
6349
6544
|
// src/commands/sandbox/templates.ts
|
|
6350
6545
|
var SANDBOX_PACKAGE_VERSION = "16.2.6";
|
|
@@ -6844,12 +7039,12 @@ function toPascalCase(s) {
|
|
|
6844
7039
|
|
|
6845
7040
|
// src/commands/sandbox/scaffold.ts
|
|
6846
7041
|
function writeScaffold(sandboxDir, manifest, port, options = {}) {
|
|
6847
|
-
|
|
7042
|
+
mkdirSync10(sandboxDir, { recursive: true });
|
|
6848
7043
|
const created = [];
|
|
6849
7044
|
const writeIfNew = (rel, contents) => {
|
|
6850
|
-
const full =
|
|
6851
|
-
|
|
6852
|
-
|
|
7045
|
+
const full = join25(sandboxDir, rel);
|
|
7046
|
+
mkdirSync10(dirOf(full), { recursive: true });
|
|
7047
|
+
writeFileSync15(full, contents, "utf-8");
|
|
6853
7048
|
created.push(rel);
|
|
6854
7049
|
};
|
|
6855
7050
|
writeIfNew("package.json", packageJsonTemplate(manifest.pattern));
|
|
@@ -6875,7 +7070,7 @@ function writeScaffold(sandboxDir, manifest, port, options = {}) {
|
|
|
6875
7070
|
}
|
|
6876
7071
|
}
|
|
6877
7072
|
writeIfNew("playwright.capture.mjs", captureScriptTemplate());
|
|
6878
|
-
|
|
7073
|
+
mkdirSync10(join25(sandboxDir, "captures", "approved"), { recursive: true });
|
|
6879
7074
|
return { created };
|
|
6880
7075
|
}
|
|
6881
7076
|
function dirOf(p) {
|
|
@@ -6909,10 +7104,10 @@ function writeSandboxConfig(sandboxDir, manifest, port, options) {
|
|
|
6909
7104
|
stripChromeSelectors: options.prototypeImport.stripChromeSelectors
|
|
6910
7105
|
} : null
|
|
6911
7106
|
};
|
|
6912
|
-
|
|
7107
|
+
writeFileSync15(join25(sandboxDir, "sandbox.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
6913
7108
|
}
|
|
6914
7109
|
function sandboxIsEmpty(sandboxDir) {
|
|
6915
|
-
if (!
|
|
7110
|
+
if (!existsSync24(sandboxDir)) return true;
|
|
6916
7111
|
try {
|
|
6917
7112
|
return readdirSync12(sandboxDir).filter((f) => !f.startsWith(".")).length === 0;
|
|
6918
7113
|
} catch {
|
|
@@ -6921,8 +7116,8 @@ function sandboxIsEmpty(sandboxDir) {
|
|
|
6921
7116
|
}
|
|
6922
7117
|
|
|
6923
7118
|
// src/commands/sandbox/html-prototype.ts
|
|
6924
|
-
import { mkdirSync as
|
|
6925
|
-
import { join as
|
|
7119
|
+
import { mkdirSync as mkdirSync11, readdirSync as readdirSync13, readFileSync as readFileSync27, statSync as statSync9, writeFileSync as writeFileSync16 } from "fs";
|
|
7120
|
+
import { join as join26 } from "path";
|
|
6926
7121
|
|
|
6927
7122
|
// src/commands/sandbox/strip-chrome.ts
|
|
6928
7123
|
var DEFAULT_STRIP_SELECTORS = [
|
|
@@ -7106,8 +7301,8 @@ function stripDocumentaryChrome(html, selectors) {
|
|
|
7106
7301
|
var SCREEN_FILE_PATTERN = /^screen-(\d+)-([^/.]+)\.html$/i;
|
|
7107
7302
|
function copyHtmlPrototype(sourceDir, sandboxDir, manifest, options = {}) {
|
|
7108
7303
|
const warnings = [];
|
|
7109
|
-
const destAbs =
|
|
7110
|
-
|
|
7304
|
+
const destAbs = join26(sandboxDir, "public", "prototype");
|
|
7305
|
+
mkdirSync11(destAbs, { recursive: true });
|
|
7111
7306
|
const stripChromeSelectors = options.stripChromeSelectors ?? [];
|
|
7112
7307
|
const copiedFiles = copyTreeRelative(sourceDir, destAbs, "", stripChromeSelectors);
|
|
7113
7308
|
const screenFiles = listOrderedScreenFiles(sourceDir);
|
|
@@ -7160,24 +7355,24 @@ function deriveStateCoverageScreen(file, existingNames) {
|
|
|
7160
7355
|
}
|
|
7161
7356
|
function copyTreeRelative(srcDir, destDir, relDir = "", stripChromeSelectors = []) {
|
|
7162
7357
|
const out = [];
|
|
7163
|
-
const entries = readdirSync13(
|
|
7358
|
+
const entries = readdirSync13(join26(srcDir, relDir));
|
|
7164
7359
|
for (const name of entries) {
|
|
7165
7360
|
if (name.startsWith(".")) continue;
|
|
7166
|
-
const srcPath =
|
|
7167
|
-
const destPath =
|
|
7361
|
+
const srcPath = join26(srcDir, relDir, name);
|
|
7362
|
+
const destPath = join26(destDir, relDir, name);
|
|
7168
7363
|
const stat = statSync9(srcPath);
|
|
7169
7364
|
if (stat.isDirectory()) {
|
|
7170
|
-
|
|
7171
|
-
out.push(...copyTreeRelative(srcDir, destDir,
|
|
7365
|
+
mkdirSync11(destPath, { recursive: true });
|
|
7366
|
+
out.push(...copyTreeRelative(srcDir, destDir, join26(relDir, name), stripChromeSelectors));
|
|
7172
7367
|
} else if (stat.isFile()) {
|
|
7173
7368
|
if (stripChromeSelectors.length > 0 && name.toLowerCase().endsWith(".html")) {
|
|
7174
|
-
const html =
|
|
7369
|
+
const html = readFileSync27(srcPath, "utf-8");
|
|
7175
7370
|
const stripped = stripDocumentaryChrome(html, stripChromeSelectors);
|
|
7176
|
-
|
|
7371
|
+
writeFileSync16(destPath, stripped);
|
|
7177
7372
|
} else {
|
|
7178
|
-
|
|
7373
|
+
writeFileSync16(destPath, readFileSync27(srcPath));
|
|
7179
7374
|
}
|
|
7180
|
-
out.push(
|
|
7375
|
+
out.push(join26("public", "prototype", relDir, name).replace(/\\/g, "/"));
|
|
7181
7376
|
}
|
|
7182
7377
|
}
|
|
7183
7378
|
return out;
|
|
@@ -7229,8 +7424,8 @@ async function runInit(name, cwd, options) {
|
|
|
7229
7424
|
if (!name || !/^[a-z0-9][a-z0-9-_]*$/i.test(name)) {
|
|
7230
7425
|
throw new Error(`Invalid sandbox name '${name}'. Use letters, digits, '-' or '_'.`);
|
|
7231
7426
|
}
|
|
7232
|
-
const handoffPath =
|
|
7233
|
-
if (!
|
|
7427
|
+
const handoffPath = isAbsolute4(options.handoff) ? options.handoff : resolve18(cwd, options.handoff);
|
|
7428
|
+
if (!existsSync25(handoffPath)) {
|
|
7234
7429
|
throw new Error(`Handoff manifest not found: ${handoffPath}`);
|
|
7235
7430
|
}
|
|
7236
7431
|
const manifest = parseHandoff(handoffPath);
|
|
@@ -7239,7 +7434,7 @@ async function runInit(name, cwd, options) {
|
|
|
7239
7434
|
`Handoff has no primitives \u2014 refusing to scaffold an empty sandbox. Check the manifest at ${handoffPath}`
|
|
7240
7435
|
);
|
|
7241
7436
|
}
|
|
7242
|
-
const sandboxDir =
|
|
7437
|
+
const sandboxDir = join27(cwd, ".lo", "sandbox", name);
|
|
7243
7438
|
if (!sandboxIsEmpty(sandboxDir)) {
|
|
7244
7439
|
if (!options.overwrite) {
|
|
7245
7440
|
throw new Error(
|
|
@@ -7248,12 +7443,12 @@ async function runInit(name, cwd, options) {
|
|
|
7248
7443
|
}
|
|
7249
7444
|
rmSync2(sandboxDir, { recursive: true, force: true });
|
|
7250
7445
|
}
|
|
7251
|
-
|
|
7446
|
+
mkdirSync12(sandboxDir, { recursive: true });
|
|
7252
7447
|
const port = await findOpenPort();
|
|
7253
7448
|
let prototypeImport;
|
|
7254
7449
|
if (options.fromHtmlPrototype) {
|
|
7255
|
-
const prototypeDir =
|
|
7256
|
-
if (!
|
|
7450
|
+
const prototypeDir = isAbsolute4(options.fromHtmlPrototype) ? options.fromHtmlPrototype : resolve18(cwd, options.fromHtmlPrototype);
|
|
7451
|
+
if (!existsSync25(prototypeDir)) {
|
|
7257
7452
|
throw new Error(`HTML prototype directory not found: ${prototypeDir}`);
|
|
7258
7453
|
}
|
|
7259
7454
|
const stripChromeSelectors = resolveStripSelectors(
|
|
@@ -7312,22 +7507,22 @@ async function runInit(name, cwd, options) {
|
|
|
7312
7507
|
function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile) {
|
|
7313
7508
|
const candidates = [];
|
|
7314
7509
|
if (themeFile) {
|
|
7315
|
-
candidates.push(
|
|
7510
|
+
candidates.push(isAbsolute4(themeFile) ? themeFile : resolve18(cwd, themeFile));
|
|
7316
7511
|
}
|
|
7317
|
-
candidates.push(
|
|
7512
|
+
candidates.push(isAbsolute4(theme2) ? theme2 : resolve18(cwd, theme2));
|
|
7318
7513
|
const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
|
|
7319
7514
|
if (privateRoot && privateRoot.length > 0) {
|
|
7320
|
-
candidates.push(
|
|
7515
|
+
candidates.push(join27(privateRoot, "themes", theme2, "theme.visor.yaml"));
|
|
7321
7516
|
}
|
|
7322
7517
|
candidates.push(
|
|
7323
|
-
|
|
7324
|
-
|
|
7518
|
+
join27(cwd, "themes", `${theme2}.visor.yaml`),
|
|
7519
|
+
join27(cwd, "custom-themes", `${theme2}.visor.yaml`)
|
|
7325
7520
|
);
|
|
7326
|
-
const yamlPath = candidates.find((p) =>
|
|
7521
|
+
const yamlPath = candidates.find((p) => existsSync25(p));
|
|
7327
7522
|
if (!yamlPath) {
|
|
7328
7523
|
const searched = candidates.join(", ");
|
|
7329
7524
|
manifest.warnings.push(
|
|
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 ${
|
|
7525
|
+
`Theme '${theme2}' not found (searched: ${searched}) \u2014 sandbox uses placeholder globals.css. Run 'npx visor theme apply <path-to-theme.visor.yaml> --adapter nextjs -o ${join27(sandboxDir, "app", "globals.css")}' manually, or re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH to a directory containing themes/${theme2}/theme.visor.yaml.`
|
|
7331
7526
|
);
|
|
7332
7527
|
if (!json) {
|
|
7333
7528
|
logger.warn(`Theme '${theme2}' not found \u2014 leaving placeholder globals.css.`);
|
|
@@ -7335,12 +7530,12 @@ function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile
|
|
|
7335
7530
|
`Re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH=<dir-containing-themes/${theme2}/theme.visor.yaml>.`
|
|
7336
7531
|
);
|
|
7337
7532
|
logger.item(
|
|
7338
|
-
`Or apply manually: npx visor theme apply <path> --adapter nextjs -o ${
|
|
7533
|
+
`Or apply manually: npx visor theme apply <path> --adapter nextjs -o ${join27(sandboxDir, "app", "globals.css")}`
|
|
7339
7534
|
);
|
|
7340
7535
|
}
|
|
7341
7536
|
return;
|
|
7342
7537
|
}
|
|
7343
|
-
const globalsOut =
|
|
7538
|
+
const globalsOut = join27(sandboxDir, "app", "globals.css");
|
|
7344
7539
|
try {
|
|
7345
7540
|
themeApplyCommand(yamlPath, sandboxDir, {
|
|
7346
7541
|
output: globalsOut,
|
|
@@ -7393,12 +7588,12 @@ function loadKnownPrimitives() {
|
|
|
7393
7588
|
}
|
|
7394
7589
|
function readCliVersion() {
|
|
7395
7590
|
try {
|
|
7396
|
-
const here =
|
|
7591
|
+
const here = dirname11(fileURLToPath3(import.meta.url));
|
|
7397
7592
|
for (let i = 0; i < 6; i++) {
|
|
7398
7593
|
const segments = new Array(i).fill("..");
|
|
7399
|
-
const candidate =
|
|
7594
|
+
const candidate = join27(here, ...segments, "package.json");
|
|
7400
7595
|
try {
|
|
7401
|
-
const pkg2 = JSON.parse(
|
|
7596
|
+
const pkg2 = JSON.parse(readFileSync28(candidate, "utf-8"));
|
|
7402
7597
|
if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) return pkg2.version;
|
|
7403
7598
|
} catch {
|
|
7404
7599
|
}
|
|
@@ -7409,14 +7604,14 @@ function readCliVersion() {
|
|
|
7409
7604
|
}
|
|
7410
7605
|
|
|
7411
7606
|
// src/commands/sandbox/dev.ts
|
|
7412
|
-
import { existsSync as
|
|
7413
|
-
import { join as
|
|
7607
|
+
import { existsSync as existsSync26, readFileSync as readFileSync29 } from "fs";
|
|
7608
|
+
import { join as join28 } from "path";
|
|
7414
7609
|
import * as childProcess4 from "child_process";
|
|
7415
7610
|
function sandboxDevCommand(cwd, options) {
|
|
7416
7611
|
const json = options.json ?? false;
|
|
7417
|
-
const sandboxDir =
|
|
7418
|
-
const configPath =
|
|
7419
|
-
if (!
|
|
7612
|
+
const sandboxDir = join28(cwd, ".lo", "sandbox", options.name);
|
|
7613
|
+
const configPath = join28(sandboxDir, "sandbox.json");
|
|
7614
|
+
if (!existsSync26(configPath)) {
|
|
7420
7615
|
fail(
|
|
7421
7616
|
json,
|
|
7422
7617
|
`Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init ${options.name} --handoff ... --theme ...' first.`
|
|
@@ -7425,7 +7620,7 @@ function sandboxDevCommand(cwd, options) {
|
|
|
7425
7620
|
}
|
|
7426
7621
|
let config;
|
|
7427
7622
|
try {
|
|
7428
|
-
config = JSON.parse(
|
|
7623
|
+
config = JSON.parse(readFileSync29(configPath, "utf-8"));
|
|
7429
7624
|
} catch (err) {
|
|
7430
7625
|
fail(json, `Invalid sandbox.json at ${configPath}: ${err.message}`);
|
|
7431
7626
|
return;
|
|
@@ -7483,20 +7678,20 @@ function fail(json, message) {
|
|
|
7483
7678
|
// src/commands/sandbox/approve.ts
|
|
7484
7679
|
import {
|
|
7485
7680
|
copyFileSync as copyFileSync2,
|
|
7486
|
-
existsSync as
|
|
7487
|
-
mkdirSync as
|
|
7488
|
-
readFileSync as
|
|
7681
|
+
existsSync as existsSync27,
|
|
7682
|
+
mkdirSync as mkdirSync13,
|
|
7683
|
+
readFileSync as readFileSync30,
|
|
7489
7684
|
readdirSync as readdirSync14,
|
|
7490
7685
|
rmSync as rmSync3,
|
|
7491
|
-
writeFileSync as
|
|
7686
|
+
writeFileSync as writeFileSync17
|
|
7492
7687
|
} from "fs";
|
|
7493
|
-
import { basename as basename7, join as
|
|
7688
|
+
import { basename as basename7, join as join29 } from "path";
|
|
7494
7689
|
import * as childProcess5 from "child_process";
|
|
7495
7690
|
function sandboxApproveCommand(cwd, options) {
|
|
7496
7691
|
const json = options.json ?? false;
|
|
7497
|
-
const sandboxDir =
|
|
7498
|
-
const configPath =
|
|
7499
|
-
if (!
|
|
7692
|
+
const sandboxDir = join29(cwd, ".lo", "sandbox", options.name);
|
|
7693
|
+
const configPath = join29(sandboxDir, "sandbox.json");
|
|
7694
|
+
if (!existsSync27(configPath)) {
|
|
7500
7695
|
fail2(
|
|
7501
7696
|
json,
|
|
7502
7697
|
`Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init' first.`
|
|
@@ -7510,9 +7705,9 @@ function sandboxApproveCommand(cwd, options) {
|
|
|
7510
7705
|
runCapture(sandboxDir, configPath, options.name, json);
|
|
7511
7706
|
}
|
|
7512
7707
|
function runCapture(sandboxDir, configPath, sandboxName, json) {
|
|
7513
|
-
const config = JSON.parse(
|
|
7514
|
-
const captureScriptPath =
|
|
7515
|
-
|
|
7708
|
+
const config = JSON.parse(readFileSync30(configPath, "utf-8"));
|
|
7709
|
+
const captureScriptPath = join29(sandboxDir, "playwright.capture.mjs");
|
|
7710
|
+
writeFileSync17(captureScriptPath, captureScriptTemplate(), "utf-8");
|
|
7516
7711
|
ensurePlaywrightInstalled(sandboxDir, json);
|
|
7517
7712
|
const result = childProcess5.spawnSync(
|
|
7518
7713
|
"npx",
|
|
@@ -7533,9 +7728,9 @@ function runCapture(sandboxDir, configPath, sandboxName, json) {
|
|
|
7533
7728
|
${result.stderr}`);
|
|
7534
7729
|
return;
|
|
7535
7730
|
}
|
|
7536
|
-
const pendingDir =
|
|
7537
|
-
const diffsDir =
|
|
7538
|
-
const approvedDir =
|
|
7731
|
+
const pendingDir = join29(sandboxDir, "captures", "pending");
|
|
7732
|
+
const diffsDir = join29(sandboxDir, "captures", "diffs");
|
|
7733
|
+
const approvedDir = join29(sandboxDir, "captures", "approved");
|
|
7539
7734
|
const pendingFiles = safeListPngs(pendingDir);
|
|
7540
7735
|
const diffFiles = safeListPngs(diffsDir);
|
|
7541
7736
|
const hasBaseline = safeListPngs(approvedDir).length > 0;
|
|
@@ -7572,9 +7767,9 @@ ${result.stderr}`);
|
|
|
7572
7767
|
}
|
|
7573
7768
|
}
|
|
7574
7769
|
function runPromotion(sandboxDir, sandboxName, json) {
|
|
7575
|
-
const pendingDir =
|
|
7576
|
-
const approvedDir =
|
|
7577
|
-
const diffsDir =
|
|
7770
|
+
const pendingDir = join29(sandboxDir, "captures", "pending");
|
|
7771
|
+
const approvedDir = join29(sandboxDir, "captures", "approved");
|
|
7772
|
+
const diffsDir = join29(sandboxDir, "captures", "diffs");
|
|
7578
7773
|
const pendingFiles = safeListPngs(pendingDir);
|
|
7579
7774
|
if (pendingFiles.length === 0) {
|
|
7580
7775
|
fail2(
|
|
@@ -7583,10 +7778,10 @@ function runPromotion(sandboxDir, sandboxName, json) {
|
|
|
7583
7778
|
);
|
|
7584
7779
|
return;
|
|
7585
7780
|
}
|
|
7586
|
-
|
|
7781
|
+
mkdirSync13(approvedDir, { recursive: true });
|
|
7587
7782
|
const promoted = [];
|
|
7588
7783
|
for (const src of pendingFiles) {
|
|
7589
|
-
const dest =
|
|
7784
|
+
const dest = join29(approvedDir, basename7(src));
|
|
7590
7785
|
copyFileSync2(src, dest);
|
|
7591
7786
|
promoted.push(dest);
|
|
7592
7787
|
}
|
|
@@ -7606,8 +7801,8 @@ function runPromotion(sandboxDir, sandboxName, json) {
|
|
|
7606
7801
|
}
|
|
7607
7802
|
}
|
|
7608
7803
|
function ensurePlaywrightInstalled(sandboxDir, json) {
|
|
7609
|
-
const markerPath =
|
|
7610
|
-
if (
|
|
7804
|
+
const markerPath = join29(sandboxDir, ".playwright-installed");
|
|
7805
|
+
if (existsSync27(markerPath)) return;
|
|
7611
7806
|
if (!json) logger.info("Installing Playwright Chromium (one-time)...");
|
|
7612
7807
|
const result = childProcess5.spawnSync(
|
|
7613
7808
|
"npx",
|
|
@@ -7623,11 +7818,11 @@ function ensurePlaywrightInstalled(sandboxDir, json) {
|
|
|
7623
7818
|
if (typeof result.status === "number" && result.status !== 0) {
|
|
7624
7819
|
throw new Error(`playwright install exited with code ${result.status}`);
|
|
7625
7820
|
}
|
|
7626
|
-
|
|
7821
|
+
writeFileSync17(markerPath, (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
|
|
7627
7822
|
}
|
|
7628
7823
|
function safeListPngs(dir) {
|
|
7629
|
-
if (!
|
|
7630
|
-
return readdirSync14(dir).filter((f) => f.endsWith(".png")).map((f) =>
|
|
7824
|
+
if (!existsSync27(dir)) return [];
|
|
7825
|
+
return readdirSync14(dir).filter((f) => f.endsWith(".png")).map((f) => join29(dir, f));
|
|
7631
7826
|
}
|
|
7632
7827
|
function tryParseJson(s) {
|
|
7633
7828
|
try {
|
|
@@ -7648,14 +7843,12 @@ function fail2(json, message) {
|
|
|
7648
7843
|
// src/commands/spawn.ts
|
|
7649
7844
|
import {
|
|
7650
7845
|
cpSync,
|
|
7651
|
-
existsSync as
|
|
7652
|
-
mkdirSync as mkdirSync12,
|
|
7846
|
+
existsSync as existsSync29,
|
|
7653
7847
|
readFileSync as readFileSync31,
|
|
7654
7848
|
readdirSync as readdirSync16,
|
|
7655
|
-
rmSync as rmSync4
|
|
7656
|
-
writeFileSync as writeFileSync16
|
|
7849
|
+
rmSync as rmSync4
|
|
7657
7850
|
} from "fs";
|
|
7658
|
-
import { basename as basename8, isAbsolute as
|
|
7851
|
+
import { basename as basename8, isAbsolute as isAbsolute5, join as join31, resolve as resolve19 } from "path";
|
|
7659
7852
|
import { homedir as homedir3 } from "os";
|
|
7660
7853
|
import * as childProcess6 from "child_process";
|
|
7661
7854
|
import { parse as parseYaml5 } from "yaml";
|
|
@@ -7664,64 +7857,8 @@ import { nextjsAdapter as nextjsAdapter3 } from "@loworbitstudio/visor-theme-eng
|
|
|
7664
7857
|
import { validate as validate3 } from "@loworbitstudio/visor-theme-engine";
|
|
7665
7858
|
|
|
7666
7859
|
// src/lib/blessed-discovery.ts
|
|
7667
|
-
import { existsSync as
|
|
7668
|
-
import { join as
|
|
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
|
|
7860
|
+
import { existsSync as existsSync28, readdirSync as readdirSync15 } from "fs";
|
|
7861
|
+
import { join as join30 } from "path";
|
|
7725
7862
|
var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
7726
7863
|
"node_modules",
|
|
7727
7864
|
".next",
|
|
@@ -7734,7 +7871,7 @@ var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
|
|
|
7734
7871
|
function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
|
|
7735
7872
|
const builds = [];
|
|
7736
7873
|
const errors = [];
|
|
7737
|
-
if (!
|
|
7874
|
+
if (!existsSync28(blessedDir)) {
|
|
7738
7875
|
return { builds, errors };
|
|
7739
7876
|
}
|
|
7740
7877
|
walk2(blessedDir, 0);
|
|
@@ -7768,7 +7905,7 @@ function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
|
|
|
7768
7905
|
for (const entry of entries) {
|
|
7769
7906
|
if (!entry.isDirectory()) continue;
|
|
7770
7907
|
if (WALK_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
7771
|
-
walk2(
|
|
7908
|
+
walk2(join30(dir, entry.name), depth + 1);
|
|
7772
7909
|
}
|
|
7773
7910
|
}
|
|
7774
7911
|
}
|
|
@@ -7781,7 +7918,7 @@ function resolveBlessedBuild(blessedDir, shape, pattern2) {
|
|
|
7781
7918
|
}
|
|
7782
7919
|
|
|
7783
7920
|
// src/commands/spawn.ts
|
|
7784
|
-
var DEFAULT_BLESSED_DIR =
|
|
7921
|
+
var DEFAULT_BLESSED_DIR = join31(
|
|
7785
7922
|
homedir3(),
|
|
7786
7923
|
"Code",
|
|
7787
7924
|
"low-orbit",
|
|
@@ -7817,23 +7954,23 @@ function parseBlessedIdentifier(from) {
|
|
|
7817
7954
|
}
|
|
7818
7955
|
function resolveBlessedDir(cwd, options) {
|
|
7819
7956
|
const raw = options.blessedDir ?? (process.env.VISOR_BLESSED_DIR && process.env.VISOR_BLESSED_DIR.length > 0 ? process.env.VISOR_BLESSED_DIR : void 0) ?? DEFAULT_BLESSED_DIR;
|
|
7820
|
-
return
|
|
7957
|
+
return isAbsolute5(raw) ? raw : resolve19(cwd, raw);
|
|
7821
7958
|
}
|
|
7822
7959
|
function resolveThemeFile(theme2, cwd, themeFile) {
|
|
7823
7960
|
const candidates = [];
|
|
7824
7961
|
if (themeFile) {
|
|
7825
|
-
candidates.push(
|
|
7962
|
+
candidates.push(isAbsolute5(themeFile) ? themeFile : resolve19(cwd, themeFile));
|
|
7826
7963
|
}
|
|
7827
|
-
candidates.push(
|
|
7964
|
+
candidates.push(isAbsolute5(theme2) ? theme2 : resolve19(cwd, theme2));
|
|
7828
7965
|
const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
|
|
7829
7966
|
if (privateRoot && privateRoot.length > 0) {
|
|
7830
|
-
candidates.push(
|
|
7967
|
+
candidates.push(join31(privateRoot, "themes", theme2, "theme.visor.yaml"));
|
|
7831
7968
|
}
|
|
7832
7969
|
candidates.push(
|
|
7833
|
-
|
|
7834
|
-
|
|
7970
|
+
join31(cwd, "themes", `${theme2}.visor.yaml`),
|
|
7971
|
+
join31(cwd, "custom-themes", `${theme2}.visor.yaml`)
|
|
7835
7972
|
);
|
|
7836
|
-
const found = candidates.find((candidate) =>
|
|
7973
|
+
const found = candidates.find((candidate) => existsSync29(candidate));
|
|
7837
7974
|
if (!found) {
|
|
7838
7975
|
throw new Error(
|
|
7839
7976
|
`Theme '${theme2}' not found (searched: ${candidates.join(", ")}). Pass --theme-file <path>, use a direct path, or set VISOR_THEMES_PRIVATE_PATH.`
|
|
@@ -7841,23 +7978,25 @@ function resolveThemeFile(theme2, cwd, themeFile) {
|
|
|
7841
7978
|
}
|
|
7842
7979
|
return { path: found, searched: candidates };
|
|
7843
7980
|
}
|
|
7844
|
-
function
|
|
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) {
|
|
7981
|
+
function applyThemeToFork(themeFile, outputDir, manifest) {
|
|
7853
7982
|
const yaml = readFileSync31(themeFile, "utf-8");
|
|
7854
7983
|
const data = generateThemeData7(yaml);
|
|
7984
|
+
const themeId = data.config.name;
|
|
7985
|
+
if (!themeId || themeId.length === 0) {
|
|
7986
|
+
throw new Error(
|
|
7987
|
+
`Theme file '${themeFile}' is missing a config.name; required to derive the theme id for spawn's theme-apply dispatch. See docs/blessed-builds.md.`
|
|
7988
|
+
);
|
|
7989
|
+
}
|
|
7855
7990
|
const css = nextjsAdapter3(
|
|
7856
7991
|
{ primitives: data.primitives, tokens: data.tokens, config: data.config },
|
|
7857
7992
|
{}
|
|
7858
7993
|
);
|
|
7859
|
-
|
|
7860
|
-
|
|
7994
|
+
applyThemeToBuild({
|
|
7995
|
+
manifest,
|
|
7996
|
+
buildDir: outputDir,
|
|
7997
|
+
themeId,
|
|
7998
|
+
adapterCss: css
|
|
7999
|
+
});
|
|
7861
8000
|
}
|
|
7862
8001
|
function runNpmInstall2(outputDir, json) {
|
|
7863
8002
|
const result = childProcess6.spawnSync("npm", ["install", "--no-audit", "--no-fund"], {
|
|
@@ -7886,8 +8025,8 @@ Available builds:
|
|
|
7886
8025
|
${list}`
|
|
7887
8026
|
);
|
|
7888
8027
|
}
|
|
7889
|
-
const outputDir =
|
|
7890
|
-
if (
|
|
8028
|
+
const outputDir = isAbsolute5(options.output) ? options.output : resolve19(cwd, options.output);
|
|
8029
|
+
if (existsSync29(outputDir) && readdirSync16(outputDir).length > 0) {
|
|
7891
8030
|
throw new Error(`Output directory already exists and is not empty: ${outputDir}`);
|
|
7892
8031
|
}
|
|
7893
8032
|
const { path: themeFile } = resolveThemeFile(options.theme, cwd, options.themeFile);
|
|
@@ -7898,8 +8037,7 @@ ${list}`
|
|
|
7898
8037
|
let validated = false;
|
|
7899
8038
|
let installed = false;
|
|
7900
8039
|
try {
|
|
7901
|
-
|
|
7902
|
-
applyThemeToFork(themeFile, globalsOut);
|
|
8040
|
+
applyThemeToFork(themeFile, outputDir, build.manifest);
|
|
7903
8041
|
if (options.validate) {
|
|
7904
8042
|
const parsed = parseYaml5(readFileSync31(themeFile, "utf-8"));
|
|
7905
8043
|
const result = validate3(parsed);
|
|
@@ -8003,9 +8141,9 @@ function spawnCommand(cwd, options) {
|
|
|
8003
8141
|
}
|
|
8004
8142
|
|
|
8005
8143
|
// src/index.ts
|
|
8006
|
-
var __dirname2 =
|
|
8144
|
+
var __dirname2 = dirname12(fileURLToPath4(import.meta.url));
|
|
8007
8145
|
var pkg = JSON.parse(
|
|
8008
|
-
readFileSync32(
|
|
8146
|
+
readFileSync32(join32(__dirname2, "..", "package.json"), "utf-8")
|
|
8009
8147
|
);
|
|
8010
8148
|
var program = new Command2();
|
|
8011
8149
|
program.name("visor").description("CLI for the Visor design system").version(pkg.version);
|
|
@@ -8058,6 +8196,9 @@ theme.command("apply").description(
|
|
|
8058
8196
|
).option(
|
|
8059
8197
|
"--theme-class-name <name>",
|
|
8060
8198
|
"(flutter) class name for generated theme (default: VisorAppTheme)"
|
|
8199
|
+
).option(
|
|
8200
|
+
"--target-path <path>",
|
|
8201
|
+
"(nextjs) path to a blessed-build root; dispatches CSS through the build's theme_apply_target (VI-601), ignores --output"
|
|
8061
8202
|
).action(
|
|
8062
8203
|
(file, options) => {
|
|
8063
8204
|
themeApplyCommand(file, process.cwd(), {
|