@dalexto/lexsys-cli 0.0.6 → 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);
@@ -822,6 +826,21 @@ var parseRegistryStyles = (styles) => {
822
826
  }
823
827
  return styles;
824
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
+ };
825
844
  var parseRemoteRegistry = (value) => {
826
845
  if (Array.isArray(value)) {
827
846
  return {
@@ -846,8 +865,36 @@ var parseRemoteRegistry = (value) => {
846
865
  }
847
866
  parsed.styles = parseRegistryStyles(manifest.styles);
848
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);
849
877
  return parsed;
850
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
+ };
851
898
  var fetchRemoteRegistry = async (url) => {
852
899
  const response = await fetch(url);
853
900
  if (!response.ok) {
@@ -860,7 +907,16 @@ var fetchRemoteRegistry = async (url) => {
860
907
  // src/registry/source.ts
861
908
  var getRegistrySource = async () => {
862
909
  const config = await loadConfig();
863
- 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;
864
920
  };
865
921
 
866
922
  // src/registry/provider.ts
@@ -1447,8 +1503,13 @@ Shows template drift for tracked components (up to date vs out of sync).
1447
1503
  For project paths and registry connectivity, use \`lexsys doctor\`.
1448
1504
 
1449
1505
  Options
1506
+ --json, -j Print installed component drift as JSON
1450
1507
  --no-fallback Fail instead of falling back to local registry
1451
1508
  --help, -h Show this help
1509
+
1510
+ Examples
1511
+ lexsys st
1512
+ lexsys st --json
1452
1513
  `,
1453
1514
  reset: `
1454
1515
  Usage
@@ -2666,6 +2727,10 @@ var runStatus = async (options = {}) => {
2666
2727
  const config = await loadConfig();
2667
2728
  const installed = config.installed ?? [];
2668
2729
  if (!installed.length) {
2730
+ if (options.json) {
2731
+ console.log(JSON.stringify({ installed: [] }, null, 2));
2732
+ return;
2733
+ }
2669
2734
  console.log("No Lexsys components are currently tracked.");
2670
2735
  return;
2671
2736
  }
@@ -2674,19 +2739,52 @@ var runStatus = async (options = {}) => {
2674
2739
  fallback: !options.noFallback
2675
2740
  });
2676
2741
  } catch (error) {
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
+ }
2677
2755
  printRegistryResolveFailure(error);
2678
2756
  return;
2679
2757
  }
2680
- console.log("Installed Lexsys components:\n");
2758
+ const entries = [];
2681
2759
  for (const name of installed) {
2682
2760
  const item = await findItem(name);
2683
2761
  if (!item) {
2684
- console.log(`- ${name} (missing from registry)`);
2762
+ entries.push({
2763
+ name,
2764
+ canonicalName: name,
2765
+ drift: "missing"
2766
+ });
2685
2767
  continue;
2686
2768
  }
2687
2769
  const driftStatus = await getComponentDriftStatus(name);
2688
- const status = driftStatus === "drift" ? "out of sync with registry" : "up to date with registry";
2689
- 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})`);
2690
2788
  }
2691
2789
  };
2692
2790
 
@@ -3429,6 +3527,7 @@ try {
3429
3527
  process.exit(0);
3430
3528
  }
3431
3529
  await runStatus({
3530
+ json: hasFlag(args, "--json", "-j"),
3432
3531
  noFallback: args.includes("--no-fallback")
3433
3532
  });
3434
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>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dalexto/lexsys-cli",
3
- "version": "0.0.6",
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.6"
35
+ "@dalexto/lexsys-registry": "0.1.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^25.9.1",