@loworbitstudio/visor 0.4.0 → 0.5.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 +19 -0
- package/dist/CHANGELOG.json +7 -1
- package/dist/index.js +1009 -273
- package/dist/registry.json +644 -13
- package/dist/visor-manifest.json +97 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -61,6 +61,7 @@ function resolveTransitiveDeps(registry, names, onWarning) {
|
|
|
61
61
|
function collectDependencies(items) {
|
|
62
62
|
const deps = /* @__PURE__ */ new Set();
|
|
63
63
|
const devDeps = /* @__PURE__ */ new Set();
|
|
64
|
+
const pubDeps = /* @__PURE__ */ new Map();
|
|
64
65
|
for (const item of items) {
|
|
65
66
|
if (item.dependencies) {
|
|
66
67
|
for (const dep of item.dependencies) {
|
|
@@ -72,12 +73,36 @@ function collectDependencies(items) {
|
|
|
72
73
|
devDeps.add(dep);
|
|
73
74
|
}
|
|
74
75
|
}
|
|
76
|
+
if (item.pubDependencies) {
|
|
77
|
+
for (const dep of item.pubDependencies) {
|
|
78
|
+
pubDeps.set(dep.pub, dep);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
75
81
|
}
|
|
76
82
|
return {
|
|
77
83
|
dependencies: Array.from(deps).sort(),
|
|
78
|
-
devDependencies: Array.from(devDeps).sort()
|
|
84
|
+
devDependencies: Array.from(devDeps).sort(),
|
|
85
|
+
pubDependencies: Array.from(pubDeps.values()).sort(
|
|
86
|
+
(a, b) => a.pub.localeCompare(b.pub)
|
|
87
|
+
)
|
|
79
88
|
};
|
|
80
89
|
}
|
|
90
|
+
function filterItemsByTarget(items, target) {
|
|
91
|
+
return items.filter(
|
|
92
|
+
(item) => item.target === target || item.target === void 0
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
function slug(name) {
|
|
96
|
+
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
97
|
+
}
|
|
98
|
+
function findItemForTarget(registry, name, target) {
|
|
99
|
+
const needle = slug(name);
|
|
100
|
+
return registry.items.find(
|
|
101
|
+
(item) => slug(item.name) === needle && item.target === target
|
|
102
|
+
) ?? registry.items.find(
|
|
103
|
+
(item) => slug(item.name) === needle && item.target === void 0
|
|
104
|
+
);
|
|
105
|
+
}
|
|
81
106
|
|
|
82
107
|
// src/check/catalog.ts
|
|
83
108
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -524,8 +549,10 @@ function checkCommand() {
|
|
|
524
549
|
}
|
|
525
550
|
|
|
526
551
|
// src/commands/init.ts
|
|
527
|
-
import { existsSync as existsSync3, writeFileSync as writeFileSync2, mkdirSync } from "fs";
|
|
552
|
+
import { existsSync as existsSync3, writeFileSync as writeFileSync2, mkdirSync, readFileSync as readFileSync5 } from "fs";
|
|
528
553
|
import { join as join5, dirname as dirname2 } from "path";
|
|
554
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
555
|
+
import * as childProcess from "child_process";
|
|
529
556
|
|
|
530
557
|
// src/config/config.ts
|
|
531
558
|
import { readFileSync as readFileSync3, writeFileSync, existsSync } from "fs";
|
|
@@ -536,6 +563,7 @@ var DEFAULT_CONFIG = {
|
|
|
536
563
|
paths: {
|
|
537
564
|
components: "components/ui",
|
|
538
565
|
deckComponents: "components/deck",
|
|
566
|
+
flutterComponents: "lib/visor/components",
|
|
539
567
|
blocks: "blocks",
|
|
540
568
|
hooks: "hooks",
|
|
541
569
|
lib: "lib"
|
|
@@ -624,11 +652,45 @@ function installPackages(packages, cwd, dev = false) {
|
|
|
624
652
|
}
|
|
625
653
|
|
|
626
654
|
// src/commands/templates/nextjs.ts
|
|
655
|
+
var NEXTJS_PINNED_VERSION = "15.1.6";
|
|
656
|
+
var CREATE_NEXT_APP_FLAGS = [
|
|
657
|
+
"--ts",
|
|
658
|
+
"--app",
|
|
659
|
+
"--no-tailwind",
|
|
660
|
+
"--no-eslint",
|
|
661
|
+
"--no-src-dir",
|
|
662
|
+
"--import-alias",
|
|
663
|
+
"@/*",
|
|
664
|
+
"--use-npm"
|
|
665
|
+
];
|
|
627
666
|
var NEXTJS_STARTER_YAML = `name: my-app
|
|
628
667
|
version: 1
|
|
629
668
|
colors:
|
|
630
669
|
primary: "#2563EB"
|
|
631
670
|
`;
|
|
671
|
+
function generateNextjsLayout() {
|
|
672
|
+
return `import "./globals.css";
|
|
673
|
+
import { FOWT_SCRIPT } from "@loworbitstudio/visor-theme-engine/fowt";
|
|
674
|
+
import type { Metadata } from "next";
|
|
675
|
+
import type { ReactNode } from "react";
|
|
676
|
+
|
|
677
|
+
export const metadata: Metadata = {
|
|
678
|
+
title: "My Visor App",
|
|
679
|
+
description: "Built with Visor \u2014 Low Orbit Studio's design system.",
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
683
|
+
return (
|
|
684
|
+
<html lang="en">
|
|
685
|
+
<head>
|
|
686
|
+
<script>{FOWT_SCRIPT}</script>
|
|
687
|
+
</head>
|
|
688
|
+
<body>{children}</body>
|
|
689
|
+
</html>
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
`;
|
|
693
|
+
}
|
|
632
694
|
|
|
633
695
|
// src/commands/init.ts
|
|
634
696
|
import { generateThemeData } from "@loworbitstudio/visor-theme-engine";
|
|
@@ -639,18 +701,14 @@ function initCommand(cwd, options) {
|
|
|
639
701
|
const filesSkipped = [];
|
|
640
702
|
const warnings = [];
|
|
641
703
|
if (options?.template && options.template !== "nextjs") {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
} else {
|
|
651
|
-
logger.error(`Unknown template: ${options.template}`);
|
|
652
|
-
logger.info("Available templates: nextjs");
|
|
653
|
-
}
|
|
704
|
+
emitError(json, `Unknown template: ${options.template}. Available templates: nextjs`);
|
|
705
|
+
process.exit(1);
|
|
706
|
+
}
|
|
707
|
+
if (options?.template === "nextjs" && existsSync3(join5(cwd, "package.json"))) {
|
|
708
|
+
emitError(
|
|
709
|
+
json,
|
|
710
|
+
"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"
|
|
711
|
+
);
|
|
654
712
|
process.exit(1);
|
|
655
713
|
}
|
|
656
714
|
if (configExists(cwd)) {
|
|
@@ -673,51 +731,62 @@ function initCommand(cwd, options) {
|
|
|
673
731
|
}
|
|
674
732
|
}
|
|
675
733
|
if (options?.template === "nextjs") {
|
|
676
|
-
scaffoldNextjs(cwd, json, filesCreated, filesSkipped);
|
|
734
|
+
scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings);
|
|
677
735
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
736
|
+
if (options?.template !== "nextjs") {
|
|
737
|
+
const missingTokens = !hasVisorTokens(cwd);
|
|
738
|
+
if (missingTokens) {
|
|
739
|
+
const warning = "@loworbitstudio/visor-core is not installed. Components require it for styling.";
|
|
740
|
+
warnings.push(warning);
|
|
741
|
+
if (!json) {
|
|
742
|
+
logger.blank();
|
|
743
|
+
logger.warn(warning);
|
|
744
|
+
logger.info(" For a complete one-command setup: run `npx @loworbitstudio/visor init --template nextjs` in an empty directory.");
|
|
745
|
+
}
|
|
686
746
|
}
|
|
687
747
|
}
|
|
688
748
|
if (json) {
|
|
689
|
-
const nextSteps =
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
nextSteps
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
nextSteps.push("Re-run with --template nextjs to generate tokens inline (no npm package needed)");
|
|
699
|
-
}
|
|
700
|
-
console.log(
|
|
701
|
-
JSON.stringify(
|
|
702
|
-
{
|
|
703
|
-
success: true,
|
|
704
|
-
config: DEFAULT_CONFIG,
|
|
705
|
-
files: { created: filesCreated, skipped: filesSkipped },
|
|
706
|
-
warnings,
|
|
707
|
-
nextSteps
|
|
708
|
-
},
|
|
709
|
-
null,
|
|
710
|
-
2
|
|
711
|
-
)
|
|
712
|
-
);
|
|
749
|
+
const nextSteps = buildNextSteps(options, warnings);
|
|
750
|
+
const result = {
|
|
751
|
+
success: true,
|
|
752
|
+
config: DEFAULT_CONFIG,
|
|
753
|
+
files: { created: filesCreated, skipped: filesSkipped },
|
|
754
|
+
warnings,
|
|
755
|
+
nextSteps
|
|
756
|
+
};
|
|
757
|
+
console.log(JSON.stringify(result, null, 2));
|
|
713
758
|
process.exit(0);
|
|
714
759
|
}
|
|
715
760
|
}
|
|
716
|
-
function
|
|
761
|
+
function buildNextSteps(options, warnings) {
|
|
762
|
+
const steps = [];
|
|
763
|
+
if (options?.template === "nextjs") {
|
|
764
|
+
steps.push("Run: npm run dev \u2014 start the development server");
|
|
765
|
+
steps.push("Customize colors in .visor.yaml, then re-run `npx visor theme apply .visor.yaml --adapter nextjs`");
|
|
766
|
+
steps.push("Run: npx visor add button \u2014 add your first component");
|
|
767
|
+
} else {
|
|
768
|
+
steps.push("Run: npx visor add button \u2014 add your first component");
|
|
769
|
+
}
|
|
770
|
+
if (warnings.some((w) => w.includes("visor-core"))) {
|
|
771
|
+
steps.push("For a complete one-command setup: re-run with --template nextjs in an empty directory");
|
|
772
|
+
}
|
|
773
|
+
return steps;
|
|
774
|
+
}
|
|
775
|
+
function emitError(json, message) {
|
|
776
|
+
if (json) {
|
|
777
|
+
const result = { success: false, error: message };
|
|
778
|
+
console.log(JSON.stringify(result, null, 2));
|
|
779
|
+
} else {
|
|
780
|
+
logger.error(message);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
|
|
717
784
|
if (!json) {
|
|
718
785
|
logger.blank();
|
|
719
|
-
logger.info("Scaffolding
|
|
786
|
+
logger.info("Scaffolding a Borealis-native Next.js app...");
|
|
720
787
|
}
|
|
788
|
+
runCreateNextApp(cwd, json);
|
|
789
|
+
runInstallVisorDeps(cwd, json);
|
|
721
790
|
const yamlPath = join5(cwd, ".visor.yaml");
|
|
722
791
|
if (existsSync3(yamlPath)) {
|
|
723
792
|
filesSkipped.push(".visor.yaml");
|
|
@@ -739,32 +808,110 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped) {
|
|
|
739
808
|
});
|
|
740
809
|
const globalsPath = join5(cwd, "app", "globals.css");
|
|
741
810
|
const globalsDir = dirname2(globalsPath);
|
|
811
|
+
mkdirSync(globalsDir, { recursive: true });
|
|
742
812
|
if (existsSync3(globalsPath)) {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
logger.warn("app/globals.css already exists. Skipping.");
|
|
746
|
-
}
|
|
813
|
+
writeFileSync2(globalsPath, css, "utf-8");
|
|
814
|
+
filesCreated.push("app/globals.css");
|
|
747
815
|
} else {
|
|
748
|
-
mkdirSync(globalsDir, { recursive: true });
|
|
749
816
|
writeFileSync2(globalsPath, css, "utf-8");
|
|
750
817
|
filesCreated.push("app/globals.css");
|
|
818
|
+
}
|
|
819
|
+
if (!json) {
|
|
820
|
+
logger.success("Created app/globals.css with theme tokens");
|
|
821
|
+
}
|
|
822
|
+
const layoutPath = join5(cwd, "app", "layout.tsx");
|
|
823
|
+
writeFileSync2(layoutPath, generateNextjsLayout(), "utf-8");
|
|
824
|
+
filesCreated.push("app/layout.tsx");
|
|
825
|
+
if (!json) {
|
|
826
|
+
logger.success("Wired app/layout.tsx with FOWT prevention and theme tokens");
|
|
827
|
+
}
|
|
828
|
+
const stampDir = join5(cwd, ".lo");
|
|
829
|
+
const stampPath = join5(stampDir, "borealis.json");
|
|
830
|
+
if (existsSync3(stampPath)) {
|
|
831
|
+
filesSkipped.push(".lo/borealis.json");
|
|
832
|
+
if (!json) {
|
|
833
|
+
logger.warn(".lo/borealis.json already exists. Skipping.");
|
|
834
|
+
}
|
|
835
|
+
} else {
|
|
836
|
+
mkdirSync(stampDir, { recursive: true });
|
|
837
|
+
const stamp = {
|
|
838
|
+
visorVersion: readVisorCliVersion(),
|
|
839
|
+
initializedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
840
|
+
};
|
|
841
|
+
writeFileSync2(stampPath, JSON.stringify(stamp, null, 2) + "\n", "utf-8");
|
|
842
|
+
filesCreated.push(".lo/borealis.json");
|
|
751
843
|
if (!json) {
|
|
752
|
-
logger.success("
|
|
844
|
+
logger.success("Stamped .lo/borealis.json");
|
|
753
845
|
}
|
|
754
846
|
}
|
|
755
847
|
if (!json) {
|
|
848
|
+
logger.blank();
|
|
849
|
+
logger.success("Your Borealis-native Next.js app is ready.");
|
|
756
850
|
logger.blank();
|
|
757
851
|
logger.info("Next steps:");
|
|
758
|
-
logger.item("
|
|
759
|
-
logger.item("
|
|
760
|
-
logger.item("
|
|
852
|
+
logger.item("npm run dev # start the dev server");
|
|
853
|
+
logger.item("Edit .visor.yaml to customize tokens, then re-run theme apply");
|
|
854
|
+
logger.item("npx visor add button # add your first component");
|
|
855
|
+
}
|
|
856
|
+
void warnings;
|
|
857
|
+
}
|
|
858
|
+
function runCreateNextApp(cwd, json) {
|
|
859
|
+
if (!json) {
|
|
860
|
+
logger.info(`Running create-next-app@${NEXTJS_PINNED_VERSION}...`);
|
|
761
861
|
}
|
|
862
|
+
const result = childProcess.spawnSync(
|
|
863
|
+
"npx",
|
|
864
|
+
[`create-next-app@${NEXTJS_PINNED_VERSION}`, ".", ...CREATE_NEXT_APP_FLAGS],
|
|
865
|
+
{ cwd, stdio: json ? "ignore" : "inherit" }
|
|
866
|
+
);
|
|
867
|
+
assertSpawnSuccess(result, "create-next-app");
|
|
868
|
+
}
|
|
869
|
+
function runInstallVisorDeps(cwd, json) {
|
|
870
|
+
if (!json) {
|
|
871
|
+
logger.info("Installing @loworbitstudio/visor-core and visor-theme-engine...");
|
|
872
|
+
}
|
|
873
|
+
const result = childProcess.spawnSync(
|
|
874
|
+
"npm",
|
|
875
|
+
[
|
|
876
|
+
"install",
|
|
877
|
+
"@loworbitstudio/visor-core",
|
|
878
|
+
"@loworbitstudio/visor-theme-engine"
|
|
879
|
+
],
|
|
880
|
+
{ cwd, stdio: json ? "ignore" : "inherit" }
|
|
881
|
+
);
|
|
882
|
+
assertSpawnSuccess(result, "npm install");
|
|
883
|
+
}
|
|
884
|
+
function assertSpawnSuccess(result, label) {
|
|
885
|
+
if (result.error) {
|
|
886
|
+
throw new Error(`${label} failed to start: ${result.error.message}`);
|
|
887
|
+
}
|
|
888
|
+
if (typeof result.status === "number" && result.status !== 0) {
|
|
889
|
+
throw new Error(`${label} exited with code ${result.status}`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
function readVisorCliVersion() {
|
|
893
|
+
try {
|
|
894
|
+
const here = dirname2(fileURLToPath2(import.meta.url));
|
|
895
|
+
for (let i = 0; i < 5; i++) {
|
|
896
|
+
const segments = new Array(i).fill("..");
|
|
897
|
+
const candidate = join5(here, ...segments, "package.json");
|
|
898
|
+
try {
|
|
899
|
+
const pkg = JSON.parse(readFileSync5(candidate, "utf-8"));
|
|
900
|
+
if (pkg.name === "@loworbitstudio/visor" && pkg.version) {
|
|
901
|
+
return pkg.version;
|
|
902
|
+
}
|
|
903
|
+
} catch {
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
} catch {
|
|
907
|
+
}
|
|
908
|
+
return "0.0.0-dev";
|
|
762
909
|
}
|
|
763
910
|
|
|
764
911
|
// src/utils/fs.ts
|
|
765
912
|
import {
|
|
766
913
|
writeFileSync as writeFileSync3,
|
|
767
|
-
readFileSync as
|
|
914
|
+
readFileSync as readFileSync6,
|
|
768
915
|
existsSync as existsSync4,
|
|
769
916
|
mkdirSync as mkdirSync2
|
|
770
917
|
} from "fs";
|
|
@@ -780,6 +927,10 @@ function resolveOutputPath(registryPath, type, config, cwd) {
|
|
|
780
927
|
relativePath = registryPath.replace(/^components\/deck\//, "");
|
|
781
928
|
return join6(cwd, config.paths.deckComponents, relativePath);
|
|
782
929
|
}
|
|
930
|
+
if (registryPath.startsWith("components/flutter/")) {
|
|
931
|
+
relativePath = registryPath.replace(/^components\/flutter\//, "");
|
|
932
|
+
return join6(cwd, config.paths.flutterComponents, relativePath);
|
|
933
|
+
}
|
|
783
934
|
relativePath = registryPath.replace(/^components\/ui\//, "");
|
|
784
935
|
return join6(cwd, config.paths.components, relativePath);
|
|
785
936
|
}
|
|
@@ -802,7 +953,7 @@ function writeFile(filePath, content) {
|
|
|
802
953
|
}
|
|
803
954
|
function readFile(filePath) {
|
|
804
955
|
if (!existsSync4(filePath)) return null;
|
|
805
|
-
return
|
|
956
|
+
return readFileSync6(filePath, "utf-8");
|
|
806
957
|
}
|
|
807
958
|
function fileExists(filePath) {
|
|
808
959
|
return existsSync4(filePath);
|
|
@@ -945,10 +1096,140 @@ function listCommand(cwd, options = {}) {
|
|
|
945
1096
|
}
|
|
946
1097
|
}
|
|
947
1098
|
|
|
1099
|
+
// src/utils/pubspec.ts
|
|
1100
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
1101
|
+
import { join as join7 } from "path";
|
|
1102
|
+
import { parseDocument, YAMLMap } from "yaml";
|
|
1103
|
+
function mergePubspec(pubspecText, deps) {
|
|
1104
|
+
const doc = parseDocument(pubspecText);
|
|
1105
|
+
const added = [];
|
|
1106
|
+
const skipped = [];
|
|
1107
|
+
let depsNode = doc.get("dependencies");
|
|
1108
|
+
if (depsNode == null || !(depsNode instanceof YAMLMap)) {
|
|
1109
|
+
depsNode = new YAMLMap();
|
|
1110
|
+
doc.set("dependencies", depsNode);
|
|
1111
|
+
}
|
|
1112
|
+
for (const dep of deps) {
|
|
1113
|
+
if (depsNode.has(dep.pub)) {
|
|
1114
|
+
skipped.push(dep.pub);
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
depsNode.set(dep.pub, dep.version);
|
|
1118
|
+
added.push(dep.pub);
|
|
1119
|
+
}
|
|
1120
|
+
return { text: doc.toString(), added, skipped };
|
|
1121
|
+
}
|
|
1122
|
+
function pubspecPath(cwd) {
|
|
1123
|
+
return join7(cwd, "pubspec.yaml");
|
|
1124
|
+
}
|
|
1125
|
+
function pubspecExists(cwd) {
|
|
1126
|
+
return existsSync5(pubspecPath(cwd));
|
|
1127
|
+
}
|
|
1128
|
+
function isPubPackageInstalled(packageName, cwd) {
|
|
1129
|
+
if (!pubspecExists(cwd)) return false;
|
|
1130
|
+
const text = readFileSync7(pubspecPath(cwd), "utf-8");
|
|
1131
|
+
const doc = parseDocument(text);
|
|
1132
|
+
const depsNode = doc.get("dependencies");
|
|
1133
|
+
if (!(depsNode instanceof YAMLMap)) return false;
|
|
1134
|
+
return depsNode.has(packageName);
|
|
1135
|
+
}
|
|
1136
|
+
function getUninstalledPubDeps(deps, cwd) {
|
|
1137
|
+
return deps.filter((d) => !isPubPackageInstalled(d.pub, cwd));
|
|
1138
|
+
}
|
|
1139
|
+
function addPubDependencies(deps, cwd) {
|
|
1140
|
+
const path2 = pubspecPath(cwd);
|
|
1141
|
+
if (!existsSync5(path2)) {
|
|
1142
|
+
throw new Error(
|
|
1143
|
+
`No pubspec.yaml found at ${path2}. Run this command from a Flutter project root.`
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
const text = readFileSync7(path2, "utf-8");
|
|
1147
|
+
const result = mergePubspec(text, deps);
|
|
1148
|
+
if (result.added.length > 0) {
|
|
1149
|
+
writeFileSync4(path2, result.text, "utf-8");
|
|
1150
|
+
}
|
|
1151
|
+
return result;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// src/utils/flutter.ts
|
|
1155
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
1156
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
1157
|
+
import { homedir } from "os";
|
|
1158
|
+
import { join as join8 } from "path";
|
|
1159
|
+
function isExecutable(path2) {
|
|
1160
|
+
try {
|
|
1161
|
+
const s = statSync2(path2);
|
|
1162
|
+
return s.isFile();
|
|
1163
|
+
} catch {
|
|
1164
|
+
return false;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
function fromPath(env) {
|
|
1168
|
+
const pathVar = env.PATH ?? "";
|
|
1169
|
+
const sep = process.platform === "win32" ? ";" : ":";
|
|
1170
|
+
const bin = process.platform === "win32" ? "flutter.bat" : "flutter";
|
|
1171
|
+
for (const dir of pathVar.split(sep)) {
|
|
1172
|
+
if (!dir) continue;
|
|
1173
|
+
const candidate = join8(dir, bin);
|
|
1174
|
+
if (isExecutable(candidate)) return candidate;
|
|
1175
|
+
}
|
|
1176
|
+
return null;
|
|
1177
|
+
}
|
|
1178
|
+
function fromFvm(home) {
|
|
1179
|
+
const fvmDefault = join8(home, "fvm", "default", "bin", "flutter");
|
|
1180
|
+
if (isExecutable(fvmDefault)) return fvmDefault;
|
|
1181
|
+
const versionsDir = join8(home, "fvm", "versions");
|
|
1182
|
+
if (!existsSync6(versionsDir)) return null;
|
|
1183
|
+
let best = null;
|
|
1184
|
+
try {
|
|
1185
|
+
for (const name of readdirSync2(versionsDir)) {
|
|
1186
|
+
const candidate = join8(versionsDir, name, "bin", "flutter");
|
|
1187
|
+
if (!isExecutable(candidate)) continue;
|
|
1188
|
+
if (!best || compareSemver(name, best.version) > 0) {
|
|
1189
|
+
best = { version: name, path: candidate };
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
} catch {
|
|
1193
|
+
return null;
|
|
1194
|
+
}
|
|
1195
|
+
return best?.path ?? null;
|
|
1196
|
+
}
|
|
1197
|
+
function compareSemver(a, b) {
|
|
1198
|
+
const pa = a.split(".").map((x) => parseInt(x, 10) || 0);
|
|
1199
|
+
const pb = b.split(".").map((x) => parseInt(x, 10) || 0);
|
|
1200
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
1201
|
+
const av = pa[i] ?? 0;
|
|
1202
|
+
const bv = pb[i] ?? 0;
|
|
1203
|
+
if (av !== bv) return av - bv;
|
|
1204
|
+
}
|
|
1205
|
+
return 0;
|
|
1206
|
+
}
|
|
1207
|
+
function findFlutterBin(options = {}) {
|
|
1208
|
+
const env = options.env ?? process.env;
|
|
1209
|
+
const home = options.home ?? homedir();
|
|
1210
|
+
const envRoot = env.FLUTTER_ROOT;
|
|
1211
|
+
if (envRoot) {
|
|
1212
|
+
const bin = join8(envRoot, "bin", "flutter");
|
|
1213
|
+
if (isExecutable(bin)) return bin;
|
|
1214
|
+
}
|
|
1215
|
+
const fromPathBin = fromPath(env);
|
|
1216
|
+
if (fromPathBin) return fromPathBin;
|
|
1217
|
+
return fromFvm(home);
|
|
1218
|
+
}
|
|
1219
|
+
function runFlutterPubGet(cwd, bin) {
|
|
1220
|
+
try {
|
|
1221
|
+
execFileSync2(bin, ["pub", "get"], { cwd, stdio: "inherit" });
|
|
1222
|
+
return true;
|
|
1223
|
+
} catch {
|
|
1224
|
+
return false;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
948
1228
|
// src/commands/add.ts
|
|
949
1229
|
function addCommand(components, cwd, options = {}) {
|
|
950
1230
|
const json = options.json ?? false;
|
|
951
1231
|
const dryRun = options.dryRun ?? false;
|
|
1232
|
+
const target = options.target ?? "react";
|
|
952
1233
|
const prefix = dryRun ? "[dry-run] " : "";
|
|
953
1234
|
let autoInitialized = false;
|
|
954
1235
|
if (!configExists(cwd)) {
|
|
@@ -973,9 +1254,12 @@ function addCommand(components, cwd, options = {}) {
|
|
|
973
1254
|
}
|
|
974
1255
|
process.exit(1);
|
|
975
1256
|
}
|
|
1257
|
+
const targetRegistry = {
|
|
1258
|
+
items: filterItemsByTarget(registry.items, target)
|
|
1259
|
+
};
|
|
976
1260
|
if (options.block && components.length > 0) {
|
|
977
1261
|
for (const name of components) {
|
|
978
|
-
const item =
|
|
1262
|
+
const item = targetRegistry.items.find((i) => i.name === name);
|
|
979
1263
|
if (item && item.type !== "registry:block") {
|
|
980
1264
|
if (json) {
|
|
981
1265
|
console.log(
|
|
@@ -1012,7 +1296,7 @@ function addCommand(components, cwd, options = {}) {
|
|
|
1012
1296
|
}
|
|
1013
1297
|
process.exit(1);
|
|
1014
1298
|
}
|
|
1015
|
-
const categoryItems =
|
|
1299
|
+
const categoryItems = targetRegistry.items.filter(
|
|
1016
1300
|
(item) => item.category === options.category
|
|
1017
1301
|
);
|
|
1018
1302
|
if (categoryItems.length === 0) {
|
|
@@ -1038,7 +1322,7 @@ function addCommand(components, cwd, options = {}) {
|
|
|
1038
1322
|
}
|
|
1039
1323
|
if (itemNames.length === 0) {
|
|
1040
1324
|
if (options.block) {
|
|
1041
|
-
const blockItems =
|
|
1325
|
+
const blockItems = targetRegistry.items.filter(
|
|
1042
1326
|
(item) => item.type === "registry:block"
|
|
1043
1327
|
);
|
|
1044
1328
|
if (json) {
|
|
@@ -1077,10 +1361,24 @@ function addCommand(components, cwd, options = {}) {
|
|
|
1077
1361
|
}
|
|
1078
1362
|
process.exit(1);
|
|
1079
1363
|
}
|
|
1364
|
+
const canonicalNames = [];
|
|
1365
|
+
for (const name of itemNames) {
|
|
1366
|
+
const resolved = findItemForTarget(targetRegistry, name, target);
|
|
1367
|
+
if (!resolved) {
|
|
1368
|
+
const message = `Registry item "${name}" not found for target "${target}".`;
|
|
1369
|
+
if (json) {
|
|
1370
|
+
console.log(JSON.stringify({ success: false, error: message }, null, 2));
|
|
1371
|
+
} else {
|
|
1372
|
+
logger.error(message);
|
|
1373
|
+
}
|
|
1374
|
+
process.exit(1);
|
|
1375
|
+
}
|
|
1376
|
+
canonicalNames.push(resolved.name);
|
|
1377
|
+
}
|
|
1080
1378
|
const circularWarnings = [];
|
|
1081
1379
|
let items;
|
|
1082
1380
|
try {
|
|
1083
|
-
items = resolveTransitiveDeps(
|
|
1381
|
+
items = resolveTransitiveDeps(targetRegistry, canonicalNames, (msg) => {
|
|
1084
1382
|
circularWarnings.push(msg);
|
|
1085
1383
|
});
|
|
1086
1384
|
} catch (error) {
|
|
@@ -1134,66 +1432,124 @@ function addCommand(components, cwd, options = {}) {
|
|
|
1134
1432
|
`${prefix}Files: ${writtenFiles.length} written, ${skippedFiles.length} skipped`
|
|
1135
1433
|
);
|
|
1136
1434
|
}
|
|
1137
|
-
const { dependencies, devDependencies } = collectDependencies(items);
|
|
1138
|
-
const uninstalledDeps = dryRun ? dependencies : getUninstalledDeps(dependencies, cwd);
|
|
1139
|
-
const uninstalledDevDeps = dryRun ? devDependencies : getUninstalledDeps(devDependencies, cwd);
|
|
1435
|
+
const { dependencies, devDependencies, pubDependencies } = collectDependencies(items);
|
|
1140
1436
|
const installedDeps = [];
|
|
1141
1437
|
const failedDeps = [];
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1438
|
+
const warnings = [];
|
|
1439
|
+
if (target === "flutter") {
|
|
1440
|
+
const uninstalledPubDeps = dryRun ? pubDependencies : pubspecExists(cwd) ? getUninstalledPubDeps(pubDependencies, cwd) : pubDependencies;
|
|
1441
|
+
if (uninstalledPubDeps.length > 0) {
|
|
1442
|
+
if (dryRun) {
|
|
1443
|
+
if (!json) {
|
|
1444
|
+
logger.blank();
|
|
1445
|
+
logger.info(
|
|
1446
|
+
`${prefix}Would add pub dependencies: ${uninstalledPubDeps.map((d) => `${d.pub}@${d.version}`).join(", ")}`
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
installedDeps.push(...uninstalledPubDeps.map((d) => d.pub));
|
|
1450
|
+
} else if (!pubspecExists(cwd)) {
|
|
1451
|
+
const message = "No pubspec.yaml found. Run this from a Flutter project root, or add " + uninstalledPubDeps.map((d) => `${d.pub}: ${d.version}`).join(", ") + " to pubspec.yaml manually.";
|
|
1452
|
+
warnings.push(message);
|
|
1453
|
+
if (!json) {
|
|
1454
|
+
logger.blank();
|
|
1455
|
+
logger.warn(message);
|
|
1456
|
+
}
|
|
1457
|
+
} else {
|
|
1458
|
+
if (!json) {
|
|
1459
|
+
logger.blank();
|
|
1460
|
+
logger.info("Updating pubspec.yaml...");
|
|
1461
|
+
}
|
|
1462
|
+
const result = addPubDependencies(uninstalledPubDeps, cwd);
|
|
1463
|
+
installedDeps.push(...result.added);
|
|
1464
|
+
const flutterBin = findFlutterBin();
|
|
1465
|
+
if (flutterBin) {
|
|
1466
|
+
if (!json) {
|
|
1467
|
+
logger.info("Running flutter pub get...");
|
|
1468
|
+
}
|
|
1469
|
+
if (!runFlutterPubGet(cwd, flutterBin)) {
|
|
1470
|
+
const warning = "flutter pub get failed. Run it manually to refresh dependencies.";
|
|
1471
|
+
warnings.push(warning);
|
|
1472
|
+
if (!json) logger.warn(warning);
|
|
1473
|
+
}
|
|
1474
|
+
} else {
|
|
1475
|
+
const warning = "flutter CLI not found. Run `flutter pub get` manually after setting up Flutter (or FVM).";
|
|
1476
|
+
warnings.push(warning);
|
|
1477
|
+
if (!json) logger.warn(warning);
|
|
1478
|
+
}
|
|
1153
1479
|
}
|
|
1154
|
-
|
|
1480
|
+
}
|
|
1481
|
+
} else {
|
|
1482
|
+
const uninstalledDeps = dryRun ? dependencies : getUninstalledDeps(dependencies, cwd);
|
|
1483
|
+
const uninstalledDevDeps = dryRun ? devDependencies : getUninstalledDeps(devDependencies, cwd);
|
|
1484
|
+
if (uninstalledDeps.length > 0) {
|
|
1485
|
+
if (dryRun) {
|
|
1486
|
+
if (!json) {
|
|
1487
|
+
logger.blank();
|
|
1488
|
+
logger.info(
|
|
1489
|
+
`${prefix}Would install dependencies: ${uninstalledDeps.join(", ")}`
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1155
1492
|
installedDeps.push(...uninstalledDeps);
|
|
1156
1493
|
} else {
|
|
1157
|
-
failedDeps.push(...uninstalledDeps);
|
|
1158
1494
|
if (!json) {
|
|
1159
|
-
logger.
|
|
1160
|
-
logger.info(
|
|
1495
|
+
logger.blank();
|
|
1496
|
+
logger.info("Installing dependencies...");
|
|
1497
|
+
}
|
|
1498
|
+
if (installPackages(uninstalledDeps, cwd)) {
|
|
1499
|
+
installedDeps.push(...uninstalledDeps);
|
|
1500
|
+
} else {
|
|
1501
|
+
failedDeps.push(...uninstalledDeps);
|
|
1502
|
+
if (!json) {
|
|
1503
|
+
logger.warn(
|
|
1504
|
+
"Some dependencies failed to install. Install them manually:"
|
|
1505
|
+
);
|
|
1506
|
+
logger.info(` npm install ${uninstalledDeps.join(" ")}`);
|
|
1507
|
+
}
|
|
1161
1508
|
}
|
|
1162
1509
|
}
|
|
1163
1510
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
} else {
|
|
1173
|
-
if (!json) {
|
|
1174
|
-
logger.blank();
|
|
1175
|
-
logger.info("Installing dev dependencies...");
|
|
1176
|
-
}
|
|
1177
|
-
if (installPackages(uninstalledDevDeps, cwd, true)) {
|
|
1511
|
+
if (uninstalledDevDeps.length > 0) {
|
|
1512
|
+
if (dryRun) {
|
|
1513
|
+
if (!json) {
|
|
1514
|
+
logger.blank();
|
|
1515
|
+
logger.info(
|
|
1516
|
+
`${prefix}Would install dev dependencies: ${uninstalledDevDeps.join(", ")}`
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1178
1519
|
installedDeps.push(...uninstalledDevDeps);
|
|
1179
1520
|
} else {
|
|
1180
|
-
failedDeps.push(...uninstalledDevDeps);
|
|
1181
1521
|
if (!json) {
|
|
1182
|
-
logger.
|
|
1183
|
-
logger.info(
|
|
1522
|
+
logger.blank();
|
|
1523
|
+
logger.info("Installing dev dependencies...");
|
|
1524
|
+
}
|
|
1525
|
+
if (installPackages(uninstalledDevDeps, cwd, true)) {
|
|
1526
|
+
installedDeps.push(...uninstalledDevDeps);
|
|
1527
|
+
} else {
|
|
1528
|
+
failedDeps.push(...uninstalledDevDeps);
|
|
1529
|
+
if (!json) {
|
|
1530
|
+
logger.warn(
|
|
1531
|
+
"Some dev dependencies failed to install. Install them manually:"
|
|
1532
|
+
);
|
|
1533
|
+
logger.info(
|
|
1534
|
+
` npm install --save-dev ${uninstalledDevDeps.join(" ")}`
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1184
1537
|
}
|
|
1185
1538
|
}
|
|
1186
1539
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1540
|
+
if (!hasVisorTokens(cwd)) {
|
|
1541
|
+
const warning = "@loworbitstudio/visor-core is not installed. Components require it for styling.";
|
|
1542
|
+
warnings.push(warning);
|
|
1543
|
+
if (!json) {
|
|
1544
|
+
logger.blank();
|
|
1545
|
+
logger.warn(warning);
|
|
1546
|
+
logger.info(
|
|
1547
|
+
" For Next.js: npx @loworbitstudio/visor init --template nextjs"
|
|
1548
|
+
);
|
|
1549
|
+
logger.info(
|
|
1550
|
+
" This generates all tokens inline \u2014 no npm package needed."
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1197
1553
|
}
|
|
1198
1554
|
}
|
|
1199
1555
|
if (json) {
|
|
@@ -1496,14 +1852,15 @@ function infoCommand(name, cwd, options = {}) {
|
|
|
1496
1852
|
}
|
|
1497
1853
|
|
|
1498
1854
|
// src/commands/theme-apply.ts
|
|
1499
|
-
import { readFileSync as
|
|
1500
|
-
import { resolve as resolve2, dirname as dirname4 } from "path";
|
|
1855
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
1856
|
+
import { resolve as resolve2, dirname as dirname4, join as join9 } from "path";
|
|
1501
1857
|
import { generateTheme, generateThemeData as generateThemeData2 } from "@loworbitstudio/visor-theme-engine";
|
|
1502
1858
|
import {
|
|
1503
1859
|
nextjsAdapter as nextjsAdapter2,
|
|
1504
1860
|
fumadocsAdapter,
|
|
1505
1861
|
deckAdapter,
|
|
1506
|
-
docsAdapter
|
|
1862
|
+
docsAdapter,
|
|
1863
|
+
flutterAdapter
|
|
1507
1864
|
} from "@loworbitstudio/visor-theme-engine/adapters";
|
|
1508
1865
|
function defaultOutputPath(adapter, themeName) {
|
|
1509
1866
|
switch (adapter) {
|
|
@@ -1512,13 +1869,15 @@ function defaultOutputPath(adapter, themeName) {
|
|
|
1512
1869
|
case "fumadocs":
|
|
1513
1870
|
return "visor-fumadocs-bridge.css";
|
|
1514
1871
|
case "deck": {
|
|
1515
|
-
const
|
|
1516
|
-
return `visor-deck-${
|
|
1872
|
+
const slug2 = (themeName ?? "theme").toLowerCase().replace(/\s+/g, "-");
|
|
1873
|
+
return `visor-deck-${slug2}.css`;
|
|
1517
1874
|
}
|
|
1518
1875
|
case "docs": {
|
|
1519
|
-
const
|
|
1520
|
-
return `${
|
|
1876
|
+
const slug2 = (themeName ?? "theme").toLowerCase().replace(/\s+/g, "-");
|
|
1877
|
+
return `${slug2}-theme.css`;
|
|
1521
1878
|
}
|
|
1879
|
+
case "flutter":
|
|
1880
|
+
return "packages/ui";
|
|
1522
1881
|
default:
|
|
1523
1882
|
return "visor-theme.css";
|
|
1524
1883
|
}
|
|
@@ -1527,7 +1886,7 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
1527
1886
|
const filePath = resolve2(cwd, file);
|
|
1528
1887
|
let yamlContent;
|
|
1529
1888
|
try {
|
|
1530
|
-
yamlContent =
|
|
1889
|
+
yamlContent = readFileSync8(filePath, "utf-8");
|
|
1531
1890
|
} catch {
|
|
1532
1891
|
if (options.json) {
|
|
1533
1892
|
console.log(
|
|
@@ -1542,7 +1901,8 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
1542
1901
|
}
|
|
1543
1902
|
process.exit(2);
|
|
1544
1903
|
}
|
|
1545
|
-
let css;
|
|
1904
|
+
let css = null;
|
|
1905
|
+
let fileMap = null;
|
|
1546
1906
|
let themeName;
|
|
1547
1907
|
let sections;
|
|
1548
1908
|
try {
|
|
@@ -1567,6 +1927,17 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
1567
1927
|
case "docs":
|
|
1568
1928
|
css = docsAdapter(adapterInput);
|
|
1569
1929
|
break;
|
|
1930
|
+
case "flutter": {
|
|
1931
|
+
const flutterOptions = {
|
|
1932
|
+
packageName: options.packageName,
|
|
1933
|
+
tokensOnly: options.tokensOnly,
|
|
1934
|
+
lightOnly: options.lightOnly,
|
|
1935
|
+
darkOnly: options.darkOnly,
|
|
1936
|
+
themeClassName: options.themeClassName
|
|
1937
|
+
};
|
|
1938
|
+
fileMap = flutterAdapter(adapterInput, flutterOptions);
|
|
1939
|
+
break;
|
|
1940
|
+
}
|
|
1570
1941
|
default:
|
|
1571
1942
|
throw new Error(`Unknown adapter: ${options.adapter}`);
|
|
1572
1943
|
}
|
|
@@ -1591,12 +1962,56 @@ function themeApplyCommand(file, cwd, options) {
|
|
|
1591
1962
|
}
|
|
1592
1963
|
process.exit(1);
|
|
1593
1964
|
}
|
|
1594
|
-
const
|
|
1595
|
-
const outputPath = resolve2(cwd,
|
|
1965
|
+
const outputTarget = options.output ?? defaultOutputPath(options.adapter, themeName);
|
|
1966
|
+
const outputPath = resolve2(cwd, outputTarget);
|
|
1967
|
+
if (fileMap) {
|
|
1968
|
+
try {
|
|
1969
|
+
mkdirSync3(outputPath, { recursive: true });
|
|
1970
|
+
let totalBytes = 0;
|
|
1971
|
+
for (const [relPath, content] of Object.entries(fileMap.files)) {
|
|
1972
|
+
const filePath2 = join9(outputPath, relPath);
|
|
1973
|
+
mkdirSync3(dirname4(filePath2), { recursive: true });
|
|
1974
|
+
writeFileSync5(filePath2, content, "utf-8");
|
|
1975
|
+
totalBytes += content.length;
|
|
1976
|
+
}
|
|
1977
|
+
if (options.json) {
|
|
1978
|
+
console.log(
|
|
1979
|
+
JSON.stringify({
|
|
1980
|
+
success: true,
|
|
1981
|
+
directory: outputPath,
|
|
1982
|
+
adapter: options.adapter,
|
|
1983
|
+
files: Object.keys(fileMap.files),
|
|
1984
|
+
size: totalBytes
|
|
1985
|
+
})
|
|
1986
|
+
);
|
|
1987
|
+
} else {
|
|
1988
|
+
logger.success(`Flutter theme package generated: ${outputPath}`);
|
|
1989
|
+
logger.info(`Adapter: ${options.adapter}`);
|
|
1990
|
+
logger.item(`Files: ${Object.keys(fileMap.files).length}`);
|
|
1991
|
+
logger.item(`Size: ${formatSize(totalBytes)}`);
|
|
1992
|
+
}
|
|
1993
|
+
} catch {
|
|
1994
|
+
if (options.json) {
|
|
1995
|
+
console.log(
|
|
1996
|
+
JSON.stringify({
|
|
1997
|
+
success: false,
|
|
1998
|
+
error: `Could not write package to: ${outputPath}`
|
|
1999
|
+
})
|
|
2000
|
+
);
|
|
2001
|
+
} else {
|
|
2002
|
+
logger.error(`Could not write package to: ${outputPath}`);
|
|
2003
|
+
}
|
|
2004
|
+
process.exit(2);
|
|
2005
|
+
}
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
if (css === null) {
|
|
2009
|
+
process.exit(1);
|
|
2010
|
+
}
|
|
1596
2011
|
const outputDir = dirname4(outputPath);
|
|
1597
2012
|
try {
|
|
1598
2013
|
mkdirSync3(outputDir, { recursive: true });
|
|
1599
|
-
|
|
2014
|
+
writeFileSync5(outputPath, css, "utf-8");
|
|
1600
2015
|
} catch {
|
|
1601
2016
|
if (options.json) {
|
|
1602
2017
|
console.log(
|
|
@@ -1637,7 +2052,7 @@ function formatSize(bytes) {
|
|
|
1637
2052
|
}
|
|
1638
2053
|
|
|
1639
2054
|
// src/commands/theme-export.ts
|
|
1640
|
-
import { readFileSync as
|
|
2055
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
1641
2056
|
import { resolve as resolve3 } from "path";
|
|
1642
2057
|
import {
|
|
1643
2058
|
parseConfig,
|
|
@@ -1649,7 +2064,7 @@ function themeExportCommand(file, cwd, options) {
|
|
|
1649
2064
|
const filePath = resolve3(cwd, file ?? ".visor.yaml");
|
|
1650
2065
|
let yamlContent;
|
|
1651
2066
|
try {
|
|
1652
|
-
yamlContent =
|
|
2067
|
+
yamlContent = readFileSync9(filePath, "utf-8");
|
|
1653
2068
|
} catch {
|
|
1654
2069
|
if (options.json) {
|
|
1655
2070
|
console.log(
|
|
@@ -1701,7 +2116,7 @@ function themeExportCommand(file, cwd, options) {
|
|
|
1701
2116
|
}
|
|
1702
2117
|
|
|
1703
2118
|
// src/commands/theme-validate.ts
|
|
1704
|
-
import { readFileSync as
|
|
2119
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
1705
2120
|
import { resolve as resolve4 } from "path";
|
|
1706
2121
|
import { parse as parseYaml } from "yaml";
|
|
1707
2122
|
import { validate } from "@loworbitstudio/visor-theme-engine";
|
|
@@ -1710,7 +2125,7 @@ function themeValidateCommand(file, cwd, options) {
|
|
|
1710
2125
|
const filePath = resolve4(cwd, file);
|
|
1711
2126
|
let fileContent;
|
|
1712
2127
|
try {
|
|
1713
|
-
fileContent =
|
|
2128
|
+
fileContent = readFileSync10(filePath, "utf-8");
|
|
1714
2129
|
} catch {
|
|
1715
2130
|
if (options.json) {
|
|
1716
2131
|
console.log(
|
|
@@ -1797,8 +2212,8 @@ function printIssue(issue) {
|
|
|
1797
2212
|
}
|
|
1798
2213
|
|
|
1799
2214
|
// src/commands/theme-extract.ts
|
|
1800
|
-
import { readFileSync as
|
|
1801
|
-
import { resolve as resolve5, join as
|
|
2215
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
2216
|
+
import { resolve as resolve5, join as join10, basename, extname as extname2, relative } from "path";
|
|
1802
2217
|
import { stringify as stringifyYaml } from "yaml";
|
|
1803
2218
|
import {
|
|
1804
2219
|
extractFromCSS,
|
|
@@ -1828,7 +2243,7 @@ var CSS_DIRS = [
|
|
|
1828
2243
|
];
|
|
1829
2244
|
function themeExtractCommand(cwd, options) {
|
|
1830
2245
|
const targetDir = resolve5(cwd, options.from ?? ".");
|
|
1831
|
-
if (!
|
|
2246
|
+
if (!existsSync7(targetDir)) {
|
|
1832
2247
|
if (options.json) {
|
|
1833
2248
|
console.log(JSON.stringify({ success: false, error: `Directory not found: ${targetDir}` }));
|
|
1834
2249
|
} else {
|
|
@@ -1882,16 +2297,16 @@ function collectCSSFiles(targetDir) {
|
|
|
1882
2297
|
const files = [];
|
|
1883
2298
|
const seen = /* @__PURE__ */ new Set();
|
|
1884
2299
|
for (const pattern2 of CSS_FILE_PATTERNS) {
|
|
1885
|
-
const rootPath =
|
|
2300
|
+
const rootPath = join10(targetDir, pattern2);
|
|
1886
2301
|
addFileIfExists(rootPath, files, seen);
|
|
1887
2302
|
for (const dir of CSS_DIRS) {
|
|
1888
|
-
const dirPath =
|
|
2303
|
+
const dirPath = join10(targetDir, dir, pattern2);
|
|
1889
2304
|
addFileIfExists(dirPath, files, seen);
|
|
1890
2305
|
}
|
|
1891
2306
|
}
|
|
1892
2307
|
for (const dir of CSS_DIRS) {
|
|
1893
|
-
const dirPath =
|
|
1894
|
-
if (
|
|
2308
|
+
const dirPath = join10(targetDir, dir);
|
|
2309
|
+
if (existsSync7(dirPath) && statSync3(dirPath).isDirectory()) {
|
|
1895
2310
|
scanDirForCSS(dirPath, files, seen, 2);
|
|
1896
2311
|
}
|
|
1897
2312
|
}
|
|
@@ -1901,9 +2316,9 @@ function collectCSSFiles(targetDir) {
|
|
|
1901
2316
|
function addFileIfExists(filePath, files, seen) {
|
|
1902
2317
|
const resolved = resolve5(filePath);
|
|
1903
2318
|
if (seen.has(resolved)) return;
|
|
1904
|
-
if (!
|
|
2319
|
+
if (!existsSync7(resolved)) return;
|
|
1905
2320
|
try {
|
|
1906
|
-
const content =
|
|
2321
|
+
const content = readFileSync11(resolved, "utf-8");
|
|
1907
2322
|
if (content.includes("--")) {
|
|
1908
2323
|
files.push({ path: resolved, content });
|
|
1909
2324
|
seen.add(resolved);
|
|
@@ -1912,7 +2327,7 @@ function addFileIfExists(filePath, files, seen) {
|
|
|
1912
2327
|
}
|
|
1913
2328
|
}
|
|
1914
2329
|
function scanDirForCSS(dir, files, seen, maxDepth) {
|
|
1915
|
-
if (!
|
|
2330
|
+
if (!existsSync7(dir)) return;
|
|
1916
2331
|
const SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
1917
2332
|
"node_modules",
|
|
1918
2333
|
".next",
|
|
@@ -1926,15 +2341,15 @@ function scanDirForCSS(dir, files, seen, maxDepth) {
|
|
|
1926
2341
|
".vercel"
|
|
1927
2342
|
]);
|
|
1928
2343
|
try {
|
|
1929
|
-
const entries =
|
|
2344
|
+
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
1930
2345
|
for (const entry of entries) {
|
|
1931
2346
|
if (entry.isDirectory()) {
|
|
1932
2347
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
1933
2348
|
if (maxDepth > 0) {
|
|
1934
|
-
scanDirForCSS(
|
|
2349
|
+
scanDirForCSS(join10(dir, entry.name), files, seen, maxDepth - 1);
|
|
1935
2350
|
}
|
|
1936
2351
|
} else if (entry.isFile() && extname2(entry.name) === ".css") {
|
|
1937
|
-
addFileIfExists(
|
|
2352
|
+
addFileIfExists(join10(dir, entry.name), files, seen);
|
|
1938
2353
|
}
|
|
1939
2354
|
}
|
|
1940
2355
|
} catch {
|
|
@@ -2016,10 +2431,10 @@ function extractVarName(varExpr) {
|
|
|
2016
2431
|
function parseNextFontFromLayouts(targetDir) {
|
|
2017
2432
|
const fontMap = /* @__PURE__ */ new Map();
|
|
2018
2433
|
for (const relPath of LAYOUT_FILE_PATHS) {
|
|
2019
|
-
const fullPath =
|
|
2020
|
-
if (!
|
|
2434
|
+
const fullPath = join10(targetDir, relPath);
|
|
2435
|
+
if (!existsSync7(fullPath)) continue;
|
|
2021
2436
|
try {
|
|
2022
|
-
const content =
|
|
2437
|
+
const content = readFileSync11(fullPath, "utf-8");
|
|
2023
2438
|
parseNextFontDeclarations(content, fontMap);
|
|
2024
2439
|
} catch {
|
|
2025
2440
|
}
|
|
@@ -2104,10 +2519,10 @@ var MONO_FONT_NAMES = /* @__PURE__ */ new Set([
|
|
|
2104
2519
|
"IBM Plex Mono"
|
|
2105
2520
|
]);
|
|
2106
2521
|
function extractFontHints(targetDir) {
|
|
2107
|
-
const pkgPath =
|
|
2108
|
-
if (!
|
|
2522
|
+
const pkgPath = join10(targetDir, "package.json");
|
|
2523
|
+
if (!existsSync7(pkgPath)) return void 0;
|
|
2109
2524
|
try {
|
|
2110
|
-
const pkg = JSON.parse(
|
|
2525
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
2111
2526
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2112
2527
|
const fonts2 = [];
|
|
2113
2528
|
for (const [dep, _] of Object.entries(allDeps)) {
|
|
@@ -2143,10 +2558,10 @@ function extractFontHints(targetDir) {
|
|
|
2143
2558
|
}
|
|
2144
2559
|
}
|
|
2145
2560
|
function inferThemeName(targetDir) {
|
|
2146
|
-
const pkgPath =
|
|
2147
|
-
if (
|
|
2561
|
+
const pkgPath = join10(targetDir, "package.json");
|
|
2562
|
+
if (existsSync7(pkgPath)) {
|
|
2148
2563
|
try {
|
|
2149
|
-
const pkg = JSON.parse(
|
|
2564
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
2150
2565
|
if (pkg.name) {
|
|
2151
2566
|
const name = pkg.name.replace(/^@[\w-]+\//, "");
|
|
2152
2567
|
return `${name}-theme`;
|
|
@@ -2211,7 +2626,7 @@ function outputYAML(result, outputPath, cwd, validationResult) {
|
|
|
2211
2626
|
}
|
|
2212
2627
|
logger.blank();
|
|
2213
2628
|
}
|
|
2214
|
-
|
|
2629
|
+
writeFileSync6(outFile, yamlStr, "utf-8");
|
|
2215
2630
|
logger.success(`Theme written to ${relative(cwd, outFile)}`);
|
|
2216
2631
|
if (validationResult) {
|
|
2217
2632
|
logger.blank();
|
|
@@ -2269,14 +2684,14 @@ function buildAnnotatedYAML(result) {
|
|
|
2269
2684
|
}
|
|
2270
2685
|
|
|
2271
2686
|
// src/commands/theme-register.ts
|
|
2272
|
-
import { readFileSync as
|
|
2273
|
-
import { resolve as resolve7, join as
|
|
2687
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync4, existsSync as existsSync9 } from "fs";
|
|
2688
|
+
import { resolve as resolve7, join as join12 } from "path";
|
|
2274
2689
|
import { generateThemeData as generateThemeData3 } from "@loworbitstudio/visor-theme-engine";
|
|
2275
2690
|
import { docsAdapter as docsAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
2276
2691
|
|
|
2277
2692
|
// src/utils/theme-helpers.ts
|
|
2278
|
-
import { existsSync as
|
|
2279
|
-
import { resolve as resolve6, dirname as dirname5, join as
|
|
2693
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2694
|
+
import { resolve as resolve6, dirname as dirname5, join as join11 } from "path";
|
|
2280
2695
|
function toSlug(name) {
|
|
2281
2696
|
return name.toLowerCase().replace(/\s+/g, "-");
|
|
2282
2697
|
}
|
|
@@ -2286,7 +2701,7 @@ function toLabel(name) {
|
|
|
2286
2701
|
function findRepoRoot(startDir) {
|
|
2287
2702
|
let current = resolve6(startDir);
|
|
2288
2703
|
while (true) {
|
|
2289
|
-
if (
|
|
2704
|
+
if (existsSync8(join11(current, "packages", "docs"))) {
|
|
2290
2705
|
return current;
|
|
2291
2706
|
}
|
|
2292
2707
|
const parent = dirname5(current);
|
|
@@ -2297,8 +2712,8 @@ function findRepoRoot(startDir) {
|
|
|
2297
2712
|
}
|
|
2298
2713
|
|
|
2299
2714
|
// src/commands/theme-register.ts
|
|
2300
|
-
function insertGlobalsImport(content,
|
|
2301
|
-
const importLine = `@import './${
|
|
2715
|
+
function insertGlobalsImport(content, slug2) {
|
|
2716
|
+
const importLine = `@import './${slug2}-theme.css';`;
|
|
2302
2717
|
if (content.includes(importLine)) {
|
|
2303
2718
|
return { updated: content, changed: false };
|
|
2304
2719
|
}
|
|
@@ -2329,8 +2744,8 @@ function insertGlobalsImport(content, slug) {
|
|
|
2329
2744
|
lines.splice(insertAt, 0, importLine);
|
|
2330
2745
|
return { updated: lines.join("\n"), changed: true };
|
|
2331
2746
|
}
|
|
2332
|
-
function insertThemeConfig(content,
|
|
2333
|
-
if (content.includes(`value: "${
|
|
2747
|
+
function insertThemeConfig(content, slug2, label, group) {
|
|
2748
|
+
if (content.includes(`value: "${slug2}"`)) {
|
|
2334
2749
|
return { updated: content, changed: false };
|
|
2335
2750
|
}
|
|
2336
2751
|
const escapedGroup = group.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -2363,7 +2778,7 @@ function insertThemeConfig(content, slug, label, group) {
|
|
|
2363
2778
|
}
|
|
2364
2779
|
let insertPos = closingBracket;
|
|
2365
2780
|
for (const e of entries) {
|
|
2366
|
-
if (
|
|
2781
|
+
if (slug2 < e.value) {
|
|
2367
2782
|
insertPos = e.start;
|
|
2368
2783
|
break;
|
|
2369
2784
|
}
|
|
@@ -2372,7 +2787,7 @@ function insertThemeConfig(content, slug, label, group) {
|
|
|
2372
2787
|
const lineContent = content.slice(prevNewline + 1, insertPos);
|
|
2373
2788
|
const indentMatch = /^(\s*)/.exec(lineContent);
|
|
2374
2789
|
const indent = indentMatch ? indentMatch[1] : " ";
|
|
2375
|
-
const newEntry = `{ value: "${
|
|
2790
|
+
const newEntry = `{ value: "${slug2}", label: "${label}" }`;
|
|
2376
2791
|
const insertion = entries.length === 0 ? `
|
|
2377
2792
|
${indent}${newEntry},
|
|
2378
2793
|
` : `${indent}${newEntry},
|
|
@@ -2384,7 +2799,7 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
2384
2799
|
const filePath = resolve7(cwd, file);
|
|
2385
2800
|
let yamlContent;
|
|
2386
2801
|
try {
|
|
2387
|
-
yamlContent =
|
|
2802
|
+
yamlContent = readFileSync12(filePath, "utf-8");
|
|
2388
2803
|
} catch {
|
|
2389
2804
|
if (options.json) {
|
|
2390
2805
|
console.log(JSON.stringify({ success: false, error: `Could not read file: ${filePath}` }));
|
|
@@ -2408,7 +2823,7 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
2408
2823
|
process.exit(1);
|
|
2409
2824
|
return;
|
|
2410
2825
|
}
|
|
2411
|
-
const
|
|
2826
|
+
const slug2 = toSlug(data.config.name);
|
|
2412
2827
|
const label = toLabel(data.config.name);
|
|
2413
2828
|
const adapterInput = {
|
|
2414
2829
|
primitives: data.primitives,
|
|
@@ -2427,11 +2842,11 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
2427
2842
|
process.exit(1);
|
|
2428
2843
|
return;
|
|
2429
2844
|
}
|
|
2430
|
-
const docsAppDir =
|
|
2431
|
-
const cssFilePath =
|
|
2432
|
-
const globalsPath =
|
|
2433
|
-
const themeConfigPath =
|
|
2434
|
-
if (!
|
|
2845
|
+
const docsAppDir = join12(repoRoot, "packages", "docs", "app");
|
|
2846
|
+
const cssFilePath = join12(docsAppDir, `${slug2}-theme.css`);
|
|
2847
|
+
const globalsPath = join12(docsAppDir, "globals.css");
|
|
2848
|
+
const themeConfigPath = join12(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
2849
|
+
if (!existsSync9(docsAppDir)) {
|
|
2435
2850
|
const msg = `Docs app directory not found: ${docsAppDir}`;
|
|
2436
2851
|
if (options.json) {
|
|
2437
2852
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
@@ -2444,8 +2859,8 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
2444
2859
|
let globalsContent = "";
|
|
2445
2860
|
let themeConfigContent = "";
|
|
2446
2861
|
try {
|
|
2447
|
-
globalsContent =
|
|
2448
|
-
themeConfigContent =
|
|
2862
|
+
globalsContent = readFileSync12(globalsPath, "utf-8");
|
|
2863
|
+
themeConfigContent = readFileSync12(themeConfigPath, "utf-8");
|
|
2449
2864
|
} catch (err) {
|
|
2450
2865
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
2451
2866
|
if (options.json) {
|
|
@@ -2456,12 +2871,12 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
2456
2871
|
process.exit(1);
|
|
2457
2872
|
return;
|
|
2458
2873
|
}
|
|
2459
|
-
const cssExists =
|
|
2460
|
-
const cssChanged = !cssExists ||
|
|
2461
|
-
const { updated: newGlobals, changed: globalsChanged } = insertGlobalsImport(globalsContent,
|
|
2874
|
+
const cssExists = existsSync9(cssFilePath);
|
|
2875
|
+
const cssChanged = !cssExists || readFileSync12(cssFilePath, "utf-8") !== css;
|
|
2876
|
+
const { updated: newGlobals, changed: globalsChanged } = insertGlobalsImport(globalsContent, slug2);
|
|
2462
2877
|
const { updated: newThemeConfig, changed: themeConfigChanged, error: configError } = insertThemeConfig(
|
|
2463
2878
|
themeConfigContent,
|
|
2464
|
-
|
|
2879
|
+
slug2,
|
|
2465
2880
|
label,
|
|
2466
2881
|
options.group
|
|
2467
2882
|
);
|
|
@@ -2479,7 +2894,7 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
2479
2894
|
console.log(JSON.stringify({
|
|
2480
2895
|
success: true,
|
|
2481
2896
|
dryRun: true,
|
|
2482
|
-
slug,
|
|
2897
|
+
slug: slug2,
|
|
2483
2898
|
label,
|
|
2484
2899
|
group: options.group,
|
|
2485
2900
|
changes: {
|
|
@@ -2490,7 +2905,7 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
2490
2905
|
}));
|
|
2491
2906
|
} else {
|
|
2492
2907
|
logger.info("Dry run \u2014 no files written");
|
|
2493
|
-
logger.item(`Theme: ${label} (${
|
|
2908
|
+
logger.item(`Theme: ${label} (${slug2})`);
|
|
2494
2909
|
logger.item(`Group: ${options.group}`);
|
|
2495
2910
|
logger.item(`CSS file: ${cssFilePath} \u2014 ${cssChanged ? cssExists ? "update" : "create" : "no change"}`);
|
|
2496
2911
|
logger.item(`globals.css: ${globalsChanged ? "add import" : "already registered"}`);
|
|
@@ -2501,13 +2916,13 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
2501
2916
|
try {
|
|
2502
2917
|
if (cssChanged) {
|
|
2503
2918
|
mkdirSync4(docsAppDir, { recursive: true });
|
|
2504
|
-
|
|
2919
|
+
writeFileSync7(cssFilePath, css, "utf-8");
|
|
2505
2920
|
}
|
|
2506
2921
|
if (globalsChanged) {
|
|
2507
|
-
|
|
2922
|
+
writeFileSync7(globalsPath, newGlobals, "utf-8");
|
|
2508
2923
|
}
|
|
2509
2924
|
if (themeConfigChanged) {
|
|
2510
|
-
|
|
2925
|
+
writeFileSync7(themeConfigPath, newThemeConfig, "utf-8");
|
|
2511
2926
|
}
|
|
2512
2927
|
} catch (err) {
|
|
2513
2928
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
@@ -2522,14 +2937,14 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
2522
2937
|
if (options.json) {
|
|
2523
2938
|
console.log(JSON.stringify({
|
|
2524
2939
|
success: true,
|
|
2525
|
-
slug,
|
|
2940
|
+
slug: slug2,
|
|
2526
2941
|
label,
|
|
2527
2942
|
group: options.group,
|
|
2528
2943
|
files: { css: cssFilePath, globals: globalsPath, themeConfig: themeConfigPath },
|
|
2529
2944
|
changes: { cssFile: cssChanged, globalsCSS: globalsChanged, themeConfig: themeConfigChanged }
|
|
2530
2945
|
}));
|
|
2531
2946
|
} else {
|
|
2532
|
-
logger.success(`Theme registered: ${label} (${
|
|
2947
|
+
logger.success(`Theme registered: ${label} (${slug2})`);
|
|
2533
2948
|
logger.item(`Group: ${options.group}`);
|
|
2534
2949
|
if (cssChanged) logger.item(`CSS: ${cssFilePath}`);
|
|
2535
2950
|
if (globalsChanged) logger.item(`globals.css updated`);
|
|
@@ -2541,28 +2956,28 @@ function themeRegisterCommand(file, cwd, options) {
|
|
|
2541
2956
|
}
|
|
2542
2957
|
|
|
2543
2958
|
// src/commands/theme-unregister.ts
|
|
2544
|
-
import { readFileSync as
|
|
2545
|
-
import { join as
|
|
2546
|
-
function removeGlobalsImport(content,
|
|
2547
|
-
const importLine = `@import './${
|
|
2959
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync10, unlinkSync } from "fs";
|
|
2960
|
+
import { join as join13 } from "path";
|
|
2961
|
+
function removeGlobalsImport(content, slug2) {
|
|
2962
|
+
const importLine = `@import './${slug2}-theme.css';`;
|
|
2548
2963
|
if (!content.includes(importLine)) {
|
|
2549
2964
|
return { updated: content, changed: false };
|
|
2550
2965
|
}
|
|
2551
2966
|
const updated = content.split("\n").filter((line) => line !== importLine).join("\n");
|
|
2552
2967
|
return { updated, changed: true };
|
|
2553
2968
|
}
|
|
2554
|
-
function removeThemeConfigEntry(content,
|
|
2555
|
-
if (!content.includes(`value: "${
|
|
2969
|
+
function removeThemeConfigEntry(content, slug2) {
|
|
2970
|
+
if (!content.includes(`value: "${slug2}"`)) {
|
|
2556
2971
|
return { updated: content, changed: false };
|
|
2557
2972
|
}
|
|
2558
2973
|
const entryPattern = new RegExp(
|
|
2559
|
-
`\\s*\\{\\s*value:\\s*"${
|
|
2974
|
+
`\\s*\\{\\s*value:\\s*"${slug2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"[^}]*\\},?`,
|
|
2560
2975
|
"g"
|
|
2561
2976
|
);
|
|
2562
2977
|
const updated = content.replace(entryPattern, "");
|
|
2563
2978
|
return { updated, changed: true };
|
|
2564
2979
|
}
|
|
2565
|
-
function themeUnregisterCommand(
|
|
2980
|
+
function themeUnregisterCommand(slug2, cwd, options) {
|
|
2566
2981
|
const repoRoot = findRepoRoot(cwd);
|
|
2567
2982
|
if (!repoRoot) {
|
|
2568
2983
|
const msg = "Could not locate repo root (packages/docs/ not found). Run from within the visor repo.";
|
|
@@ -2574,11 +2989,11 @@ function themeUnregisterCommand(slug, cwd, options) {
|
|
|
2574
2989
|
process.exit(1);
|
|
2575
2990
|
return;
|
|
2576
2991
|
}
|
|
2577
|
-
const docsAppDir =
|
|
2578
|
-
const cssFilePath =
|
|
2579
|
-
const globalsPath =
|
|
2580
|
-
const themeConfigPath =
|
|
2581
|
-
if (!
|
|
2992
|
+
const docsAppDir = join13(repoRoot, "packages", "docs", "app");
|
|
2993
|
+
const cssFilePath = join13(docsAppDir, `${slug2}-theme.css`);
|
|
2994
|
+
const globalsPath = join13(docsAppDir, "globals.css");
|
|
2995
|
+
const themeConfigPath = join13(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
2996
|
+
if (!existsSync10(docsAppDir)) {
|
|
2582
2997
|
const msg = `Docs app directory not found: ${docsAppDir}`;
|
|
2583
2998
|
if (options.json) {
|
|
2584
2999
|
console.log(JSON.stringify({ success: false, error: msg }));
|
|
@@ -2591,8 +3006,8 @@ function themeUnregisterCommand(slug, cwd, options) {
|
|
|
2591
3006
|
let globalsContent = "";
|
|
2592
3007
|
let themeConfigContent = "";
|
|
2593
3008
|
try {
|
|
2594
|
-
globalsContent =
|
|
2595
|
-
themeConfigContent =
|
|
3009
|
+
globalsContent = readFileSync13(globalsPath, "utf-8");
|
|
3010
|
+
themeConfigContent = readFileSync13(themeConfigPath, "utf-8");
|
|
2596
3011
|
} catch (err) {
|
|
2597
3012
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
2598
3013
|
if (options.json) {
|
|
@@ -2603,21 +3018,21 @@ function themeUnregisterCommand(slug, cwd, options) {
|
|
|
2603
3018
|
process.exit(1);
|
|
2604
3019
|
return;
|
|
2605
3020
|
}
|
|
2606
|
-
const cssExists =
|
|
2607
|
-
const { updated: newGlobals, changed: globalsChanged } = removeGlobalsImport(globalsContent,
|
|
2608
|
-
const { updated: newThemeConfig, changed: themeConfigChanged } = removeThemeConfigEntry(themeConfigContent,
|
|
3021
|
+
const cssExists = existsSync10(cssFilePath);
|
|
3022
|
+
const { updated: newGlobals, changed: globalsChanged } = removeGlobalsImport(globalsContent, slug2);
|
|
3023
|
+
const { updated: newThemeConfig, changed: themeConfigChanged } = removeThemeConfigEntry(themeConfigContent, slug2);
|
|
2609
3024
|
if (!cssExists && !globalsChanged && !themeConfigChanged) {
|
|
2610
3025
|
if (options.json) {
|
|
2611
|
-
console.log(JSON.stringify({ success: true, slug, changes: { cssFile: false, globalsCSS: false, themeConfig: false } }));
|
|
3026
|
+
console.log(JSON.stringify({ success: true, slug: slug2, changes: { cssFile: false, globalsCSS: false, themeConfig: false } }));
|
|
2612
3027
|
} else {
|
|
2613
|
-
logger.info(`Theme "${
|
|
3028
|
+
logger.info(`Theme "${slug2}" is not registered \u2014 nothing to remove.`);
|
|
2614
3029
|
}
|
|
2615
3030
|
return;
|
|
2616
3031
|
}
|
|
2617
3032
|
try {
|
|
2618
3033
|
if (cssExists) unlinkSync(cssFilePath);
|
|
2619
|
-
if (globalsChanged)
|
|
2620
|
-
if (themeConfigChanged)
|
|
3034
|
+
if (globalsChanged) writeFileSync8(globalsPath, newGlobals, "utf-8");
|
|
3035
|
+
if (themeConfigChanged) writeFileSync8(themeConfigPath, newThemeConfig, "utf-8");
|
|
2621
3036
|
} catch (err) {
|
|
2622
3037
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
2623
3038
|
if (options.json) {
|
|
@@ -2631,11 +3046,11 @@ function themeUnregisterCommand(slug, cwd, options) {
|
|
|
2631
3046
|
if (options.json) {
|
|
2632
3047
|
console.log(JSON.stringify({
|
|
2633
3048
|
success: true,
|
|
2634
|
-
slug,
|
|
3049
|
+
slug: slug2,
|
|
2635
3050
|
changes: { cssFile: cssExists, globalsCSS: globalsChanged, themeConfig: themeConfigChanged }
|
|
2636
3051
|
}));
|
|
2637
3052
|
} else {
|
|
2638
|
-
logger.success(`Theme unregistered: ${
|
|
3053
|
+
logger.success(`Theme unregistered: ${slug2}`);
|
|
2639
3054
|
if (cssExists) logger.item(`CSS file removed: ${cssFilePath}`);
|
|
2640
3055
|
if (globalsChanged) logger.item(`globals.css updated`);
|
|
2641
3056
|
if (themeConfigChanged) logger.item(`theme-config.ts updated`);
|
|
@@ -2644,15 +3059,15 @@ function themeUnregisterCommand(slug, cwd, options) {
|
|
|
2644
3059
|
|
|
2645
3060
|
// src/commands/theme-sync.ts
|
|
2646
3061
|
import {
|
|
2647
|
-
readFileSync as
|
|
2648
|
-
writeFileSync as
|
|
3062
|
+
readFileSync as readFileSync14,
|
|
3063
|
+
writeFileSync as writeFileSync9,
|
|
2649
3064
|
mkdirSync as mkdirSync5,
|
|
2650
|
-
existsSync as
|
|
2651
|
-
readdirSync as
|
|
3065
|
+
existsSync as existsSync11,
|
|
3066
|
+
readdirSync as readdirSync4,
|
|
2652
3067
|
unlinkSync as unlinkSync2,
|
|
2653
3068
|
copyFileSync
|
|
2654
3069
|
} from "fs";
|
|
2655
|
-
import { join as
|
|
3070
|
+
import { join as join14, basename as basename2 } from "path";
|
|
2656
3071
|
import { parse as parseYaml2 } from "yaml";
|
|
2657
3072
|
import { generateThemeData as generateThemeData4 } from "@loworbitstudio/visor-theme-engine";
|
|
2658
3073
|
import { docsAdapter as docsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
@@ -2666,8 +3081,8 @@ var CUSTOM_OVERLAY_CSS_PATH = "packages/docs/app/custom-themes.generated.css";
|
|
|
2666
3081
|
var CUSTOM_OVERLAY_TS_PATH = "packages/docs/lib/theme-config.custom.generated.ts";
|
|
2667
3082
|
var CUSTOM_OVERLAY_IMPORT_LINE = "@import './custom-themes.generated.css';";
|
|
2668
3083
|
function scanThemeDir(dir) {
|
|
2669
|
-
if (!
|
|
2670
|
-
return
|
|
3084
|
+
if (!existsSync11(dir)) return [];
|
|
3085
|
+
return readdirSync4(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join14(dir, f));
|
|
2671
3086
|
}
|
|
2672
3087
|
function extractGroup(yamlContent) {
|
|
2673
3088
|
const parsed = parseYaml2(yamlContent);
|
|
@@ -2693,7 +3108,7 @@ function sortGroups(groups) {
|
|
|
2693
3108
|
});
|
|
2694
3109
|
}
|
|
2695
3110
|
function updateGlobalsImports(content, stockSlugs) {
|
|
2696
|
-
const importLines = [...stockSlugs].sort().map((
|
|
3111
|
+
const importLines = [...stockSlugs].sort().map((slug2) => `@import './${slug2}-theme.css';`).join("\n");
|
|
2697
3112
|
const newBlock = `${GLOBALS_BEGIN_MARKER}
|
|
2698
3113
|
${importLines}
|
|
2699
3114
|
${GLOBALS_END_MARKER}`;
|
|
@@ -2827,7 +3242,7 @@ ${groupsTs}
|
|
|
2827
3242
|
`;
|
|
2828
3243
|
}
|
|
2829
3244
|
function updateGitignoreBlock(content, customSlugs) {
|
|
2830
|
-
const cssLines = customSlugs.sort().map((
|
|
3245
|
+
const cssLines = customSlugs.sort().map((slug2) => `packages/docs/app/${slug2}-theme.css`).join("\n");
|
|
2831
3246
|
const newBlock = `${GITIGNORE_BEGIN_MARKER}
|
|
2832
3247
|
${cssLines}
|
|
2833
3248
|
${GITIGNORE_END_MARKER}`;
|
|
@@ -2850,16 +3265,16 @@ function themeSyncCommand(cwd, options) {
|
|
|
2850
3265
|
process.exit(1);
|
|
2851
3266
|
return;
|
|
2852
3267
|
}
|
|
2853
|
-
const themesDir =
|
|
2854
|
-
const customThemesDir =
|
|
2855
|
-
const docsAppDir =
|
|
2856
|
-
const docsLibDir =
|
|
2857
|
-
const docsPublicThemesDir =
|
|
2858
|
-
const themeConfigPath =
|
|
2859
|
-
const globalsPath =
|
|
2860
|
-
const gitignorePath =
|
|
2861
|
-
const customOverlayCssPath =
|
|
2862
|
-
const customOverlayTsPath =
|
|
3268
|
+
const themesDir = join14(repoRoot, "themes");
|
|
3269
|
+
const customThemesDir = join14(repoRoot, "custom-themes");
|
|
3270
|
+
const docsAppDir = join14(repoRoot, "packages", "docs", "app");
|
|
3271
|
+
const docsLibDir = join14(repoRoot, "packages", "docs", "lib");
|
|
3272
|
+
const docsPublicThemesDir = join14(repoRoot, "packages", "docs", "public", "themes");
|
|
3273
|
+
const themeConfigPath = join14(repoRoot, "packages", "docs", "lib", "theme-config.ts");
|
|
3274
|
+
const globalsPath = join14(docsAppDir, "globals.css");
|
|
3275
|
+
const gitignorePath = join14(repoRoot, ".gitignore");
|
|
3276
|
+
const customOverlayCssPath = join14(repoRoot, CUSTOM_OVERLAY_CSS_PATH);
|
|
3277
|
+
const customOverlayTsPath = join14(repoRoot, CUSTOM_OVERLAY_TS_PATH);
|
|
2863
3278
|
const stockFiles = scanThemeDir(themesDir);
|
|
2864
3279
|
const customFiles = scanThemeDir(customThemesDir);
|
|
2865
3280
|
if (stockFiles.length === 0 && customFiles.length === 0) {
|
|
@@ -2876,7 +3291,7 @@ function themeSyncCommand(cwd, options) {
|
|
|
2876
3291
|
const processFile = (filePath, isCustom) => {
|
|
2877
3292
|
let yamlContent;
|
|
2878
3293
|
try {
|
|
2879
|
-
yamlContent =
|
|
3294
|
+
yamlContent = readFileSync14(filePath, "utf-8");
|
|
2880
3295
|
} catch {
|
|
2881
3296
|
errors.push(`Could not read: ${filePath}`);
|
|
2882
3297
|
return;
|
|
@@ -2888,13 +3303,13 @@ function themeSyncCommand(cwd, options) {
|
|
|
2888
3303
|
errors.push(`Failed to parse ${basename2(filePath)}: ${err instanceof Error ? err.message : "Unknown error"}`);
|
|
2889
3304
|
return;
|
|
2890
3305
|
}
|
|
2891
|
-
const
|
|
3306
|
+
const slug2 = toSlug(data.config.name);
|
|
2892
3307
|
const label = extractLabel(yamlContent) ?? toLabel(data.config.name);
|
|
2893
3308
|
const group = extractGroup(yamlContent) ?? (isCustom ? "Custom" : "Visor");
|
|
2894
3309
|
const defaultMode = extractDefaultMode(yamlContent);
|
|
2895
3310
|
const css = docsAdapter3({ primitives: data.primitives, tokens: data.tokens, config: data.config });
|
|
2896
3311
|
const yamlFilename = basename2(filePath).replace(/\.visor\.yaml$/, "");
|
|
2897
|
-
manifest.push({ slug, label, group, defaultMode, css, yamlFilename, isCustom });
|
|
3312
|
+
manifest.push({ slug: slug2, label, group, defaultMode, css, yamlFilename, isCustom });
|
|
2898
3313
|
};
|
|
2899
3314
|
for (const f of stockFiles) processFile(f, false);
|
|
2900
3315
|
for (const f of customFiles) processFile(f, true);
|
|
@@ -2916,9 +3331,9 @@ function themeSyncCommand(cwd, options) {
|
|
|
2916
3331
|
let themeConfigContent;
|
|
2917
3332
|
let gitignoreContent;
|
|
2918
3333
|
try {
|
|
2919
|
-
globalsContent =
|
|
2920
|
-
themeConfigContent =
|
|
2921
|
-
gitignoreContent =
|
|
3334
|
+
globalsContent = readFileSync14(globalsPath, "utf-8");
|
|
3335
|
+
themeConfigContent = readFileSync14(themeConfigPath, "utf-8");
|
|
3336
|
+
gitignoreContent = existsSync11(gitignorePath) ? readFileSync14(gitignorePath, "utf-8") : "";
|
|
2922
3337
|
} catch (err) {
|
|
2923
3338
|
const msg = err instanceof Error ? err.message : "Could not read docs files";
|
|
2924
3339
|
if (options.json) {
|
|
@@ -2934,12 +3349,12 @@ function themeSyncCommand(cwd, options) {
|
|
|
2934
3349
|
const newGitignore = customSlugs.length > 0 ? updateGitignoreBlock(gitignoreContent, customSlugs) : gitignoreContent;
|
|
2935
3350
|
const newCustomOverlayCss = generateCustomOverlayCss(customManifest);
|
|
2936
3351
|
const newCustomOverlayTs = generateCustomOverlayTs(customManifest);
|
|
2937
|
-
const existingCssFiles =
|
|
3352
|
+
const existingCssFiles = existsSync11(docsAppDir) ? readdirSync4(docsAppDir).filter(
|
|
2938
3353
|
(f) => f.endsWith("-theme.css") && f !== "custom-themes.generated.css"
|
|
2939
3354
|
) : [];
|
|
2940
3355
|
const newCssSet = new Set(allSlugs.map((s) => `${s}-theme.css`));
|
|
2941
3356
|
const staleCssFiles = existingCssFiles.filter((f) => !newCssSet.has(f));
|
|
2942
|
-
const existingPublicYamls =
|
|
3357
|
+
const existingPublicYamls = existsSync11(docsPublicThemesDir) ? readdirSync4(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
|
|
2943
3358
|
const newPublicYamlSet = new Set(manifest.map((e) => `${e.yamlFilename}.visor.yaml`));
|
|
2944
3359
|
const stalePublicYamls = existingPublicYamls.filter((f) => !newPublicYamlSet.has(f));
|
|
2945
3360
|
if (options.dryRun) {
|
|
@@ -2971,25 +3386,25 @@ function themeSyncCommand(cwd, options) {
|
|
|
2971
3386
|
mkdirSync5(docsLibDir, { recursive: true });
|
|
2972
3387
|
mkdirSync5(docsPublicThemesDir, { recursive: true });
|
|
2973
3388
|
for (const entry of manifest) {
|
|
2974
|
-
|
|
3389
|
+
writeFileSync9(join14(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
|
|
2975
3390
|
}
|
|
2976
3391
|
for (const stale of staleCssFiles) {
|
|
2977
|
-
unlinkSync2(
|
|
3392
|
+
unlinkSync2(join14(docsAppDir, stale));
|
|
2978
3393
|
}
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
if (
|
|
2984
|
-
|
|
3394
|
+
writeFileSync9(customOverlayCssPath, newCustomOverlayCss, "utf-8");
|
|
3395
|
+
writeFileSync9(customOverlayTsPath, newCustomOverlayTs, "utf-8");
|
|
3396
|
+
writeFileSync9(themeConfigPath, newThemeConfig, "utf-8");
|
|
3397
|
+
writeFileSync9(globalsPath, newGlobals, "utf-8");
|
|
3398
|
+
if (existsSync11(gitignorePath)) {
|
|
3399
|
+
writeFileSync9(gitignorePath, newGitignore, "utf-8");
|
|
2985
3400
|
}
|
|
2986
3401
|
const allSourceFiles = [...stockFiles, ...customFiles];
|
|
2987
3402
|
for (const srcFile of allSourceFiles) {
|
|
2988
3403
|
const filename = basename2(srcFile);
|
|
2989
|
-
copyFileSync(srcFile,
|
|
3404
|
+
copyFileSync(srcFile, join14(docsPublicThemesDir, filename));
|
|
2990
3405
|
}
|
|
2991
3406
|
for (const stale of stalePublicYamls) {
|
|
2992
|
-
unlinkSync2(
|
|
3407
|
+
unlinkSync2(join14(docsPublicThemesDir, stale));
|
|
2993
3408
|
}
|
|
2994
3409
|
} catch (err) {
|
|
2995
3410
|
const msg = err instanceof Error ? err.message : "Write failed";
|
|
@@ -3023,12 +3438,304 @@ function themeSyncCommand(cwd, options) {
|
|
|
3023
3438
|
}
|
|
3024
3439
|
}
|
|
3025
3440
|
|
|
3441
|
+
// src/commands/theme-batch-apply-flutter.ts
|
|
3442
|
+
import {
|
|
3443
|
+
readFileSync as readFileSync15,
|
|
3444
|
+
writeFileSync as writeFileSync10,
|
|
3445
|
+
mkdirSync as mkdirSync6,
|
|
3446
|
+
existsSync as existsSync12,
|
|
3447
|
+
readdirSync as readdirSync5,
|
|
3448
|
+
rmSync
|
|
3449
|
+
} from "fs";
|
|
3450
|
+
import { join as join15, basename as basename3, dirname as dirname6 } from "path";
|
|
3451
|
+
import { generateThemeData as generateThemeData5 } from "@loworbitstudio/visor-theme-engine";
|
|
3452
|
+
import { flutterAdapter as flutterAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
|
|
3453
|
+
function scanThemeDir2(dir) {
|
|
3454
|
+
if (!existsSync12(dir)) return [];
|
|
3455
|
+
return readdirSync5(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join15(dir, f)).sort();
|
|
3456
|
+
}
|
|
3457
|
+
function slugToCamel(slug2) {
|
|
3458
|
+
return slug2.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
3459
|
+
}
|
|
3460
|
+
function mapAdapterPath(relPath) {
|
|
3461
|
+
if (relPath === "pubspec.yaml") return null;
|
|
3462
|
+
if (relPath === "lib/ui.dart") return null;
|
|
3463
|
+
if (relPath.startsWith("lib/src/")) {
|
|
3464
|
+
return relPath.slice("lib/src/".length);
|
|
3465
|
+
}
|
|
3466
|
+
return null;
|
|
3467
|
+
}
|
|
3468
|
+
function emitVisorThemesPubspec() {
|
|
3469
|
+
return [
|
|
3470
|
+
`name: visor_themes`,
|
|
3471
|
+
`description: All Visor-generated Flutter ThemeData packages \u2014 4 stock + 7 custom themes. GENERATED \u2014 regenerate with \`npm run themes:apply-flutter\`.`,
|
|
3472
|
+
`version: 0.1.0+1`,
|
|
3473
|
+
`publish_to: none`,
|
|
3474
|
+
``,
|
|
3475
|
+
`environment:`,
|
|
3476
|
+
` sdk: ^3.5.0`,
|
|
3477
|
+
` flutter: ^3.24.0`,
|
|
3478
|
+
``,
|
|
3479
|
+
`dependencies:`,
|
|
3480
|
+
` flutter:`,
|
|
3481
|
+
` sdk: flutter`,
|
|
3482
|
+
` visor_core: ^0.1.0`,
|
|
3483
|
+
``,
|
|
3484
|
+
`dev_dependencies:`,
|
|
3485
|
+
` flutter_test:`,
|
|
3486
|
+
` sdk: flutter`,
|
|
3487
|
+
` flutter_lints: ^5.0.0`,
|
|
3488
|
+
``,
|
|
3489
|
+
`flutter:`,
|
|
3490
|
+
` uses-material-design: true`,
|
|
3491
|
+
``
|
|
3492
|
+
].join("\n");
|
|
3493
|
+
}
|
|
3494
|
+
function emitVisorThemesPubspecOverrides() {
|
|
3495
|
+
return [
|
|
3496
|
+
`# Path overrides for in-monorepo development.`,
|
|
3497
|
+
`# visor_core is not yet published to pub.dev; this forces the local copy.`,
|
|
3498
|
+
`dependency_overrides:`,
|
|
3499
|
+
` visor_core:`,
|
|
3500
|
+
` path: ../visor-flutter`,
|
|
3501
|
+
``
|
|
3502
|
+
].join("\n");
|
|
3503
|
+
}
|
|
3504
|
+
function emitAnalysisOptions() {
|
|
3505
|
+
return [
|
|
3506
|
+
`include: package:flutter_lints/flutter.yaml`,
|
|
3507
|
+
``,
|
|
3508
|
+
`linter:`,
|
|
3509
|
+
` rules:`,
|
|
3510
|
+
` - avoid_print`,
|
|
3511
|
+
``
|
|
3512
|
+
].join("\n");
|
|
3513
|
+
}
|
|
3514
|
+
function slugToDartPrefix(slug2) {
|
|
3515
|
+
return slug2.replace(/-/g, "_") + "_t";
|
|
3516
|
+
}
|
|
3517
|
+
function emitMetaBarrel(slugs) {
|
|
3518
|
+
const lines = [
|
|
3519
|
+
`// GENERATED BY visor \u2014 DO NOT EDIT.`,
|
|
3520
|
+
`// Regenerate with \`npm run themes:apply-flutter\`.`,
|
|
3521
|
+
`//`,
|
|
3522
|
+
`// Aggregates Dart ThemeData for all Visor themes (4 stock + 7 custom).`,
|
|
3523
|
+
`// Access themes via [VisorThemes], e.g. VisorThemes.blackout.light`,
|
|
3524
|
+
``,
|
|
3525
|
+
`import 'package:flutter/material.dart';`,
|
|
3526
|
+
``,
|
|
3527
|
+
`// Re-export visor_core so consumers access VisorColorsData etc. with`,
|
|
3528
|
+
`// a single import of this package.`,
|
|
3529
|
+
`export 'package:visor_core/visor_core.dart';`,
|
|
3530
|
+
``
|
|
3531
|
+
];
|
|
3532
|
+
for (const slug2 of slugs) {
|
|
3533
|
+
const prefix = slugToDartPrefix(slug2);
|
|
3534
|
+
lines.push(`import 'src/${slug2}/theme/visor_theme.dart' as ${prefix};`);
|
|
3535
|
+
}
|
|
3536
|
+
lines.push(``);
|
|
3537
|
+
lines.push(`/// A light/dark [ThemeData] pair for a single Visor theme.`);
|
|
3538
|
+
lines.push(`class VisorThemePair {`);
|
|
3539
|
+
lines.push(` final ThemeData light;`);
|
|
3540
|
+
lines.push(` final ThemeData dark;`);
|
|
3541
|
+
lines.push(` const VisorThemePair({required this.light, required this.dark});`);
|
|
3542
|
+
lines.push(`}`);
|
|
3543
|
+
lines.push(``);
|
|
3544
|
+
lines.push(`/// Static access to all Visor-generated Flutter themes.`);
|
|
3545
|
+
lines.push(`///`);
|
|
3546
|
+
lines.push(`/// Usage:`);
|
|
3547
|
+
lines.push(`/// \`\`\`dart`);
|
|
3548
|
+
lines.push(`/// MaterialApp(`);
|
|
3549
|
+
lines.push(`/// theme: VisorThemes.blackout.light,`);
|
|
3550
|
+
lines.push(`/// darkTheme: VisorThemes.blackout.dark,`);
|
|
3551
|
+
lines.push(`/// );`);
|
|
3552
|
+
lines.push(`/// \`\`\``);
|
|
3553
|
+
lines.push(`sealed class VisorThemes {`);
|
|
3554
|
+
for (const slug2 of slugs) {
|
|
3555
|
+
const camel = slugToCamel(slug2);
|
|
3556
|
+
const prefix = slugToDartPrefix(slug2);
|
|
3557
|
+
lines.push(` static VisorThemePair get ${camel} => VisorThemePair(`);
|
|
3558
|
+
lines.push(` light: ${prefix}.VisorAppTheme.light,`);
|
|
3559
|
+
lines.push(` dark: ${prefix}.VisorAppTheme.dark,`);
|
|
3560
|
+
lines.push(` );`);
|
|
3561
|
+
}
|
|
3562
|
+
lines.push(`}`);
|
|
3563
|
+
lines.push(``);
|
|
3564
|
+
return lines.join("\n");
|
|
3565
|
+
}
|
|
3566
|
+
function emitGitignore() {
|
|
3567
|
+
return [
|
|
3568
|
+
`.dart_tool/`,
|
|
3569
|
+
`.packages`,
|
|
3570
|
+
`build/`,
|
|
3571
|
+
`pubspec.lock`,
|
|
3572
|
+
`*.g.dart`,
|
|
3573
|
+
``
|
|
3574
|
+
].join("\n");
|
|
3575
|
+
}
|
|
3576
|
+
function themeBatchApplyFlutterCommand(cwd, options) {
|
|
3577
|
+
const repoRoot = findRepoRoot(cwd);
|
|
3578
|
+
if (!repoRoot) {
|
|
3579
|
+
const msg = "Could not locate repo root (packages/docs/ not found). Run from within the visor repo.";
|
|
3580
|
+
if (options.json) {
|
|
3581
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
3582
|
+
} else {
|
|
3583
|
+
logger.error(msg);
|
|
3584
|
+
}
|
|
3585
|
+
process.exit(1);
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3588
|
+
const themesDir = join15(repoRoot, "themes");
|
|
3589
|
+
const customThemesDir = join15(repoRoot, "custom-themes");
|
|
3590
|
+
const outputDir = join15(repoRoot, "packages", "visor_themes");
|
|
3591
|
+
const stockFiles = scanThemeDir2(themesDir);
|
|
3592
|
+
const customFiles = scanThemeDir2(customThemesDir);
|
|
3593
|
+
const allFiles = [...stockFiles, ...customFiles];
|
|
3594
|
+
if (allFiles.length === 0) {
|
|
3595
|
+
const msg = `No .visor.yaml files found in themes/ or custom-themes/. Nothing to generate.`;
|
|
3596
|
+
if (options.json) {
|
|
3597
|
+
console.log(JSON.stringify({ success: false, error: msg }));
|
|
3598
|
+
} else {
|
|
3599
|
+
logger.warn(msg);
|
|
3600
|
+
}
|
|
3601
|
+
return;
|
|
3602
|
+
}
|
|
3603
|
+
if (!options.json) {
|
|
3604
|
+
logger.info(`Found ${allFiles.length} theme YAML files (${stockFiles.length} stock, ${customFiles.length} custom)`);
|
|
3605
|
+
}
|
|
3606
|
+
const processed = [];
|
|
3607
|
+
const errors = [];
|
|
3608
|
+
for (const filePath of allFiles) {
|
|
3609
|
+
let yamlContent;
|
|
3610
|
+
try {
|
|
3611
|
+
yamlContent = readFileSync15(filePath, "utf-8");
|
|
3612
|
+
} catch {
|
|
3613
|
+
errors.push(`Could not read: ${filePath}`);
|
|
3614
|
+
continue;
|
|
3615
|
+
}
|
|
3616
|
+
let data;
|
|
3617
|
+
try {
|
|
3618
|
+
data = generateThemeData5(yamlContent);
|
|
3619
|
+
} catch (err) {
|
|
3620
|
+
errors.push(
|
|
3621
|
+
`Failed to parse ${basename3(filePath)}: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
3622
|
+
);
|
|
3623
|
+
continue;
|
|
3624
|
+
}
|
|
3625
|
+
const slug2 = data.config.name.toLowerCase().replace(/\s+/g, "-");
|
|
3626
|
+
const camel = slugToCamel(slug2);
|
|
3627
|
+
const flutterOptions = {
|
|
3628
|
+
packageName: `visor_themes_${slug2.replace(/-/g, "_")}`,
|
|
3629
|
+
themeClassName: "VisorAppTheme"
|
|
3630
|
+
};
|
|
3631
|
+
let fileMap;
|
|
3632
|
+
try {
|
|
3633
|
+
fileMap = flutterAdapter2(
|
|
3634
|
+
{
|
|
3635
|
+
primitives: data.primitives,
|
|
3636
|
+
tokens: data.tokens,
|
|
3637
|
+
config: data.config
|
|
3638
|
+
},
|
|
3639
|
+
flutterOptions
|
|
3640
|
+
);
|
|
3641
|
+
} catch (err) {
|
|
3642
|
+
errors.push(
|
|
3643
|
+
`Failed flutter adapter for ${slug2}: ${err instanceof Error ? err.message : "Unknown error"}`
|
|
3644
|
+
);
|
|
3645
|
+
continue;
|
|
3646
|
+
}
|
|
3647
|
+
const primsAsUnknown = data.primitives;
|
|
3648
|
+
const primitivesMap = primsAsUnknown;
|
|
3649
|
+
const primaryHex = typeof primitivesMap?.primary500 === "string" ? primitivesMap.primary500 : "#000000";
|
|
3650
|
+
const tokenFiles = {};
|
|
3651
|
+
for (const [relPath, content] of Object.entries(fileMap.files)) {
|
|
3652
|
+
const mapped = mapAdapterPath(relPath);
|
|
3653
|
+
if (mapped !== null) {
|
|
3654
|
+
tokenFiles[mapped] = content;
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
if (tokenFiles["theme/visor_theme.dart"]) {
|
|
3658
|
+
tokenFiles["theme/visor_theme.dart"] = tokenFiles["theme/visor_theme.dart"].replace(/import '\.\.\/colors\/visor_colors\.dart';/g, "import '../colors/visor_colors.dart';").replace(/import '\.\.\/typography\/visor_text_styles\.dart';/g, "import '../typography/visor_text_styles.dart';").replace(/import '\.\.\/spacing\/visor_spacing\.dart';/g, "import '../spacing/visor_spacing.dart';").replace(/import '\.\.\/radius\/visor_radius\.dart';/g, "import '../radius/visor_radius.dart';").replace(/import '\.\.\/shadows\/visor_shadows\.dart';/g, "import '../shadows/visor_shadows.dart';").replace(/import '\.\.\/strokes\/visor_stroke_widths\.dart';/g, "import '../strokes/visor_stroke_widths.dart';").replace(/import '\.\.\/opacity\/visor_opacity\.dart';/g, "import '../opacity/visor_opacity.dart';").replace(/import '\.\.\/motion\/visor_motion\.dart';/g, "import '../motion/visor_motion.dart';");
|
|
3659
|
+
}
|
|
3660
|
+
processed.push({ slug: slug2, camel, primaryHex, tokenFiles });
|
|
3661
|
+
}
|
|
3662
|
+
if (errors.length > 0) {
|
|
3663
|
+
if (options.json) {
|
|
3664
|
+
console.log(JSON.stringify({ success: false, errors }));
|
|
3665
|
+
} else {
|
|
3666
|
+
errors.forEach((e) => logger.error(e));
|
|
3667
|
+
}
|
|
3668
|
+
process.exit(1);
|
|
3669
|
+
return;
|
|
3670
|
+
}
|
|
3671
|
+
if (options.dryRun) {
|
|
3672
|
+
if (!options.json) {
|
|
3673
|
+
logger.info(`[dry-run] Would generate ${processed.length} theme packages in ${outputDir}`);
|
|
3674
|
+
for (const { slug: slug2 } of processed) {
|
|
3675
|
+
logger.item(` packages/visor_themes/lib/src/${slug2}/`);
|
|
3676
|
+
}
|
|
3677
|
+
} else {
|
|
3678
|
+
console.log(
|
|
3679
|
+
JSON.stringify({
|
|
3680
|
+
success: true,
|
|
3681
|
+
dryRun: true,
|
|
3682
|
+
themes: processed.map((p) => p.slug),
|
|
3683
|
+
outputDir
|
|
3684
|
+
})
|
|
3685
|
+
);
|
|
3686
|
+
}
|
|
3687
|
+
return;
|
|
3688
|
+
}
|
|
3689
|
+
const slugs = processed.map((p) => p.slug);
|
|
3690
|
+
const libSrcDir = join15(outputDir, "lib", "src");
|
|
3691
|
+
if (existsSync12(libSrcDir)) {
|
|
3692
|
+
rmSync(libSrcDir, { recursive: true, force: true });
|
|
3693
|
+
}
|
|
3694
|
+
const packageFiles = {
|
|
3695
|
+
"pubspec.yaml": emitVisorThemesPubspec(),
|
|
3696
|
+
"pubspec_overrides.yaml": emitVisorThemesPubspecOverrides(),
|
|
3697
|
+
"analysis_options.yaml": emitAnalysisOptions(),
|
|
3698
|
+
".gitignore": emitGitignore(),
|
|
3699
|
+
"lib/visor_themes.dart": emitMetaBarrel(slugs)
|
|
3700
|
+
};
|
|
3701
|
+
let totalFiles = 0;
|
|
3702
|
+
for (const [relPath, content] of Object.entries(packageFiles)) {
|
|
3703
|
+
const absPath = join15(outputDir, relPath);
|
|
3704
|
+
mkdirSync6(dirname6(absPath), { recursive: true });
|
|
3705
|
+
writeFileSync10(absPath, content, "utf-8");
|
|
3706
|
+
totalFiles++;
|
|
3707
|
+
}
|
|
3708
|
+
for (const { slug: slug2, tokenFiles } of processed) {
|
|
3709
|
+
const themeBaseDir = join15(outputDir, "lib", "src", slug2);
|
|
3710
|
+
for (const [relPath, content] of Object.entries(tokenFiles)) {
|
|
3711
|
+
const absPath = join15(themeBaseDir, relPath);
|
|
3712
|
+
mkdirSync6(dirname6(absPath), { recursive: true });
|
|
3713
|
+
writeFileSync10(absPath, content, "utf-8");
|
|
3714
|
+
totalFiles++;
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
if (options.json) {
|
|
3718
|
+
console.log(
|
|
3719
|
+
JSON.stringify({
|
|
3720
|
+
success: true,
|
|
3721
|
+
outputDir,
|
|
3722
|
+
themes: slugs,
|
|
3723
|
+
totalFiles
|
|
3724
|
+
})
|
|
3725
|
+
);
|
|
3726
|
+
} else {
|
|
3727
|
+
logger.success(`Flutter theme package generated: ${outputDir}`);
|
|
3728
|
+
logger.item(`Themes: ${slugs.join(", ")}`);
|
|
3729
|
+
logger.item(`Total files written: ${totalFiles}`);
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
|
|
3026
3733
|
// src/commands/fonts-add.ts
|
|
3027
|
-
import { existsSync as
|
|
3028
|
-
import { resolve as resolve8, basename as
|
|
3734
|
+
import { existsSync as existsSync13, statSync as statSync4, readdirSync as readdirSync6, readFileSync as readFileSync16 } from "fs";
|
|
3735
|
+
import { resolve as resolve8, basename as basename4, extname as extname3 } from "path";
|
|
3029
3736
|
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
|
|
3030
3737
|
function deriveFamilySlug(filename) {
|
|
3031
|
-
const name =
|
|
3738
|
+
const name = basename4(filename, extname3(filename));
|
|
3032
3739
|
const WEIGHT_STYLE_SUFFIXES = /* @__PURE__ */ new Set([
|
|
3033
3740
|
"thin",
|
|
3034
3741
|
"hairline",
|
|
@@ -3070,26 +3777,26 @@ function deriveFamilySlug(filename) {
|
|
|
3070
3777
|
}
|
|
3071
3778
|
function collectWoff2Files(inputPath) {
|
|
3072
3779
|
const resolved = resolve8(inputPath);
|
|
3073
|
-
if (!
|
|
3780
|
+
if (!existsSync13(resolved)) {
|
|
3074
3781
|
throw new Error(`Path not found: ${resolved}`);
|
|
3075
3782
|
}
|
|
3076
|
-
const stat =
|
|
3783
|
+
const stat = statSync4(resolved);
|
|
3077
3784
|
if (stat.isFile()) {
|
|
3078
3785
|
if (extname3(resolved).toLowerCase() !== ".woff2") {
|
|
3079
3786
|
throw new Error(
|
|
3080
|
-
`Invalid file format: ${
|
|
3787
|
+
`Invalid file format: ${basename4(resolved)}. Only .woff2 files are accepted.`
|
|
3081
3788
|
);
|
|
3082
3789
|
}
|
|
3083
3790
|
return [resolved];
|
|
3084
3791
|
}
|
|
3085
3792
|
if (stat.isDirectory()) {
|
|
3086
|
-
const files =
|
|
3793
|
+
const files = readdirSync6(resolved).filter((f) => extname3(f).toLowerCase() === ".woff2").map((f) => resolve8(resolved, f));
|
|
3087
3794
|
if (files.length === 0) {
|
|
3088
3795
|
throw new Error(
|
|
3089
3796
|
`No .woff2 files found in directory: ${resolved}`
|
|
3090
3797
|
);
|
|
3091
3798
|
}
|
|
3092
|
-
const nonWoff2Fonts =
|
|
3799
|
+
const nonWoff2Fonts = readdirSync6(resolved).filter((f) => {
|
|
3093
3800
|
const ext = extname3(f).toLowerCase();
|
|
3094
3801
|
return [".ttf", ".otf", ".woff", ".eot"].includes(ext);
|
|
3095
3802
|
});
|
|
@@ -3099,10 +3806,10 @@ function collectWoff2Files(inputPath) {
|
|
|
3099
3806
|
}
|
|
3100
3807
|
function getNonWoff2Fonts(inputPath) {
|
|
3101
3808
|
const resolved = resolve8(inputPath);
|
|
3102
|
-
if (!
|
|
3809
|
+
if (!existsSync13(resolved) || !statSync4(resolved).isDirectory()) {
|
|
3103
3810
|
return [];
|
|
3104
3811
|
}
|
|
3105
|
-
return
|
|
3812
|
+
return readdirSync6(resolved).filter((f) => {
|
|
3106
3813
|
const ext = extname3(f).toLowerCase();
|
|
3107
3814
|
return [".ttf", ".otf", ".woff", ".eot"].includes(ext);
|
|
3108
3815
|
});
|
|
@@ -3136,7 +3843,7 @@ function createR2Client(config) {
|
|
|
3136
3843
|
});
|
|
3137
3844
|
}
|
|
3138
3845
|
async function uploadFile(client, bucket, key, filePath) {
|
|
3139
|
-
const body =
|
|
3846
|
+
const body = readFileSync16(filePath);
|
|
3140
3847
|
await client.send(
|
|
3141
3848
|
new PutObjectCommand({
|
|
3142
3849
|
Bucket: bucket,
|
|
@@ -3151,9 +3858,9 @@ async function fontsAddCommand(inputPath, options) {
|
|
|
3151
3858
|
try {
|
|
3152
3859
|
const r2Config = getR2Config();
|
|
3153
3860
|
const files = collectWoff2Files(inputPath);
|
|
3154
|
-
const familySlug = options.family ?? deriveFamilySlug(
|
|
3861
|
+
const familySlug = options.family ?? deriveFamilySlug(basename4(files[0]));
|
|
3155
3862
|
const resolved = resolve8(inputPath);
|
|
3156
|
-
const nonWoff2 =
|
|
3863
|
+
const nonWoff2 = statSync4(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
|
|
3157
3864
|
if (!json) {
|
|
3158
3865
|
logger.heading("Visor Font Upload");
|
|
3159
3866
|
logger.info(`Organization: ${org}`);
|
|
@@ -3171,13 +3878,13 @@ async function fontsAddCommand(inputPath, options) {
|
|
|
3171
3878
|
const bucket = "visor-fonts";
|
|
3172
3879
|
const results = [];
|
|
3173
3880
|
for (const filePath of files) {
|
|
3174
|
-
const filename =
|
|
3881
|
+
const filename = basename4(filePath);
|
|
3175
3882
|
const key = buildS3Key(org, familySlug, filename);
|
|
3176
3883
|
if (!json) {
|
|
3177
3884
|
logger.info(`Uploading ${filename}...`);
|
|
3178
3885
|
}
|
|
3179
3886
|
await uploadFile(client, bucket, key, filePath);
|
|
3180
|
-
const size =
|
|
3887
|
+
const size = statSync4(filePath).size;
|
|
3181
3888
|
results.push({ file: filename, key, size });
|
|
3182
3889
|
if (!json) {
|
|
3183
3890
|
logger.success(`Uploaded: ${key} (${formatBytes(size)})`);
|
|
@@ -3227,7 +3934,7 @@ function formatBytes(bytes) {
|
|
|
3227
3934
|
// src/commands/doctor.ts
|
|
3228
3935
|
import * as fs from "fs";
|
|
3229
3936
|
import * as path from "path";
|
|
3230
|
-
import { execFileSync as
|
|
3937
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
3231
3938
|
async function doctorCommand(cwd, options, cliVersion) {
|
|
3232
3939
|
const checks = [];
|
|
3233
3940
|
const visorJsonPath = path.join(cwd, "visor.json");
|
|
@@ -3362,9 +4069,9 @@ async function doctorCommand(cwd, options, cliVersion) {
|
|
|
3362
4069
|
}
|
|
3363
4070
|
if (process.platform !== "win32") {
|
|
3364
4071
|
try {
|
|
3365
|
-
const globalPath =
|
|
4072
|
+
const globalPath = execFileSync3("which", ["visor"], { encoding: "utf-8" }).trim();
|
|
3366
4073
|
if (globalPath) {
|
|
3367
|
-
const globalVersionRaw =
|
|
4074
|
+
const globalVersionRaw = execFileSync3(globalPath, ["--version"], { encoding: "utf-8" }).trim();
|
|
3368
4075
|
const globalVersion = globalVersionRaw.split(/\s+/).pop() ?? "";
|
|
3369
4076
|
if (isOlder(globalVersion, cliVersion)) {
|
|
3370
4077
|
checks.push({
|
|
@@ -3440,27 +4147,27 @@ function findCssFiles(dir, maxDepth = 3) {
|
|
|
3440
4147
|
}
|
|
3441
4148
|
|
|
3442
4149
|
// src/utils/patterns.ts
|
|
3443
|
-
import { existsSync as
|
|
3444
|
-
import { join as
|
|
4150
|
+
import { existsSync as existsSync15, readdirSync as readdirSync8, readFileSync as readFileSync18 } from "fs";
|
|
4151
|
+
import { join as join17 } from "path";
|
|
3445
4152
|
import { parse as parseYAML } from "yaml";
|
|
3446
4153
|
function loadPatternsFromYaml(repoRoot) {
|
|
3447
|
-
const patternsDir =
|
|
3448
|
-
if (!
|
|
3449
|
-
const files =
|
|
4154
|
+
const patternsDir = join17(repoRoot, "patterns");
|
|
4155
|
+
if (!existsSync15(patternsDir)) return [];
|
|
4156
|
+
const files = readdirSync8(patternsDir).filter(
|
|
3450
4157
|
(f) => f.endsWith(".visor-pattern.yaml")
|
|
3451
4158
|
);
|
|
3452
4159
|
return files.map((file) => {
|
|
3453
|
-
const content =
|
|
4160
|
+
const content = readFileSync18(join17(patternsDir, file), "utf-8");
|
|
3454
4161
|
return parseYAML(content);
|
|
3455
4162
|
}).filter(Boolean);
|
|
3456
4163
|
}
|
|
3457
4164
|
function findRepoRoot2(startDir) {
|
|
3458
4165
|
let current = startDir;
|
|
3459
4166
|
while (true) {
|
|
3460
|
-
if (
|
|
4167
|
+
if (existsSync15(join17(current, "patterns"))) {
|
|
3461
4168
|
return current;
|
|
3462
4169
|
}
|
|
3463
|
-
const parent =
|
|
4170
|
+
const parent = join17(current, "..");
|
|
3464
4171
|
if (parent === current) return null;
|
|
3465
4172
|
current = parent;
|
|
3466
4173
|
}
|
|
@@ -3842,14 +4549,15 @@ Visor Tokens (${categoryLabel}) \u2014 ${tokens2.length} tokens
|
|
|
3842
4549
|
var program = new Command2();
|
|
3843
4550
|
program.name("visor").description("CLI for the Visor design system").version("0.3.0");
|
|
3844
4551
|
program.addCommand(checkCommand());
|
|
3845
|
-
program.command("init").description("Initialize Visor in
|
|
4552
|
+
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) => {
|
|
3846
4553
|
initCommand(process.cwd(), options);
|
|
3847
4554
|
});
|
|
3848
4555
|
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) => {
|
|
3849
4556
|
listCommand(process.cwd(), options);
|
|
3850
4557
|
});
|
|
3851
|
-
program.command("add").description("Add components, hooks, blocks, or utilities to your project").argument("[items...]", "names of registry items to add").option("--overwrite", "overwrite existing files", false).option("--category <name>", "install all items from a category").option("--block", "install blocks instead of components").option("--dry-run", "preview what would be added without writing files").option("--json", "output structured JSON (for AI agents)").action((items, options) => {
|
|
3852
|
-
|
|
4558
|
+
program.command("add").description("Add components, hooks, blocks, or utilities to your project").argument("[items...]", "names of registry items to add").option("--overwrite", "overwrite existing files", false).option("--category <name>", "install all items from a category").option("--block", "install blocks instead of components").option("--target <platform>", "target platform: react (default) or flutter", "react").option("--dry-run", "preview what would be added without writing files").option("--json", "output structured JSON (for AI agents)").action((items, options) => {
|
|
4559
|
+
const target = options.target === "flutter" ? "flutter" : "react";
|
|
4560
|
+
addCommand(items, process.cwd(), { overwrite: options.overwrite, category: options.category, block: options.block, target, dryRun: options.dryRun, json: options.json });
|
|
3853
4561
|
});
|
|
3854
4562
|
program.command("diff").description(
|
|
3855
4563
|
"Show differences between local files and the registry"
|
|
@@ -3864,8 +4572,29 @@ program.command("doctor").description("Run diagnostics on a Visor installation")
|
|
|
3864
4572
|
});
|
|
3865
4573
|
var theme = program.command("theme").description("Theme management commands");
|
|
3866
4574
|
theme.command("apply").description(
|
|
3867
|
-
"Read a .visor.yaml file and generate
|
|
3868
|
-
).argument("<file>", "path to .visor.yaml file").option(
|
|
4575
|
+
"Read a .visor.yaml file and generate token overrides (CSS or Flutter)"
|
|
4576
|
+
).argument("<file>", "path to .visor.yaml file").option(
|
|
4577
|
+
"-o, --output <path>",
|
|
4578
|
+
"output CSS file path (or directory for --adapter flutter)"
|
|
4579
|
+
).option("--json", "output structured JSON (for AI agents)").option(
|
|
4580
|
+
"--adapter <name>",
|
|
4581
|
+
"target adapter: nextjs, fumadocs, deck, docs, flutter"
|
|
4582
|
+
).option(
|
|
4583
|
+
"--package-name <name>",
|
|
4584
|
+
"(flutter) Dart package name for generated pubspec.yaml (default: ui)"
|
|
4585
|
+
).option(
|
|
4586
|
+
"--tokens-only",
|
|
4587
|
+
"(flutter) emit only token files \u2014 skip pubspec.yaml and theme scaffolding"
|
|
4588
|
+
).option(
|
|
4589
|
+
"--light-only",
|
|
4590
|
+
"(flutter) emit only the light-brightness theme getter"
|
|
4591
|
+
).option(
|
|
4592
|
+
"--dark-only",
|
|
4593
|
+
"(flutter) emit only the dark-brightness theme getter"
|
|
4594
|
+
).option(
|
|
4595
|
+
"--theme-class-name <name>",
|
|
4596
|
+
"(flutter) class name for generated theme (default: VisorAppTheme)"
|
|
4597
|
+
).action(
|
|
3869
4598
|
(file, options) => {
|
|
3870
4599
|
themeApplyCommand(file, process.cwd(), {
|
|
3871
4600
|
...options,
|
|
@@ -3909,8 +4638,8 @@ theme.command("register").description(
|
|
|
3909
4638
|
theme.command("unregister").description(
|
|
3910
4639
|
"Remove a theme from the docs site \u2014 deletes CSS file, removes globals.css import and theme-config.ts entry"
|
|
3911
4640
|
).argument("<slug>", "theme slug to unregister (e.g. entr, kaiah)").option("--json", "output structured JSON (for AI agents)").action(
|
|
3912
|
-
(
|
|
3913
|
-
themeUnregisterCommand(
|
|
4641
|
+
(slug2, options) => {
|
|
4642
|
+
themeUnregisterCommand(slug2, process.cwd(), options);
|
|
3914
4643
|
}
|
|
3915
4644
|
);
|
|
3916
4645
|
theme.command("sync").description(
|
|
@@ -3920,6 +4649,13 @@ theme.command("sync").description(
|
|
|
3920
4649
|
themeSyncCommand(process.cwd(), options);
|
|
3921
4650
|
}
|
|
3922
4651
|
);
|
|
4652
|
+
theme.command("batch-apply-flutter").description(
|
|
4653
|
+
"Batch-generate Dart ThemeData for all .visor.yaml themes (themes/ + custom-themes/) into packages/visor_themes/"
|
|
4654
|
+
).option("--dry-run", "show what would be generated without writing files").option("--json", "output structured JSON (for AI agents)").action(
|
|
4655
|
+
(options) => {
|
|
4656
|
+
themeBatchApplyFlutterCommand(process.cwd(), options);
|
|
4657
|
+
}
|
|
4658
|
+
);
|
|
3923
4659
|
var fonts = program.command("fonts").description("Font library management commands");
|
|
3924
4660
|
fonts.command("add").description("Upload woff2 font files to the Visor Font Library on R2").argument("<path>", "path to a .woff2 file or directory containing .woff2 files").requiredOption("--org <org>", "organization namespace (e.g. low-orbit)").option("--family <name>", "font family slug (auto-inferred from filename if omitted)").option("--json", "output structured JSON (for AI agents)").action(
|
|
3925
4661
|
(path2, options) => {
|