@dalexto/lexsys-cli 0.0.5 → 0.1.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.
@@ -1,5 +1,11 @@
1
1
  interface RunStatusOptions {
2
+ json?: boolean;
2
3
  noFallback?: boolean;
3
4
  }
5
+ export interface StatusEntry {
6
+ name: string;
7
+ canonicalName: string;
8
+ drift: "synced" | "drift" | "missing";
9
+ }
4
10
  export declare const runStatus: (options?: RunStatusOptions) => Promise<void>;
5
11
  export {};
@@ -17,6 +17,7 @@ export interface LexsysConfig {
17
17
  tailwind: LexsysTailwindConfig;
18
18
  installed?: string[];
19
19
  registryUrl?: string | null;
20
+ registryAllowlist?: string[];
20
21
  }
21
22
  export interface LexsysTailwindConfig {
22
23
  version: "v4";
package/dist/index.js CHANGED
@@ -131,7 +131,8 @@ var defaultConfig = {
131
131
  aliases: defaultAliasesConfig,
132
132
  tailwind: defaultTailwindConfig,
133
133
  installed: [],
134
- registryUrl: null
134
+ registryUrl: null,
135
+ registryAllowlist: []
135
136
  };
136
137
  var getConfigPath = () => {
137
138
  return join(getCwd(), "lexsys.config.json");
@@ -158,7 +159,10 @@ var loadConfig = async () => {
158
159
  ...defaultTailwindConfig,
159
160
  ...parsed.tailwind
160
161
  },
161
- installed: normalizeInstalled(parsed.installed)
162
+ installed: normalizeInstalled(parsed.installed),
163
+ registryAllowlist: Array.isArray(parsed.registryAllowlist) ? parsed.registryAllowlist.filter((entry) => {
164
+ return typeof entry === "string" && entry.length > 0;
165
+ }) : defaultConfig.registryAllowlist
162
166
  };
163
167
  if (isLegacyInstalledRecord(parsed.installed)) {
164
168
  await saveConfig(config);
@@ -313,9 +317,14 @@ var getRegistryTemplatePath = (templatePath) => {
313
317
  );
314
318
  return fileURLToPath(templateUrl);
315
319
  };
316
- var generatedStyleHeader = "/* Generated by @dalexto/lexsys-tokens. Do not edit directly. */";
320
+ var generatedStyleFileHeader = `/**
321
+ * AUTO-GENERATED FILE.
322
+ *
323
+ * Generated by @dalexto/lexsys-tokens.
324
+ * Manual changes will be overwritten.
325
+ `;
317
326
  var isGeneratedLexsysStyle = (content) => {
318
- return content.startsWith(generatedStyleHeader);
327
+ return content.startsWith(generatedStyleFileHeader);
319
328
  };
320
329
  var ensureProjectStructure = async (config) => {
321
330
  await mkdir(join(getCwd(), config.paths.components), { recursive: true });
@@ -817,6 +826,21 @@ var parseRegistryStyles = (styles) => {
817
826
  }
818
827
  return styles;
819
828
  };
829
+ var computeRemoteRegistryChecksum = (manifest) => {
830
+ return hashContent(JSON.stringify(manifest));
831
+ };
832
+ var verifyRemoteRegistryChecksum = (manifest) => {
833
+ if (!manifest.checksum) {
834
+ return;
835
+ }
836
+ const { checksum, ...rest } = manifest;
837
+ const computed = computeRemoteRegistryChecksum(rest);
838
+ if (computed !== checksum) {
839
+ throw new Error(
840
+ `Remote registry checksum mismatch. Expected ${checksum}, computed ${computed}.`
841
+ );
842
+ }
843
+ };
820
844
  var parseRemoteRegistry = (value) => {
821
845
  if (Array.isArray(value)) {
822
846
  return {
@@ -841,8 +865,36 @@ var parseRemoteRegistry = (value) => {
841
865
  }
842
866
  parsed.styles = parseRegistryStyles(manifest.styles);
843
867
  }
868
+ if (manifest.checksum !== void 0) {
869
+ if (typeof manifest.checksum !== "string" || manifest.checksum.length === 0) {
870
+ throw new Error(
871
+ "Remote registry manifest checksum must be a non-empty string."
872
+ );
873
+ }
874
+ parsed.checksum = manifest.checksum;
875
+ }
876
+ verifyRemoteRegistryChecksum(parsed);
844
877
  return parsed;
845
878
  };
879
+ var isRegistryUrlAllowed = (url, allowlist) => {
880
+ if (!allowlist?.length) {
881
+ return true;
882
+ }
883
+ let parsed;
884
+ try {
885
+ parsed = new URL(url);
886
+ } catch {
887
+ return false;
888
+ }
889
+ const host = parsed.hostname;
890
+ const origin = parsed.origin;
891
+ return allowlist.some((entry) => {
892
+ if (entry === host || entry === origin || entry === url) {
893
+ return true;
894
+ }
895
+ return url.startsWith(entry);
896
+ });
897
+ };
846
898
  var fetchRemoteRegistry = async (url) => {
847
899
  const response = await fetch(url);
848
900
  if (!response.ok) {
@@ -855,7 +907,16 @@ var fetchRemoteRegistry = async (url) => {
855
907
  // src/registry/source.ts
856
908
  var getRegistrySource = async () => {
857
909
  const config = await loadConfig();
858
- return config.registryUrl ?? "local";
910
+ const registryUrl = config.registryUrl;
911
+ if (!registryUrl) {
912
+ return "local";
913
+ }
914
+ if (!isRegistryUrlAllowed(registryUrl, config.registryAllowlist)) {
915
+ throw new Error(
916
+ `Registry URL is not allowed by registryAllowlist: ${registryUrl}`
917
+ );
918
+ }
919
+ return registryUrl;
859
920
  };
860
921
 
861
922
  // src/registry/provider.ts
@@ -1119,7 +1180,14 @@ var runAdd = async (args2) => {
1119
1180
  ]);
1120
1181
  if (!items.length) {
1121
1182
  if (yes) {
1122
- console.log("No components specified. Pass component names to add them.");
1183
+ console.log(
1184
+ "No components specified. Pass names to install non-interactively, for example:"
1185
+ );
1186
+ console.log(" lexsys add button dialog --yes");
1187
+ console.log(
1188
+ "To refresh all tracked components, use `lexsys update --yes` instead."
1189
+ );
1190
+ process.exitCode = 1;
1123
1191
  return;
1124
1192
  }
1125
1193
  items = await promptSelectItems();
@@ -1255,6 +1323,29 @@ var runConfig = async (options = {}) => {
1255
1323
  const config = await loadConfig();
1256
1324
  console.log(JSON.stringify(config, null, 2));
1257
1325
  };
1326
+
1327
+ // src/utils/registry-errors.ts
1328
+ var formatRegistryResolveError = (error) => {
1329
+ return error instanceof Error ? error.message : String(error);
1330
+ };
1331
+ var markRegistryResolveFailure = () => {
1332
+ process.exitCode = 1;
1333
+ };
1334
+ var printRegistryResolveFailure = (error) => {
1335
+ console.log("Failed to resolve registry.");
1336
+ console.log(formatRegistryResolveError(error));
1337
+ markRegistryResolveFailure();
1338
+ };
1339
+ var printRegistryResolveFailureChecklist = (error, options = {}) => {
1340
+ if (options.sectionHeading) {
1341
+ console.log(options.sectionHeading);
1342
+ }
1343
+ console.log("\xD7 failed to resolve registry");
1344
+ console.log(formatRegistryResolveError(error));
1345
+ markRegistryResolveFailure();
1346
+ };
1347
+
1348
+ // src/commands/doctor.ts
1258
1349
  var runDoctor = async (options = {}) => {
1259
1350
  console.log("Lexsys doctor\n");
1260
1351
  const config = await loadConfig();
@@ -1295,10 +1386,9 @@ var runDoctor = async (options = {}) => {
1295
1386
  console.log(`\u2713 items: ${registryResult.items.length}`);
1296
1387
  } catch (error) {
1297
1388
  registryFailed = true;
1298
- console.log("\nRegistry:");
1299
- console.log("\xD7 failed to resolve registry");
1300
- console.log(error instanceof Error ? error.message : String(error));
1301
- process.exitCode = 1;
1389
+ printRegistryResolveFailureChecklist(error, {
1390
+ sectionHeading: "\nRegistry:"
1391
+ });
1302
1392
  }
1303
1393
  if (registryFailed && options.noFallback) {
1304
1394
  return;
@@ -1352,7 +1442,7 @@ Usage
1352
1442
 
1353
1443
  Options
1354
1444
  --dry-run, -d Preview files, dependencies, and install paths
1355
- --yes, -y Auto-confirm safe prompts
1445
+ --yes, -y Non-interactive when component names are provided
1356
1446
  --no-fallback Fail instead of falling back to local registry
1357
1447
  --cwd, -C <path> Run from a different project directory
1358
1448
  --help, -h Show this help
@@ -1381,6 +1471,7 @@ Options
1381
1471
  --help, -h Show this help
1382
1472
 
1383
1473
  Run without arguments for guided update picker.
1474
+ With --yes and no names, updates all tracked components.
1384
1475
 
1385
1476
  Examples
1386
1477
  lexsys up
@@ -1408,9 +1499,17 @@ Usage
1408
1499
  lexsys status
1409
1500
  lexsys st
1410
1501
 
1502
+ Shows template drift for tracked components (up to date vs out of sync).
1503
+ For project paths and registry connectivity, use \`lexsys doctor\`.
1504
+
1411
1505
  Options
1506
+ --json, -j Print installed component drift as JSON
1412
1507
  --no-fallback Fail instead of falling back to local registry
1413
1508
  --help, -h Show this help
1509
+
1510
+ Examples
1511
+ lexsys st
1512
+ lexsys st --json
1414
1513
  `,
1415
1514
  reset: `
1416
1515
  Usage
@@ -1419,6 +1518,7 @@ Usage
1419
1518
  Options
1420
1519
  --dry-run, -d Preview reset without writing files
1421
1520
  --with-deps, -w Also reset installed registry dependencies in the closure
1521
+ --yes, -y Non-interactive; with no names, resets all tracked components
1422
1522
  --no-fallback Fail instead of falling back to local registry
1423
1523
  --cwd, -C <path> Run from a different project directory
1424
1524
  --help, -h Show this help
@@ -1438,6 +1538,8 @@ Usage
1438
1538
  Options
1439
1539
  --dry-run, -d Preview uninstall without removing files
1440
1540
  --with-deps, -w Also remove registry-owned shared dependencies
1541
+ --yes, -y Non-interactive; with no names, uninstalls all tracked components
1542
+ --no-fallback Fail instead of falling back to local registry
1441
1543
  --help, -h Show this help
1442
1544
 
1443
1545
  Run without arguments for guided uninstall picker.
@@ -1451,6 +1553,9 @@ Usage
1451
1553
  lexsys doctor
1452
1554
  lexsys dr
1453
1555
 
1556
+ Checks project paths, registry connectivity, and on-disk component folders.
1557
+ For template drift vs the registry, use \`lexsys status\`.
1558
+
1454
1559
  Options
1455
1560
  --no-fallback Fail instead of falling back to local registry
1456
1561
  --help, -h Show this help
@@ -1516,8 +1621,8 @@ Components
1516
1621
 
1517
1622
  Inspect
1518
1623
  list List available registry items [alias: ls]
1519
- status Show installed component status [alias: st]
1520
- doctor Check local project setup [alias: dr]
1624
+ status Template drift for installed components [alias: st]
1625
+ doctor Project paths and registry health [alias: dr]
1521
1626
  registry Inspect registry source and manifest [alias: reg]
1522
1627
  config Print or update Lexsys config [alias: cfg]
1523
1628
 
@@ -1527,7 +1632,7 @@ Meta
1527
1632
 
1528
1633
  Global Options
1529
1634
  --cwd, -C <path> Run from a different project directory
1530
- --yes, -y Auto-confirm safe prompts where supported
1635
+ --yes, -y Non-interactive mode (add, update, reset, uninstall)
1531
1636
  --no-fallback Disable local registry fallback
1532
1637
  --help, -h Show help
1533
1638
  --version, -v Show CLI version
@@ -2622,6 +2727,10 @@ var runStatus = async (options = {}) => {
2622
2727
  const config = await loadConfig();
2623
2728
  const installed = config.installed ?? [];
2624
2729
  if (!installed.length) {
2730
+ if (options.json) {
2731
+ console.log(JSON.stringify({ installed: [] }, null, 2));
2732
+ return;
2733
+ }
2625
2734
  console.log("No Lexsys components are currently tracked.");
2626
2735
  return;
2627
2736
  }
@@ -2630,21 +2739,52 @@ var runStatus = async (options = {}) => {
2630
2739
  fallback: !options.noFallback
2631
2740
  });
2632
2741
  } catch (error) {
2633
- console.log("Failed to resolve registry.");
2634
- console.log(error instanceof Error ? error.message : String(error));
2635
- process.exitCode = 1;
2742
+ if (options.json) {
2743
+ console.log(
2744
+ JSON.stringify(
2745
+ {
2746
+ error: error instanceof Error ? error.message : String(error)
2747
+ },
2748
+ null,
2749
+ 2
2750
+ )
2751
+ );
2752
+ process.exitCode = 1;
2753
+ return;
2754
+ }
2755
+ printRegistryResolveFailure(error);
2636
2756
  return;
2637
2757
  }
2638
- console.log("Installed Lexsys components:\n");
2758
+ const entries = [];
2639
2759
  for (const name of installed) {
2640
2760
  const item = await findItem(name);
2641
2761
  if (!item) {
2642
- console.log(`- ${name} (missing from registry)`);
2762
+ entries.push({
2763
+ name,
2764
+ canonicalName: name,
2765
+ drift: "missing"
2766
+ });
2643
2767
  continue;
2644
2768
  }
2645
2769
  const driftStatus = await getComponentDriftStatus(name);
2646
- const status = driftStatus === "drift" ? "out of sync with registry" : "up to date with registry";
2647
- console.log(`- ${item.canonicalName} (${status})`);
2770
+ entries.push({
2771
+ name: item.name,
2772
+ canonicalName: item.canonicalName,
2773
+ drift: driftStatus === "drift" ? "drift" : "synced"
2774
+ });
2775
+ }
2776
+ if (options.json) {
2777
+ console.log(JSON.stringify({ installed: entries }, null, 2));
2778
+ return;
2779
+ }
2780
+ console.log("Installed Lexsys components:\n");
2781
+ for (const entry of entries) {
2782
+ if (entry.drift === "missing") {
2783
+ console.log(`- ${entry.name} (missing from registry)`);
2784
+ continue;
2785
+ }
2786
+ const status = entry.drift === "drift" ? "out of sync with registry" : "up to date with registry";
2787
+ console.log(`- ${entry.canonicalName} (${status})`);
2648
2788
  }
2649
2789
  };
2650
2790
 
@@ -2862,6 +3002,7 @@ var resolveInstalledKey = async (name, installed) => {
2862
3002
  var runReset = async (args2) => {
2863
3003
  const dryRun = hasFlag(args2, "--dry-run", "-d");
2864
3004
  const withDeps = hasFlag(args2, "--with-deps", "-w");
3005
+ const yes = hasFlag(args2, "--yes", "-y");
2865
3006
  const noFallback = hasFlag(args2, "--no-fallback");
2866
3007
  const targetArgs = removeFlagsWithValues(
2867
3008
  removeFlags(args2, [
@@ -2869,6 +3010,8 @@ var runReset = async (args2) => {
2869
3010
  "-d",
2870
3011
  "--with-deps",
2871
3012
  "-w",
3013
+ "--yes",
3014
+ "-y",
2872
3015
  "--no-fallback"
2873
3016
  ]),
2874
3017
  ["--cwd", "-C"]
@@ -2890,13 +3033,17 @@ var runReset = async (args2) => {
2890
3033
  return;
2891
3034
  }
2892
3035
  if (!targetArgs.length) {
2893
- const selected = await promptMultiselect(
2894
- "Select components to reset",
2895
- installed.map((name) => ({ title: name, value: name })),
2896
- { min: 1 }
2897
- );
2898
- if (!selected.length) return;
2899
- targetArgs.push(...selected);
3036
+ if (yes) {
3037
+ targetArgs.push(...installed);
3038
+ } else {
3039
+ const selected = await promptMultiselect(
3040
+ "Select components to reset",
3041
+ installed.map((name) => ({ title: name, value: name })),
3042
+ { min: 1 }
3043
+ );
3044
+ if (!selected.length) return;
3045
+ targetArgs.push(...selected);
3046
+ }
2900
3047
  }
2901
3048
  const resetNames = /* @__PURE__ */ new Set();
2902
3049
  for (const name of targetArgs) {
@@ -2963,6 +3110,7 @@ var collectOrphanedSharedResources = (removedItems, remainingItems) => {
2963
3110
  var runUninstall = async (args2) => {
2964
3111
  const dryRun = hasFlag(args2, "--dry-run", "-d");
2965
3112
  const withDeps = hasFlag(args2, "--with-deps", "-w");
3113
+ const yes = hasFlag(args2, "--yes", "-y");
2966
3114
  const noFallback = hasFlag(args2, "--no-fallback");
2967
3115
  const targetArgs = removeFlagsWithValues(
2968
3116
  removeFlags(args2, [
@@ -2970,27 +3118,31 @@ var runUninstall = async (args2) => {
2970
3118
  "-d",
2971
3119
  "--with-deps",
2972
3120
  "-w",
3121
+ "--yes",
3122
+ "-y",
2973
3123
  "--no-fallback"
2974
3124
  ]),
2975
3125
  ["--cwd", "-C"]
2976
3126
  );
3127
+ const config = await loadConfig();
3128
+ const installed = [...config.installed ?? []];
2977
3129
  if (!targetArgs.length) {
2978
- const preConfig = await loadConfig();
2979
- const installedNames = preConfig.installed ?? [];
2980
- if (installedNames.length === 0) {
3130
+ if (!installed.length) {
2981
3131
  console.log("No components installed.");
2982
3132
  return;
2983
3133
  }
2984
- const selected = await promptMultiselect(
2985
- "Select components to uninstall",
2986
- installedNames.map((name) => ({ title: name, value: name })),
2987
- { min: 1 }
2988
- );
2989
- if (!selected.length) return;
2990
- targetArgs.push(...selected);
3134
+ if (yes) {
3135
+ targetArgs.push(...installed);
3136
+ } else {
3137
+ const selected = await promptMultiselect(
3138
+ "Select components to uninstall",
3139
+ installed.map((name) => ({ title: name, value: name })),
3140
+ { min: 1 }
3141
+ );
3142
+ if (!selected.length) return;
3143
+ targetArgs.push(...selected);
3144
+ }
2991
3145
  }
2992
- const config = await loadConfig();
2993
- const installed = [...config.installed ?? []];
2994
3146
  const resolvedTargets = [];
2995
3147
  const notTracked = [];
2996
3148
  for (const name of targetArgs) {
@@ -3375,6 +3527,7 @@ try {
3375
3527
  process.exit(0);
3376
3528
  }
3377
3529
  await runStatus({
3530
+ json: hasFlag(args, "--json", "-j"),
3378
3531
  noFallback: args.includes("--no-fallback")
3379
3532
  });
3380
3533
  process.exit(0);
@@ -3,13 +3,26 @@ export interface RemoteRegistryManifest {
3
3
  version: string;
4
4
  items: RegistryItem[];
5
5
  styles?: RegistryStyle[];
6
+ checksum?: string;
6
7
  }
8
+ /**
9
+ * Computes a SHA-256 checksum for a remote manifest (excluding the checksum field).
10
+ */
11
+ export declare const computeRemoteRegistryChecksum: (manifest: Omit<RemoteRegistryManifest, "checksum">) => string;
12
+ /**
13
+ * Verifies an optional manifest checksum when publishers include one.
14
+ */
15
+ export declare const verifyRemoteRegistryChecksum: (manifest: RemoteRegistryManifest) => void;
7
16
  /**
8
17
  * Parses a remote registry JSON payload into a manifest object.
9
18
  *
10
19
  * Accepts either:
11
- * - a manifest object `{ version, items, styles? }`
20
+ * - a manifest object `{ version, items, styles?, checksum? }`
12
21
  * - a legacy bare array of registry items (version becomes `"unknown"`)
13
22
  */
14
23
  export declare const parseRemoteRegistry: (value: unknown) => RemoteRegistryManifest;
24
+ /**
25
+ * Returns true when the registry URL host or prefix matches an allowlist entry.
26
+ */
27
+ export declare const isRegistryUrlAllowed: (url: string, allowlist: string[] | undefined) => boolean;
15
28
  export declare const fetchRemoteRegistry: (url: string) => Promise<RemoteRegistryManifest>;
@@ -0,0 +1,6 @@
1
+ export declare const formatRegistryResolveError: (error: unknown) => string;
2
+ export declare const markRegistryResolveFailure: () => void;
3
+ export declare const printRegistryResolveFailure: (error: unknown) => void;
4
+ export declare const printRegistryResolveFailureChecklist: (error: unknown, options?: {
5
+ sectionHeading?: string;
6
+ }) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dalexto/lexsys-cli",
3
- "version": "0.0.5",
3
+ "version": "0.1.0",
4
4
  "description": "Registry-first CLI that installs Lexsys React UI components into your project",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "prompts": "^2.4.2",
35
- "@dalexto/lexsys-registry": "0.0.5"
35
+ "@dalexto/lexsys-registry": "0.1.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^25.9.1",