@dalexto/lexsys-cli 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import prompts from 'prompts';
3
2
  import { registryManifest, getInstallLayer, registryItems, validateRegistry, registryStyles, registryVersion, validateRegistryItem, registryUtilities as registryUtilities$1 } from '@dalexto/lexsys-registry';
4
3
  import { readFile, access, mkdir, copyFile, writeFile, unlink, readdir, rm } from 'fs/promises';
5
4
  import { dirname, join, resolve, relative, basename } from 'path';
@@ -7,8 +6,9 @@ import { createHash } from 'crypto';
7
6
  import { constants } from 'fs';
8
7
  import { fileURLToPath } from 'url';
9
8
  import { execFileSync } from 'child_process';
9
+ import prompts from 'prompts';
10
10
 
11
- // src/core/cli-error.ts
11
+ // src/utils/cli-error.ts
12
12
  var CliError = class extends Error {
13
13
  suggestion;
14
14
  constructor(message, suggestion) {
@@ -37,6 +37,38 @@ var handleCliError = (error) => {
37
37
  }
38
38
  process.exit(1);
39
39
  };
40
+
41
+ // src/config/installed.ts
42
+ var isLegacyInstalledRecord = (value) => {
43
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
44
+ };
45
+ var normalizeInstalled = (value) => {
46
+ if (Array.isArray(value)) {
47
+ return value.filter((entry) => typeof entry === "string");
48
+ }
49
+ if (value && typeof value === "object" && !Array.isArray(value)) {
50
+ return Object.keys(value);
51
+ }
52
+ return [];
53
+ };
54
+ var isInstalled = (installed, name) => {
55
+ const normalized = name.toLowerCase();
56
+ return installed.some((entry) => entry.toLowerCase() === normalized);
57
+ };
58
+ var addInstalled = (installed, itemName) => {
59
+ if (isInstalled(installed, itemName)) {
60
+ return installed;
61
+ }
62
+ return [...installed, itemName];
63
+ };
64
+ var removeInstalled = (installed, itemName) => {
65
+ const normalized = itemName.toLowerCase();
66
+ return installed.filter((entry) => entry.toLowerCase() !== normalized);
67
+ };
68
+ var findInstalledKey = (installed, name) => {
69
+ const normalized = name.toLowerCase();
70
+ return installed.find((entry) => entry.toLowerCase() === normalized);
71
+ };
40
72
  var hashContent = (content) => {
41
73
  return createHash("sha256").update(content, "utf-8").digest("hex");
42
74
  };
@@ -67,7 +99,7 @@ var filesAreEqual = async (sourcePath, targetPath) => {
67
99
  return sourceHash === targetHash;
68
100
  };
69
101
 
70
- // src/core/context.ts
102
+ // src/utils/context.ts
71
103
  var currentWorkingDirectory = process.cwd();
72
104
  var setCwd = (cwd2) => {
73
105
  currentWorkingDirectory = cwd2;
@@ -98,7 +130,7 @@ var defaultConfig = {
98
130
  paths: defaultPathsConfig,
99
131
  aliases: defaultAliasesConfig,
100
132
  tailwind: defaultTailwindConfig,
101
- installed: {},
133
+ installed: [],
102
134
  registryUrl: null
103
135
  };
104
136
  var getConfigPath = () => {
@@ -111,7 +143,7 @@ var loadConfig = async () => {
111
143
  }
112
144
  const content = await readFile(configPath, "utf-8");
113
145
  const parsed = JSON.parse(content);
114
- return {
146
+ const config = {
115
147
  ...defaultConfig,
116
148
  ...parsed,
117
149
  paths: {
@@ -125,8 +157,13 @@ var loadConfig = async () => {
125
157
  tailwind: {
126
158
  ...defaultTailwindConfig,
127
159
  ...parsed.tailwind
128
- }
160
+ },
161
+ installed: normalizeInstalled(parsed.installed)
129
162
  };
163
+ if (isLegacyInstalledRecord(parsed.installed)) {
164
+ await saveConfig(config);
165
+ }
166
+ return config;
130
167
  };
131
168
  var saveConfig = async (config) => {
132
169
  const configPath = getConfigPath();
@@ -191,12 +228,10 @@ var validateTemplateFiles = async (item) => {
191
228
 
192
229
  // src/install/results.ts
193
230
  var createInstallResourceResult = () => {
194
- return {
195
- created: [],
196
- updated: [],
197
- skipped: [],
198
- conflicted: []
199
- };
231
+ return { created: [], updated: [], skipped: [], conflicted: [] };
232
+ };
233
+ var createUninstallResourceResult = () => {
234
+ return { removed: [], skipped: [], conflicted: [], missing: [] };
200
235
  };
201
236
  var mergeInstallResults = (results) => {
202
237
  return results.reduce(
@@ -209,33 +244,6 @@ var mergeInstallResults = (results) => {
209
244
  createInstallResourceResult()
210
245
  );
211
246
  };
212
- var hasInstallConflicts = (result) => {
213
- return result.conflicted.length > 0;
214
- };
215
- var printResourceSummary = (label, result) => {
216
- const total = result.created.length + result.updated.length + result.skipped.length + result.conflicted.length;
217
- if (!total) {
218
- console.log(`- ${label}: no changes`);
219
- return;
220
- }
221
- const parts = [
222
- result.created.length ? `${result.created.length} created` : void 0,
223
- result.updated.length ? `${result.updated.length} updated` : void 0,
224
- result.skipped.length ? `${result.skipped.length} skipped` : void 0,
225
- result.conflicted.length ? `${result.conflicted.length} conflicted` : void 0
226
- ].filter((part) => typeof part === "string");
227
- console.log(`- ${label}: ${parts.join(", ")}`);
228
- };
229
-
230
- // src/install/uninstall-results.ts
231
- var createUninstallResourceResult = () => {
232
- return {
233
- removed: [],
234
- skipped: [],
235
- conflicted: [],
236
- missing: []
237
- };
238
- };
239
247
  var mergeUninstallResults = (results) => {
240
248
  return results.reduce(
241
249
  (merged, result) => ({
@@ -247,21 +255,39 @@ var mergeUninstallResults = (results) => {
247
255
  createUninstallResourceResult()
248
256
  );
249
257
  };
258
+ var hasInstallConflicts = (result) => {
259
+ return result.conflicted.length > 0;
260
+ };
250
261
  var hasUninstallConflicts = (result) => {
251
262
  return result.conflicted.length > 0;
252
263
  };
264
+ var formatParts = (parts) => {
265
+ return parts.filter((p) => p.count > 0).map((p) => `${p.count} ${p.label}`);
266
+ };
267
+ var printResourceSummary = (label, result) => {
268
+ const parts = formatParts([
269
+ { label: "created", count: result.created.length },
270
+ { label: "updated", count: result.updated.length },
271
+ { label: "skipped", count: result.skipped.length },
272
+ { label: "conflicted", count: result.conflicted.length }
273
+ ]);
274
+ if (!parts.length) {
275
+ console.log(`- ${label}: no changes`);
276
+ return;
277
+ }
278
+ console.log(`- ${label}: ${parts.join(", ")}`);
279
+ };
253
280
  var printUninstallSummary = (label, result) => {
254
- const total = result.removed.length + result.skipped.length + result.conflicted.length + result.missing.length;
255
- if (!total) {
281
+ const parts = formatParts([
282
+ { label: "removed", count: result.removed.length },
283
+ { label: "skipped", count: result.skipped.length },
284
+ { label: "missing", count: result.missing.length },
285
+ { label: "conflicted", count: result.conflicted.length }
286
+ ]);
287
+ if (!parts.length) {
256
288
  console.log(`- ${label}: no changes`);
257
289
  return;
258
290
  }
259
- const parts = [
260
- result.removed.length ? `${result.removed.length} removed` : void 0,
261
- result.skipped.length ? `${result.skipped.length} skipped` : void 0,
262
- result.missing.length ? `${result.missing.length} missing` : void 0,
263
- result.conflicted.length ? `${result.conflicted.length} conflicted` : void 0
264
- ].filter((part) => typeof part === "string");
265
291
  console.log(`- ${label}: ${parts.join(", ")}`);
266
292
  };
267
293
 
@@ -749,7 +775,7 @@ var isRegistryItem = (value) => {
749
775
  return false;
750
776
  }
751
777
  const item = value;
752
- return typeof item.name === "string" && typeof item.canonicalName === "string" && typeof item.version === "string" && typeof item.type === "string" && typeof item.category === "string" && Array.isArray(item.aliases) && isStringArray(item.files) && isStringArray(item.dependencies) && isStringArray(item.registryDependencies) && isStringArray(item.utilities) && isStringArray(item.styles) && typeof item.target === "string";
778
+ return typeof item.name === "string" && typeof item.canonicalName === "string" && typeof item.type === "string" && typeof item.category === "string" && Array.isArray(item.aliases) && isStringArray(item.files) && isStringArray(item.dependencies) && isStringArray(item.registryDependencies) && isStringArray(item.utilities) && isStringArray(item.styles) && typeof item.target === "string";
753
779
  };
754
780
  var findInvalidRegistryItemIndex = (items) => {
755
781
  return items.findIndex((item) => {
@@ -1010,7 +1036,7 @@ var resolveRegistryUtilities = (names) => {
1010
1036
  });
1011
1037
  };
1012
1038
 
1013
- // src/core/flags.ts
1039
+ // src/utils/flags.ts
1014
1040
  var hasFlag = (args2, ...flags) => {
1015
1041
  return flags.some((f) => args2.includes(f));
1016
1042
  };
@@ -1036,30 +1062,37 @@ var removeFlagsWithValues = (args2, flags) => {
1036
1062
  }
1037
1063
  return result;
1038
1064
  };
1039
-
1040
- // src/commands/add.ts
1041
- var promptSelectItems = async () => {
1065
+ var promptMultiselect = async (message, choices, options = {}) => {
1042
1066
  const response = await prompts({
1043
1067
  type: "multiselect",
1044
- name: "items",
1045
- message: "Select components to add",
1046
- choices: registryItems.map((item) => ({
1047
- title: `${item.canonicalName} (${item.category})`,
1048
- value: item.name
1049
- }))
1068
+ name: "selected",
1069
+ message,
1070
+ choices,
1071
+ ...options.min !== void 0 ? { min: options.min } : {}
1050
1072
  });
1051
1073
  if (typeof response !== "object" || response === null) {
1052
1074
  return [];
1053
1075
  }
1054
- const items = response.items;
1055
- if (!Array.isArray(items)) {
1076
+ const selected = response.selected;
1077
+ if (!Array.isArray(selected)) {
1056
1078
  return [];
1057
1079
  }
1058
- return items.filter((item) => typeof item === "string");
1080
+ return selected.filter((item) => typeof item === "string");
1081
+ };
1082
+
1083
+ // src/commands/add.ts
1084
+ var promptSelectItems = async () => {
1085
+ return promptMultiselect(
1086
+ "Select components to add",
1087
+ registryItems.map((item) => ({
1088
+ title: `${item.canonicalName} (${item.category})`,
1089
+ value: item.name
1090
+ }))
1091
+ );
1059
1092
  };
1060
1093
  var runAdd = async (args2) => {
1061
1094
  const dryRun = hasFlag(args2, "--dry-run", "-d");
1062
- hasFlag(args2, "--yes", "-y");
1095
+ const yes = hasFlag(args2, "--yes", "-y");
1063
1096
  const noFallback = hasFlag(args2, "--no-fallback");
1064
1097
  let items = removeFlagsWithValues(args2, ["--cwd", "-C"]);
1065
1098
  items = removeFlags(items, [
@@ -1070,6 +1103,10 @@ var runAdd = async (args2) => {
1070
1103
  "--no-fallback"
1071
1104
  ]);
1072
1105
  if (!items.length) {
1106
+ if (yes) {
1107
+ console.log("No components specified. Pass component names to add them.");
1108
+ return;
1109
+ }
1073
1110
  items = await promptSelectItems();
1074
1111
  if (!items.length) {
1075
1112
  console.log("No components selected.");
@@ -1106,7 +1143,7 @@ var runAdd = async (args2) => {
1106
1143
  console.log("Dry run: no files or dependencies will be changed.\n");
1107
1144
  console.log("Components:");
1108
1145
  for (const item of resolvedItems) {
1109
- console.log(`- ${item.canonicalName} v${item.version}`);
1146
+ console.log(`- ${item.canonicalName}`);
1110
1147
  }
1111
1148
  console.log("\nDependencies:");
1112
1149
  for (const dependency of dependencies) {
@@ -1145,11 +1182,9 @@ var runAdd = async (args2) => {
1145
1182
  }
1146
1183
  console.log("");
1147
1184
  }
1148
- const installed = {
1149
- ...config.installed ?? {}
1150
- };
1185
+ let installed = [...config.installed ?? []];
1151
1186
  for (const item of successfullyInstalled) {
1152
- installed[item.name] = item.version;
1187
+ installed = addInstalled(installed, item.name);
1153
1188
  }
1154
1189
  await saveConfig({
1155
1190
  ...config,
@@ -1253,13 +1288,13 @@ var runDoctor = async (options = {}) => {
1253
1288
  if (registryFailed && options.noFallback) {
1254
1289
  return;
1255
1290
  }
1256
- const installed = config.installed ?? {};
1257
- if (Object.keys(installed).length) {
1291
+ const installed = config.installed ?? [];
1292
+ if (installed.length) {
1258
1293
  console.log("\nTracked components:");
1259
- for (const [name, version] of Object.entries(installed)) {
1294
+ for (const name of installed) {
1260
1295
  const item = await findItem(name);
1261
1296
  if (!item) {
1262
- console.log(`\xD7 ${name} v${version} (missing from registry)`);
1297
+ console.log(`\xD7 ${name} (missing from registry)`);
1263
1298
  continue;
1264
1299
  }
1265
1300
  const componentPath = join(
@@ -1268,9 +1303,7 @@ var runDoctor = async (options = {}) => {
1268
1303
  );
1269
1304
  const exists = await fileExists(componentPath);
1270
1305
  const layer = getInstallLayer(item) ?? "unknown";
1271
- console.log(
1272
- `${exists ? "\u2713" : "\xD7"} ${item.canonicalName} v${version} (${layer})`
1273
- );
1306
+ console.log(`${exists ? "\u2713" : "\xD7"} ${item.canonicalName} (${layer})`);
1274
1307
  }
1275
1308
  }
1276
1309
  };
@@ -1324,7 +1357,7 @@ Options
1324
1357
  --all, -a Update all tracked components
1325
1358
  --styles, -S Update generated token/theme CSS files
1326
1359
  --utilities, -u Update shared utility files
1327
- --sync Refresh tracked components even when versions match
1360
+ --sync Refresh tracked components even when templates already match
1328
1361
  --dry-run, -d Preview update without writing files
1329
1362
  --force, -f Write conflicted updates after creating backups
1330
1363
  --yes, -y Auto-confirm safe prompts
@@ -1363,6 +1396,24 @@ Usage
1363
1396
  Options
1364
1397
  --no-fallback Fail instead of falling back to local registry
1365
1398
  --help, -h Show this help
1399
+ `,
1400
+ reset: `
1401
+ Usage
1402
+ lexsys reset [component...]
1403
+
1404
+ Options
1405
+ --dry-run, -d Preview reset without writing files
1406
+ --with-deps, -w Also reset installed registry dependencies in the closure
1407
+ --no-fallback Fail instead of falling back to local registry
1408
+ --cwd, -C <path> Run from a different project directory
1409
+ --help, -h Show this help
1410
+
1411
+ Run without arguments for guided reset picker.
1412
+
1413
+ Examples
1414
+ lexsys reset button
1415
+ lexsys reset sidebar --with-deps
1416
+ lexsys reset button --dry-run
1366
1417
  `,
1367
1418
  uninstall: `
1368
1419
  Usage
@@ -1445,6 +1496,7 @@ Scaffold
1445
1496
  Components
1446
1497
  add <component...> Install components into your project [alias: a]
1447
1498
  update [component...] Update installed components [alias: up]
1499
+ reset [component...] Restore components from registry templates
1448
1500
  uninstall [component...] Remove installed components [alias: rm]
1449
1501
 
1450
1502
  Inspect
@@ -1468,6 +1520,49 @@ Global Options
1468
1520
  Run \`lexsys <command> --help\` for command-specific options.
1469
1521
  `);
1470
1522
  };
1523
+ var writeScaffoldFile = async (targetPath, content, options = {}) => {
1524
+ if (await fileExists(targetPath)) {
1525
+ const existingContent = await readFile(targetPath, "utf-8");
1526
+ if (existingContent === content) {
1527
+ console.log(`Skipped identical scaffold file: ${targetPath}`);
1528
+ return;
1529
+ }
1530
+ if (options.allowExisting) {
1531
+ console.log(`Skipped existing scaffold file: ${targetPath}`);
1532
+ return;
1533
+ }
1534
+ throw new Error(
1535
+ `Refusing to overwrite existing scaffold file: ${targetPath}`
1536
+ );
1537
+ }
1538
+ await mkdir(dirname(targetPath), { recursive: true });
1539
+ await writeFile(targetPath, content, "utf-8");
1540
+ console.log(`Created scaffold file: ${targetPath}`);
1541
+ };
1542
+ var writePackageJsonFile = async (targetDirectory, getContent, mergeContent) => {
1543
+ const targetPath = join(targetDirectory, "package.json");
1544
+ if (!await fileExists(targetPath)) {
1545
+ await writeFile(targetPath, getContent(targetDirectory), "utf-8");
1546
+ console.log(`Created scaffold file: ${targetPath}`);
1547
+ return;
1548
+ }
1549
+ let parsed;
1550
+ try {
1551
+ parsed = JSON.parse(await readFile(targetPath, "utf-8"));
1552
+ } catch {
1553
+ throw new Error(`Invalid existing package.json: ${targetPath}`);
1554
+ }
1555
+ const content = await readFile(targetPath, "utf-8");
1556
+ const mergedContent = mergeContent(targetDirectory, parsed);
1557
+ if (content === mergedContent) {
1558
+ console.log(`Skipped package.json: ${targetPath} already configured`);
1559
+ return;
1560
+ }
1561
+ await writeFile(targetPath, mergedContent, "utf-8");
1562
+ console.log(`Updated package.json: ${targetPath}`);
1563
+ };
1564
+
1565
+ // src/scaffold/next.ts
1471
1566
  var NEXT_VERSION = "15.3.3";
1472
1567
  var gitIgnore = `node_modules
1473
1568
  .next
@@ -1648,50 +1743,9 @@ var mergePackageJson = (targetDirectory, existingPackageJson) => {
1648
1743
  }
1649
1744
  return JSON.stringify(mergedPackageJson, null, 2) + "\n";
1650
1745
  };
1651
- var writePackageJson = async (targetDirectory) => {
1652
- const targetPath = join(targetDirectory, "package.json");
1653
- if (!await fileExists(targetPath)) {
1654
- await writeFile(targetPath, getPackageJson(targetDirectory), "utf-8");
1655
- console.log(`Created scaffold file: ${targetPath}`);
1656
- return;
1657
- }
1658
- let parsed;
1659
- try {
1660
- parsed = JSON.parse(await readFile(targetPath, "utf-8"));
1661
- } catch {
1662
- throw new Error(`Invalid existing package.json: ${targetPath}`);
1663
- }
1664
- const content = await readFile(targetPath, "utf-8");
1665
- const mergedContent = mergePackageJson(targetDirectory, parsed);
1666
- if (content === mergedContent) {
1667
- console.log(`Skipped package.json: ${targetPath} already configured`);
1668
- return;
1669
- }
1670
- await writeFile(targetPath, mergedContent, "utf-8");
1671
- console.log(`Updated package.json: ${targetPath}`);
1672
- };
1673
- var writeScaffoldFile = async (targetPath, content, options = {}) => {
1674
- if (await fileExists(targetPath)) {
1675
- const existingContent = await readFile(targetPath, "utf-8");
1676
- if (existingContent === content) {
1677
- console.log(`Skipped identical scaffold file: ${targetPath}`);
1678
- return;
1679
- }
1680
- if (options.allowExisting) {
1681
- console.log(`Skipped existing scaffold file: ${targetPath}`);
1682
- return;
1683
- }
1684
- throw new Error(
1685
- `Refusing to overwrite existing scaffold file: ${targetPath}`
1686
- );
1687
- }
1688
- await mkdir(dirname(targetPath), { recursive: true });
1689
- await writeFile(targetPath, content, "utf-8");
1690
- console.log(`Created scaffold file: ${targetPath}`);
1691
- };
1692
1746
  var scaffoldNextProject = async (targetDirectory) => {
1693
1747
  await mkdir(targetDirectory, { recursive: true });
1694
- await writePackageJson(targetDirectory);
1748
+ await writePackageJsonFile(targetDirectory, getPackageJson, mergePackageJson);
1695
1749
  await writeScaffoldFile(join(targetDirectory, ".gitignore"), gitIgnore, {
1696
1750
  allowExisting: true
1697
1751
  });
@@ -2176,83 +2230,42 @@ var mergePackageJson2 = (targetDirectory, existingPackageJson) => {
2176
2230
  }
2177
2231
  return JSON.stringify(mergedPackageJson, null, 2) + "\n";
2178
2232
  };
2179
- var writePackageJson2 = async (targetDirectory) => {
2180
- const targetPath = join(targetDirectory, "package.json");
2181
- if (!await fileExists(targetPath)) {
2182
- await writeFile(targetPath, getPackageJson2(targetDirectory), "utf-8");
2183
- console.log(`Created scaffold file: ${targetPath}`);
2184
- return;
2185
- }
2186
- let parsed;
2187
- try {
2188
- parsed = JSON.parse(await readFile(targetPath, "utf-8"));
2189
- } catch {
2190
- throw new Error(`Invalid existing package.json: ${targetPath}`);
2191
- }
2192
- const content = await readFile(targetPath, "utf-8");
2193
- const mergedContent = mergePackageJson2(targetDirectory, parsed);
2194
- if (content === mergedContent) {
2195
- console.log(`Skipped package.json: ${targetPath} already configured`);
2196
- return;
2197
- }
2198
- await writeFile(targetPath, mergedContent, "utf-8");
2199
- console.log(`Updated package.json: ${targetPath}`);
2200
- };
2201
- var writeScaffoldFile2 = async (targetPath, content, options = {}) => {
2202
- if (await fileExists(targetPath)) {
2203
- const existingContent = await readFile(targetPath, "utf-8");
2204
- if (existingContent === content) {
2205
- console.log(`Skipped identical scaffold file: ${targetPath}`);
2206
- return;
2207
- }
2208
- if (options.allowExisting) {
2209
- console.log(`Skipped existing scaffold file: ${targetPath}`);
2210
- return;
2211
- }
2212
- throw new Error(
2213
- `Refusing to overwrite existing scaffold file: ${targetPath}`
2214
- );
2215
- }
2216
- await mkdir(dirname(targetPath), { recursive: true });
2217
- await writeFile(targetPath, content, "utf-8");
2218
- console.log(`Created scaffold file: ${targetPath}`);
2219
- };
2220
2233
  var scaffoldViteProject = async (targetDirectory) => {
2221
2234
  await mkdir(targetDirectory, { recursive: true });
2222
- await writePackageJson2(targetDirectory);
2223
- await writeScaffoldFile2(join(targetDirectory, ".gitignore"), gitIgnore2, {
2235
+ await writePackageJsonFile(targetDirectory, getPackageJson2, mergePackageJson2);
2236
+ await writeScaffoldFile(join(targetDirectory, ".gitignore"), gitIgnore2, {
2224
2237
  allowExisting: true
2225
2238
  });
2226
- await writeScaffoldFile2(
2239
+ await writeScaffoldFile(
2227
2240
  join(targetDirectory, ".prettierignore"),
2228
2241
  prettierIgnore2,
2229
2242
  {
2230
2243
  allowExisting: true
2231
2244
  }
2232
2245
  );
2233
- await writeScaffoldFile2(
2246
+ await writeScaffoldFile(
2234
2247
  join(targetDirectory, ".prettierrc"),
2235
2248
  prettierConfig2,
2236
2249
  {
2237
2250
  allowExisting: true
2238
2251
  }
2239
2252
  );
2240
- await writeScaffoldFile2(join(targetDirectory, "index.html"), indexHtml);
2241
- await writeScaffoldFile2(join(targetDirectory, "tsconfig.json"), tsConfig2);
2242
- await writeScaffoldFile2(
2253
+ await writeScaffoldFile(join(targetDirectory, "index.html"), indexHtml);
2254
+ await writeScaffoldFile(join(targetDirectory, "tsconfig.json"), tsConfig2);
2255
+ await writeScaffoldFile(
2243
2256
  join(targetDirectory, "tsconfig.app.json"),
2244
2257
  tsConfigApp
2245
2258
  );
2246
- await writeScaffoldFile2(
2259
+ await writeScaffoldFile(
2247
2260
  join(targetDirectory, "tsconfig.node.json"),
2248
2261
  tsConfigNode
2249
2262
  );
2250
- await writeScaffoldFile2(join(targetDirectory, "vite.config.ts"), viteConfig, {
2263
+ await writeScaffoldFile(join(targetDirectory, "vite.config.ts"), viteConfig, {
2251
2264
  allowExisting: true
2252
2265
  });
2253
- await writeScaffoldFile2(join(targetDirectory, "src", "main.tsx"), mainTsx);
2254
- await writeScaffoldFile2(join(targetDirectory, "src", "App.tsx"), appTsx);
2255
- await writeScaffoldFile2(join(targetDirectory, "src", "style.css"), styleCss, {
2266
+ await writeScaffoldFile(join(targetDirectory, "src", "main.tsx"), mainTsx);
2267
+ await writeScaffoldFile(join(targetDirectory, "src", "App.tsx"), appTsx);
2268
+ await writeScaffoldFile(join(targetDirectory, "src", "style.css"), styleCss, {
2256
2269
  allowExisting: true
2257
2270
  });
2258
2271
  };
@@ -2426,7 +2439,6 @@ var runList = async (options = {}) => {
2426
2439
  const simplified = registryItems3.map((item) => ({
2427
2440
  name: item.name,
2428
2441
  canonicalName: item.canonicalName,
2429
- version: item.version,
2430
2442
  category: item.category,
2431
2443
  layer: getInstallLayer(item)
2432
2444
  }));
@@ -2448,9 +2460,7 @@ var runList = async (options = {}) => {
2448
2460
  }
2449
2461
  console.log(`${layerLabels[layer] ?? layer}:`);
2450
2462
  for (const item of items) {
2451
- console.log(
2452
- `- ${item.canonicalName} v${item.version} (${item.category})`
2453
- );
2463
+ console.log(`- ${item.canonicalName} (${item.category})`);
2454
2464
  }
2455
2465
  console.log("");
2456
2466
  }
@@ -2474,7 +2484,7 @@ var printRegistrySummary = (result) => {
2474
2484
  for (const item of result.items) {
2475
2485
  const remoteFileCount = item.remoteFiles?.length ?? 0;
2476
2486
  console.log(
2477
- `- ${item.canonicalName} v${item.version} (${item.type}/${item.category}, remote files: ${remoteFileCount})`
2487
+ `- ${item.canonicalName} (${item.type}/${item.category}, remote files: ${remoteFileCount})`
2478
2488
  );
2479
2489
  }
2480
2490
  };
@@ -2560,12 +2570,43 @@ var runRegistry = async (options = {}) => {
2560
2570
  process.exitCode = 1;
2561
2571
  }
2562
2572
  };
2573
+ var getComponentDriftStatus = async (name) => {
2574
+ const item = await findItem(name);
2575
+ if (!item) {
2576
+ return "missing";
2577
+ }
2578
+ const hasDrift = await itemHasTemplateDrift(item);
2579
+ return hasDrift ? "drift" : "in-sync";
2580
+ };
2581
+ var itemHasTemplateDrift = async (item) => {
2582
+ const config = await loadConfig();
2583
+ const installTarget = resolveItemInstallTarget(config, item);
2584
+ for (const file of item.files) {
2585
+ const fileName = file.split("/").at(-1);
2586
+ if (!fileName) {
2587
+ return true;
2588
+ }
2589
+ const targetPath = join(getCwd(), installTarget, fileName);
2590
+ if (!await fileExists(targetPath)) {
2591
+ return true;
2592
+ }
2593
+ const preparedContent = prepareInstalledFileContent(
2594
+ await readFile(getRegistryTemplatePath(file), "utf-8"),
2595
+ item
2596
+ );
2597
+ const targetContent = await readFile(targetPath, "utf-8");
2598
+ if (!hashesAreEqual(preparedContent, targetContent)) {
2599
+ return true;
2600
+ }
2601
+ }
2602
+ return false;
2603
+ };
2563
2604
 
2564
2605
  // src/commands/status.ts
2565
2606
  var runStatus = async (options = {}) => {
2566
2607
  const config = await loadConfig();
2567
- const installed = config.installed ?? {};
2568
- if (!Object.keys(installed).length) {
2608
+ const installed = config.installed ?? [];
2609
+ if (!installed.length) {
2569
2610
  console.log("No Lexsys components are currently tracked.");
2570
2611
  return;
2571
2612
  }
@@ -2580,14 +2621,15 @@ var runStatus = async (options = {}) => {
2580
2621
  return;
2581
2622
  }
2582
2623
  console.log("Installed Lexsys components:\n");
2583
- for (const [name, installedVersion] of Object.entries(installed)) {
2624
+ for (const name of installed) {
2584
2625
  const item = await findItem(name);
2585
2626
  if (!item) {
2586
- console.log(`- ${name} v${installedVersion} (missing from registry)`);
2627
+ console.log(`- ${name} (missing from registry)`);
2587
2628
  continue;
2588
2629
  }
2589
- const status = item.version === installedVersion ? "up to date" : `update available: v${installedVersion} \u2192 v${item.version}`;
2590
- console.log(`- ${item.canonicalName} v${installedVersion} (${status})`);
2630
+ const driftStatus = await getComponentDriftStatus(name);
2631
+ const status = driftStatus === "drift" ? "out of sync with registry" : "up to date with registry";
2632
+ console.log(`- ${item.canonicalName} (${status})`);
2591
2633
  }
2592
2634
  };
2593
2635
 
@@ -2612,7 +2654,7 @@ var computeRegistryClosure = (rootNames, items) => {
2612
2654
  return closure;
2613
2655
  };
2614
2656
  var findOrphanInstalledItems = (removedTargetNames, remainingInstalled, items) => {
2615
- const remainingNames = Object.keys(remainingInstalled);
2657
+ const remainingNames = remainingInstalled;
2616
2658
  const removedDependencyNames = /* @__PURE__ */ new Set();
2617
2659
  for (const removedName of removedTargetNames) {
2618
2660
  const closure = computeRegistryClosure([removedName], items);
@@ -2631,43 +2673,281 @@ var findOrphanInstalledItems = (removedTargetNames, remainingInstalled, items) =
2631
2673
  )
2632
2674
  ).filter((item) => Boolean(item)).filter((item) => removedDependencyNames.has(item.name));
2633
2675
  };
2634
-
2635
- // src/commands/uninstall.ts
2636
- var normalizeInstalledKey = (name) => {
2637
- return name.toLowerCase();
2638
- };
2639
- var resolveInstalledItems = async (installed) => {
2640
- const names = Object.keys(installed);
2641
- if (!names.length) {
2642
- return [];
2643
- }
2644
- return resolveRegistryItems(names);
2645
- };
2646
- var collectOrphanedSharedResources = (removedItems, remainingItems) => {
2647
- const removedUtilities = collectUtilities(removedItems);
2648
- const removedStyles = collectStyles(removedItems);
2649
- const remainingUtilities = new Set(collectUtilities(remainingItems));
2650
- const remainingStyles = new Set(collectStyles(remainingItems));
2651
- return {
2652
- utilities: removedUtilities.filter((utility) => {
2653
- return !remainingUtilities.has(utility);
2654
- }),
2655
- styles: removedStyles.filter((style) => {
2656
- return !remainingStyles.has(style);
2657
- })
2658
- };
2659
- };
2660
- var removeInstalledKey = (installed, itemName) => {
2661
- const installedKey = Object.keys(installed).find((key) => {
2662
- return normalizeInstalledKey(key) === normalizeInstalledKey(itemName);
2663
- });
2664
- if (installedKey) {
2665
- delete installed[installedKey];
2676
+ var checkItemFiles = async (name) => {
2677
+ const item = await findItem(name);
2678
+ const config = await loadConfig();
2679
+ if (!item) {
2680
+ console.log(`Component "${name}" no longer exists in the registry.`);
2681
+ return;
2666
2682
  }
2667
- };
2668
- var runUninstall = async (args2) => {
2669
- const dryRun = hasFlag(args2, "--dry-run", "-d");
2670
- const withDeps = hasFlag(args2, "--with-deps", "-w");
2683
+ const installTarget = resolveItemInstallTarget(config, item);
2684
+ console.log(`File check for ${item.canonicalName}:`);
2685
+ for (const file of item.files) {
2686
+ const fileName = file.split("/").at(-1);
2687
+ if (!fileName) {
2688
+ console.log(`- invalid registry file path: ${file}`);
2689
+ continue;
2690
+ }
2691
+ const targetPath = join(getCwd(), installTarget, fileName);
2692
+ if (!await fileExists(targetPath)) {
2693
+ console.log(`- missing: ${targetPath}`);
2694
+ continue;
2695
+ }
2696
+ const preparedContent = prepareInstalledFileContent(
2697
+ await readFile(getRegistryTemplatePath(file), "utf-8"),
2698
+ item
2699
+ );
2700
+ const targetContent = await readFile(targetPath, "utf-8");
2701
+ if (hashesAreEqual(preparedContent, targetContent)) {
2702
+ console.log(`- identical: ${targetPath}`);
2703
+ continue;
2704
+ }
2705
+ console.log(`- conflict: ${targetPath}`);
2706
+ }
2707
+ };
2708
+ var applyItemOverwrite = async (name, force) => {
2709
+ const item = await findItem(name);
2710
+ const config = await loadConfig();
2711
+ if (!item) {
2712
+ console.log(`Component "${name}" no longer exists in the registry.`);
2713
+ return false;
2714
+ }
2715
+ const installTarget = resolveItemInstallTarget(config, item);
2716
+ let hasConflict = false;
2717
+ console.log(`Applying update for ${item.canonicalName}:`);
2718
+ for (const file of item.files) {
2719
+ const sourcePath = getRegistryTemplatePath(file);
2720
+ const fileName = file.split("/").at(-1);
2721
+ if (!fileName) {
2722
+ console.log(`- invalid registry file path: ${file}`);
2723
+ hasConflict = true;
2724
+ continue;
2725
+ }
2726
+ const targetPath = join(getCwd(), installTarget, fileName);
2727
+ const preparedContent = prepareInstalledFileContent(
2728
+ await readFile(sourcePath, "utf-8"),
2729
+ item
2730
+ );
2731
+ await mkdir(dirname(targetPath), { recursive: true });
2732
+ if (!await fileExists(targetPath)) {
2733
+ await writeFile(targetPath, preparedContent, "utf-8");
2734
+ console.log(`- restored missing file: ${targetPath}`);
2735
+ continue;
2736
+ }
2737
+ const targetContent = await readFile(targetPath, "utf-8");
2738
+ if (hashesAreEqual(preparedContent, targetContent)) {
2739
+ console.log(`- identical: ${targetPath}`);
2740
+ continue;
2741
+ }
2742
+ if (force) {
2743
+ const backupPath = await createBackupFile(targetPath);
2744
+ if (backupPath) {
2745
+ console.log(`- backup created: ${backupPath}`);
2746
+ }
2747
+ await writeFile(targetPath, preparedContent, "utf-8");
2748
+ console.log(`- force updated file: ${targetPath}`);
2749
+ continue;
2750
+ }
2751
+ hasConflict = true;
2752
+ console.log(`- conflict (user modified): ${targetPath}`);
2753
+ }
2754
+ if (hasConflict) {
2755
+ console.log(
2756
+ "Update finished with conflicts. Review conflicted files before retrying."
2757
+ );
2758
+ return false;
2759
+ }
2760
+ console.log(`\u2714 ${item.canonicalName} updated successfully`);
2761
+ return true;
2762
+ };
2763
+ var checkItemUpdate = async (name, dryRun, force, sync = false) => {
2764
+ const item = await findItem(name);
2765
+ if (!item) {
2766
+ console.log(`Component "${name}" no longer exists in the registry.`);
2767
+ return false;
2768
+ }
2769
+ const driftStatus = await getComponentDriftStatus(name);
2770
+ if (driftStatus === "missing") {
2771
+ console.log(`Component "${name}" no longer exists in the registry.`);
2772
+ return false;
2773
+ }
2774
+ const hasDrift = driftStatus === "drift";
2775
+ if (!sync && !hasDrift) {
2776
+ console.log(`${item.canonicalName} is up to date with the registry.`);
2777
+ return false;
2778
+ }
2779
+ if (sync && !hasDrift) {
2780
+ console.log(
2781
+ `${item.canonicalName} template sync (already matches registry)`
2782
+ );
2783
+ } else {
2784
+ console.log(`${item.canonicalName} can be updated from registry templates`);
2785
+ }
2786
+ if (dryRun) {
2787
+ console.log("\nChanged file candidates:");
2788
+ for (const file of item.files) {
2789
+ console.log(`~ ${file}`);
2790
+ }
2791
+ console.log("\nDry run: no files will be changed.");
2792
+ console.log("Update plan:");
2793
+ if (force) {
2794
+ console.log(
2795
+ "- Force mode requested: conflicted files require backup before overwrite"
2796
+ );
2797
+ console.log(
2798
+ "- Dry run: backups would be created before forced overwrites"
2799
+ );
2800
+ }
2801
+ console.log(`- Check installed files for ${item.canonicalName}`);
2802
+ console.log("- Compare existing files with registry templates");
2803
+ console.log("- Report conflicts before writing changes");
2804
+ console.log("- Never overwrite user-modified files silently");
2805
+ await checkItemFiles(name);
2806
+ return false;
2807
+ }
2808
+ return await applyItemOverwrite(name, force);
2809
+ };
2810
+ var resetItem = async (name, dryRun) => {
2811
+ const item = await findItem(name);
2812
+ if (!item) {
2813
+ console.log(`Component "${name}" no longer exists in the registry.`);
2814
+ return false;
2815
+ }
2816
+ const hasDrift = await itemHasTemplateDrift(item);
2817
+ if (!hasDrift) {
2818
+ console.log(`${item.canonicalName} already matches registry templates.`);
2819
+ return false;
2820
+ }
2821
+ if (dryRun) {
2822
+ console.log(`Reset plan for ${item.canonicalName}:`);
2823
+ console.log("- Backups would be created before overwriting changed files");
2824
+ console.log("- Files would be restored from registry templates");
2825
+ for (const file of item.files) {
2826
+ console.log(`~ ${file}`);
2827
+ }
2828
+ console.log("\nDry run: no files will be changed.");
2829
+ await checkItemFiles(name);
2830
+ return false;
2831
+ }
2832
+ return await applyItemOverwrite(name, true);
2833
+ };
2834
+
2835
+ // src/commands/reset.ts
2836
+ var resolveInstalledKey = async (name, installed) => {
2837
+ const direct = findInstalledKey(installed, name);
2838
+ if (direct) {
2839
+ return direct;
2840
+ }
2841
+ const item = await findItem(name);
2842
+ if (!item) {
2843
+ return void 0;
2844
+ }
2845
+ return findInstalledKey(installed, item.name);
2846
+ };
2847
+ var runReset = async (args2) => {
2848
+ const dryRun = hasFlag(args2, "--dry-run", "-d");
2849
+ const withDeps = hasFlag(args2, "--with-deps", "-w");
2850
+ const noFallback = hasFlag(args2, "--no-fallback");
2851
+ const targetArgs = removeFlagsWithValues(
2852
+ removeFlags(args2, [
2853
+ "--dry-run",
2854
+ "-d",
2855
+ "--with-deps",
2856
+ "-w",
2857
+ "--no-fallback"
2858
+ ]),
2859
+ ["--cwd", "-C"]
2860
+ );
2861
+ const config = await loadConfig();
2862
+ const installed = [...config.installed ?? []];
2863
+ if (!installed.length) {
2864
+ console.log("No components installed.");
2865
+ return;
2866
+ }
2867
+ try {
2868
+ await getRegistryProviderResult({
2869
+ fallback: !noFallback
2870
+ });
2871
+ } catch (error) {
2872
+ console.log("Failed to resolve registry.");
2873
+ console.log(error instanceof Error ? error.message : String(error));
2874
+ process.exitCode = 1;
2875
+ return;
2876
+ }
2877
+ if (!targetArgs.length) {
2878
+ const selected = await promptMultiselect(
2879
+ "Select components to reset",
2880
+ installed.map((name) => ({ title: name, value: name })),
2881
+ { min: 1 }
2882
+ );
2883
+ if (!selected.length) return;
2884
+ targetArgs.push(...selected);
2885
+ }
2886
+ const resetNames = /* @__PURE__ */ new Set();
2887
+ for (const name of targetArgs) {
2888
+ const installedKey = await resolveInstalledKey(name, installed);
2889
+ if (!installedKey) {
2890
+ console.log(`Component "${name}" is not tracked as installed.`);
2891
+ continue;
2892
+ }
2893
+ resetNames.add(installedKey);
2894
+ }
2895
+ if (!resetNames.size) {
2896
+ console.log("No installed components matched the request.");
2897
+ return;
2898
+ }
2899
+ if (withDeps) {
2900
+ const allItems = await getRegistryItems({ fallback: !noFallback });
2901
+ for (const rootName of [...resetNames]) {
2902
+ const closure = computeRegistryClosure([rootName], allItems);
2903
+ for (const dependencyName of closure) {
2904
+ if (isInstalled(installed, dependencyName)) {
2905
+ resetNames.add(dependencyName);
2906
+ }
2907
+ }
2908
+ }
2909
+ }
2910
+ const resetItems = await resolveRegistryItems([...resetNames], {
2911
+ fallback: !noFallback
2912
+ });
2913
+ if (dryRun) {
2914
+ console.log("Dry run: no files will be changed.\n");
2915
+ console.log("Components:");
2916
+ for (const item of resetItems) {
2917
+ console.log(`- ${item.canonicalName}`);
2918
+ }
2919
+ console.log("");
2920
+ }
2921
+ for (const item of resetItems) {
2922
+ await resetItem(item.name, dryRun);
2923
+ console.log("");
2924
+ }
2925
+ };
2926
+
2927
+ // src/commands/uninstall.ts
2928
+ var resolveInstalledItems = async (installed) => {
2929
+ if (!installed.length) {
2930
+ return [];
2931
+ }
2932
+ return resolveRegistryItems(installed);
2933
+ };
2934
+ var collectOrphanedSharedResources = (removedItems, remainingItems) => {
2935
+ const removedUtilities = collectUtilities(removedItems);
2936
+ const removedStyles = collectStyles(removedItems);
2937
+ const remainingUtilities = new Set(collectUtilities(remainingItems));
2938
+ const remainingStyles = new Set(collectStyles(remainingItems));
2939
+ return {
2940
+ utilities: removedUtilities.filter((utility) => {
2941
+ return !remainingUtilities.has(utility);
2942
+ }),
2943
+ styles: removedStyles.filter((style) => {
2944
+ return !remainingStyles.has(style);
2945
+ })
2946
+ };
2947
+ };
2948
+ var runUninstall = async (args2) => {
2949
+ const dryRun = hasFlag(args2, "--dry-run", "-d");
2950
+ const withDeps = hasFlag(args2, "--with-deps", "-w");
2671
2951
  const noFallback = hasFlag(args2, "--no-fallback");
2672
2952
  const targetArgs = removeFlagsWithValues(
2673
2953
  removeFlags(args2, [
@@ -2681,26 +2961,21 @@ var runUninstall = async (args2) => {
2681
2961
  );
2682
2962
  if (!targetArgs.length) {
2683
2963
  const preConfig = await loadConfig();
2684
- const installedNames = Object.keys(preConfig.installed ?? {});
2964
+ const installedNames = preConfig.installed ?? [];
2685
2965
  if (installedNames.length === 0) {
2686
2966
  console.log("No components installed.");
2687
2967
  return;
2688
2968
  }
2689
- const response = await prompts({
2690
- type: "multiselect",
2691
- name: "components",
2692
- message: "Select components to uninstall",
2693
- choices: installedNames.map((name) => ({ title: name, value: name })),
2694
- min: 1
2695
- });
2696
- const selected = response.components;
2697
- if (!Array.isArray(selected) || !selected.length) return;
2698
- targetArgs.push(
2699
- ...selected.filter((c) => typeof c === "string")
2969
+ const selected = await promptMultiselect(
2970
+ "Select components to uninstall",
2971
+ installedNames.map((name) => ({ title: name, value: name })),
2972
+ { min: 1 }
2700
2973
  );
2974
+ if (!selected.length) return;
2975
+ targetArgs.push(...selected);
2701
2976
  }
2702
2977
  const config = await loadConfig();
2703
- const installed = config.installed ?? {};
2978
+ const installed = [...config.installed ?? []];
2704
2979
  const resolvedTargets = [];
2705
2980
  const notTracked = [];
2706
2981
  for (const name of targetArgs) {
@@ -2709,10 +2984,7 @@ var runUninstall = async (args2) => {
2709
2984
  console.log(`Component "${name}" not found in registry.`);
2710
2985
  continue;
2711
2986
  }
2712
- const installedKey = Object.keys(installed).find((key) => {
2713
- return normalizeInstalledKey(key) === normalizeInstalledKey(item.name);
2714
- });
2715
- if (!installedKey) {
2987
+ if (!isInstalled(installed, item.name)) {
2716
2988
  notTracked.push(name);
2717
2989
  console.log(`Component "${name}" is not tracked as installed.`);
2718
2990
  continue;
@@ -2725,10 +2997,11 @@ var runUninstall = async (args2) => {
2725
2997
  }
2726
2998
  return;
2727
2999
  }
2728
- const remainingInstalled = { ...installed };
2729
- for (const item of resolvedTargets) {
2730
- removeInstalledKey(remainingInstalled, item.name);
2731
- }
3000
+ const remainingInstalled = installed.filter((entry) => {
3001
+ return !resolvedTargets.some((item) => {
3002
+ return findInstalledKey([entry], item.name) !== void 0;
3003
+ });
3004
+ });
2732
3005
  const allItems = await getRegistryItems({ fallback: !noFallback });
2733
3006
  const orphanItems = withDeps ? findOrphanInstalledItems(
2734
3007
  resolvedTargets.map((item) => item.name),
@@ -2750,9 +3023,7 @@ var runUninstall = async (args2) => {
2750
3023
  console.log("Dry run: no files will be removed.\n");
2751
3024
  console.log("Components:");
2752
3025
  for (const item of resolvedTargets) {
2753
- console.log(
2754
- `- ${item.canonicalName} v${installed[item.name] ?? "unknown"}`
2755
- );
3026
+ console.log(`- ${item.canonicalName}`);
2756
3027
  }
2757
3028
  if (withDeps && orphanItems.length) {
2758
3029
  console.log("\nOrphan registry items (--with-deps):");
@@ -2768,10 +3039,12 @@ var runUninstall = async (args2) => {
2768
3039
  console.log(`- ${item.canonicalName}`);
2769
3040
  }
2770
3041
  }
2771
- const postOrphanRemaining = { ...remainingInstalled };
2772
- for (const item of withDeps ? orphanItems : []) {
2773
- removeInstalledKey(postOrphanRemaining, item.name);
2774
- }
3042
+ const postOrphanRemaining = remainingInstalled.filter((entry) => {
3043
+ if (!withDeps) {
3044
+ return true;
3045
+ }
3046
+ return !orphanItems.some((item) => isInstalled([entry], item.name));
3047
+ });
2775
3048
  const dryRunOrphans = collectOrphanedSharedResources(
2776
3049
  allRemovalTargets,
2777
3050
  await resolveInstalledItems(postOrphanRemaining)
@@ -2805,11 +3078,11 @@ var runUninstall = async (args2) => {
2805
3078
  }
2806
3079
  console.log("");
2807
3080
  }
2808
- const postUninstallInstalled = { ...installed };
3081
+ let updatedInstalled = [...installed];
2809
3082
  for (const item of successfullyUninstalled) {
2810
- removeInstalledKey(postUninstallInstalled, item.name);
3083
+ updatedInstalled = removeInstalled(updatedInstalled, item.name);
2811
3084
  }
2812
- const remainingItems = await resolveInstalledItems(postUninstallInstalled);
3085
+ const remainingItems = await resolveInstalledItems(updatedInstalled);
2813
3086
  const orphanedResources = collectOrphanedSharedResources(
2814
3087
  successfullyUninstalled,
2815
3088
  remainingItems
@@ -2829,12 +3102,6 @@ var runUninstall = async (args2) => {
2829
3102
  }
2830
3103
  const utilitiesResult = await uninstallUtilities(resolvedUtilities, config);
2831
3104
  const stylesResult = await uninstallStyles(resolvedStyles, config);
2832
- const updatedInstalled = {
2833
- ...installed
2834
- };
2835
- for (const item of successfullyUninstalled) {
2836
- removeInstalledKey(updatedInstalled, item.name);
2837
- }
2838
3105
  await saveConfig({
2839
3106
  ...config,
2840
3107
  installed: updatedInstalled
@@ -2854,172 +3121,18 @@ var runUninstall = async (args2) => {
2854
3121
  }
2855
3122
  };
2856
3123
 
2857
- // src/utils/version.ts
2858
- var compareVersions = (a, b) => {
2859
- const pa = a.split(".").map(Number);
2860
- const pb = b.split(".").map(Number);
2861
- const length = Math.max(pa.length, pb.length);
2862
- for (let i = 0; i < length; i++) {
2863
- const va = pa[i] ?? 0;
2864
- const vb = pb[i] ?? 0;
2865
- if (va > vb) return 1;
2866
- if (va < vb) return -1;
2867
- }
2868
- return 0;
2869
- };
2870
- var isUpdateAvailable = (installed, latest) => {
2871
- return compareVersions(latest, installed) === 1;
2872
- };
2873
-
2874
- // src/install/update-engine.ts
2875
- var checkItemFiles = async (name) => {
2876
- const item = await findItem(name);
2877
- const config = await loadConfig();
2878
- if (!item) {
2879
- console.log(`Component "${name}" no longer exists in the registry.`);
2880
- return;
2881
- }
2882
- const installTarget = resolveItemInstallTarget(config, item);
2883
- console.log(`File check for ${item.canonicalName}:`);
2884
- for (const file of item.files) {
2885
- const fileName = file.split("/").at(-1);
2886
- if (!fileName) {
2887
- console.log(`- invalid registry file path: ${file}`);
2888
- continue;
2889
- }
2890
- const targetPath = join(getCwd(), installTarget, fileName);
2891
- if (!await fileExists(targetPath)) {
2892
- console.log(`- missing: ${targetPath}`);
2893
- continue;
2894
- }
2895
- const preparedContent = prepareInstalledFileContent(
2896
- await readFile(getRegistryTemplatePath(file), "utf-8"),
2897
- item
2898
- );
2899
- const targetContent = await readFile(targetPath, "utf-8");
2900
- if (hashesAreEqual(preparedContent, targetContent)) {
2901
- console.log(`- identical: ${targetPath}`);
2902
- continue;
2903
- }
2904
- console.log(`- conflict: ${targetPath}`);
2905
- }
2906
- };
2907
- var applySafeItemUpdate = async (name, force) => {
2908
- const item = await findItem(name);
2909
- const config = await loadConfig();
2910
- if (!item) {
2911
- console.log(`Component "${name}" no longer exists in the registry.`);
2912
- return false;
2913
- }
2914
- const installTarget = resolveItemInstallTarget(config, item);
2915
- let hasConflict = false;
2916
- console.log(`Applying update for ${item.canonicalName}:`);
2917
- for (const file of item.files) {
2918
- const sourcePath = getRegistryTemplatePath(file);
2919
- const fileName = file.split("/").at(-1);
2920
- if (!fileName) {
2921
- console.log(`- invalid registry file path: ${file}`);
2922
- hasConflict = true;
2923
- continue;
2924
- }
2925
- const targetPath = join(getCwd(), installTarget, fileName);
2926
- const preparedContent = prepareInstalledFileContent(
2927
- await readFile(sourcePath, "utf-8"),
2928
- item
2929
- );
2930
- await mkdir(dirname(targetPath), { recursive: true });
2931
- if (!await fileExists(targetPath)) {
2932
- await writeFile(targetPath, preparedContent, "utf-8");
2933
- console.log(`- restored missing file: ${targetPath}`);
2934
- continue;
2935
- }
2936
- const targetContent = await readFile(targetPath, "utf-8");
2937
- if (hashesAreEqual(preparedContent, targetContent)) {
2938
- console.log(`- identical: ${targetPath}`);
2939
- continue;
2940
- }
2941
- if (force) {
2942
- const backupPath = await createBackupFile(targetPath);
2943
- if (backupPath) {
2944
- console.log(`- backup created: ${backupPath}`);
2945
- }
2946
- await writeFile(targetPath, preparedContent, "utf-8");
2947
- console.log(`- force updated file: ${targetPath}`);
2948
- continue;
2949
- }
2950
- hasConflict = true;
2951
- console.log(`- conflict (user modified): ${targetPath}`);
2952
- }
2953
- if (hasConflict) {
2954
- console.log(
2955
- "Update finished with conflicts. Installed version was not changed."
2956
- );
2957
- return false;
2958
- }
2959
- console.log(
2960
- `\u2714 ${item.canonicalName} updated successfully to v${item.version}`
2961
- );
2962
- return true;
2963
- };
2964
- var checkItemUpdate = async (name, installedVersion, dryRun, force, sync = false) => {
2965
- const item = await findItem(name);
2966
- if (!item) {
2967
- console.log(`Component "${name}" no longer exists in the registry.`);
2968
- return false;
2969
- }
2970
- const versionUpdateAvailable = isUpdateAvailable(
2971
- installedVersion,
2972
- item.version
2973
- );
2974
- if (!sync && !versionUpdateAvailable) {
2975
- console.log(`${item.canonicalName} is up to date (v${installedVersion}).`);
2976
- return false;
2977
- }
2978
- if (sync && !versionUpdateAvailable) {
2979
- console.log(
2980
- `${item.canonicalName} template sync (installed v${installedVersion}, registry v${item.version})`
2981
- );
2982
- } else {
2983
- console.log(
2984
- `${item.canonicalName} can be updated: v${installedVersion} \u2192 v${item.version}`
2985
- );
2986
- }
2987
- if (dryRun) {
2988
- console.log("\nChanged file candidates:");
2989
- for (const file of item.files) {
2990
- console.log(`~ ${file}`);
2991
- }
2992
- console.log("\nDry run: no files will be changed.");
2993
- console.log("Update plan:");
2994
- if (force) {
2995
- console.log(
2996
- "- Force mode requested: conflicted files require backup before overwrite"
2997
- );
2998
- console.log(
2999
- "- Dry run: backups would be created before forced overwrites"
3000
- );
3001
- }
3002
- console.log(`- Check installed files for ${item.canonicalName}`);
3003
- console.log("- Compare existing files with registry templates");
3004
- console.log("- Report conflicts before writing changes");
3005
- console.log("- Never overwrite user-modified files silently");
3006
- await checkItemFiles(name);
3007
- return false;
3008
- }
3009
- return await applySafeItemUpdate(name, force);
3010
- };
3011
-
3012
3124
  // src/commands/update.ts
3013
3125
  var styleUpdateNames = ["theme"];
3014
- var resolveInstalledKey = async (name, installed) => {
3015
- if (installed[name]) {
3016
- return name;
3126
+ var resolveInstalledKey2 = async (name, installed) => {
3127
+ const direct = findInstalledKey(installed, name);
3128
+ if (direct) {
3129
+ return direct;
3017
3130
  }
3018
3131
  const item = await findItem(name);
3019
3132
  if (!item) {
3020
3133
  return void 0;
3021
3134
  }
3022
- return installed[item.name] ? item.name : void 0;
3135
+ return findInstalledKey(installed, item.name);
3023
3136
  };
3024
3137
  var runStylesUpdate = async (config, dryRun) => {
3025
3138
  const styles = resolveRegistryStyles(styleUpdateNames);
@@ -3046,9 +3159,7 @@ Tailwind CSS entrypoint: ${config.tailwind.css}`);
3046
3159
  }
3047
3160
  };
3048
3161
  var runUtilitiesUpdate = async (config, installed, dryRun, force) => {
3049
- const installedItems = (await Promise.all(
3050
- Object.keys(installed).map(async (name) => findItem(name))
3051
- )).filter((item) => Boolean(item));
3162
+ const installedItems = (await Promise.all(installed.map(async (name) => findItem(name)))).filter((item) => Boolean(item));
3052
3163
  const utilityNames = collectUtilities(installedItems);
3053
3164
  if (!utilityNames.length) {
3054
3165
  console.log("No shared utilities are tracked for installed components.");
@@ -3073,26 +3184,18 @@ var runUtilitiesUpdate = async (config, installed, dryRun, force) => {
3073
3184
  );
3074
3185
  }
3075
3186
  };
3076
- var runComponentUpdates = async (config, installed, targetNames, dryRun, force, sync) => {
3077
- let changed = false;
3078
- if (!Object.keys(installed).length) {
3187
+ var runComponentUpdates = async (installed, targetNames, dryRun, force, sync) => {
3188
+ if (!installed.length) {
3079
3189
  console.log("No Lexsys components are currently tracked.");
3080
- return false;
3190
+ return;
3081
3191
  }
3082
3192
  console.log("Checking installed Lexsys components:\n");
3083
3193
  for (const name of targetNames) {
3084
- const version = installed[name];
3085
- if (!version) {
3194
+ if (!isInstalled(installed, name)) {
3086
3195
  continue;
3087
3196
  }
3088
- const didUpdate = await checkItemUpdate(name, version, dryRun, force, sync);
3089
- const item = await findItem(name);
3090
- if (didUpdate && item) {
3091
- installed[name] = item.version;
3092
- changed = true;
3093
- }
3197
+ await checkItemUpdate(name, dryRun, force, sync);
3094
3198
  }
3095
- return changed;
3096
3199
  };
3097
3200
  var runUpdate = async (args2) => {
3098
3201
  const dryRun = hasFlag(args2, "--dry-run", "-d");
@@ -3120,7 +3223,7 @@ var runUpdate = async (args2) => {
3120
3223
  "-a"
3121
3224
  ]);
3122
3225
  const config = await loadConfig();
3123
- const installed = { ...config.installed ?? {} };
3226
+ const installed = [...config.installed ?? []];
3124
3227
  try {
3125
3228
  await getRegistryProviderResult({
3126
3229
  fallback: !noFallback
@@ -3131,29 +3234,24 @@ var runUpdate = async (args2) => {
3131
3234
  process.exitCode = 1;
3132
3235
  return;
3133
3236
  }
3134
- if (yes) {
3135
- console.log("Auto-confirm mode is enabled.");
3136
- }
3137
3237
  if (!updateAll && !stylesFlag && !utilitiesFlag && targetArgs.length === 0) {
3138
- const installedNames = Object.keys(installed);
3139
- if (installedNames.length === 0) {
3238
+ if (installed.length === 0) {
3140
3239
  console.log(
3141
3240
  "No components installed. Run `lexsys add <component>` first."
3142
3241
  );
3143
3242
  return;
3144
3243
  }
3145
- const response = await prompts({
3146
- type: "multiselect",
3147
- name: "components",
3148
- message: "Select components to update",
3149
- choices: installedNames.map((name) => ({ title: name, value: name })),
3150
- min: 1
3151
- });
3152
- const selected = response.components;
3153
- if (!Array.isArray(selected) || !selected.length) return;
3154
- targetArgs.push(
3155
- ...selected.filter((c) => typeof c === "string")
3156
- );
3244
+ if (yes) {
3245
+ targetArgs.push(...installed);
3246
+ } else {
3247
+ const selected = await promptMultiselect(
3248
+ "Select components to update",
3249
+ installed.map((name) => ({ title: name, value: name })),
3250
+ { min: 1 }
3251
+ );
3252
+ if (!selected.length) return;
3253
+ targetArgs.push(...selected);
3254
+ }
3157
3255
  }
3158
3256
  const shouldUpdateComponents = updateAll || targetArgs.length > 0;
3159
3257
  const resourcesOnly = (stylesFlag || utilitiesFlag) && !shouldUpdateComponents;
@@ -3175,42 +3273,18 @@ var runUpdate = async (args2) => {
3175
3273
  if (!shouldUpdateComponents) {
3176
3274
  return;
3177
3275
  }
3178
- let changed = false;
3179
3276
  if (updateAll) {
3180
- changed = await runComponentUpdates(
3181
- config,
3182
- installed,
3183
- Object.keys(installed),
3184
- dryRun,
3185
- force,
3186
- sync
3187
- );
3277
+ await runComponentUpdates(installed, installed, dryRun, force, sync);
3188
3278
  } else {
3189
3279
  for (const name of targetArgs) {
3190
- const installedKey = await resolveInstalledKey(name, installed);
3280
+ const installedKey = await resolveInstalledKey2(name, installed);
3191
3281
  if (!installedKey) {
3192
3282
  console.log(`Component "${name}" is not tracked as installed.`);
3193
3283
  continue;
3194
3284
  }
3195
- const didUpdateOne = await runComponentUpdates(
3196
- config,
3197
- installed,
3198
- [installedKey],
3199
- dryRun,
3200
- force,
3201
- sync
3202
- );
3203
- if (didUpdateOne) {
3204
- changed = true;
3205
- }
3285
+ await runComponentUpdates(installed, [installedKey], dryRun, force, sync);
3206
3286
  }
3207
3287
  }
3208
- if (changed) {
3209
- await saveConfig({
3210
- ...config,
3211
- installed
3212
- });
3213
- }
3214
3288
  };
3215
3289
  var cliDistDir = dirname(fileURLToPath(import.meta.url));
3216
3290
  var packageJsonPath = join(cliDistDir, "..", "package.json");
@@ -3317,6 +3391,14 @@ try {
3317
3391
  });
3318
3392
  process.exit(0);
3319
3393
  }
3394
+ if (command === "reset") {
3395
+ if (hasFlag(args, "--help", "-h")) {
3396
+ runHelpFor("reset");
3397
+ process.exit(0);
3398
+ }
3399
+ await runReset(args);
3400
+ process.exit(0);
3401
+ }
3320
3402
  if (command === "uninstall" || command === "rm") {
3321
3403
  if (hasFlag(args, "--help", "-h")) {
3322
3404
  runHelpFor("uninstall");
@@ -3329,5 +3411,3 @@ try {
3329
3411
  } catch (error) {
3330
3412
  handleCliError(error);
3331
3413
  }
3332
- runHelp();
3333
- process.exit(1);