@dalexto/lexsys-cli 0.0.3 → 0.0.5
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 +8 -15
- package/dist/commands/reset.d.ts +1 -0
- package/dist/config/config.d.ts +1 -1
- package/dist/config/installed.d.ts +6 -0
- package/dist/index.js +423 -278
- package/dist/install/component-drift.d.ts +4 -0
- package/dist/install/update-engine.d.ts +3 -1
- package/dist/registry/closure.d.ts +1 -1
- package/package.json +2 -2
- package/dist/utils/version.d.ts +0 -2
package/README.md
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
**Source of truth for:** Package role, command surface, core module boundaries
|
|
6
6
|
**Full CLI reference:** [docs/reference/cli/CLI.md](../../docs/reference/cli/CLI.md)
|
|
7
7
|
**Verified against:** `packages/cli/src/`
|
|
8
|
+
**Last reviewed:** 2026-05-30
|
|
8
9
|
|
|
9
10
|
---
|
|
10
11
|
|
|
@@ -50,8 +51,9 @@ Installed via `npm install -g lexsys` or run directly with `pnpm exec lexsys`.
|
|
|
50
51
|
| `lexsys init next [directory]` | Scaffold a new Next.js App Router consumer (pinned Next.js 15.3.3) |
|
|
51
52
|
| `lexsys add <component>` | Install one or more components into the consumer project |
|
|
52
53
|
| `lexsys update [component]` | Update installed components; `--sync`, `--utilities`, `--styles`, `--force` |
|
|
54
|
+
| `lexsys reset [component]` | Restore components from registry templates (backup + overwrite) |
|
|
53
55
|
| `lexsys list` | List available registry components |
|
|
54
|
-
| `lexsys status` | Show installed
|
|
56
|
+
| `lexsys status` | Show installed components and template drift vs registry |
|
|
55
57
|
| `lexsys doctor` | Check project health and config validity |
|
|
56
58
|
| `lexsys config` | Read or modify `lexsys.config.json` |
|
|
57
59
|
| `lexsys registry` | Inspect the active registry source |
|
|
@@ -69,20 +71,11 @@ Installed via `npm install -g lexsys` or run directly with `pnpm exec lexsys`.
|
|
|
69
71
|
|
|
70
72
|
---
|
|
71
73
|
|
|
72
|
-
##
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
| `core/installer.ts` | File copy, conflict detection, idempotent installs |
|
|
78
|
-
| `core/registry-provider.ts` | Selects local vs remote registry source |
|
|
79
|
-
| `core/registry-resolver.ts` | Resolves registry items, utilities, and styles from active registry |
|
|
80
|
-
| `core/tailwind-setup.ts` | Detects and wires Tailwind v4 CSS entrypoint |
|
|
81
|
-
| `core/vite-scaffold.ts` | Detects and patches Vite config for Tailwind plugin |
|
|
82
|
-
| `core/package-manager.ts` | Detects npm/pnpm/yarn and runs installs |
|
|
83
|
-
| `core/context.ts` | Process-level `cwd` override via `--cwd` flag |
|
|
84
|
-
| `core/flags.ts` | Shared flag parsing utilities |
|
|
85
|
-
| `core/cli-error.ts` | Typed CLI error class and top-level error handler |
|
|
74
|
+
## Source layout
|
|
75
|
+
|
|
76
|
+
Domain modules under `packages/cli/src/`: `config/`, `install/`, `registry/`,
|
|
77
|
+
`commands/`, `scaffold/`, `utils/`. Command behavior and config schema:
|
|
78
|
+
[docs/reference/cli/CLI.md](../../docs/reference/cli/CLI.md).
|
|
86
79
|
|
|
87
80
|
---
|
|
88
81
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const runReset: (args: string[]) => Promise<void>;
|
package/dist/config/config.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface LexsysConfig {
|
|
|
15
15
|
paths: LexsysPathsConfig;
|
|
16
16
|
aliases: LexsysAliasesConfig;
|
|
17
17
|
tailwind: LexsysTailwindConfig;
|
|
18
|
-
installed?:
|
|
18
|
+
installed?: string[];
|
|
19
19
|
registryUrl?: string | null;
|
|
20
20
|
}
|
|
21
21
|
export interface LexsysTailwindConfig {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const isLegacyInstalledRecord: (value: unknown) => boolean;
|
|
2
|
+
export declare const normalizeInstalled: (value: unknown) => string[];
|
|
3
|
+
export declare const isInstalled: (installed: string[], name: string) => boolean;
|
|
4
|
+
export declare const addInstalled: (installed: string[], itemName: string) => string[];
|
|
5
|
+
export declare const removeInstalled: (installed: string[], itemName: string) => string[];
|
|
6
|
+
export declare const findInstalledKey: (installed: string[], name: string) => string | undefined;
|
package/dist/index.js
CHANGED
|
@@ -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
|
};
|
|
@@ -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
|
-
|
|
146
|
+
const config = {
|
|
115
147
|
...defaultConfig,
|
|
116
148
|
...parsed,
|
|
117
149
|
paths: {
|
|
@@ -125,23 +157,43 @@ 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();
|
|
133
170
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
134
171
|
};
|
|
135
|
-
var
|
|
172
|
+
var monorepoCrossLayerPatterns = [
|
|
136
173
|
/\.\.\/\.\.\/primitives\//g,
|
|
137
174
|
/\.\.\/\.\.\/blocks\//g,
|
|
138
175
|
/\.\.\/\.\.\/templates\//g
|
|
139
176
|
];
|
|
177
|
+
var registryTemplateLayers = ["primitives", "blocks", "templates"];
|
|
178
|
+
var toFlatSiblingImportPath = (importPath) => {
|
|
179
|
+
const segments = importPath.split("/").filter(Boolean);
|
|
180
|
+
if (segments.length >= 2) {
|
|
181
|
+
return `../${segments.join("/")}`;
|
|
182
|
+
}
|
|
183
|
+
const name = segments[0] ?? importPath;
|
|
184
|
+
return `../${name}/${name}`;
|
|
185
|
+
};
|
|
140
186
|
var rewriteCrossLayerImports = (content) => {
|
|
141
187
|
let rewritten = content;
|
|
142
|
-
for (const pattern of
|
|
188
|
+
for (const pattern of monorepoCrossLayerPatterns) {
|
|
143
189
|
rewritten = rewritten.replace(pattern, "../");
|
|
144
190
|
}
|
|
191
|
+
for (const layer of registryTemplateLayers) {
|
|
192
|
+
const pattern = new RegExp(`from "@/components/${layer}/([^"]+)"`, "g");
|
|
193
|
+
rewritten = rewritten.replace(pattern, (_, importPath) => {
|
|
194
|
+
return `from "${toFlatSiblingImportPath(importPath)}"`;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
145
197
|
return rewritten;
|
|
146
198
|
};
|
|
147
199
|
var prepareInstalledFileContent = (content, item) => {
|
|
@@ -738,7 +790,7 @@ var isRegistryItem = (value) => {
|
|
|
738
790
|
return false;
|
|
739
791
|
}
|
|
740
792
|
const item = value;
|
|
741
|
-
return typeof item.name === "string" && typeof item.canonicalName === "string" && typeof item.
|
|
793
|
+
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";
|
|
742
794
|
};
|
|
743
795
|
var findInvalidRegistryItemIndex = (items) => {
|
|
744
796
|
return items.findIndex((item) => {
|
|
@@ -1106,7 +1158,7 @@ var runAdd = async (args2) => {
|
|
|
1106
1158
|
console.log("Dry run: no files or dependencies will be changed.\n");
|
|
1107
1159
|
console.log("Components:");
|
|
1108
1160
|
for (const item of resolvedItems) {
|
|
1109
|
-
console.log(`- ${item.canonicalName}
|
|
1161
|
+
console.log(`- ${item.canonicalName}`);
|
|
1110
1162
|
}
|
|
1111
1163
|
console.log("\nDependencies:");
|
|
1112
1164
|
for (const dependency of dependencies) {
|
|
@@ -1145,11 +1197,9 @@ var runAdd = async (args2) => {
|
|
|
1145
1197
|
}
|
|
1146
1198
|
console.log("");
|
|
1147
1199
|
}
|
|
1148
|
-
|
|
1149
|
-
...config.installed ?? {}
|
|
1150
|
-
};
|
|
1200
|
+
let installed = [...config.installed ?? []];
|
|
1151
1201
|
for (const item of successfullyInstalled) {
|
|
1152
|
-
installed
|
|
1202
|
+
installed = addInstalled(installed, item.name);
|
|
1153
1203
|
}
|
|
1154
1204
|
await saveConfig({
|
|
1155
1205
|
...config,
|
|
@@ -1253,13 +1303,13 @@ var runDoctor = async (options = {}) => {
|
|
|
1253
1303
|
if (registryFailed && options.noFallback) {
|
|
1254
1304
|
return;
|
|
1255
1305
|
}
|
|
1256
|
-
const installed = config.installed ??
|
|
1257
|
-
if (
|
|
1306
|
+
const installed = config.installed ?? [];
|
|
1307
|
+
if (installed.length) {
|
|
1258
1308
|
console.log("\nTracked components:");
|
|
1259
|
-
for (const
|
|
1309
|
+
for (const name of installed) {
|
|
1260
1310
|
const item = await findItem(name);
|
|
1261
1311
|
if (!item) {
|
|
1262
|
-
console.log(`\xD7 ${name}
|
|
1312
|
+
console.log(`\xD7 ${name} (missing from registry)`);
|
|
1263
1313
|
continue;
|
|
1264
1314
|
}
|
|
1265
1315
|
const componentPath = join(
|
|
@@ -1268,9 +1318,7 @@ var runDoctor = async (options = {}) => {
|
|
|
1268
1318
|
);
|
|
1269
1319
|
const exists = await fileExists(componentPath);
|
|
1270
1320
|
const layer = getInstallLayer(item) ?? "unknown";
|
|
1271
|
-
console.log(
|
|
1272
|
-
`${exists ? "\u2713" : "\xD7"} ${item.canonicalName} v${version} (${layer})`
|
|
1273
|
-
);
|
|
1321
|
+
console.log(`${exists ? "\u2713" : "\xD7"} ${item.canonicalName} (${layer})`);
|
|
1274
1322
|
}
|
|
1275
1323
|
}
|
|
1276
1324
|
};
|
|
@@ -1324,7 +1372,7 @@ Options
|
|
|
1324
1372
|
--all, -a Update all tracked components
|
|
1325
1373
|
--styles, -S Update generated token/theme CSS files
|
|
1326
1374
|
--utilities, -u Update shared utility files
|
|
1327
|
-
--sync Refresh tracked components even when
|
|
1375
|
+
--sync Refresh tracked components even when templates already match
|
|
1328
1376
|
--dry-run, -d Preview update without writing files
|
|
1329
1377
|
--force, -f Write conflicted updates after creating backups
|
|
1330
1378
|
--yes, -y Auto-confirm safe prompts
|
|
@@ -1363,6 +1411,24 @@ Usage
|
|
|
1363
1411
|
Options
|
|
1364
1412
|
--no-fallback Fail instead of falling back to local registry
|
|
1365
1413
|
--help, -h Show this help
|
|
1414
|
+
`,
|
|
1415
|
+
reset: `
|
|
1416
|
+
Usage
|
|
1417
|
+
lexsys reset [component...]
|
|
1418
|
+
|
|
1419
|
+
Options
|
|
1420
|
+
--dry-run, -d Preview reset without writing files
|
|
1421
|
+
--with-deps, -w Also reset installed registry dependencies in the closure
|
|
1422
|
+
--no-fallback Fail instead of falling back to local registry
|
|
1423
|
+
--cwd, -C <path> Run from a different project directory
|
|
1424
|
+
--help, -h Show this help
|
|
1425
|
+
|
|
1426
|
+
Run without arguments for guided reset picker.
|
|
1427
|
+
|
|
1428
|
+
Examples
|
|
1429
|
+
lexsys reset button
|
|
1430
|
+
lexsys reset sidebar --with-deps
|
|
1431
|
+
lexsys reset button --dry-run
|
|
1366
1432
|
`,
|
|
1367
1433
|
uninstall: `
|
|
1368
1434
|
Usage
|
|
@@ -1445,6 +1511,7 @@ Scaffold
|
|
|
1445
1511
|
Components
|
|
1446
1512
|
add <component...> Install components into your project [alias: a]
|
|
1447
1513
|
update [component...] Update installed components [alias: up]
|
|
1514
|
+
reset [component...] Restore components from registry templates
|
|
1448
1515
|
uninstall [component...] Remove installed components [alias: rm]
|
|
1449
1516
|
|
|
1450
1517
|
Inspect
|
|
@@ -2387,7 +2454,6 @@ var runList = async (options = {}) => {
|
|
|
2387
2454
|
const simplified = registryItems3.map((item) => ({
|
|
2388
2455
|
name: item.name,
|
|
2389
2456
|
canonicalName: item.canonicalName,
|
|
2390
|
-
version: item.version,
|
|
2391
2457
|
category: item.category,
|
|
2392
2458
|
layer: getInstallLayer(item)
|
|
2393
2459
|
}));
|
|
@@ -2409,9 +2475,7 @@ var runList = async (options = {}) => {
|
|
|
2409
2475
|
}
|
|
2410
2476
|
console.log(`${layerLabels[layer] ?? layer}:`);
|
|
2411
2477
|
for (const item of items) {
|
|
2412
|
-
console.log(
|
|
2413
|
-
`- ${item.canonicalName} v${item.version} (${item.category})`
|
|
2414
|
-
);
|
|
2478
|
+
console.log(`- ${item.canonicalName} (${item.category})`);
|
|
2415
2479
|
}
|
|
2416
2480
|
console.log("");
|
|
2417
2481
|
}
|
|
@@ -2435,7 +2499,7 @@ var printRegistrySummary = (result) => {
|
|
|
2435
2499
|
for (const item of result.items) {
|
|
2436
2500
|
const remoteFileCount = item.remoteFiles?.length ?? 0;
|
|
2437
2501
|
console.log(
|
|
2438
|
-
`- ${item.canonicalName}
|
|
2502
|
+
`- ${item.canonicalName} (${item.type}/${item.category}, remote files: ${remoteFileCount})`
|
|
2439
2503
|
);
|
|
2440
2504
|
}
|
|
2441
2505
|
};
|
|
@@ -2521,12 +2585,43 @@ var runRegistry = async (options = {}) => {
|
|
|
2521
2585
|
process.exitCode = 1;
|
|
2522
2586
|
}
|
|
2523
2587
|
};
|
|
2588
|
+
var getComponentDriftStatus = async (name) => {
|
|
2589
|
+
const item = await findItem(name);
|
|
2590
|
+
if (!item) {
|
|
2591
|
+
return "missing";
|
|
2592
|
+
}
|
|
2593
|
+
const hasDrift = await itemHasTemplateDrift(item);
|
|
2594
|
+
return hasDrift ? "drift" : "in-sync";
|
|
2595
|
+
};
|
|
2596
|
+
var itemHasTemplateDrift = async (item) => {
|
|
2597
|
+
const config = await loadConfig();
|
|
2598
|
+
const installTarget = resolveItemInstallTarget(config, item);
|
|
2599
|
+
for (const file of item.files) {
|
|
2600
|
+
const fileName = file.split("/").at(-1);
|
|
2601
|
+
if (!fileName) {
|
|
2602
|
+
return true;
|
|
2603
|
+
}
|
|
2604
|
+
const targetPath = join(getCwd(), installTarget, fileName);
|
|
2605
|
+
if (!await fileExists(targetPath)) {
|
|
2606
|
+
return true;
|
|
2607
|
+
}
|
|
2608
|
+
const preparedContent = prepareInstalledFileContent(
|
|
2609
|
+
await readFile(getRegistryTemplatePath(file), "utf-8"),
|
|
2610
|
+
item
|
|
2611
|
+
);
|
|
2612
|
+
const targetContent = await readFile(targetPath, "utf-8");
|
|
2613
|
+
if (!hashesAreEqual(preparedContent, targetContent)) {
|
|
2614
|
+
return true;
|
|
2615
|
+
}
|
|
2616
|
+
}
|
|
2617
|
+
return false;
|
|
2618
|
+
};
|
|
2524
2619
|
|
|
2525
2620
|
// src/commands/status.ts
|
|
2526
2621
|
var runStatus = async (options = {}) => {
|
|
2527
2622
|
const config = await loadConfig();
|
|
2528
|
-
const installed = config.installed ??
|
|
2529
|
-
if (!
|
|
2623
|
+
const installed = config.installed ?? [];
|
|
2624
|
+
if (!installed.length) {
|
|
2530
2625
|
console.log("No Lexsys components are currently tracked.");
|
|
2531
2626
|
return;
|
|
2532
2627
|
}
|
|
@@ -2541,14 +2636,15 @@ var runStatus = async (options = {}) => {
|
|
|
2541
2636
|
return;
|
|
2542
2637
|
}
|
|
2543
2638
|
console.log("Installed Lexsys components:\n");
|
|
2544
|
-
for (const
|
|
2639
|
+
for (const name of installed) {
|
|
2545
2640
|
const item = await findItem(name);
|
|
2546
2641
|
if (!item) {
|
|
2547
|
-
console.log(`- ${name}
|
|
2642
|
+
console.log(`- ${name} (missing from registry)`);
|
|
2548
2643
|
continue;
|
|
2549
2644
|
}
|
|
2550
|
-
const
|
|
2551
|
-
|
|
2645
|
+
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})`);
|
|
2552
2648
|
}
|
|
2553
2649
|
};
|
|
2554
2650
|
|
|
@@ -2573,7 +2669,7 @@ var computeRegistryClosure = (rootNames, items) => {
|
|
|
2573
2669
|
return closure;
|
|
2574
2670
|
};
|
|
2575
2671
|
var findOrphanInstalledItems = (removedTargetNames, remainingInstalled, items) => {
|
|
2576
|
-
const remainingNames =
|
|
2672
|
+
const remainingNames = remainingInstalled;
|
|
2577
2673
|
const removedDependencyNames = /* @__PURE__ */ new Set();
|
|
2578
2674
|
for (const removedName of removedTargetNames) {
|
|
2579
2675
|
const closure = computeRegistryClosure([removedName], items);
|
|
@@ -2592,17 +2688,263 @@ var findOrphanInstalledItems = (removedTargetNames, remainingInstalled, items) =
|
|
|
2592
2688
|
)
|
|
2593
2689
|
).filter((item) => Boolean(item)).filter((item) => removedDependencyNames.has(item.name));
|
|
2594
2690
|
};
|
|
2691
|
+
var checkItemFiles = async (name) => {
|
|
2692
|
+
const item = await findItem(name);
|
|
2693
|
+
const config = await loadConfig();
|
|
2694
|
+
if (!item) {
|
|
2695
|
+
console.log(`Component "${name}" no longer exists in the registry.`);
|
|
2696
|
+
return;
|
|
2697
|
+
}
|
|
2698
|
+
const installTarget = resolveItemInstallTarget(config, item);
|
|
2699
|
+
console.log(`File check for ${item.canonicalName}:`);
|
|
2700
|
+
for (const file of item.files) {
|
|
2701
|
+
const fileName = file.split("/").at(-1);
|
|
2702
|
+
if (!fileName) {
|
|
2703
|
+
console.log(`- invalid registry file path: ${file}`);
|
|
2704
|
+
continue;
|
|
2705
|
+
}
|
|
2706
|
+
const targetPath = join(getCwd(), installTarget, fileName);
|
|
2707
|
+
if (!await fileExists(targetPath)) {
|
|
2708
|
+
console.log(`- missing: ${targetPath}`);
|
|
2709
|
+
continue;
|
|
2710
|
+
}
|
|
2711
|
+
const preparedContent = prepareInstalledFileContent(
|
|
2712
|
+
await readFile(getRegistryTemplatePath(file), "utf-8"),
|
|
2713
|
+
item
|
|
2714
|
+
);
|
|
2715
|
+
const targetContent = await readFile(targetPath, "utf-8");
|
|
2716
|
+
if (hashesAreEqual(preparedContent, targetContent)) {
|
|
2717
|
+
console.log(`- identical: ${targetPath}`);
|
|
2718
|
+
continue;
|
|
2719
|
+
}
|
|
2720
|
+
console.log(`- conflict: ${targetPath}`);
|
|
2721
|
+
}
|
|
2722
|
+
};
|
|
2723
|
+
var applyItemOverwrite = async (name, force) => {
|
|
2724
|
+
const item = await findItem(name);
|
|
2725
|
+
const config = await loadConfig();
|
|
2726
|
+
if (!item) {
|
|
2727
|
+
console.log(`Component "${name}" no longer exists in the registry.`);
|
|
2728
|
+
return false;
|
|
2729
|
+
}
|
|
2730
|
+
const installTarget = resolveItemInstallTarget(config, item);
|
|
2731
|
+
let hasConflict = false;
|
|
2732
|
+
console.log(`Applying update for ${item.canonicalName}:`);
|
|
2733
|
+
for (const file of item.files) {
|
|
2734
|
+
const sourcePath = getRegistryTemplatePath(file);
|
|
2735
|
+
const fileName = file.split("/").at(-1);
|
|
2736
|
+
if (!fileName) {
|
|
2737
|
+
console.log(`- invalid registry file path: ${file}`);
|
|
2738
|
+
hasConflict = true;
|
|
2739
|
+
continue;
|
|
2740
|
+
}
|
|
2741
|
+
const targetPath = join(getCwd(), installTarget, fileName);
|
|
2742
|
+
const preparedContent = prepareInstalledFileContent(
|
|
2743
|
+
await readFile(sourcePath, "utf-8"),
|
|
2744
|
+
item
|
|
2745
|
+
);
|
|
2746
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
2747
|
+
if (!await fileExists(targetPath)) {
|
|
2748
|
+
await writeFile(targetPath, preparedContent, "utf-8");
|
|
2749
|
+
console.log(`- restored missing file: ${targetPath}`);
|
|
2750
|
+
continue;
|
|
2751
|
+
}
|
|
2752
|
+
const targetContent = await readFile(targetPath, "utf-8");
|
|
2753
|
+
if (hashesAreEqual(preparedContent, targetContent)) {
|
|
2754
|
+
console.log(`- identical: ${targetPath}`);
|
|
2755
|
+
continue;
|
|
2756
|
+
}
|
|
2757
|
+
if (force) {
|
|
2758
|
+
const backupPath = await createBackupFile(targetPath);
|
|
2759
|
+
if (backupPath) {
|
|
2760
|
+
console.log(`- backup created: ${backupPath}`);
|
|
2761
|
+
}
|
|
2762
|
+
await writeFile(targetPath, preparedContent, "utf-8");
|
|
2763
|
+
console.log(`- force updated file: ${targetPath}`);
|
|
2764
|
+
continue;
|
|
2765
|
+
}
|
|
2766
|
+
hasConflict = true;
|
|
2767
|
+
console.log(`- conflict (user modified): ${targetPath}`);
|
|
2768
|
+
}
|
|
2769
|
+
if (hasConflict) {
|
|
2770
|
+
console.log(
|
|
2771
|
+
"Update finished with conflicts. Review conflicted files before retrying."
|
|
2772
|
+
);
|
|
2773
|
+
return false;
|
|
2774
|
+
}
|
|
2775
|
+
console.log(`\u2714 ${item.canonicalName} updated successfully`);
|
|
2776
|
+
return true;
|
|
2777
|
+
};
|
|
2778
|
+
var checkItemUpdate = async (name, dryRun, force, sync = false) => {
|
|
2779
|
+
const item = await findItem(name);
|
|
2780
|
+
if (!item) {
|
|
2781
|
+
console.log(`Component "${name}" no longer exists in the registry.`);
|
|
2782
|
+
return false;
|
|
2783
|
+
}
|
|
2784
|
+
const driftStatus = await getComponentDriftStatus(name);
|
|
2785
|
+
if (driftStatus === "missing") {
|
|
2786
|
+
console.log(`Component "${name}" no longer exists in the registry.`);
|
|
2787
|
+
return false;
|
|
2788
|
+
}
|
|
2789
|
+
const hasDrift = driftStatus === "drift";
|
|
2790
|
+
if (!sync && !hasDrift) {
|
|
2791
|
+
console.log(`${item.canonicalName} is up to date with the registry.`);
|
|
2792
|
+
return false;
|
|
2793
|
+
}
|
|
2794
|
+
if (sync && !hasDrift) {
|
|
2795
|
+
console.log(
|
|
2796
|
+
`${item.canonicalName} template sync (already matches registry)`
|
|
2797
|
+
);
|
|
2798
|
+
} else {
|
|
2799
|
+
console.log(`${item.canonicalName} can be updated from registry templates`);
|
|
2800
|
+
}
|
|
2801
|
+
if (dryRun) {
|
|
2802
|
+
console.log("\nChanged file candidates:");
|
|
2803
|
+
for (const file of item.files) {
|
|
2804
|
+
console.log(`~ ${file}`);
|
|
2805
|
+
}
|
|
2806
|
+
console.log("\nDry run: no files will be changed.");
|
|
2807
|
+
console.log("Update plan:");
|
|
2808
|
+
if (force) {
|
|
2809
|
+
console.log(
|
|
2810
|
+
"- Force mode requested: conflicted files require backup before overwrite"
|
|
2811
|
+
);
|
|
2812
|
+
console.log(
|
|
2813
|
+
"- Dry run: backups would be created before forced overwrites"
|
|
2814
|
+
);
|
|
2815
|
+
}
|
|
2816
|
+
console.log(`- Check installed files for ${item.canonicalName}`);
|
|
2817
|
+
console.log("- Compare existing files with registry templates");
|
|
2818
|
+
console.log("- Report conflicts before writing changes");
|
|
2819
|
+
console.log("- Never overwrite user-modified files silently");
|
|
2820
|
+
await checkItemFiles(name);
|
|
2821
|
+
return false;
|
|
2822
|
+
}
|
|
2823
|
+
return await applyItemOverwrite(name, force);
|
|
2824
|
+
};
|
|
2825
|
+
var resetItem = async (name, dryRun) => {
|
|
2826
|
+
const item = await findItem(name);
|
|
2827
|
+
if (!item) {
|
|
2828
|
+
console.log(`Component "${name}" no longer exists in the registry.`);
|
|
2829
|
+
return false;
|
|
2830
|
+
}
|
|
2831
|
+
const hasDrift = await itemHasTemplateDrift(item);
|
|
2832
|
+
if (!hasDrift) {
|
|
2833
|
+
console.log(`${item.canonicalName} already matches registry templates.`);
|
|
2834
|
+
return false;
|
|
2835
|
+
}
|
|
2836
|
+
if (dryRun) {
|
|
2837
|
+
console.log(`Reset plan for ${item.canonicalName}:`);
|
|
2838
|
+
console.log("- Backups would be created before overwriting changed files");
|
|
2839
|
+
console.log("- Files would be restored from registry templates");
|
|
2840
|
+
for (const file of item.files) {
|
|
2841
|
+
console.log(`~ ${file}`);
|
|
2842
|
+
}
|
|
2843
|
+
console.log("\nDry run: no files will be changed.");
|
|
2844
|
+
await checkItemFiles(name);
|
|
2845
|
+
return false;
|
|
2846
|
+
}
|
|
2847
|
+
return await applyItemOverwrite(name, true);
|
|
2848
|
+
};
|
|
2595
2849
|
|
|
2596
|
-
// src/commands/
|
|
2597
|
-
var
|
|
2598
|
-
|
|
2850
|
+
// src/commands/reset.ts
|
|
2851
|
+
var resolveInstalledKey = async (name, installed) => {
|
|
2852
|
+
const direct = findInstalledKey(installed, name);
|
|
2853
|
+
if (direct) {
|
|
2854
|
+
return direct;
|
|
2855
|
+
}
|
|
2856
|
+
const item = await findItem(name);
|
|
2857
|
+
if (!item) {
|
|
2858
|
+
return void 0;
|
|
2859
|
+
}
|
|
2860
|
+
return findInstalledKey(installed, item.name);
|
|
2861
|
+
};
|
|
2862
|
+
var runReset = async (args2) => {
|
|
2863
|
+
const dryRun = hasFlag(args2, "--dry-run", "-d");
|
|
2864
|
+
const withDeps = hasFlag(args2, "--with-deps", "-w");
|
|
2865
|
+
const noFallback = hasFlag(args2, "--no-fallback");
|
|
2866
|
+
const targetArgs = removeFlagsWithValues(
|
|
2867
|
+
removeFlags(args2, [
|
|
2868
|
+
"--dry-run",
|
|
2869
|
+
"-d",
|
|
2870
|
+
"--with-deps",
|
|
2871
|
+
"-w",
|
|
2872
|
+
"--no-fallback"
|
|
2873
|
+
]),
|
|
2874
|
+
["--cwd", "-C"]
|
|
2875
|
+
);
|
|
2876
|
+
const config = await loadConfig();
|
|
2877
|
+
const installed = [...config.installed ?? []];
|
|
2878
|
+
if (!installed.length) {
|
|
2879
|
+
console.log("No components installed.");
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
try {
|
|
2883
|
+
await getRegistryProviderResult({
|
|
2884
|
+
fallback: !noFallback
|
|
2885
|
+
});
|
|
2886
|
+
} catch (error) {
|
|
2887
|
+
console.log("Failed to resolve registry.");
|
|
2888
|
+
console.log(error instanceof Error ? error.message : String(error));
|
|
2889
|
+
process.exitCode = 1;
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
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);
|
|
2900
|
+
}
|
|
2901
|
+
const resetNames = /* @__PURE__ */ new Set();
|
|
2902
|
+
for (const name of targetArgs) {
|
|
2903
|
+
const installedKey = await resolveInstalledKey(name, installed);
|
|
2904
|
+
if (!installedKey) {
|
|
2905
|
+
console.log(`Component "${name}" is not tracked as installed.`);
|
|
2906
|
+
continue;
|
|
2907
|
+
}
|
|
2908
|
+
resetNames.add(installedKey);
|
|
2909
|
+
}
|
|
2910
|
+
if (!resetNames.size) {
|
|
2911
|
+
console.log("No installed components matched the request.");
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2914
|
+
if (withDeps) {
|
|
2915
|
+
const allItems = await getRegistryItems({ fallback: !noFallback });
|
|
2916
|
+
for (const rootName of [...resetNames]) {
|
|
2917
|
+
const closure = computeRegistryClosure([rootName], allItems);
|
|
2918
|
+
for (const dependencyName of closure) {
|
|
2919
|
+
if (isInstalled(installed, dependencyName)) {
|
|
2920
|
+
resetNames.add(dependencyName);
|
|
2921
|
+
}
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
const resetItems = await resolveRegistryItems([...resetNames], {
|
|
2926
|
+
fallback: !noFallback
|
|
2927
|
+
});
|
|
2928
|
+
if (dryRun) {
|
|
2929
|
+
console.log("Dry run: no files will be changed.\n");
|
|
2930
|
+
console.log("Components:");
|
|
2931
|
+
for (const item of resetItems) {
|
|
2932
|
+
console.log(`- ${item.canonicalName}`);
|
|
2933
|
+
}
|
|
2934
|
+
console.log("");
|
|
2935
|
+
}
|
|
2936
|
+
for (const item of resetItems) {
|
|
2937
|
+
await resetItem(item.name, dryRun);
|
|
2938
|
+
console.log("");
|
|
2939
|
+
}
|
|
2599
2940
|
};
|
|
2941
|
+
|
|
2942
|
+
// src/commands/uninstall.ts
|
|
2600
2943
|
var resolveInstalledItems = async (installed) => {
|
|
2601
|
-
|
|
2602
|
-
if (!names.length) {
|
|
2944
|
+
if (!installed.length) {
|
|
2603
2945
|
return [];
|
|
2604
2946
|
}
|
|
2605
|
-
return resolveRegistryItems(
|
|
2947
|
+
return resolveRegistryItems(installed);
|
|
2606
2948
|
};
|
|
2607
2949
|
var collectOrphanedSharedResources = (removedItems, remainingItems) => {
|
|
2608
2950
|
const removedUtilities = collectUtilities(removedItems);
|
|
@@ -2618,14 +2960,6 @@ var collectOrphanedSharedResources = (removedItems, remainingItems) => {
|
|
|
2618
2960
|
})
|
|
2619
2961
|
};
|
|
2620
2962
|
};
|
|
2621
|
-
var removeInstalledKey = (installed, itemName) => {
|
|
2622
|
-
const installedKey = Object.keys(installed).find((key) => {
|
|
2623
|
-
return normalizeInstalledKey(key) === normalizeInstalledKey(itemName);
|
|
2624
|
-
});
|
|
2625
|
-
if (installedKey) {
|
|
2626
|
-
delete installed[installedKey];
|
|
2627
|
-
}
|
|
2628
|
-
};
|
|
2629
2963
|
var runUninstall = async (args2) => {
|
|
2630
2964
|
const dryRun = hasFlag(args2, "--dry-run", "-d");
|
|
2631
2965
|
const withDeps = hasFlag(args2, "--with-deps", "-w");
|
|
@@ -2642,7 +2976,7 @@ var runUninstall = async (args2) => {
|
|
|
2642
2976
|
);
|
|
2643
2977
|
if (!targetArgs.length) {
|
|
2644
2978
|
const preConfig = await loadConfig();
|
|
2645
|
-
const installedNames =
|
|
2979
|
+
const installedNames = preConfig.installed ?? [];
|
|
2646
2980
|
if (installedNames.length === 0) {
|
|
2647
2981
|
console.log("No components installed.");
|
|
2648
2982
|
return;
|
|
@@ -2656,7 +2990,7 @@ var runUninstall = async (args2) => {
|
|
|
2656
2990
|
targetArgs.push(...selected);
|
|
2657
2991
|
}
|
|
2658
2992
|
const config = await loadConfig();
|
|
2659
|
-
const installed = config.installed ??
|
|
2993
|
+
const installed = [...config.installed ?? []];
|
|
2660
2994
|
const resolvedTargets = [];
|
|
2661
2995
|
const notTracked = [];
|
|
2662
2996
|
for (const name of targetArgs) {
|
|
@@ -2665,10 +2999,7 @@ var runUninstall = async (args2) => {
|
|
|
2665
2999
|
console.log(`Component "${name}" not found in registry.`);
|
|
2666
3000
|
continue;
|
|
2667
3001
|
}
|
|
2668
|
-
|
|
2669
|
-
return normalizeInstalledKey(key) === normalizeInstalledKey(item.name);
|
|
2670
|
-
});
|
|
2671
|
-
if (!installedKey) {
|
|
3002
|
+
if (!isInstalled(installed, item.name)) {
|
|
2672
3003
|
notTracked.push(name);
|
|
2673
3004
|
console.log(`Component "${name}" is not tracked as installed.`);
|
|
2674
3005
|
continue;
|
|
@@ -2681,10 +3012,11 @@ var runUninstall = async (args2) => {
|
|
|
2681
3012
|
}
|
|
2682
3013
|
return;
|
|
2683
3014
|
}
|
|
2684
|
-
const remainingInstalled =
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
3015
|
+
const remainingInstalled = installed.filter((entry) => {
|
|
3016
|
+
return !resolvedTargets.some((item) => {
|
|
3017
|
+
return findInstalledKey([entry], item.name) !== void 0;
|
|
3018
|
+
});
|
|
3019
|
+
});
|
|
2688
3020
|
const allItems = await getRegistryItems({ fallback: !noFallback });
|
|
2689
3021
|
const orphanItems = withDeps ? findOrphanInstalledItems(
|
|
2690
3022
|
resolvedTargets.map((item) => item.name),
|
|
@@ -2706,9 +3038,7 @@ var runUninstall = async (args2) => {
|
|
|
2706
3038
|
console.log("Dry run: no files will be removed.\n");
|
|
2707
3039
|
console.log("Components:");
|
|
2708
3040
|
for (const item of resolvedTargets) {
|
|
2709
|
-
console.log(
|
|
2710
|
-
`- ${item.canonicalName} v${installed[item.name] ?? "unknown"}`
|
|
2711
|
-
);
|
|
3041
|
+
console.log(`- ${item.canonicalName}`);
|
|
2712
3042
|
}
|
|
2713
3043
|
if (withDeps && orphanItems.length) {
|
|
2714
3044
|
console.log("\nOrphan registry items (--with-deps):");
|
|
@@ -2724,10 +3054,12 @@ var runUninstall = async (args2) => {
|
|
|
2724
3054
|
console.log(`- ${item.canonicalName}`);
|
|
2725
3055
|
}
|
|
2726
3056
|
}
|
|
2727
|
-
const postOrphanRemaining =
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
3057
|
+
const postOrphanRemaining = remainingInstalled.filter((entry) => {
|
|
3058
|
+
if (!withDeps) {
|
|
3059
|
+
return true;
|
|
3060
|
+
}
|
|
3061
|
+
return !orphanItems.some((item) => isInstalled([entry], item.name));
|
|
3062
|
+
});
|
|
2731
3063
|
const dryRunOrphans = collectOrphanedSharedResources(
|
|
2732
3064
|
allRemovalTargets,
|
|
2733
3065
|
await resolveInstalledItems(postOrphanRemaining)
|
|
@@ -2761,11 +3093,11 @@ var runUninstall = async (args2) => {
|
|
|
2761
3093
|
}
|
|
2762
3094
|
console.log("");
|
|
2763
3095
|
}
|
|
2764
|
-
|
|
3096
|
+
let updatedInstalled = [...installed];
|
|
2765
3097
|
for (const item of successfullyUninstalled) {
|
|
2766
|
-
|
|
3098
|
+
updatedInstalled = removeInstalled(updatedInstalled, item.name);
|
|
2767
3099
|
}
|
|
2768
|
-
const remainingItems = await resolveInstalledItems(
|
|
3100
|
+
const remainingItems = await resolveInstalledItems(updatedInstalled);
|
|
2769
3101
|
const orphanedResources = collectOrphanedSharedResources(
|
|
2770
3102
|
successfullyUninstalled,
|
|
2771
3103
|
remainingItems
|
|
@@ -2785,12 +3117,6 @@ var runUninstall = async (args2) => {
|
|
|
2785
3117
|
}
|
|
2786
3118
|
const utilitiesResult = await uninstallUtilities(resolvedUtilities, config);
|
|
2787
3119
|
const stylesResult = await uninstallStyles(resolvedStyles, config);
|
|
2788
|
-
const updatedInstalled = {
|
|
2789
|
-
...installed
|
|
2790
|
-
};
|
|
2791
|
-
for (const item of successfullyUninstalled) {
|
|
2792
|
-
removeInstalledKey(updatedInstalled, item.name);
|
|
2793
|
-
}
|
|
2794
3120
|
await saveConfig({
|
|
2795
3121
|
...config,
|
|
2796
3122
|
installed: updatedInstalled
|
|
@@ -2810,172 +3136,18 @@ var runUninstall = async (args2) => {
|
|
|
2810
3136
|
}
|
|
2811
3137
|
};
|
|
2812
3138
|
|
|
2813
|
-
// src/utils/version.ts
|
|
2814
|
-
var compareVersions = (a, b) => {
|
|
2815
|
-
const pa = a.split(".").map(Number);
|
|
2816
|
-
const pb = b.split(".").map(Number);
|
|
2817
|
-
const length = Math.max(pa.length, pb.length);
|
|
2818
|
-
for (let i = 0; i < length; i++) {
|
|
2819
|
-
const va = pa[i] ?? 0;
|
|
2820
|
-
const vb = pb[i] ?? 0;
|
|
2821
|
-
if (va > vb) return 1;
|
|
2822
|
-
if (va < vb) return -1;
|
|
2823
|
-
}
|
|
2824
|
-
return 0;
|
|
2825
|
-
};
|
|
2826
|
-
var isUpdateAvailable = (installed, latest) => {
|
|
2827
|
-
return compareVersions(latest, installed) === 1;
|
|
2828
|
-
};
|
|
2829
|
-
|
|
2830
|
-
// src/install/update-engine.ts
|
|
2831
|
-
var checkItemFiles = async (name) => {
|
|
2832
|
-
const item = await findItem(name);
|
|
2833
|
-
const config = await loadConfig();
|
|
2834
|
-
if (!item) {
|
|
2835
|
-
console.log(`Component "${name}" no longer exists in the registry.`);
|
|
2836
|
-
return;
|
|
2837
|
-
}
|
|
2838
|
-
const installTarget = resolveItemInstallTarget(config, item);
|
|
2839
|
-
console.log(`File check for ${item.canonicalName}:`);
|
|
2840
|
-
for (const file of item.files) {
|
|
2841
|
-
const fileName = file.split("/").at(-1);
|
|
2842
|
-
if (!fileName) {
|
|
2843
|
-
console.log(`- invalid registry file path: ${file}`);
|
|
2844
|
-
continue;
|
|
2845
|
-
}
|
|
2846
|
-
const targetPath = join(getCwd(), installTarget, fileName);
|
|
2847
|
-
if (!await fileExists(targetPath)) {
|
|
2848
|
-
console.log(`- missing: ${targetPath}`);
|
|
2849
|
-
continue;
|
|
2850
|
-
}
|
|
2851
|
-
const preparedContent = prepareInstalledFileContent(
|
|
2852
|
-
await readFile(getRegistryTemplatePath(file), "utf-8"),
|
|
2853
|
-
item
|
|
2854
|
-
);
|
|
2855
|
-
const targetContent = await readFile(targetPath, "utf-8");
|
|
2856
|
-
if (hashesAreEqual(preparedContent, targetContent)) {
|
|
2857
|
-
console.log(`- identical: ${targetPath}`);
|
|
2858
|
-
continue;
|
|
2859
|
-
}
|
|
2860
|
-
console.log(`- conflict: ${targetPath}`);
|
|
2861
|
-
}
|
|
2862
|
-
};
|
|
2863
|
-
var applySafeItemUpdate = async (name, force) => {
|
|
2864
|
-
const item = await findItem(name);
|
|
2865
|
-
const config = await loadConfig();
|
|
2866
|
-
if (!item) {
|
|
2867
|
-
console.log(`Component "${name}" no longer exists in the registry.`);
|
|
2868
|
-
return false;
|
|
2869
|
-
}
|
|
2870
|
-
const installTarget = resolveItemInstallTarget(config, item);
|
|
2871
|
-
let hasConflict = false;
|
|
2872
|
-
console.log(`Applying update for ${item.canonicalName}:`);
|
|
2873
|
-
for (const file of item.files) {
|
|
2874
|
-
const sourcePath = getRegistryTemplatePath(file);
|
|
2875
|
-
const fileName = file.split("/").at(-1);
|
|
2876
|
-
if (!fileName) {
|
|
2877
|
-
console.log(`- invalid registry file path: ${file}`);
|
|
2878
|
-
hasConflict = true;
|
|
2879
|
-
continue;
|
|
2880
|
-
}
|
|
2881
|
-
const targetPath = join(getCwd(), installTarget, fileName);
|
|
2882
|
-
const preparedContent = prepareInstalledFileContent(
|
|
2883
|
-
await readFile(sourcePath, "utf-8"),
|
|
2884
|
-
item
|
|
2885
|
-
);
|
|
2886
|
-
await mkdir(dirname(targetPath), { recursive: true });
|
|
2887
|
-
if (!await fileExists(targetPath)) {
|
|
2888
|
-
await writeFile(targetPath, preparedContent, "utf-8");
|
|
2889
|
-
console.log(`- restored missing file: ${targetPath}`);
|
|
2890
|
-
continue;
|
|
2891
|
-
}
|
|
2892
|
-
const targetContent = await readFile(targetPath, "utf-8");
|
|
2893
|
-
if (hashesAreEqual(preparedContent, targetContent)) {
|
|
2894
|
-
console.log(`- identical: ${targetPath}`);
|
|
2895
|
-
continue;
|
|
2896
|
-
}
|
|
2897
|
-
if (force) {
|
|
2898
|
-
const backupPath = await createBackupFile(targetPath);
|
|
2899
|
-
if (backupPath) {
|
|
2900
|
-
console.log(`- backup created: ${backupPath}`);
|
|
2901
|
-
}
|
|
2902
|
-
await writeFile(targetPath, preparedContent, "utf-8");
|
|
2903
|
-
console.log(`- force updated file: ${targetPath}`);
|
|
2904
|
-
continue;
|
|
2905
|
-
}
|
|
2906
|
-
hasConflict = true;
|
|
2907
|
-
console.log(`- conflict (user modified): ${targetPath}`);
|
|
2908
|
-
}
|
|
2909
|
-
if (hasConflict) {
|
|
2910
|
-
console.log(
|
|
2911
|
-
"Update finished with conflicts. Installed version was not changed."
|
|
2912
|
-
);
|
|
2913
|
-
return false;
|
|
2914
|
-
}
|
|
2915
|
-
console.log(
|
|
2916
|
-
`\u2714 ${item.canonicalName} updated successfully to v${item.version}`
|
|
2917
|
-
);
|
|
2918
|
-
return true;
|
|
2919
|
-
};
|
|
2920
|
-
var checkItemUpdate = async (name, installedVersion, dryRun, force, sync = false) => {
|
|
2921
|
-
const item = await findItem(name);
|
|
2922
|
-
if (!item) {
|
|
2923
|
-
console.log(`Component "${name}" no longer exists in the registry.`);
|
|
2924
|
-
return false;
|
|
2925
|
-
}
|
|
2926
|
-
const versionUpdateAvailable = isUpdateAvailable(
|
|
2927
|
-
installedVersion,
|
|
2928
|
-
item.version
|
|
2929
|
-
);
|
|
2930
|
-
if (!sync && !versionUpdateAvailable) {
|
|
2931
|
-
console.log(`${item.canonicalName} is up to date (v${installedVersion}).`);
|
|
2932
|
-
return false;
|
|
2933
|
-
}
|
|
2934
|
-
if (sync && !versionUpdateAvailable) {
|
|
2935
|
-
console.log(
|
|
2936
|
-
`${item.canonicalName} template sync (installed v${installedVersion}, registry v${item.version})`
|
|
2937
|
-
);
|
|
2938
|
-
} else {
|
|
2939
|
-
console.log(
|
|
2940
|
-
`${item.canonicalName} can be updated: v${installedVersion} \u2192 v${item.version}`
|
|
2941
|
-
);
|
|
2942
|
-
}
|
|
2943
|
-
if (dryRun) {
|
|
2944
|
-
console.log("\nChanged file candidates:");
|
|
2945
|
-
for (const file of item.files) {
|
|
2946
|
-
console.log(`~ ${file}`);
|
|
2947
|
-
}
|
|
2948
|
-
console.log("\nDry run: no files will be changed.");
|
|
2949
|
-
console.log("Update plan:");
|
|
2950
|
-
if (force) {
|
|
2951
|
-
console.log(
|
|
2952
|
-
"- Force mode requested: conflicted files require backup before overwrite"
|
|
2953
|
-
);
|
|
2954
|
-
console.log(
|
|
2955
|
-
"- Dry run: backups would be created before forced overwrites"
|
|
2956
|
-
);
|
|
2957
|
-
}
|
|
2958
|
-
console.log(`- Check installed files for ${item.canonicalName}`);
|
|
2959
|
-
console.log("- Compare existing files with registry templates");
|
|
2960
|
-
console.log("- Report conflicts before writing changes");
|
|
2961
|
-
console.log("- Never overwrite user-modified files silently");
|
|
2962
|
-
await checkItemFiles(name);
|
|
2963
|
-
return false;
|
|
2964
|
-
}
|
|
2965
|
-
return await applySafeItemUpdate(name, force);
|
|
2966
|
-
};
|
|
2967
|
-
|
|
2968
3139
|
// src/commands/update.ts
|
|
2969
3140
|
var styleUpdateNames = ["theme"];
|
|
2970
|
-
var
|
|
2971
|
-
|
|
2972
|
-
|
|
3141
|
+
var resolveInstalledKey2 = async (name, installed) => {
|
|
3142
|
+
const direct = findInstalledKey(installed, name);
|
|
3143
|
+
if (direct) {
|
|
3144
|
+
return direct;
|
|
2973
3145
|
}
|
|
2974
3146
|
const item = await findItem(name);
|
|
2975
3147
|
if (!item) {
|
|
2976
3148
|
return void 0;
|
|
2977
3149
|
}
|
|
2978
|
-
return installed
|
|
3150
|
+
return findInstalledKey(installed, item.name);
|
|
2979
3151
|
};
|
|
2980
3152
|
var runStylesUpdate = async (config, dryRun) => {
|
|
2981
3153
|
const styles = resolveRegistryStyles(styleUpdateNames);
|
|
@@ -3002,9 +3174,7 @@ Tailwind CSS entrypoint: ${config.tailwind.css}`);
|
|
|
3002
3174
|
}
|
|
3003
3175
|
};
|
|
3004
3176
|
var runUtilitiesUpdate = async (config, installed, dryRun, force) => {
|
|
3005
|
-
const installedItems = (await Promise.all(
|
|
3006
|
-
Object.keys(installed).map(async (name) => findItem(name))
|
|
3007
|
-
)).filter((item) => Boolean(item));
|
|
3177
|
+
const installedItems = (await Promise.all(installed.map(async (name) => findItem(name)))).filter((item) => Boolean(item));
|
|
3008
3178
|
const utilityNames = collectUtilities(installedItems);
|
|
3009
3179
|
if (!utilityNames.length) {
|
|
3010
3180
|
console.log("No shared utilities are tracked for installed components.");
|
|
@@ -3029,26 +3199,18 @@ var runUtilitiesUpdate = async (config, installed, dryRun, force) => {
|
|
|
3029
3199
|
);
|
|
3030
3200
|
}
|
|
3031
3201
|
};
|
|
3032
|
-
var runComponentUpdates = async (
|
|
3033
|
-
|
|
3034
|
-
if (!Object.keys(installed).length) {
|
|
3202
|
+
var runComponentUpdates = async (installed, targetNames, dryRun, force, sync) => {
|
|
3203
|
+
if (!installed.length) {
|
|
3035
3204
|
console.log("No Lexsys components are currently tracked.");
|
|
3036
|
-
return
|
|
3205
|
+
return;
|
|
3037
3206
|
}
|
|
3038
3207
|
console.log("Checking installed Lexsys components:\n");
|
|
3039
3208
|
for (const name of targetNames) {
|
|
3040
|
-
|
|
3041
|
-
if (!version) {
|
|
3209
|
+
if (!isInstalled(installed, name)) {
|
|
3042
3210
|
continue;
|
|
3043
3211
|
}
|
|
3044
|
-
|
|
3045
|
-
const item = await findItem(name);
|
|
3046
|
-
if (didUpdate && item) {
|
|
3047
|
-
installed[name] = item.version;
|
|
3048
|
-
changed = true;
|
|
3049
|
-
}
|
|
3212
|
+
await checkItemUpdate(name, dryRun, force, sync);
|
|
3050
3213
|
}
|
|
3051
|
-
return changed;
|
|
3052
3214
|
};
|
|
3053
3215
|
var runUpdate = async (args2) => {
|
|
3054
3216
|
const dryRun = hasFlag(args2, "--dry-run", "-d");
|
|
@@ -3076,7 +3238,7 @@ var runUpdate = async (args2) => {
|
|
|
3076
3238
|
"-a"
|
|
3077
3239
|
]);
|
|
3078
3240
|
const config = await loadConfig();
|
|
3079
|
-
const installed =
|
|
3241
|
+
const installed = [...config.installed ?? []];
|
|
3080
3242
|
try {
|
|
3081
3243
|
await getRegistryProviderResult({
|
|
3082
3244
|
fallback: !noFallback
|
|
@@ -3088,19 +3250,18 @@ var runUpdate = async (args2) => {
|
|
|
3088
3250
|
return;
|
|
3089
3251
|
}
|
|
3090
3252
|
if (!updateAll && !stylesFlag && !utilitiesFlag && targetArgs.length === 0) {
|
|
3091
|
-
|
|
3092
|
-
if (installedNames.length === 0) {
|
|
3253
|
+
if (installed.length === 0) {
|
|
3093
3254
|
console.log(
|
|
3094
3255
|
"No components installed. Run `lexsys add <component>` first."
|
|
3095
3256
|
);
|
|
3096
3257
|
return;
|
|
3097
3258
|
}
|
|
3098
3259
|
if (yes) {
|
|
3099
|
-
targetArgs.push(...
|
|
3260
|
+
targetArgs.push(...installed);
|
|
3100
3261
|
} else {
|
|
3101
3262
|
const selected = await promptMultiselect(
|
|
3102
3263
|
"Select components to update",
|
|
3103
|
-
|
|
3264
|
+
installed.map((name) => ({ title: name, value: name })),
|
|
3104
3265
|
{ min: 1 }
|
|
3105
3266
|
);
|
|
3106
3267
|
if (!selected.length) return;
|
|
@@ -3127,42 +3288,18 @@ var runUpdate = async (args2) => {
|
|
|
3127
3288
|
if (!shouldUpdateComponents) {
|
|
3128
3289
|
return;
|
|
3129
3290
|
}
|
|
3130
|
-
let changed = false;
|
|
3131
3291
|
if (updateAll) {
|
|
3132
|
-
|
|
3133
|
-
config,
|
|
3134
|
-
installed,
|
|
3135
|
-
Object.keys(installed),
|
|
3136
|
-
dryRun,
|
|
3137
|
-
force,
|
|
3138
|
-
sync
|
|
3139
|
-
);
|
|
3292
|
+
await runComponentUpdates(installed, installed, dryRun, force, sync);
|
|
3140
3293
|
} else {
|
|
3141
3294
|
for (const name of targetArgs) {
|
|
3142
|
-
const installedKey = await
|
|
3295
|
+
const installedKey = await resolveInstalledKey2(name, installed);
|
|
3143
3296
|
if (!installedKey) {
|
|
3144
3297
|
console.log(`Component "${name}" is not tracked as installed.`);
|
|
3145
3298
|
continue;
|
|
3146
3299
|
}
|
|
3147
|
-
|
|
3148
|
-
config,
|
|
3149
|
-
installed,
|
|
3150
|
-
[installedKey],
|
|
3151
|
-
dryRun,
|
|
3152
|
-
force,
|
|
3153
|
-
sync
|
|
3154
|
-
);
|
|
3155
|
-
if (didUpdateOne) {
|
|
3156
|
-
changed = true;
|
|
3157
|
-
}
|
|
3300
|
+
await runComponentUpdates(installed, [installedKey], dryRun, force, sync);
|
|
3158
3301
|
}
|
|
3159
3302
|
}
|
|
3160
|
-
if (changed) {
|
|
3161
|
-
await saveConfig({
|
|
3162
|
-
...config,
|
|
3163
|
-
installed
|
|
3164
|
-
});
|
|
3165
|
-
}
|
|
3166
3303
|
};
|
|
3167
3304
|
var cliDistDir = dirname(fileURLToPath(import.meta.url));
|
|
3168
3305
|
var packageJsonPath = join(cliDistDir, "..", "package.json");
|
|
@@ -3269,6 +3406,14 @@ try {
|
|
|
3269
3406
|
});
|
|
3270
3407
|
process.exit(0);
|
|
3271
3408
|
}
|
|
3409
|
+
if (command === "reset") {
|
|
3410
|
+
if (hasFlag(args, "--help", "-h")) {
|
|
3411
|
+
runHelpFor("reset");
|
|
3412
|
+
process.exit(0);
|
|
3413
|
+
}
|
|
3414
|
+
await runReset(args);
|
|
3415
|
+
process.exit(0);
|
|
3416
|
+
}
|
|
3272
3417
|
if (command === "uninstall" || command === "rm") {
|
|
3273
3418
|
if (hasFlag(args, "--help", "-h")) {
|
|
3274
3419
|
runHelpFor("uninstall");
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RegistryItem } from "@dalexto/lexsys-registry";
|
|
2
|
+
export type ComponentDriftStatus = "missing" | "in-sync" | "drift";
|
|
3
|
+
export declare const getComponentDriftStatus: (name: string) => Promise<ComponentDriftStatus>;
|
|
4
|
+
export declare const itemHasTemplateDrift: (item: RegistryItem) => Promise<boolean>;
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
export declare const checkItemFiles: (name: string) => Promise<void>;
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const applyItemOverwrite: (name: string, force: boolean) => Promise<boolean>;
|
|
3
|
+
export declare const checkItemUpdate: (name: string, dryRun: boolean, force: boolean, sync?: boolean) => Promise<boolean>;
|
|
4
|
+
export declare const resetItem: (name: string, dryRun: boolean) => Promise<boolean>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { RegistryItem } from "@dalexto/lexsys-registry";
|
|
2
2
|
export declare const computeRegistryClosure: (rootNames: string[], items: RegistryItem[]) => Set<string>;
|
|
3
|
-
export declare const findOrphanInstalledItems: (removedTargetNames: string[], remainingInstalled:
|
|
3
|
+
export declare const findOrphanInstalledItems: (removedTargetNames: string[], remainingInstalled: string[], items: RegistryItem[]) => RegistryItem[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dalexto/lexsys-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
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.
|
|
35
|
+
"@dalexto/lexsys-registry": "0.0.5"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/node": "^25.9.1",
|
package/dist/utils/version.d.ts
DELETED