regpick 0.2.6 → 0.2.8
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.mjs +3 -1284
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -1,1220 +1,5 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
|
|
3
|
-
import fsPromises from "node:fs/promises";
|
|
4
|
-
import { cosmiconfig } from "cosmiconfig";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import crypto from "node:crypto";
|
|
7
|
-
import * as diff from "diff";
|
|
8
|
-
import fs from "node:fs";
|
|
9
|
-
import { autocompleteMultiselect, cancel, confirm, intro, isCancel, log, multiselect, outro, select, text } from "@clack/prompts";
|
|
10
|
-
import { spawnSync } from "node:child_process";
|
|
11
|
-
|
|
12
|
-
//#region src/core/errors.ts
|
|
13
|
-
function appError(kind, message, cause) {
|
|
14
|
-
return {
|
|
15
|
-
kind,
|
|
16
|
-
message,
|
|
17
|
-
cause
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
function toAppError(error, fallbackKind = "RuntimeError") {
|
|
21
|
-
if (typeof error === "object" && error !== null && "kind" in error && "message" in error && typeof error.kind === "string" && typeof error.message === "string") return error;
|
|
22
|
-
if (error instanceof Error) return appError(fallbackKind, error.message, error);
|
|
23
|
-
return appError(fallbackKind, String(error));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
//#endregion
|
|
27
|
-
//#region src/core/result.ts
|
|
28
|
-
function ok(value) {
|
|
29
|
-
return {
|
|
30
|
-
ok: true,
|
|
31
|
-
value
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
function err(error) {
|
|
35
|
-
return {
|
|
36
|
-
ok: false,
|
|
37
|
-
error
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
//#endregion
|
|
42
|
-
//#region src/domain/pathPolicy.ts
|
|
43
|
-
function normalizeSlashes(relativePath) {
|
|
44
|
-
return relativePath.replace(/\\/g, "/");
|
|
45
|
-
}
|
|
46
|
-
function assertInsideProject(projectRoot, outputPath, allowOutsideProject) {
|
|
47
|
-
const projectRootWithSep = `${path.resolve(projectRoot)}${path.sep}`;
|
|
48
|
-
const resolvedOutput = path.resolve(outputPath);
|
|
49
|
-
if (allowOutsideProject) return ok(void 0);
|
|
50
|
-
if (resolvedOutput !== path.resolve(projectRoot) && !resolvedOutput.startsWith(projectRootWithSep)) return err(appError("ValidationError", `Refusing to write outside project: ${resolvedOutput}`));
|
|
51
|
-
return ok(void 0);
|
|
52
|
-
}
|
|
53
|
-
function resolveOutputPathFromPolicy(item, file, cwd, config) {
|
|
54
|
-
const typeKey = file.type || item.type || "registry:file";
|
|
55
|
-
const mappedBase = config.targetsByType?.[typeKey];
|
|
56
|
-
const preferManifestTarget = config.preferManifestTarget !== false;
|
|
57
|
-
const fallbackFileName = path.basename(file.path || `${item.name}.txt`);
|
|
58
|
-
let relativeTarget;
|
|
59
|
-
if (preferManifestTarget && file.target) relativeTarget = file.target;
|
|
60
|
-
else if (mappedBase) relativeTarget = path.join(mappedBase, fallbackFileName);
|
|
61
|
-
else if (file.target) relativeTarget = file.target;
|
|
62
|
-
else relativeTarget = path.join("src", fallbackFileName);
|
|
63
|
-
const absoluteTarget = path.resolve(cwd, relativeTarget);
|
|
64
|
-
const assertRes = assertInsideProject(cwd, absoluteTarget, Boolean(config.allowOutsideProject));
|
|
65
|
-
if (!assertRes.ok) return assertRes;
|
|
66
|
-
return ok({
|
|
67
|
-
absoluteTarget,
|
|
68
|
-
relativeTarget: normalizeSlashes(path.relative(cwd, absoluteTarget))
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
//#endregion
|
|
73
|
-
//#region src/domain/addPlan.ts
|
|
74
|
-
function unique$1(values) {
|
|
75
|
-
return [...new Set(values.filter(Boolean))];
|
|
76
|
-
}
|
|
77
|
-
function buildDependencyPlan(selectedItems) {
|
|
78
|
-
return {
|
|
79
|
-
dependencies: unique$1(selectedItems.flatMap((item) => item.dependencies || [])),
|
|
80
|
-
devDependencies: unique$1(selectedItems.flatMap((item) => item.devDependencies || []))
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function buildInstallPlan(selectedItems, cwd, config, existingTargets = /* @__PURE__ */ new Set()) {
|
|
84
|
-
const plannedWrites = [];
|
|
85
|
-
const conflicts = [];
|
|
86
|
-
for (const item of selectedItems) for (const file of item.files) {
|
|
87
|
-
const outputRes = resolveOutputPathFromPolicy(item, file, cwd, config);
|
|
88
|
-
if (!outputRes.ok) return outputRes;
|
|
89
|
-
const { absoluteTarget, relativeTarget } = outputRes.value;
|
|
90
|
-
const planned = {
|
|
91
|
-
itemName: item.name,
|
|
92
|
-
sourceFile: file,
|
|
93
|
-
absoluteTarget,
|
|
94
|
-
relativeTarget
|
|
95
|
-
};
|
|
96
|
-
plannedWrites.push(planned);
|
|
97
|
-
if (existingTargets.has(absoluteTarget)) conflicts.push(planned);
|
|
98
|
-
}
|
|
99
|
-
return ok({
|
|
100
|
-
selectedItems,
|
|
101
|
-
plannedWrites,
|
|
102
|
-
dependencyPlan: buildDependencyPlan(selectedItems),
|
|
103
|
-
conflicts
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
//#endregion
|
|
108
|
-
//#region src/domain/selection.ts
|
|
109
|
-
function parseSelectedNames(rawSelectFlag) {
|
|
110
|
-
if (!rawSelectFlag) return [];
|
|
111
|
-
return String(rawSelectFlag).split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
112
|
-
}
|
|
113
|
-
function selectItemsFromFlags(items, context) {
|
|
114
|
-
const { flags } = context.args;
|
|
115
|
-
const explicit = parseSelectedNames(flags.select);
|
|
116
|
-
if (Boolean(flags.all)) return ok(items);
|
|
117
|
-
if (explicit.length) {
|
|
118
|
-
const selected = items.filter((item) => explicit.includes(item.name));
|
|
119
|
-
if (!selected.length) return err(appError("ValidationError", `No items matched --select=${String(flags.select)}`));
|
|
120
|
-
return ok(selected);
|
|
121
|
-
}
|
|
122
|
-
return ok(null);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
//#endregion
|
|
126
|
-
//#region src/shell/config.ts
|
|
127
|
-
const DEFAULT_CONFIG = {
|
|
128
|
-
registries: { tebra: "./tebra-icon-registry/registry" },
|
|
129
|
-
targetsByType: {
|
|
130
|
-
"registry:icon": "src/components/ui/icons",
|
|
131
|
-
"registry:component": "src/components/ui",
|
|
132
|
-
"registry:file": "src/components/ui"
|
|
133
|
-
},
|
|
134
|
-
aliases: {},
|
|
135
|
-
overwritePolicy: "prompt",
|
|
136
|
-
packageManager: "auto",
|
|
137
|
-
preferManifestTarget: true,
|
|
138
|
-
allowOutsideProject: false
|
|
139
|
-
};
|
|
140
|
-
function getConfigPath(cwd) {
|
|
141
|
-
return path.join(cwd, "regpick.json");
|
|
142
|
-
}
|
|
143
|
-
async function readConfig(cwd) {
|
|
144
|
-
const result = await cosmiconfig("regpick", { searchPlaces: [
|
|
145
|
-
"regpick.json",
|
|
146
|
-
".regpickrc",
|
|
147
|
-
".regpickrc.json"
|
|
148
|
-
] }).search(cwd);
|
|
149
|
-
if (!result || !result.config) return {
|
|
150
|
-
config: { ...DEFAULT_CONFIG },
|
|
151
|
-
configPath: null
|
|
152
|
-
};
|
|
153
|
-
const config = result.config;
|
|
154
|
-
return {
|
|
155
|
-
config: {
|
|
156
|
-
...DEFAULT_CONFIG,
|
|
157
|
-
...config,
|
|
158
|
-
registries: {
|
|
159
|
-
...DEFAULT_CONFIG.registries,
|
|
160
|
-
...config.registries || {}
|
|
161
|
-
},
|
|
162
|
-
targetsByType: {
|
|
163
|
-
...DEFAULT_CONFIG.targetsByType,
|
|
164
|
-
...config.targetsByType || {}
|
|
165
|
-
},
|
|
166
|
-
aliases: {
|
|
167
|
-
...DEFAULT_CONFIG.aliases,
|
|
168
|
-
...config.aliases || {}
|
|
169
|
-
}
|
|
170
|
-
},
|
|
171
|
-
configPath: result.filepath
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
async function writeConfig(cwd, config, { overwrite = false } = {}) {
|
|
175
|
-
const filePath = getConfigPath(cwd);
|
|
176
|
-
let exists = false;
|
|
177
|
-
try {
|
|
178
|
-
await fsPromises.access(filePath);
|
|
179
|
-
exists = true;
|
|
180
|
-
} catch {}
|
|
181
|
-
if (exists && !overwrite) return {
|
|
182
|
-
filePath,
|
|
183
|
-
written: false
|
|
184
|
-
};
|
|
185
|
-
await fsPromises.writeFile(filePath, JSON.stringify(config, null, 2), "utf8");
|
|
186
|
-
return {
|
|
187
|
-
filePath,
|
|
188
|
-
written: true
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
function resolveRegistrySource(input, config) {
|
|
192
|
-
if (!input) return null;
|
|
193
|
-
if (config.registries[input]) return String(config.registries[input]);
|
|
194
|
-
return input;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
//#endregion
|
|
198
|
-
//#region src/domain/registryModel.ts
|
|
199
|
-
function asStringArray(value) {
|
|
200
|
-
if (!value) return [];
|
|
201
|
-
if (Array.isArray(value)) return value.filter((entry) => typeof entry === "string");
|
|
202
|
-
if (typeof value === "string") return [value];
|
|
203
|
-
return [];
|
|
204
|
-
}
|
|
205
|
-
function asObjectArray(value) {
|
|
206
|
-
if (!Array.isArray(value)) return [];
|
|
207
|
-
return value.filter((entry) => Boolean(entry && typeof entry === "object"));
|
|
208
|
-
}
|
|
209
|
-
function normalizeItem(rawItem, sourceMeta) {
|
|
210
|
-
const files = asObjectArray(rawItem.files).map((file) => ({
|
|
211
|
-
path: typeof file.path === "string" ? file.path : void 0,
|
|
212
|
-
target: typeof file.target === "string" ? file.target : void 0,
|
|
213
|
-
type: typeof file.type === "string" ? file.type : typeof rawItem.type === "string" ? rawItem.type : "registry:file",
|
|
214
|
-
content: typeof file.content === "string" ? file.content : void 0,
|
|
215
|
-
url: typeof file.url === "string" ? file.url : void 0
|
|
216
|
-
}));
|
|
217
|
-
const name = typeof rawItem.name === "string" ? rawItem.name : typeof rawItem.title === "string" ? rawItem.title : "unnamed-item";
|
|
218
|
-
return {
|
|
219
|
-
name,
|
|
220
|
-
title: typeof rawItem.title === "string" ? rawItem.title : name,
|
|
221
|
-
description: typeof rawItem.description === "string" ? rawItem.description : "",
|
|
222
|
-
type: typeof rawItem.type === "string" ? rawItem.type : "registry:file",
|
|
223
|
-
dependencies: asStringArray(rawItem.dependencies),
|
|
224
|
-
devDependencies: asStringArray(rawItem.devDependencies),
|
|
225
|
-
registryDependencies: asStringArray(rawItem.registryDependencies),
|
|
226
|
-
files,
|
|
227
|
-
sourceMeta
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
function extractItemReferences(payload) {
|
|
231
|
-
return asObjectArray(payload.items).map((entry) => {
|
|
232
|
-
if (Array.isArray(entry.files)) return null;
|
|
233
|
-
return typeof entry.url === "string" ? entry.url : typeof entry.href === "string" ? entry.href : typeof entry.path === "string" ? entry.path : null;
|
|
234
|
-
}).filter((value) => Boolean(value));
|
|
235
|
-
}
|
|
236
|
-
function normalizeManifestInline(data, sourceMeta) {
|
|
237
|
-
if (Array.isArray(data)) return ok(data.filter((entry) => Boolean(entry && typeof entry === "object")).map((entry) => normalizeItem(entry, sourceMeta)));
|
|
238
|
-
if (data && typeof data === "object" && Array.isArray(data.items)) return ok(asObjectArray(data.items).filter((entry) => Array.isArray(entry.files)).map((entry) => normalizeItem(entry, sourceMeta)));
|
|
239
|
-
if (data && typeof data === "object" && Array.isArray(data.files)) return ok([normalizeItem(data, sourceMeta)]);
|
|
240
|
-
return err(appError("RegistryError", "Unsupported manifest structure."));
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
//#endregion
|
|
244
|
-
//#region src/shell/registry.ts
|
|
245
|
-
function isHttpUrl(value) {
|
|
246
|
-
return /^https?:\/\//i.test(value);
|
|
247
|
-
}
|
|
248
|
-
function isFileUrl(value) {
|
|
249
|
-
return /^file:\/\//i.test(value);
|
|
250
|
-
}
|
|
251
|
-
function joinUrl(baseUrl, relativePath) {
|
|
252
|
-
return new URL(relativePath, baseUrl).toString();
|
|
253
|
-
}
|
|
254
|
-
async function normalizeManifest(data, sourceMeta, runtime) {
|
|
255
|
-
const inlineItemsRes = normalizeManifestInline(data, sourceMeta);
|
|
256
|
-
if (!data || typeof data !== "object" || Array.isArray(data)) return inlineItemsRes;
|
|
257
|
-
const references = extractItemReferences(data);
|
|
258
|
-
if (!references.length) return inlineItemsRes;
|
|
259
|
-
const inlineItems = inlineItemsRes.ok ? inlineItemsRes.value : [];
|
|
260
|
-
const resolvedItems = [];
|
|
261
|
-
for (const itemRef of references) {
|
|
262
|
-
let itemData;
|
|
263
|
-
if (isHttpUrl(itemRef)) {
|
|
264
|
-
const res = await runtime.http.getJson(itemRef);
|
|
265
|
-
if (!res.ok) return err(res.error);
|
|
266
|
-
itemData = res.value;
|
|
267
|
-
} else if (sourceMeta.type === "http" && sourceMeta.baseUrl) {
|
|
268
|
-
const res = await runtime.http.getJson(joinUrl(sourceMeta.baseUrl, itemRef));
|
|
269
|
-
if (!res.ok) return err(res.error);
|
|
270
|
-
itemData = res.value;
|
|
271
|
-
} else if ((sourceMeta.type === "file" || sourceMeta.type === "directory") && sourceMeta.baseDir) {
|
|
272
|
-
const res = await runtime.fs.readFile(path.resolve(sourceMeta.baseDir, itemRef), "utf8");
|
|
273
|
-
if (!res.ok) return err(res.error);
|
|
274
|
-
try {
|
|
275
|
-
itemData = JSON.parse(res.value);
|
|
276
|
-
} catch {
|
|
277
|
-
return err(appError("RegistryError", `Invalid JSON: ${itemRef}`));
|
|
278
|
-
}
|
|
279
|
-
} else {
|
|
280
|
-
const res = await runtime.fs.readFile(path.resolve(itemRef), "utf8");
|
|
281
|
-
if (!res.ok) return err(res.error);
|
|
282
|
-
try {
|
|
283
|
-
itemData = JSON.parse(res.value);
|
|
284
|
-
} catch {
|
|
285
|
-
return err(appError("RegistryError", `Invalid JSON: ${itemRef}`));
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (itemData && typeof itemData === "object") resolvedItems.push(normalizeItem(itemData, sourceMeta));
|
|
289
|
-
}
|
|
290
|
-
return ok([...inlineItems, ...resolvedItems]);
|
|
291
|
-
}
|
|
292
|
-
async function loadDirectoryRegistry(directoryPath, runtime) {
|
|
293
|
-
const absoluteDir = path.resolve(directoryPath);
|
|
294
|
-
const dirRes = await runtime.fs.readdir(absoluteDir);
|
|
295
|
-
if (!dirRes.ok) return err(dirRes.error);
|
|
296
|
-
const jsonFiles = dirRes.value.filter((file) => file.endsWith(".json"));
|
|
297
|
-
const items = [];
|
|
298
|
-
for (const fileName of jsonFiles) {
|
|
299
|
-
const fullPath = path.join(absoluteDir, fileName);
|
|
300
|
-
const readRes = await runtime.fs.readFile(fullPath, "utf8");
|
|
301
|
-
if (!readRes.ok) return err(readRes.error);
|
|
302
|
-
let parsed;
|
|
303
|
-
try {
|
|
304
|
-
parsed = JSON.parse(readRes.value);
|
|
305
|
-
} catch {
|
|
306
|
-
continue;
|
|
307
|
-
}
|
|
308
|
-
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.files)) continue;
|
|
309
|
-
items.push(normalizeItem(parsed, {
|
|
310
|
-
type: "directory",
|
|
311
|
-
baseDir: absoluteDir
|
|
312
|
-
}));
|
|
313
|
-
}
|
|
314
|
-
return ok(items);
|
|
315
|
-
}
|
|
316
|
-
async function loadRegistry(source, cwd, runtime) {
|
|
317
|
-
if (!source) return err(appError("ValidationError", "Registry source is required."));
|
|
318
|
-
const resolved = isHttpUrl(source) || isFileUrl(source) ? source : path.resolve(cwd, source);
|
|
319
|
-
if (isHttpUrl(resolved)) {
|
|
320
|
-
const dataRes = await runtime.http.getJson(resolved);
|
|
321
|
-
if (!dataRes.ok) return err(dataRes.error);
|
|
322
|
-
const baseUrl = resolved.endsWith("/") ? resolved : resolved.replace(/[^/]*$/, "");
|
|
323
|
-
const itemsRes = await normalizeManifest(dataRes.value, {
|
|
324
|
-
type: "http",
|
|
325
|
-
baseUrl
|
|
326
|
-
}, runtime);
|
|
327
|
-
if (!itemsRes.ok) return err(itemsRes.error);
|
|
328
|
-
return ok({
|
|
329
|
-
items: itemsRes.value,
|
|
330
|
-
source: resolved
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
const fileSystemPath = isFileUrl(resolved) ? fileURLToPath(new URL(resolved)) : path.resolve(resolved);
|
|
334
|
-
const statsRes = await runtime.fs.stat(fileSystemPath);
|
|
335
|
-
if (!statsRes.ok) return err(appError("RegistryError", `Registry source not found: ${source}`));
|
|
336
|
-
if (statsRes.value.isDirectory()) {
|
|
337
|
-
const itemsRes = await loadDirectoryRegistry(fileSystemPath, runtime);
|
|
338
|
-
if (!itemsRes.ok) return err(itemsRes.error);
|
|
339
|
-
return ok({
|
|
340
|
-
items: itemsRes.value,
|
|
341
|
-
source: fileSystemPath
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
const readRes = await runtime.fs.readFile(fileSystemPath, "utf8");
|
|
345
|
-
if (!readRes.ok) return err(readRes.error);
|
|
346
|
-
let parsed;
|
|
347
|
-
try {
|
|
348
|
-
parsed = JSON.parse(readRes.value);
|
|
349
|
-
} catch (cause) {
|
|
350
|
-
return err(appError("RegistryError", "Failed to parse registry JSON.", cause));
|
|
351
|
-
}
|
|
352
|
-
const itemsRes = await normalizeManifest(parsed, {
|
|
353
|
-
type: "file",
|
|
354
|
-
baseDir: path.dirname(fileSystemPath)
|
|
355
|
-
}, runtime);
|
|
356
|
-
if (!itemsRes.ok) return err(itemsRes.error);
|
|
357
|
-
return ok({
|
|
358
|
-
items: itemsRes.value,
|
|
359
|
-
source: fileSystemPath
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
async function resolveFileContent(file, item, cwd, runtime) {
|
|
363
|
-
if (typeof file.content === "string") return ok(file.content);
|
|
364
|
-
const targetPathOrUrl = file.url || file.path;
|
|
365
|
-
if (!targetPathOrUrl) return err(appError("ValidationError", `File entry in "${item.name}" is missing both content and path/url.`));
|
|
366
|
-
if (isHttpUrl(targetPathOrUrl)) return await runtime.http.getText(targetPathOrUrl);
|
|
367
|
-
if (item.sourceMeta.type === "http" && item.sourceMeta.baseUrl) {
|
|
368
|
-
const remoteUrl = joinUrl(item.sourceMeta.baseUrl, targetPathOrUrl);
|
|
369
|
-
return await runtime.http.getText(remoteUrl);
|
|
370
|
-
}
|
|
371
|
-
const localPath = item.sourceMeta.baseDir && !path.isAbsolute(targetPathOrUrl) ? path.resolve(item.sourceMeta.baseDir, targetPathOrUrl) : path.resolve(cwd, targetPathOrUrl);
|
|
372
|
-
return await runtime.fs.readFile(localPath, "utf8");
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
//#endregion
|
|
376
|
-
//#region src/shell/packageManagers/strategy.ts
|
|
377
|
-
function buildNpmCommands(dependencies, devDependencies) {
|
|
378
|
-
const commands = [];
|
|
379
|
-
if (dependencies.length) commands.push({
|
|
380
|
-
command: "npm",
|
|
381
|
-
args: ["install", ...dependencies]
|
|
382
|
-
});
|
|
383
|
-
if (devDependencies.length) commands.push({
|
|
384
|
-
command: "npm",
|
|
385
|
-
args: [
|
|
386
|
-
"install",
|
|
387
|
-
"-D",
|
|
388
|
-
...devDependencies
|
|
389
|
-
]
|
|
390
|
-
});
|
|
391
|
-
return commands;
|
|
392
|
-
}
|
|
393
|
-
function buildYarnCommands(dependencies, devDependencies) {
|
|
394
|
-
const commands = [];
|
|
395
|
-
if (dependencies.length) commands.push({
|
|
396
|
-
command: "yarn",
|
|
397
|
-
args: ["add", ...dependencies]
|
|
398
|
-
});
|
|
399
|
-
if (devDependencies.length) commands.push({
|
|
400
|
-
command: "yarn",
|
|
401
|
-
args: [
|
|
402
|
-
"add",
|
|
403
|
-
"-D",
|
|
404
|
-
...devDependencies
|
|
405
|
-
]
|
|
406
|
-
});
|
|
407
|
-
return commands;
|
|
408
|
-
}
|
|
409
|
-
function buildPnpmCommands(dependencies, devDependencies) {
|
|
410
|
-
const commands = [];
|
|
411
|
-
if (dependencies.length) commands.push({
|
|
412
|
-
command: "pnpm",
|
|
413
|
-
args: ["add", ...dependencies]
|
|
414
|
-
});
|
|
415
|
-
if (devDependencies.length) commands.push({
|
|
416
|
-
command: "pnpm",
|
|
417
|
-
args: [
|
|
418
|
-
"add",
|
|
419
|
-
"-D",
|
|
420
|
-
...devDependencies
|
|
421
|
-
]
|
|
422
|
-
});
|
|
423
|
-
return commands;
|
|
424
|
-
}
|
|
425
|
-
const strategyMap = {
|
|
426
|
-
npm: {
|
|
427
|
-
manager: "npm",
|
|
428
|
-
buildInstallCommands: buildNpmCommands
|
|
429
|
-
},
|
|
430
|
-
yarn: {
|
|
431
|
-
manager: "yarn",
|
|
432
|
-
buildInstallCommands: buildYarnCommands
|
|
433
|
-
},
|
|
434
|
-
pnpm: {
|
|
435
|
-
manager: "pnpm",
|
|
436
|
-
buildInstallCommands: buildPnpmCommands
|
|
437
|
-
}
|
|
438
|
-
};
|
|
439
|
-
function getPackageManagerStrategy(manager) {
|
|
440
|
-
return strategyMap[manager];
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
//#endregion
|
|
444
|
-
//#region src/shell/installer.ts
|
|
445
|
-
function unique(values) {
|
|
446
|
-
return [...new Set(values.filter(Boolean))];
|
|
447
|
-
}
|
|
448
|
-
function collectMissingDependencies(items, cwd, runtime) {
|
|
449
|
-
const packageJsonPath = path.join(cwd, "package.json");
|
|
450
|
-
if (!runtime.fs.existsSync(packageJsonPath)) return {
|
|
451
|
-
missingDependencies: [],
|
|
452
|
-
missingDevDependencies: []
|
|
453
|
-
};
|
|
454
|
-
const packageJsonResult = runtime.fs.readJsonSync(packageJsonPath);
|
|
455
|
-
const packageJson = packageJsonResult.ok ? packageJsonResult.value : {};
|
|
456
|
-
const declared = {
|
|
457
|
-
...packageJson.dependencies || {},
|
|
458
|
-
...packageJson.devDependencies || {},
|
|
459
|
-
...packageJson.peerDependencies || {}
|
|
460
|
-
};
|
|
461
|
-
const allDeps = unique(items.flatMap((item) => item.dependencies || []));
|
|
462
|
-
const allDevDeps = unique(items.flatMap((item) => item.devDependencies || []));
|
|
463
|
-
return {
|
|
464
|
-
missingDependencies: allDeps.filter((dep) => !declared[dep]),
|
|
465
|
-
missingDevDependencies: allDevDeps.filter((dep) => !declared[dep])
|
|
466
|
-
};
|
|
467
|
-
}
|
|
468
|
-
function installDependencies(cwd, packageManager, dependencies, devDependencies, runtime) {
|
|
469
|
-
if (!dependencies.length && !devDependencies.length) return ok(void 0);
|
|
470
|
-
const commands = getPackageManagerStrategy(packageManager).buildInstallCommands(dependencies, devDependencies);
|
|
471
|
-
for (const command of commands) if (runtime.process.run(command.command, command.args, cwd).status !== 0) return err(appError("InstallError", `Dependency install failed: ${command.command} ${command.args.join(" ")}`));
|
|
472
|
-
return ok(void 0);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
//#endregion
|
|
476
|
-
//#region src/shell/packageManagers/resolver.ts
|
|
477
|
-
function resolvePackageManager(cwd, configured, runtime) {
|
|
478
|
-
if (configured && configured !== "auto") return configured;
|
|
479
|
-
if (runtime.fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
480
|
-
if (runtime.fs.existsSync(path.join(cwd, "yarn.lock"))) return "yarn";
|
|
481
|
-
if (runtime.fs.existsSync(path.join(cwd, "package-lock.json"))) return "npm";
|
|
482
|
-
return "npm";
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
//#endregion
|
|
486
|
-
//#region src/shell/lockfile.ts
|
|
487
|
-
const LOCKFILE_NAME = "regpick-lock.json";
|
|
488
|
-
function getLockfilePath(cwd) {
|
|
489
|
-
return path.join(cwd, LOCKFILE_NAME);
|
|
490
|
-
}
|
|
491
|
-
async function readLockfile(cwd, runtime) {
|
|
492
|
-
const lockfilePath = getLockfilePath(cwd);
|
|
493
|
-
if (!await runtime.fs.pathExists(lockfilePath)) return { components: {} };
|
|
494
|
-
const readRes = runtime.fs.readJsonSync(lockfilePath);
|
|
495
|
-
if (!readRes.ok) return { components: {} };
|
|
496
|
-
return readRes.value;
|
|
497
|
-
}
|
|
498
|
-
async function writeLockfile(cwd, lockfile, runtime) {
|
|
499
|
-
const lockfilePath = getLockfilePath(cwd);
|
|
500
|
-
await runtime.fs.writeJson(lockfilePath, lockfile, { spaces: 2 });
|
|
501
|
-
}
|
|
502
|
-
function computeHash(content) {
|
|
503
|
-
return crypto.createHash("sha256").update(content).digest("hex");
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
//#endregion
|
|
507
|
-
//#region src/domain/aliasCore.ts
|
|
508
|
-
function applyAliases(content, config) {
|
|
509
|
-
let result = content;
|
|
510
|
-
for (const [oldAlias, newAlias] of Object.entries(config.aliases || {})) {
|
|
511
|
-
const regex = new RegExp(`from ["']${oldAlias}(.*?)["']`, "g");
|
|
512
|
-
result = result.replace(regex, `from "${newAlias}$1"`);
|
|
513
|
-
const dynRegex = new RegExp(`import\\(["']${oldAlias}(.*?)["']\\)`, "g");
|
|
514
|
-
result = result.replace(dynRegex, `import("${newAlias}$1")`);
|
|
515
|
-
}
|
|
516
|
-
return result;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
//#endregion
|
|
520
|
-
//#region src/commands/add.ts
|
|
521
|
-
async function promptForSource(context, config, positionals) {
|
|
522
|
-
const argValue = positionals[1];
|
|
523
|
-
if (argValue) return ok(resolveRegistrySource(argValue, config));
|
|
524
|
-
const aliases = Object.entries(config.registries || {}).map(([alias, value]) => ({
|
|
525
|
-
label: `${alias} -> ${value}`,
|
|
526
|
-
value: alias
|
|
527
|
-
}));
|
|
528
|
-
if (aliases.length) {
|
|
529
|
-
const picked = await context.runtime.prompt.multiselect({
|
|
530
|
-
message: "Pick registry alias (or cancel and provide URL/path manually)",
|
|
531
|
-
options: aliases,
|
|
532
|
-
maxItems: 1,
|
|
533
|
-
required: false
|
|
534
|
-
});
|
|
535
|
-
if (context.runtime.prompt.isCancel(picked)) return err(appError("UserCancelled", "Operation cancelled."));
|
|
536
|
-
if (Array.isArray(picked) && picked.length > 0) return ok(resolveRegistrySource(String(picked[0]), config));
|
|
537
|
-
}
|
|
538
|
-
const manual = await context.runtime.prompt.text({
|
|
539
|
-
message: "Registry URL/path:",
|
|
540
|
-
placeholder: "https://example.com/registry.json"
|
|
541
|
-
});
|
|
542
|
-
if (context.runtime.prompt.isCancel(manual)) return err(appError("UserCancelled", "Operation cancelled."));
|
|
543
|
-
return ok(String(manual));
|
|
544
|
-
}
|
|
545
|
-
function mapOptions(items) {
|
|
546
|
-
return items.map((item) => ({
|
|
547
|
-
value: item.name,
|
|
548
|
-
label: `${item.name} (${item.type || "registry:file"})`,
|
|
549
|
-
hint: item.description || `${item.files.length} file(s)`
|
|
550
|
-
}));
|
|
551
|
-
}
|
|
552
|
-
async function promptForItems(context, items) {
|
|
553
|
-
if (!items.length) return ok([]);
|
|
554
|
-
const selectedNames = await context.runtime.prompt.autocompleteMultiselect({
|
|
555
|
-
message: "Select items to install",
|
|
556
|
-
options: mapOptions(items),
|
|
557
|
-
maxItems: 10,
|
|
558
|
-
required: true
|
|
559
|
-
});
|
|
560
|
-
if (context.runtime.prompt.isCancel(selectedNames)) return err(appError("UserCancelled", "Operation cancelled."));
|
|
561
|
-
const selectedValues = Array.isArray(selectedNames) ? selectedNames : [];
|
|
562
|
-
const selectedSet = new Set(selectedValues.map((entry) => String(entry)));
|
|
563
|
-
return ok(items.filter((item) => selectedSet.has(item.name)));
|
|
564
|
-
}
|
|
565
|
-
async function runAddCommand(context) {
|
|
566
|
-
const assumeYes = Boolean(context.args.flags.yes);
|
|
567
|
-
const { config } = await readConfig(context.cwd);
|
|
568
|
-
const sourceResult = await promptForSource(context, config, context.args.positionals);
|
|
569
|
-
if (!sourceResult.ok) return sourceResult;
|
|
570
|
-
const source = sourceResult.value;
|
|
571
|
-
if (!source) return ok({
|
|
572
|
-
kind: "noop",
|
|
573
|
-
message: "No registry source provided."
|
|
574
|
-
});
|
|
575
|
-
const registryResult = await loadRegistry(source, context.cwd, context.runtime);
|
|
576
|
-
if (!registryResult.ok) return registryResult;
|
|
577
|
-
const { items } = registryResult.value;
|
|
578
|
-
if (!items.length) {
|
|
579
|
-
context.runtime.prompt.warn("No installable items in registry.");
|
|
580
|
-
return ok({
|
|
581
|
-
kind: "noop",
|
|
582
|
-
message: "No installable items in registry."
|
|
583
|
-
});
|
|
584
|
-
}
|
|
585
|
-
const preselected = selectItemsFromFlags(items, context);
|
|
586
|
-
const promptedSelectionResult = preselected.ok && preselected.value ? preselected : await promptForItems(context, items);
|
|
587
|
-
if (!promptedSelectionResult.ok) return promptedSelectionResult;
|
|
588
|
-
const selectedItems = promptedSelectionResult.value;
|
|
589
|
-
if (!selectedItems || !selectedItems.length) {
|
|
590
|
-
context.runtime.prompt.warn("No items selected.");
|
|
591
|
-
return ok({
|
|
592
|
-
kind: "noop",
|
|
593
|
-
message: "No items selected."
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
if (!assumeYes) {
|
|
597
|
-
const proceed = await context.runtime.prompt.confirm({
|
|
598
|
-
message: `Install ${selectedItems.length} item(s)?`,
|
|
599
|
-
initialValue: true
|
|
600
|
-
});
|
|
601
|
-
if (context.runtime.prompt.isCancel(proceed) || !proceed) return err(appError("UserCancelled", "Operation cancelled."));
|
|
602
|
-
}
|
|
603
|
-
const existingTargets = /* @__PURE__ */ new Set();
|
|
604
|
-
const installPlanProbeRes = buildInstallPlan(selectedItems, context.cwd, config);
|
|
605
|
-
if (!installPlanProbeRes.ok) return installPlanProbeRes;
|
|
606
|
-
const installPlanProbe = installPlanProbeRes.value;
|
|
607
|
-
for (const write of installPlanProbe.plannedWrites) if (await context.runtime.fs.pathExists(write.absoluteTarget)) existingTargets.add(write.absoluteTarget);
|
|
608
|
-
const installPlanRes = buildInstallPlan(selectedItems, context.cwd, config, existingTargets);
|
|
609
|
-
if (!installPlanRes.ok) return installPlanRes;
|
|
610
|
-
const installPlan = installPlanRes.value;
|
|
611
|
-
const finalWrites = [];
|
|
612
|
-
for (const write of installPlan.plannedWrites) if (existingTargets.has(write.absoluteTarget)) if (assumeYes || config.overwritePolicy === "overwrite") finalWrites.push(write);
|
|
613
|
-
else if (config.overwritePolicy === "skip") context.runtime.prompt.warn(`Skipped existing file: ${write.absoluteTarget}`);
|
|
614
|
-
else {
|
|
615
|
-
const answer = await context.runtime.prompt.select({
|
|
616
|
-
message: `File exists: ${write.absoluteTarget}`,
|
|
617
|
-
options: [
|
|
618
|
-
{
|
|
619
|
-
value: "overwrite",
|
|
620
|
-
label: "Overwrite this file"
|
|
621
|
-
},
|
|
622
|
-
{
|
|
623
|
-
value: "skip",
|
|
624
|
-
label: "Skip this file"
|
|
625
|
-
},
|
|
626
|
-
{
|
|
627
|
-
value: "abort",
|
|
628
|
-
label: "Abort installation"
|
|
629
|
-
}
|
|
630
|
-
]
|
|
631
|
-
});
|
|
632
|
-
if (context.runtime.prompt.isCancel(answer) || answer === "abort") return err(appError("UserCancelled", "Installation aborted by user."));
|
|
633
|
-
if (answer === "overwrite") finalWrites.push(write);
|
|
634
|
-
}
|
|
635
|
-
else finalWrites.push(write);
|
|
636
|
-
const { missingDependencies, missingDevDependencies } = collectMissingDependencies(selectedItems, context.cwd, context.runtime);
|
|
637
|
-
let shouldInstallDeps = false;
|
|
638
|
-
if (missingDependencies.length || missingDevDependencies.length) if (assumeYes) shouldInstallDeps = true;
|
|
639
|
-
else {
|
|
640
|
-
const packageManager = resolvePackageManager(context.cwd, config.packageManager, context.runtime);
|
|
641
|
-
const messageParts = [];
|
|
642
|
-
if (missingDependencies.length) messageParts.push(`dependencies: ${missingDependencies.join(", ")}`);
|
|
643
|
-
if (missingDevDependencies.length) messageParts.push(`devDependencies: ${missingDevDependencies.join(", ")}`);
|
|
644
|
-
const proceed = await context.runtime.prompt.confirm({
|
|
645
|
-
message: `Install missing packages with ${packageManager}? (${messageParts.join(" | ")})`,
|
|
646
|
-
initialValue: true
|
|
647
|
-
});
|
|
648
|
-
if (context.runtime.prompt.isCancel(proceed)) return err(appError("UserCancelled", "Dependency installation cancelled by user."));
|
|
649
|
-
shouldInstallDeps = Boolean(proceed);
|
|
650
|
-
if (!shouldInstallDeps) context.runtime.prompt.warn("Skipped dependency installation.");
|
|
651
|
-
}
|
|
652
|
-
let writtenFiles = 0;
|
|
653
|
-
const lockfile = await readLockfile(context.cwd, context.runtime);
|
|
654
|
-
const hashesAcc = {};
|
|
655
|
-
for (const write of finalWrites) {
|
|
656
|
-
const item = selectedItems.find((entry) => entry.name === write.itemName);
|
|
657
|
-
if (!item) continue;
|
|
658
|
-
const contentResult = await resolveFileContent(write.sourceFile, item, context.cwd, context.runtime);
|
|
659
|
-
if (!contentResult.ok) return contentResult;
|
|
660
|
-
let content = applyAliases(contentResult.value, config);
|
|
661
|
-
const ensureRes = await context.runtime.fs.ensureDir(path.dirname(write.absoluteTarget));
|
|
662
|
-
if (!ensureRes.ok) return ensureRes;
|
|
663
|
-
const writeRes = await context.runtime.fs.writeFile(write.absoluteTarget, content, "utf8");
|
|
664
|
-
if (!writeRes.ok) return writeRes;
|
|
665
|
-
const contentHash = computeHash(content);
|
|
666
|
-
if (!hashesAcc[item.name]) hashesAcc[item.name] = [];
|
|
667
|
-
hashesAcc[item.name].push(contentHash);
|
|
668
|
-
writtenFiles += 1;
|
|
669
|
-
context.runtime.prompt.success(`Wrote ${write.relativeTarget}`);
|
|
670
|
-
}
|
|
671
|
-
if (writtenFiles > 0) {
|
|
672
|
-
for (const [itemName, fileHashes] of Object.entries(hashesAcc)) {
|
|
673
|
-
const combinedHash = computeHash(fileHashes.sort().join(""));
|
|
674
|
-
lockfile.components[itemName] = {
|
|
675
|
-
source,
|
|
676
|
-
hash: combinedHash
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
await writeLockfile(context.cwd, lockfile, context.runtime);
|
|
680
|
-
}
|
|
681
|
-
if (shouldInstallDeps) {
|
|
682
|
-
const packageManager = resolvePackageManager(context.cwd, config.packageManager, context.runtime);
|
|
683
|
-
const depsRes = installDependencies(context.cwd, packageManager, missingDependencies, missingDevDependencies, context.runtime);
|
|
684
|
-
if (!depsRes.ok) return depsRes;
|
|
685
|
-
}
|
|
686
|
-
context.runtime.prompt.info(`Installed ${selectedItems.length} item(s), wrote ${writtenFiles} file(s).`);
|
|
687
|
-
return ok({
|
|
688
|
-
kind: "success",
|
|
689
|
-
message: `Installed ${selectedItems.length} item(s), wrote ${writtenFiles} file(s).`
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
//#endregion
|
|
694
|
-
//#region src/domain/initCore.ts
|
|
695
|
-
function decideInitAfterOverwritePrompt(isPromptCancelled, shouldOverwrite) {
|
|
696
|
-
if (isPromptCancelled) return "cancelled";
|
|
697
|
-
return shouldOverwrite ? "overwrite" : "keep";
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
//#endregion
|
|
701
|
-
//#region src/commands/init.ts
|
|
702
|
-
async function runInitCommand(context) {
|
|
703
|
-
const outputPath = getConfigPath(context.cwd);
|
|
704
|
-
const existsRes = await context.runtime.fs.stat(outputPath);
|
|
705
|
-
if (existsRes.ok) {
|
|
706
|
-
const shouldOverwrite = await context.runtime.prompt.confirm({
|
|
707
|
-
message: `${outputPath} already exists. Overwrite?`,
|
|
708
|
-
initialValue: false
|
|
709
|
-
});
|
|
710
|
-
const secondDecision = decideInitAfterOverwritePrompt(context.runtime.prompt.isCancel(shouldOverwrite), Boolean(shouldOverwrite));
|
|
711
|
-
if (secondDecision === "cancelled") return err(appError("UserCancelled", "Operation cancelled."));
|
|
712
|
-
if (secondDecision === "keep") {
|
|
713
|
-
context.runtime.prompt.info("Keeping existing configuration.");
|
|
714
|
-
return ok({
|
|
715
|
-
kind: "noop",
|
|
716
|
-
message: "Keeping existing configuration."
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
const { config: existingConfig } = await readConfig(context.cwd);
|
|
721
|
-
const packageManager = await context.runtime.prompt.select({
|
|
722
|
-
message: "Jakiego menedżera pakietów używasz?",
|
|
723
|
-
options: [
|
|
724
|
-
{
|
|
725
|
-
value: "auto",
|
|
726
|
-
label: "Auto (wykrywanie)"
|
|
727
|
-
},
|
|
728
|
-
{
|
|
729
|
-
value: "npm",
|
|
730
|
-
label: "npm"
|
|
731
|
-
},
|
|
732
|
-
{
|
|
733
|
-
value: "yarn",
|
|
734
|
-
label: "yarn"
|
|
735
|
-
},
|
|
736
|
-
{
|
|
737
|
-
value: "pnpm",
|
|
738
|
-
label: "pnpm"
|
|
739
|
-
}
|
|
740
|
-
]
|
|
741
|
-
});
|
|
742
|
-
if (context.runtime.prompt.isCancel(packageManager)) return err(appError("UserCancelled", "Operation cancelled."));
|
|
743
|
-
const componentsFolder = await context.runtime.prompt.text({
|
|
744
|
-
message: "W jakim folderze trzymasz komponenty UI?",
|
|
745
|
-
placeholder: "src/components/ui"
|
|
746
|
-
});
|
|
747
|
-
if (context.runtime.prompt.isCancel(componentsFolder)) return err(appError("UserCancelled", "Operation cancelled."));
|
|
748
|
-
const overwritePolicy = await context.runtime.prompt.select({
|
|
749
|
-
message: "Czy chcesz nadpisywać pliki automatycznie, czy wolisz być pytany?",
|
|
750
|
-
options: [
|
|
751
|
-
{
|
|
752
|
-
value: "prompt",
|
|
753
|
-
label: "Pytaj (prompt)"
|
|
754
|
-
},
|
|
755
|
-
{
|
|
756
|
-
value: "overwrite",
|
|
757
|
-
label: "Zawsze nadpisuj (overwrite)"
|
|
758
|
-
},
|
|
759
|
-
{
|
|
760
|
-
value: "skip",
|
|
761
|
-
label: "Pomijaj nadpisywanie (skip)"
|
|
762
|
-
}
|
|
763
|
-
]
|
|
764
|
-
});
|
|
765
|
-
if (context.runtime.prompt.isCancel(overwritePolicy)) return err(appError("UserCancelled", "Operation cancelled."));
|
|
766
|
-
const newConfig = {
|
|
767
|
-
...existingConfig,
|
|
768
|
-
packageManager: String(packageManager),
|
|
769
|
-
overwritePolicy: String(overwritePolicy),
|
|
770
|
-
targetsByType: {
|
|
771
|
-
...existingConfig.targetsByType,
|
|
772
|
-
"registry:component": String(componentsFolder || "src/components/ui"),
|
|
773
|
-
"registry:file": String(componentsFolder || "src/components/ui"),
|
|
774
|
-
"registry:icon": `${String(componentsFolder || "src/components/ui")}/icons`
|
|
775
|
-
}
|
|
776
|
-
};
|
|
777
|
-
await writeConfig(context.cwd, newConfig, { overwrite: true });
|
|
778
|
-
context.runtime.prompt.success(`${existsRes.ok ? "Overwrote" : "Created"} ${outputPath}`);
|
|
779
|
-
return ok({
|
|
780
|
-
kind: "success",
|
|
781
|
-
message: `${existsRes.ok ? "Overwrote" : "Created"} ${outputPath}`
|
|
782
|
-
});
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
//#endregion
|
|
786
|
-
//#region src/domain/listCore.ts
|
|
787
|
-
function resolveRegistrySourceFromAliases(input, registries) {
|
|
788
|
-
if (!input) return null;
|
|
789
|
-
return registries[input] ? String(registries[input]) : input;
|
|
790
|
-
}
|
|
791
|
-
function resolveListSourceDecision(providedInput, registries) {
|
|
792
|
-
const fromInput = resolveRegistrySourceFromAliases(providedInput, registries);
|
|
793
|
-
if (fromInput) return {
|
|
794
|
-
source: fromInput,
|
|
795
|
-
requiresPrompt: false
|
|
796
|
-
};
|
|
797
|
-
const defaultAlias = Object.keys(registries)[0];
|
|
798
|
-
if (defaultAlias) return {
|
|
799
|
-
source: resolveRegistrySourceFromAliases(defaultAlias, registries),
|
|
800
|
-
requiresPrompt: false
|
|
801
|
-
};
|
|
802
|
-
return {
|
|
803
|
-
source: null,
|
|
804
|
-
requiresPrompt: true
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
//#endregion
|
|
809
|
-
//#region src/commands/list.ts
|
|
810
|
-
function formatItemLabel(item) {
|
|
811
|
-
const type = item.type || "registry:file";
|
|
812
|
-
const fileCount = Array.isArray(item.files) ? item.files.length : 0;
|
|
813
|
-
return `${item.name} (${type}, files: ${fileCount})`;
|
|
814
|
-
}
|
|
815
|
-
async function runListCommand(context) {
|
|
816
|
-
const { config } = await readConfig(context.cwd);
|
|
817
|
-
const sourceDecision = resolveListSourceDecision(context.args.positionals[1], config.registries);
|
|
818
|
-
let source = sourceDecision.source;
|
|
819
|
-
if (sourceDecision.requiresPrompt) {
|
|
820
|
-
const response = await context.runtime.prompt.text({
|
|
821
|
-
message: "Registry URL/path:",
|
|
822
|
-
placeholder: "https://example.com/registry.json"
|
|
823
|
-
});
|
|
824
|
-
if (context.runtime.prompt.isCancel(response)) return err(appError("UserCancelled", "Operation cancelled."));
|
|
825
|
-
source = String(response);
|
|
826
|
-
}
|
|
827
|
-
if (!source) return ok({
|
|
828
|
-
kind: "noop",
|
|
829
|
-
message: "No registry source provided."
|
|
830
|
-
});
|
|
831
|
-
const registryResult = await loadRegistry(source, context.cwd, context.runtime);
|
|
832
|
-
if (!registryResult.ok) return registryResult;
|
|
833
|
-
const { items } = registryResult.value;
|
|
834
|
-
if (!items.length) {
|
|
835
|
-
context.runtime.prompt.warn("No items found in registry.");
|
|
836
|
-
return ok({
|
|
837
|
-
kind: "noop",
|
|
838
|
-
message: "No items found in registry."
|
|
839
|
-
});
|
|
840
|
-
}
|
|
841
|
-
context.runtime.prompt.info(`Found ${items.length} items.`);
|
|
842
|
-
for (const item of items) console.log(`- ${formatItemLabel(item)}`);
|
|
843
|
-
return ok({
|
|
844
|
-
kind: "success",
|
|
845
|
-
message: `Listed ${items.length} item(s).`
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
//#endregion
|
|
850
|
-
//#region src/commands/update.ts
|
|
851
|
-
function printDiff(oldContent, newContent) {
|
|
852
|
-
const changes = diff.diffLines(oldContent, newContent);
|
|
853
|
-
for (const part of changes) {
|
|
854
|
-
const color = part.added ? pc.green : part.removed ? pc.red : pc.gray;
|
|
855
|
-
const prefix = part.added ? "+ " : part.removed ? "- " : " ";
|
|
856
|
-
const lines = part.value.replace(/\n$/, "").split("\n");
|
|
857
|
-
for (const line of lines) console.log(color(`${prefix}${line}`));
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
async function runUpdateCommand(context) {
|
|
861
|
-
const lockfile = await readLockfile(context.cwd, context.runtime);
|
|
862
|
-
const componentNames = Object.keys(lockfile.components);
|
|
863
|
-
if (componentNames.length === 0) {
|
|
864
|
-
context.runtime.prompt.info("No components installed. Nothing to update.");
|
|
865
|
-
return ok({
|
|
866
|
-
kind: "noop",
|
|
867
|
-
message: "No components to update."
|
|
868
|
-
});
|
|
869
|
-
}
|
|
870
|
-
const { config } = await readConfig(context.cwd);
|
|
871
|
-
const bySource = {};
|
|
872
|
-
for (const name of componentNames) {
|
|
873
|
-
const source = lockfile.components[name].source;
|
|
874
|
-
if (source) {
|
|
875
|
-
if (!bySource[source]) bySource[source] = [];
|
|
876
|
-
bySource[source].push(name);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
let updatedCount = 0;
|
|
880
|
-
for (const [source, itemsToUpdate] of Object.entries(bySource)) {
|
|
881
|
-
const registryRes = await loadRegistry(source, context.cwd, context.runtime);
|
|
882
|
-
if (!registryRes.ok) {
|
|
883
|
-
context.runtime.prompt.warn(`Failed to load registry ${source}`);
|
|
884
|
-
continue;
|
|
885
|
-
}
|
|
886
|
-
const registryItems = registryRes.value.items;
|
|
887
|
-
for (const itemName of itemsToUpdate) {
|
|
888
|
-
const registryItem = registryItems.find((i) => i.name === itemName);
|
|
889
|
-
if (!registryItem) continue;
|
|
890
|
-
const remoteContents = [];
|
|
891
|
-
const remoteFiles = [];
|
|
892
|
-
for (const file of registryItem.files) {
|
|
893
|
-
const contentRes = await resolveFileContent(file, registryItem, context.cwd, context.runtime);
|
|
894
|
-
if (!contentRes.ok) continue;
|
|
895
|
-
let content = applyAliases(contentRes.value, config);
|
|
896
|
-
remoteContents.push(content);
|
|
897
|
-
const outputRes = resolveOutputPathFromPolicy(registryItem, file, context.cwd, config);
|
|
898
|
-
if (outputRes.ok) remoteFiles.push({
|
|
899
|
-
target: outputRes.value.absoluteTarget,
|
|
900
|
-
content
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
const newHash = computeHash(remoteContents.sort().join(""));
|
|
904
|
-
if (newHash !== lockfile.components[itemName].hash) {
|
|
905
|
-
context.runtime.prompt.info(`Update available for ${itemName}`);
|
|
906
|
-
const action = await context.runtime.prompt.select({
|
|
907
|
-
message: `What do you want to do with ${itemName}?`,
|
|
908
|
-
options: [
|
|
909
|
-
{
|
|
910
|
-
value: "diff",
|
|
911
|
-
label: "Show diff"
|
|
912
|
-
},
|
|
913
|
-
{
|
|
914
|
-
value: "update",
|
|
915
|
-
label: "Update"
|
|
916
|
-
},
|
|
917
|
-
{
|
|
918
|
-
value: "skip",
|
|
919
|
-
label: "Skip"
|
|
920
|
-
}
|
|
921
|
-
]
|
|
922
|
-
});
|
|
923
|
-
if (context.runtime.prompt.isCancel(action) || action === "skip") continue;
|
|
924
|
-
if (action === "diff") {
|
|
925
|
-
for (const rf of remoteFiles) {
|
|
926
|
-
const localContentRes = await context.runtime.fs.readFile(rf.target, "utf8");
|
|
927
|
-
const localContent = localContentRes.ok ? localContentRes.value : "";
|
|
928
|
-
console.log(pc.bold(`\nDiff for ${rf.target}:`));
|
|
929
|
-
printDiff(localContent, rf.content);
|
|
930
|
-
}
|
|
931
|
-
const confirm = await context.runtime.prompt.confirm({
|
|
932
|
-
message: `Update ${itemName} now?`,
|
|
933
|
-
initialValue: true
|
|
934
|
-
});
|
|
935
|
-
if (context.runtime.prompt.isCancel(confirm) || !confirm) continue;
|
|
936
|
-
}
|
|
937
|
-
for (const rf of remoteFiles) {
|
|
938
|
-
const ensureRes = await context.runtime.fs.ensureDir(path.dirname(rf.target));
|
|
939
|
-
if (!ensureRes.ok) return ensureRes;
|
|
940
|
-
const writeRes = await context.runtime.fs.writeFile(rf.target, rf.content, "utf8");
|
|
941
|
-
if (!writeRes.ok) return writeRes;
|
|
942
|
-
}
|
|
943
|
-
lockfile.components[itemName].hash = newHash;
|
|
944
|
-
updatedCount++;
|
|
945
|
-
context.runtime.prompt.success(`Updated ${itemName}`);
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
if (updatedCount > 0) {
|
|
950
|
-
await writeLockfile(context.cwd, lockfile, context.runtime);
|
|
951
|
-
return ok({
|
|
952
|
-
kind: "success",
|
|
953
|
-
message: `Updated ${updatedCount} components.`
|
|
954
|
-
});
|
|
955
|
-
}
|
|
956
|
-
return ok({
|
|
957
|
-
kind: "noop",
|
|
958
|
-
message: "All components are up to date."
|
|
959
|
-
});
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
//#endregion
|
|
963
|
-
//#region src/domain/packCore.ts
|
|
964
|
-
function extractDependencies(content) {
|
|
965
|
-
const importRegex = /import\s+[\s\S]*?from\s+["']([^"']+)["']/g;
|
|
966
|
-
const dynamicImportRegex = /import\(["']([^"']+)["']\)/g;
|
|
967
|
-
const deps = /* @__PURE__ */ new Set();
|
|
968
|
-
let match;
|
|
969
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
970
|
-
const specifier = match[1];
|
|
971
|
-
if (!specifier.startsWith(".") && !specifier.startsWith("/") && !specifier.startsWith("~") && !specifier.startsWith("@/") && !specifier.startsWith("@\\")) {
|
|
972
|
-
const parts = specifier.split("/");
|
|
973
|
-
if (specifier.startsWith("@") && parts.length > 1) deps.add(`${parts[0]}/${parts[1]}`);
|
|
974
|
-
else deps.add(parts[0]);
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
while ((match = dynamicImportRegex.exec(content)) !== null) {
|
|
978
|
-
const specifier = match[1];
|
|
979
|
-
if (!specifier.startsWith(".") && !specifier.startsWith("/") && !specifier.startsWith("~") && !specifier.startsWith("@/") && !specifier.startsWith("@\\")) {
|
|
980
|
-
const parts = specifier.split("/");
|
|
981
|
-
if (specifier.startsWith("@") && parts.length > 1) deps.add(`${parts[0]}/${parts[1]}`);
|
|
982
|
-
else deps.add(parts[0]);
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
return Array.from(deps);
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
//#endregion
|
|
989
|
-
//#region src/commands/pack.ts
|
|
990
|
-
async function getFilesRecursive(dir, context) {
|
|
991
|
-
const result = [];
|
|
992
|
-
async function scan(currentDir) {
|
|
993
|
-
const dirRes = await context.runtime.fs.readdir(currentDir);
|
|
994
|
-
if (!dirRes.ok) return dirRes;
|
|
995
|
-
for (const file of dirRes.value) {
|
|
996
|
-
const fullPath = path.join(currentDir, file);
|
|
997
|
-
const statRes = await context.runtime.fs.stat(fullPath);
|
|
998
|
-
if (!statRes.ok) return statRes;
|
|
999
|
-
if (statRes.value.isDirectory()) {
|
|
1000
|
-
const scanRes = await scan(fullPath);
|
|
1001
|
-
if (!scanRes.ok) return scanRes;
|
|
1002
|
-
} else if (fullPath.endsWith(".ts") || fullPath.endsWith(".tsx")) result.push(fullPath);
|
|
1003
|
-
}
|
|
1004
|
-
return ok(void 0);
|
|
1005
|
-
}
|
|
1006
|
-
const scanRes = await scan(dir);
|
|
1007
|
-
if (!scanRes.ok) return err(scanRes.error);
|
|
1008
|
-
return ok(result);
|
|
1009
|
-
}
|
|
1010
|
-
async function runPackCommand(context) {
|
|
1011
|
-
const targetDirArg = context.args.positionals[1] || ".";
|
|
1012
|
-
const targetDir = path.resolve(context.cwd, targetDirArg);
|
|
1013
|
-
const statRes = await context.runtime.fs.stat(targetDir);
|
|
1014
|
-
if (!statRes.ok || !statRes.value.isDirectory()) return err(appError("ValidationError", `Target is not a directory: ${targetDir}`));
|
|
1015
|
-
context.runtime.prompt.info(`Scanning ${targetDir} for components...`);
|
|
1016
|
-
const filesRes = await getFilesRecursive(targetDir, context);
|
|
1017
|
-
if (!filesRes.ok) return filesRes;
|
|
1018
|
-
const files = filesRes.value;
|
|
1019
|
-
if (files.length === 0) {
|
|
1020
|
-
context.runtime.prompt.warn("No .ts or .tsx files found.");
|
|
1021
|
-
return ok({
|
|
1022
|
-
kind: "noop",
|
|
1023
|
-
message: "No files found."
|
|
1024
|
-
});
|
|
1025
|
-
}
|
|
1026
|
-
const items = [];
|
|
1027
|
-
for (const file of files) {
|
|
1028
|
-
const contentRes = await context.runtime.fs.readFile(file, "utf8");
|
|
1029
|
-
if (!contentRes.ok) return err(contentRes.error);
|
|
1030
|
-
const dependencies = extractDependencies(contentRes.value);
|
|
1031
|
-
const relativePath = path.relative(targetDir, file).replace(/\\/g, "/");
|
|
1032
|
-
const name = path.basename(file, path.extname(file));
|
|
1033
|
-
items.push({
|
|
1034
|
-
name,
|
|
1035
|
-
title: name,
|
|
1036
|
-
description: "Packed component",
|
|
1037
|
-
type: "registry:component",
|
|
1038
|
-
dependencies,
|
|
1039
|
-
devDependencies: [],
|
|
1040
|
-
registryDependencies: [],
|
|
1041
|
-
files: [{
|
|
1042
|
-
path: relativePath,
|
|
1043
|
-
type: "registry:component"
|
|
1044
|
-
}]
|
|
1045
|
-
});
|
|
1046
|
-
}
|
|
1047
|
-
const registry = { items };
|
|
1048
|
-
const outPath = path.join(context.cwd, "registry.json");
|
|
1049
|
-
const writeRes = await context.runtime.fs.writeJson(outPath, registry, { spaces: 2 });
|
|
1050
|
-
if (!writeRes.ok) return err(writeRes.error);
|
|
1051
|
-
context.runtime.prompt.success(`Packed ${items.length} components into registry.json`);
|
|
1052
|
-
return ok({
|
|
1053
|
-
kind: "success",
|
|
1054
|
-
message: `Generated registry.json`
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
//#endregion
|
|
1059
|
-
//#region src/shell/cli/args.ts
|
|
1060
|
-
function parseValue(rawValue) {
|
|
1061
|
-
if (rawValue === "true") return true;
|
|
1062
|
-
if (rawValue === "false") return false;
|
|
1063
|
-
return rawValue;
|
|
1064
|
-
}
|
|
1065
|
-
function parseCliArgs(argv) {
|
|
1066
|
-
const flags = {};
|
|
1067
|
-
const positionals = [];
|
|
1068
|
-
for (const token of argv) {
|
|
1069
|
-
if (!token.startsWith("--")) {
|
|
1070
|
-
positionals.push(token);
|
|
1071
|
-
continue;
|
|
1072
|
-
}
|
|
1073
|
-
const withoutPrefix = token.slice(2);
|
|
1074
|
-
const separatorIndex = withoutPrefix.indexOf("=");
|
|
1075
|
-
if (separatorIndex === -1) {
|
|
1076
|
-
flags[withoutPrefix] = true;
|
|
1077
|
-
continue;
|
|
1078
|
-
}
|
|
1079
|
-
const key = withoutPrefix.slice(0, separatorIndex);
|
|
1080
|
-
flags[key] = parseValue(withoutPrefix.slice(separatorIndex + 1));
|
|
1081
|
-
}
|
|
1082
|
-
return {
|
|
1083
|
-
flags,
|
|
1084
|
-
positionals
|
|
1085
|
-
};
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
//#endregion
|
|
1089
|
-
//#region src/shell/runtime/ports.ts
|
|
1090
|
-
const createRuntimePorts = (options) => ({
|
|
1091
|
-
fs: {
|
|
1092
|
-
existsSync: (path) => fs.existsSync(path),
|
|
1093
|
-
pathExists: async (path) => {
|
|
1094
|
-
try {
|
|
1095
|
-
await fsPromises.access(path);
|
|
1096
|
-
return true;
|
|
1097
|
-
} catch {
|
|
1098
|
-
return false;
|
|
1099
|
-
}
|
|
1100
|
-
},
|
|
1101
|
-
ensureDir: async (path) => {
|
|
1102
|
-
try {
|
|
1103
|
-
await fsPromises.mkdir(path, { recursive: true });
|
|
1104
|
-
return ok(void 0);
|
|
1105
|
-
} catch (cause) {
|
|
1106
|
-
return err(appError("RuntimeError", `Failed to ensure directory: ${path}`, cause));
|
|
1107
|
-
}
|
|
1108
|
-
},
|
|
1109
|
-
writeFile: async (path, content, encoding) => {
|
|
1110
|
-
try {
|
|
1111
|
-
await fsPromises.writeFile(path, content, encoding);
|
|
1112
|
-
return ok(void 0);
|
|
1113
|
-
} catch (cause) {
|
|
1114
|
-
return err(appError("RuntimeError", `Failed to write file: ${path}`, cause));
|
|
1115
|
-
}
|
|
1116
|
-
},
|
|
1117
|
-
readFile: async (path, encoding) => {
|
|
1118
|
-
try {
|
|
1119
|
-
return ok(await fsPromises.readFile(path, encoding));
|
|
1120
|
-
} catch (cause) {
|
|
1121
|
-
return err(appError("RuntimeError", `Failed to read file: ${path}`, cause));
|
|
1122
|
-
}
|
|
1123
|
-
},
|
|
1124
|
-
readJsonSync: (path) => {
|
|
1125
|
-
try {
|
|
1126
|
-
const content = fs.readFileSync(path, "utf8");
|
|
1127
|
-
return ok(JSON.parse(content));
|
|
1128
|
-
} catch (cause) {
|
|
1129
|
-
return err(appError("RuntimeError", `Failed to read JSON: ${path}`, cause));
|
|
1130
|
-
}
|
|
1131
|
-
},
|
|
1132
|
-
writeJson: async (path, value, opts) => {
|
|
1133
|
-
try {
|
|
1134
|
-
const content = JSON.stringify(value, null, opts?.spaces ?? 2);
|
|
1135
|
-
await fsPromises.writeFile(path, content, "utf8");
|
|
1136
|
-
return ok(void 0);
|
|
1137
|
-
} catch (cause) {
|
|
1138
|
-
return err(appError("RuntimeError", `Failed to write JSON: ${path}`, cause));
|
|
1139
|
-
}
|
|
1140
|
-
},
|
|
1141
|
-
stat: async (path) => {
|
|
1142
|
-
try {
|
|
1143
|
-
return ok(await fsPromises.stat(path));
|
|
1144
|
-
} catch (cause) {
|
|
1145
|
-
return err(appError("RuntimeError", `Failed to stat path: ${path}`, cause));
|
|
1146
|
-
}
|
|
1147
|
-
},
|
|
1148
|
-
readdir: async (path) => {
|
|
1149
|
-
try {
|
|
1150
|
-
return ok(await fsPromises.readdir(path));
|
|
1151
|
-
} catch (cause) {
|
|
1152
|
-
return err(appError("RuntimeError", `Failed to read directory: ${path}`, cause));
|
|
1153
|
-
}
|
|
1154
|
-
}
|
|
1155
|
-
},
|
|
1156
|
-
http: {
|
|
1157
|
-
getJson: async (url, timeoutMs = 15e3) => {
|
|
1158
|
-
try {
|
|
1159
|
-
const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
1160
|
-
if (!response.ok) return err(appError("RuntimeError", `HTTP error! status: ${response.status} when fetching JSON from: ${url}`));
|
|
1161
|
-
return ok(await response.json());
|
|
1162
|
-
} catch (cause) {
|
|
1163
|
-
return err(appError("RuntimeError", `Failed to fetch JSON from: ${url}`, cause));
|
|
1164
|
-
}
|
|
1165
|
-
},
|
|
1166
|
-
getText: async (url, timeoutMs = 15e3) => {
|
|
1167
|
-
try {
|
|
1168
|
-
const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
|
|
1169
|
-
if (!response.ok) return err(appError("RuntimeError", `HTTP error! status: ${response.status} when fetching text from: ${url}`));
|
|
1170
|
-
return ok(await response.text());
|
|
1171
|
-
} catch (cause) {
|
|
1172
|
-
return err(appError("RuntimeError", `Failed to fetch text from: ${url}`, cause));
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
},
|
|
1176
|
-
prompt: {
|
|
1177
|
-
intro: (message) => intro(message),
|
|
1178
|
-
outro: (message) => outro(message),
|
|
1179
|
-
cancel: (message) => cancel(message),
|
|
1180
|
-
isCancel: (value) => isCancel(value),
|
|
1181
|
-
info: (message) => log.info(message),
|
|
1182
|
-
warn: (message) => log.warn(message),
|
|
1183
|
-
error: (message) => log.error(message),
|
|
1184
|
-
success: (message) => log.success(message),
|
|
1185
|
-
text: (opts) => text({
|
|
1186
|
-
signal: options?.signal,
|
|
1187
|
-
...opts
|
|
1188
|
-
}),
|
|
1189
|
-
confirm: (opts) => confirm({
|
|
1190
|
-
signal: options?.signal,
|
|
1191
|
-
...opts
|
|
1192
|
-
}),
|
|
1193
|
-
select: (opts) => select({
|
|
1194
|
-
signal: options?.signal,
|
|
1195
|
-
...opts
|
|
1196
|
-
}),
|
|
1197
|
-
multiselect: (opts) => multiselect({
|
|
1198
|
-
signal: options?.signal,
|
|
1199
|
-
...opts
|
|
1200
|
-
}),
|
|
1201
|
-
autocompleteMultiselect: (opts) => autocompleteMultiselect({
|
|
1202
|
-
signal: options?.signal,
|
|
1203
|
-
...opts
|
|
1204
|
-
})
|
|
1205
|
-
},
|
|
1206
|
-
process: { run: (command, args, cwd) => spawnSync(command, args, {
|
|
1207
|
-
cwd,
|
|
1208
|
-
stdio: "inherit",
|
|
1209
|
-
shell: process.platform === "win32"
|
|
1210
|
-
}) }
|
|
1211
|
-
});
|
|
1212
|
-
const defaultRuntimePorts = createRuntimePorts();
|
|
1213
|
-
|
|
1214
|
-
//#endregion
|
|
1215
|
-
//#region src/index.ts
|
|
1216
|
-
function printHelp() {
|
|
1217
|
-
console.log(`
|
|
1
|
+
import e from"node:path";import t from"picocolors";import n from"node:fs/promises";import{cosmiconfig as r}from"cosmiconfig";import{fileURLToPath as i}from"node:url";import a from"node:crypto";import*as o from"diff";import s from"node:fs";import{autocompleteMultiselect as c,cancel as l,confirm as u,intro as d,isCancel as f,log as p,multiselect as m,outro as h,select as g,text as _}from"@clack/prompts";import{spawnSync as v}from"node:child_process";function y(e,t,n){return{kind:e,message:t,cause:n}}function b(e,t=`RuntimeError`){return typeof e==`object`&&e&&`kind`in e&&`message`in e&&typeof e.kind==`string`&&typeof e.message==`string`?e:e instanceof Error?y(t,e.message,e):y(t,String(e))}function x(e){return{ok:!0,value:e}}function S(e){return{ok:!1,error:e}}function C(e){return e.replace(/\\/g,`/`)}function w(t,n,r){let i=`${e.resolve(t)}${e.sep}`,a=e.resolve(n);return r?x(void 0):a!==e.resolve(t)&&!a.startsWith(i)?S(y(`ValidationError`,`Refusing to write outside project: ${a}`)):x(void 0)}function T(t,n,r,i){let a=n.type||t.type||`registry:file`,o=i.targetsByType?.[a],s=i.preferManifestTarget!==!1,c=e.basename(n.path||`${t.name}.txt`),l;l=s&&n.target?n.target:o?e.join(o,c):n.target?n.target:e.join(`src`,c);let u=e.resolve(r,l),d=w(r,u,!!i.allowOutsideProject);return d.ok?x({absoluteTarget:u,relativeTarget:C(e.relative(r,u))}):d}function E(e){return[...new Set(e.filter(Boolean))]}function D(e){return{dependencies:E(e.flatMap(e=>e.dependencies||[])),devDependencies:E(e.flatMap(e=>e.devDependencies||[]))}}function O(e,t,n,r=new Set){let i=[],a=[];for(let o of e)for(let e of o.files){let s=T(o,e,t,n);if(!s.ok)return s;let{absoluteTarget:c,relativeTarget:l}=s.value,u={itemName:o.name,sourceFile:e,absoluteTarget:c,relativeTarget:l};i.push(u),r.has(c)&&a.push(u)}return x({selectedItems:e,plannedWrites:i,dependencyPlan:D(e),conflicts:a})}function ee(e){return e?String(e).split(`,`).map(e=>e.trim()).filter(Boolean):[]}function te(e,t){let{flags:n}=t.args,r=ee(n.select);if(n.all)return x(e);if(r.length){let t=e.filter(e=>r.includes(e.name));return t.length?x(t):S(y(`ValidationError`,`No items matched --select=${String(n.select)}`))}return x(null)}const k={registries:{tebra:`./tebra-icon-registry/registry`},targetsByType:{"registry:icon":`src/components/ui/icons`,"registry:component":`src/components/ui`,"registry:file":`src/components/ui`},aliases:{},overwritePolicy:`prompt`,packageManager:`auto`,preferManifestTarget:!0,allowOutsideProject:!1};function A(t){return e.join(t,`regpick.json`)}async function j(e){let t=await r(`regpick`,{searchPlaces:[`regpick.json`,`.regpickrc`,`.regpickrc.json`]}).search(e);if(!t||!t.config)return{config:{...k},configPath:null};let n=t.config;return{config:{...k,...n,registries:{...k.registries,...n.registries||{}},targetsByType:{...k.targetsByType,...n.targetsByType||{}},aliases:{...k.aliases,...n.aliases||{}}},configPath:t.filepath}}async function ne(e,t,{overwrite:r=!1}={}){let i=A(e),a=!1;try{await n.access(i),a=!0}catch{}return a&&!r?{filePath:i,written:!1}:(await n.writeFile(i,JSON.stringify(t,null,2),`utf8`),{filePath:i,written:!0})}function M(e,t){return e?t.registries[e]?String(t.registries[e]):e:null}function N(e){return e?Array.isArray(e)?e.filter(e=>typeof e==`string`):typeof e==`string`?[e]:[]:[]}function P(e){return Array.isArray(e)?e.filter(e=>!!(e&&typeof e==`object`)):[]}function F(e,t){let n=P(e.files).map(t=>({path:typeof t.path==`string`?t.path:void 0,target:typeof t.target==`string`?t.target:void 0,type:typeof t.type==`string`?t.type:typeof e.type==`string`?e.type:`registry:file`,content:typeof t.content==`string`?t.content:void 0,url:typeof t.url==`string`?t.url:void 0})),r=typeof e.name==`string`?e.name:typeof e.title==`string`?e.title:`unnamed-item`;return{name:r,title:typeof e.title==`string`?e.title:r,description:typeof e.description==`string`?e.description:``,type:typeof e.type==`string`?e.type:`registry:file`,dependencies:N(e.dependencies),devDependencies:N(e.devDependencies),registryDependencies:N(e.registryDependencies),files:n,sourceMeta:t}}function re(e){return P(e.items).map(e=>Array.isArray(e.files)?null:typeof e.url==`string`?e.url:typeof e.href==`string`?e.href:typeof e.path==`string`?e.path:null).filter(e=>!!e)}function ie(e,t){return Array.isArray(e)?x(e.filter(e=>!!(e&&typeof e==`object`)).map(e=>F(e,t))):e&&typeof e==`object`&&Array.isArray(e.items)?x(P(e.items).filter(e=>Array.isArray(e.files)).map(e=>F(e,t))):e&&typeof e==`object`&&Array.isArray(e.files)?x([F(e,t)]):S(y(`RegistryError`,`Unsupported manifest structure.`))}function I(e){return/^https?:\/\//i.test(e)}function L(e){return/^file:\/\//i.test(e)}function R(e,t){return new URL(t,e).toString()}async function z(t,n,r){let i=ie(t,n);if(!t||typeof t!=`object`||Array.isArray(t))return i;let a=re(t);if(!a.length)return i;let o=i.ok?i.value:[],s=[];for(let t of a){let i;if(I(t)){let e=await r.http.getJson(t);if(!e.ok)return S(e.error);i=e.value}else if(n.type===`http`&&n.baseUrl){let e=await r.http.getJson(R(n.baseUrl,t));if(!e.ok)return S(e.error);i=e.value}else if((n.type===`file`||n.type===`directory`)&&n.baseDir){let a=await r.fs.readFile(e.resolve(n.baseDir,t),`utf8`);if(!a.ok)return S(a.error);try{i=JSON.parse(a.value)}catch{return S(y(`RegistryError`,`Invalid JSON: ${t}`))}}else{let n=await r.fs.readFile(e.resolve(t),`utf8`);if(!n.ok)return S(n.error);try{i=JSON.parse(n.value)}catch{return S(y(`RegistryError`,`Invalid JSON: ${t}`))}}i&&typeof i==`object`&&s.push(F(i,n))}return x([...o,...s])}async function ae(t,n){let r=e.resolve(t),i=await n.fs.readdir(r);if(!i.ok)return S(i.error);let a=i.value.filter(e=>e.endsWith(`.json`)),o=[];for(let t of a){let i=e.join(r,t),a=await n.fs.readFile(i,`utf8`);if(!a.ok)return S(a.error);let s;try{s=JSON.parse(a.value)}catch{continue}!s||typeof s!=`object`||!Array.isArray(s.files)||o.push(F(s,{type:`directory`,baseDir:r}))}return x(o)}async function B(t,n,r){if(!t)return S(y(`ValidationError`,`Registry source is required.`));let a=I(t)||L(t)?t:e.resolve(n,t);if(I(a)){let e=await r.http.getJson(a);if(!e.ok)return S(e.error);let t=a.endsWith(`/`)?a:a.replace(/[^/]*$/,``),n=await z(e.value,{type:`http`,baseUrl:t},r);return n.ok?x({items:n.value,source:a}):S(n.error)}let o=L(a)?i(new URL(a)):e.resolve(a),s=await r.fs.stat(o);if(!s.ok)return S(y(`RegistryError`,`Registry source not found: ${t}`));if(s.value.isDirectory()){let e=await ae(o,r);return e.ok?x({items:e.value,source:o}):S(e.error)}let c=await r.fs.readFile(o,`utf8`);if(!c.ok)return S(c.error);let l;try{l=JSON.parse(c.value)}catch(e){return S(y(`RegistryError`,`Failed to parse registry JSON.`,e))}let u=await z(l,{type:`file`,baseDir:e.dirname(o)},r);return u.ok?x({items:u.value,source:o}):S(u.error)}async function V(t,n,r,i){if(typeof t.content==`string`)return x(t.content);let a=t.url||t.path;if(!a)return S(y(`ValidationError`,`File entry in "${n.name}" is missing both content and path/url.`));if(I(a))return await i.http.getText(a);if(n.sourceMeta.type===`http`&&n.sourceMeta.baseUrl){let e=R(n.sourceMeta.baseUrl,a);return await i.http.getText(e)}let o=n.sourceMeta.baseDir&&!e.isAbsolute(a)?e.resolve(n.sourceMeta.baseDir,a):e.resolve(r,a);return await i.fs.readFile(o,`utf8`)}function oe(e,t){let n=[];return e.length&&n.push({command:`npm`,args:[`install`,...e]}),t.length&&n.push({command:`npm`,args:[`install`,`-D`,...t]}),n}function se(e,t){let n=[];return e.length&&n.push({command:`yarn`,args:[`add`,...e]}),t.length&&n.push({command:`yarn`,args:[`add`,`-D`,...t]}),n}function ce(e,t){let n=[];return e.length&&n.push({command:`pnpm`,args:[`add`,...e]}),t.length&&n.push({command:`pnpm`,args:[`add`,`-D`,...t]}),n}const le={npm:{manager:`npm`,buildInstallCommands:oe},yarn:{manager:`yarn`,buildInstallCommands:se},pnpm:{manager:`pnpm`,buildInstallCommands:ce}};function H(e){return le[e]}function U(e){return[...new Set(e.filter(Boolean))]}function ue(t,n,r){let i=e.join(n,`package.json`);if(!r.fs.existsSync(i))return{missingDependencies:[],missingDevDependencies:[]};let a=r.fs.readJsonSync(i),o=a.ok?a.value:{},s={...o.dependencies||{},...o.devDependencies||{},...o.peerDependencies||{}},c=U(t.flatMap(e=>e.dependencies||[])),l=U(t.flatMap(e=>e.devDependencies||[]));return{missingDependencies:c.filter(e=>!s[e]),missingDevDependencies:l.filter(e=>!s[e])}}function de(e,t,n,r,i){if(!n.length&&!r.length)return x(void 0);let a=H(t).buildInstallCommands(n,r);for(let t of a)if(i.process.run(t.command,t.args,e).status!==0)return S(y(`InstallError`,`Dependency install failed: ${t.command} ${t.args.join(` `)}`));return x(void 0)}function W(t,n,r){return n&&n!==`auto`?n:r.fs.existsSync(e.join(t,`pnpm-lock.yaml`))?`pnpm`:r.fs.existsSync(e.join(t,`yarn.lock`))?`yarn`:(r.fs.existsSync(e.join(t,`package-lock.json`)),`npm`)}function G(t){return e.join(t,`regpick-lock.json`)}async function K(e,t){let n=G(e);if(!await t.fs.pathExists(n))return{components:{}};let r=t.fs.readJsonSync(n);return r.ok?r.value:{components:{}}}async function q(e,t,n){let r=G(e);await n.fs.writeJson(r,t,{spaces:2})}function J(e){return a.createHash(`sha256`).update(e).digest(`hex`)}function Y(e,t){let n=e;for(let[e,r]of Object.entries(t.aliases||{})){let t=RegExp(`from ["']${e}(.*?)["']`,`g`);n=n.replace(t,`from "${r}$1"`);let i=RegExp(`import\\(["']${e}(.*?)["']\\)`,`g`);n=n.replace(i,`import("${r}$1")`)}return n}async function fe(e,t,n){let r=n[1];if(r)return x(M(r,t));let i=Object.entries(t.registries||{}).map(([e,t])=>({label:`${e} -> ${t}`,value:e}));if(i.length){let n=await e.runtime.prompt.multiselect({message:`Pick registry alias (or cancel and provide URL/path manually)`,options:i,maxItems:1,required:!1});if(e.runtime.prompt.isCancel(n))return S(y(`UserCancelled`,`Operation cancelled.`));if(Array.isArray(n)&&n.length>0)return x(M(String(n[0]),t))}let a=await e.runtime.prompt.text({message:`Registry URL/path:`,placeholder:`https://example.com/registry.json`});return e.runtime.prompt.isCancel(a)?S(y(`UserCancelled`,`Operation cancelled.`)):x(String(a))}function pe(e){return e.map(e=>({value:e.name,label:`${e.name} (${e.type||`registry:file`})`,hint:e.description||`${e.files.length} file(s)`}))}async function me(e,t){if(!t.length)return x([]);let n=await e.runtime.prompt.autocompleteMultiselect({message:`Select items to install`,options:pe(t),maxItems:10,required:!0});if(e.runtime.prompt.isCancel(n))return S(y(`UserCancelled`,`Operation cancelled.`));let r=Array.isArray(n)?n:[],i=new Set(r.map(e=>String(e)));return x(t.filter(e=>i.has(e.name)))}async function he(t){let n=!!t.args.flags.yes,{config:r}=await j(t.cwd),i=await fe(t,r,t.args.positionals);if(!i.ok)return i;let a=i.value;if(!a)return x({kind:`noop`,message:`No registry source provided.`});let o=await B(a,t.cwd,t.runtime);if(!o.ok)return o;let{items:s}=o.value;if(!s.length)return t.runtime.prompt.warn(`No installable items in registry.`),x({kind:`noop`,message:`No installable items in registry.`});let c=te(s,t),l=c.ok&&c.value?c:await me(t,s);if(!l.ok)return l;let u=l.value;if(!u||!u.length)return t.runtime.prompt.warn(`No items selected.`),x({kind:`noop`,message:`No items selected.`});if(!n){let e=await t.runtime.prompt.confirm({message:`Install ${u.length} item(s)?`,initialValue:!0});if(t.runtime.prompt.isCancel(e)||!e)return S(y(`UserCancelled`,`Operation cancelled.`))}let d=new Set,f=O(u,t.cwd,r);if(!f.ok)return f;let p=f.value;for(let e of p.plannedWrites)await t.runtime.fs.pathExists(e.absoluteTarget)&&d.add(e.absoluteTarget);let m=O(u,t.cwd,r,d);if(!m.ok)return m;let h=m.value,g=[];for(let e of h.plannedWrites)if(d.has(e.absoluteTarget))if(n||r.overwritePolicy===`overwrite`)g.push(e);else if(r.overwritePolicy===`skip`)t.runtime.prompt.warn(`Skipped existing file: ${e.absoluteTarget}`);else{let n=await t.runtime.prompt.select({message:`File exists: ${e.absoluteTarget}`,options:[{value:`overwrite`,label:`Overwrite this file`},{value:`skip`,label:`Skip this file`},{value:`abort`,label:`Abort installation`}]});if(t.runtime.prompt.isCancel(n)||n===`abort`)return S(y(`UserCancelled`,`Installation aborted by user.`));n===`overwrite`&&g.push(e)}else g.push(e);let{missingDependencies:_,missingDevDependencies:v}=ue(u,t.cwd,t.runtime),b=!1;if(_.length||v.length)if(n)b=!0;else{let e=W(t.cwd,r.packageManager,t.runtime),n=[];_.length&&n.push(`dependencies: ${_.join(`, `)}`),v.length&&n.push(`devDependencies: ${v.join(`, `)}`);let i=await t.runtime.prompt.confirm({message:`Install missing packages with ${e}? (${n.join(` | `)})`,initialValue:!0});if(t.runtime.prompt.isCancel(i))return S(y(`UserCancelled`,`Dependency installation cancelled by user.`));b=!!i,b||t.runtime.prompt.warn(`Skipped dependency installation.`)}let C=0,w=await K(t.cwd,t.runtime),T={};for(let n of g){let i=u.find(e=>e.name===n.itemName);if(!i)continue;let a=await V(n.sourceFile,i,t.cwd,t.runtime);if(!a.ok)return a;let o=Y(a.value,r),s=await t.runtime.fs.ensureDir(e.dirname(n.absoluteTarget));if(!s.ok)return s;let c=await t.runtime.fs.writeFile(n.absoluteTarget,o,`utf8`);if(!c.ok)return c;let l=J(o);T[i.name]||(T[i.name]=[]),T[i.name].push(l),C+=1,t.runtime.prompt.success(`Wrote ${n.relativeTarget}`)}if(C>0){for(let[e,t]of Object.entries(T)){let n=J(t.sort().join(``));w.components[e]={source:a,hash:n}}await q(t.cwd,w,t.runtime)}if(b){let e=W(t.cwd,r.packageManager,t.runtime),n=de(t.cwd,e,_,v,t.runtime);if(!n.ok)return n}return t.runtime.prompt.info(`Installed ${u.length} item(s), wrote ${C} file(s).`),x({kind:`success`,message:`Installed ${u.length} item(s), wrote ${C} file(s).`})}function ge(e,t){return e?`cancelled`:t?`overwrite`:`keep`}async function _e(e){let t=A(e.cwd),n=await e.runtime.fs.stat(t);if(n.ok){let n=await e.runtime.prompt.confirm({message:`${t} already exists. Overwrite?`,initialValue:!1}),r=ge(e.runtime.prompt.isCancel(n),!!n);if(r===`cancelled`)return S(y(`UserCancelled`,`Operation cancelled.`));if(r===`keep`)return e.runtime.prompt.info(`Keeping existing configuration.`),x({kind:`noop`,message:`Keeping existing configuration.`})}let{config:r}=await j(e.cwd),i=await e.runtime.prompt.select({message:`Jakiego menedżera pakietów używasz?`,options:[{value:`auto`,label:`Auto (wykrywanie)`},{value:`npm`,label:`npm`},{value:`yarn`,label:`yarn`},{value:`pnpm`,label:`pnpm`}]});if(e.runtime.prompt.isCancel(i))return S(y(`UserCancelled`,`Operation cancelled.`));let a=await e.runtime.prompt.text({message:`W jakim folderze trzymasz komponenty UI?`,placeholder:`src/components/ui`});if(e.runtime.prompt.isCancel(a))return S(y(`UserCancelled`,`Operation cancelled.`));let o=await e.runtime.prompt.select({message:`Czy chcesz nadpisywać pliki automatycznie, czy wolisz być pytany?`,options:[{value:`prompt`,label:`Pytaj (prompt)`},{value:`overwrite`,label:`Zawsze nadpisuj (overwrite)`},{value:`skip`,label:`Pomijaj nadpisywanie (skip)`}]});if(e.runtime.prompt.isCancel(o))return S(y(`UserCancelled`,`Operation cancelled.`));let s={...r,packageManager:String(i),overwritePolicy:String(o),targetsByType:{...r.targetsByType,"registry:component":String(a||`src/components/ui`),"registry:file":String(a||`src/components/ui`),"registry:icon":`${String(a||`src/components/ui`)}/icons`}};return await ne(e.cwd,s,{overwrite:!0}),e.runtime.prompt.success(`${n.ok?`Overwrote`:`Created`} ${t}`),x({kind:`success`,message:`${n.ok?`Overwrote`:`Created`} ${t}`})}function X(e,t){return e?t[e]?String(t[e]):e:null}function ve(e,t){let n=X(e,t);if(n)return{source:n,requiresPrompt:!1};let r=Object.keys(t)[0];return r?{source:X(r,t),requiresPrompt:!1}:{source:null,requiresPrompt:!0}}function ye(e){let t=e.type||`registry:file`,n=Array.isArray(e.files)?e.files.length:0;return`${e.name} (${t}, files: ${n})`}async function be(e){let{config:t}=await j(e.cwd),n=ve(e.args.positionals[1],t.registries),r=n.source;if(n.requiresPrompt){let t=await e.runtime.prompt.text({message:`Registry URL/path:`,placeholder:`https://example.com/registry.json`});if(e.runtime.prompt.isCancel(t))return S(y(`UserCancelled`,`Operation cancelled.`));r=String(t)}if(!r)return x({kind:`noop`,message:`No registry source provided.`});let i=await B(r,e.cwd,e.runtime);if(!i.ok)return i;let{items:a}=i.value;if(!a.length)return e.runtime.prompt.warn(`No items found in registry.`),x({kind:`noop`,message:`No items found in registry.`});e.runtime.prompt.info(`Found ${a.length} items.`);for(let e of a)console.log(`- ${ye(e)}`);return x({kind:`success`,message:`Listed ${a.length} item(s).`})}function xe(e,n){let r=o.diffLines(e,n);for(let e of r){let n=e.added?t.green:e.removed?t.red:t.gray,r=e.added?`+ `:e.removed?`- `:` `,i=e.value.replace(/\n$/,``).split(`
|
|
2
|
+
`);for(let e of i)console.log(n(`${r}${e}`))}}async function Se(n){let r=await K(n.cwd,n.runtime),i=Object.keys(r.components);if(i.length===0)return n.runtime.prompt.info(`No components installed. Nothing to update.`),x({kind:`noop`,message:`No components to update.`});let{config:a}=await j(n.cwd),o={};for(let e of i){let t=r.components[e].source;t&&(o[t]||(o[t]=[]),o[t].push(e))}let s=0;for(let[i,c]of Object.entries(o)){let o=await B(i,n.cwd,n.runtime);if(!o.ok){n.runtime.prompt.warn(`Failed to load registry ${i}`);continue}let l=o.value.items;for(let i of c){let o=l.find(e=>e.name===i);if(!o)continue;let c=[],u=[];for(let e of o.files){let t=await V(e,o,n.cwd,n.runtime);if(!t.ok)continue;let r=Y(t.value,a);c.push(r);let i=T(o,e,n.cwd,a);i.ok&&u.push({target:i.value.absoluteTarget,content:r})}let d=J(c.sort().join(``));if(d!==r.components[i].hash){n.runtime.prompt.info(`Update available for ${i}`);let a=await n.runtime.prompt.select({message:`What do you want to do with ${i}?`,options:[{value:`diff`,label:`Show diff`},{value:`update`,label:`Update`},{value:`skip`,label:`Skip`}]});if(n.runtime.prompt.isCancel(a)||a===`skip`)continue;if(a===`diff`){for(let e of u){let r=await n.runtime.fs.readFile(e.target,`utf8`),i=r.ok?r.value:``;console.log(t.bold(`\nDiff for ${e.target}:`)),xe(i,e.content)}let e=await n.runtime.prompt.confirm({message:`Update ${i} now?`,initialValue:!0});if(n.runtime.prompt.isCancel(e)||!e)continue}for(let t of u){let r=await n.runtime.fs.ensureDir(e.dirname(t.target));if(!r.ok)return r;let i=await n.runtime.fs.writeFile(t.target,t.content,`utf8`);if(!i.ok)return i}r.components[i].hash=d,s++,n.runtime.prompt.success(`Updated ${i}`)}}}return s>0?(await q(n.cwd,r,n.runtime),x({kind:`success`,message:`Updated ${s} components.`})):x({kind:`noop`,message:`All components are up to date.`})}function Ce(e){let t=/import\s+[\s\S]*?from\s+["']([^"']+)["']/g,n=/import\(["']([^"']+)["']\)/g,r=new Set,i;for(;(i=t.exec(e))!==null;){let e=i[1];if(!e.startsWith(`.`)&&!e.startsWith(`/`)&&!e.startsWith(`~`)&&!e.startsWith(`@/`)&&!e.startsWith(`@\\`)){let t=e.split(`/`);e.startsWith(`@`)&&t.length>1?r.add(`${t[0]}/${t[1]}`):r.add(t[0])}}for(;(i=n.exec(e))!==null;){let e=i[1];if(!e.startsWith(`.`)&&!e.startsWith(`/`)&&!e.startsWith(`~`)&&!e.startsWith(`@/`)&&!e.startsWith(`@\\`)){let t=e.split(`/`);e.startsWith(`@`)&&t.length>1?r.add(`${t[0]}/${t[1]}`):r.add(t[0])}}return Array.from(r)}async function we(t,n){let r=[];async function i(t){let a=await n.runtime.fs.readdir(t);if(!a.ok)return a;for(let o of a.value){let a=e.join(t,o),s=await n.runtime.fs.stat(a);if(!s.ok)return s;if(s.value.isDirectory()){let e=await i(a);if(!e.ok)return e}else (a.endsWith(`.ts`)||a.endsWith(`.tsx`))&&r.push(a)}return x(void 0)}let a=await i(t);return a.ok?x(r):S(a.error)}async function Z(t){let n=t.args.positionals[1]||`.`,r=e.resolve(t.cwd,n),i=await t.runtime.fs.stat(r);if(!i.ok||!i.value.isDirectory())return S(y(`ValidationError`,`Target is not a directory: ${r}`));t.runtime.prompt.info(`Scanning ${r} for components...`);let a=await we(r,t);if(!a.ok)return a;let o=a.value;if(o.length===0)return t.runtime.prompt.warn(`No .ts or .tsx files found.`),x({kind:`noop`,message:`No files found.`});let s=[];for(let n of o){let i=await t.runtime.fs.readFile(n,`utf8`);if(!i.ok)return S(i.error);let a=Ce(i.value),o=e.relative(r,n).replace(/\\/g,`/`),c=e.basename(n,e.extname(n));s.push({name:c,title:c,description:`Packed component`,type:`registry:component`,dependencies:a,devDependencies:[],registryDependencies:[],files:[{path:o,type:`registry:component`}]})}let c={items:s},l=e.join(t.cwd,`registry.json`),u=await t.runtime.fs.writeJson(l,c,{spaces:2});return u.ok?(t.runtime.prompt.success(`Packed ${s.length} components into registry.json`),x({kind:`success`,message:`Generated registry.json`})):S(u.error)}function Te(e){return e===`true`?!0:e===`false`?!1:e}function Ee(e){let t={},n=[];for(let r of e){if(!r.startsWith(`--`)){n.push(r);continue}let e=r.slice(2),i=e.indexOf(`=`);if(i===-1){t[e]=!0;continue}let a=e.slice(0,i);t[a]=Te(e.slice(i+1))}return{flags:t,positionals:n}}const De=e=>({fs:{existsSync:e=>s.existsSync(e),pathExists:async e=>{try{return await n.access(e),!0}catch{return!1}},ensureDir:async e=>{try{return await n.mkdir(e,{recursive:!0}),x(void 0)}catch(t){return S(y(`RuntimeError`,`Failed to ensure directory: ${e}`,t))}},writeFile:async(e,t,r)=>{try{return await n.writeFile(e,t,r),x(void 0)}catch(t){return S(y(`RuntimeError`,`Failed to write file: ${e}`,t))}},readFile:async(e,t)=>{try{return x(await n.readFile(e,t))}catch(t){return S(y(`RuntimeError`,`Failed to read file: ${e}`,t))}},readJsonSync:e=>{try{let t=s.readFileSync(e,`utf8`);return x(JSON.parse(t))}catch(t){return S(y(`RuntimeError`,`Failed to read JSON: ${e}`,t))}},writeJson:async(e,t,r)=>{try{let i=JSON.stringify(t,null,r?.spaces??2);return await n.writeFile(e,i,`utf8`),x(void 0)}catch(t){return S(y(`RuntimeError`,`Failed to write JSON: ${e}`,t))}},stat:async e=>{try{return x(await n.stat(e))}catch(t){return S(y(`RuntimeError`,`Failed to stat path: ${e}`,t))}},readdir:async e=>{try{return x(await n.readdir(e))}catch(t){return S(y(`RuntimeError`,`Failed to read directory: ${e}`,t))}}},http:{getJson:async(e,t=15e3)=>{try{let n=await fetch(e,{signal:AbortSignal.timeout(t)});return n.ok?x(await n.json()):S(y(`RuntimeError`,`HTTP error! status: ${n.status} when fetching JSON from: ${e}`))}catch(t){return S(y(`RuntimeError`,`Failed to fetch JSON from: ${e}`,t))}},getText:async(e,t=15e3)=>{try{let n=await fetch(e,{signal:AbortSignal.timeout(t)});return n.ok?x(await n.text()):S(y(`RuntimeError`,`HTTP error! status: ${n.status} when fetching text from: ${e}`))}catch(t){return S(y(`RuntimeError`,`Failed to fetch text from: ${e}`,t))}}},prompt:{intro:e=>d(e),outro:e=>h(e),cancel:e=>l(e),isCancel:e=>f(e),info:e=>p.info(e),warn:e=>p.warn(e),error:e=>p.error(e),success:e=>p.success(e),text:t=>_({signal:e?.signal,...t}),confirm:t=>u({signal:e?.signal,...t}),select:t=>g({signal:e?.signal,...t}),multiselect:t=>m({signal:e?.signal,...t}),autocompleteMultiselect:t=>c({signal:e?.signal,...t})},process:{run:(e,t,n)=>v(e,t,{cwd:n,stdio:`inherit`,shell:process.platform===`win32`})}});function Q(){console.log(`
|
|
1218
3
|
Usage:
|
|
1219
4
|
regpick init
|
|
1220
5
|
regpick list [registry-name-or-url]
|
|
@@ -1228,70 +13,4 @@ Options:
|
|
|
1228
13
|
--select=a,b,c Select explicit item names in add flow
|
|
1229
14
|
--yes Skip confirmation prompts where safe
|
|
1230
15
|
--help Show this help
|
|
1231
|
-
`);
|
|
1232
|
-
}
|
|
1233
|
-
async function run() {
|
|
1234
|
-
const abortController = new AbortController();
|
|
1235
|
-
const handleTerminate = (err) => {
|
|
1236
|
-
if (!abortController.signal.aborted) abortController.abort(err);
|
|
1237
|
-
if (err instanceof Error) console.error(pc.red(`\n[Fatal Error] ${err.message}`));
|
|
1238
|
-
process.exit(1);
|
|
1239
|
-
};
|
|
1240
|
-
process.on("SIGINT", () => handleTerminate());
|
|
1241
|
-
process.on("SIGTERM", () => handleTerminate());
|
|
1242
|
-
process.on("uncaughtException", handleTerminate);
|
|
1243
|
-
process.on("unhandledRejection", (reason) => handleTerminate(reason instanceof Error ? reason : new Error(String(reason))));
|
|
1244
|
-
const runtime = createRuntimePorts({ signal: abortController.signal });
|
|
1245
|
-
const parsed = parseCliArgs(process.argv.slice(2));
|
|
1246
|
-
const command = parsed.positionals[0];
|
|
1247
|
-
if (!command || parsed.flags.help) {
|
|
1248
|
-
printHelp();
|
|
1249
|
-
return;
|
|
1250
|
-
}
|
|
1251
|
-
const context = {
|
|
1252
|
-
cwd: parsed.flags.cwd ? path.resolve(process.cwd(), String(parsed.flags.cwd)) : process.cwd(),
|
|
1253
|
-
args: parsed,
|
|
1254
|
-
runtime
|
|
1255
|
-
};
|
|
1256
|
-
runtime.prompt.intro(pc.cyan("regpick"));
|
|
1257
|
-
try {
|
|
1258
|
-
let result;
|
|
1259
|
-
if (command === "init") result = await runInitCommand(context);
|
|
1260
|
-
else if (command === "list") result = await runListCommand(context);
|
|
1261
|
-
else if (command === "add") result = await runAddCommand(context);
|
|
1262
|
-
else if (command === "update") result = await runUpdateCommand(context);
|
|
1263
|
-
else if (command === "pack") result = await runPackCommand(context);
|
|
1264
|
-
else {
|
|
1265
|
-
runtime.prompt.error(`Unknown command: ${command}`);
|
|
1266
|
-
printHelp();
|
|
1267
|
-
process.exitCode = 1;
|
|
1268
|
-
return;
|
|
1269
|
-
}
|
|
1270
|
-
if (!result.ok) {
|
|
1271
|
-
handleAppError(result.error, runtime.prompt.error);
|
|
1272
|
-
runtime.prompt.outro(pc.red("Failed."));
|
|
1273
|
-
process.exitCode = 1;
|
|
1274
|
-
return;
|
|
1275
|
-
}
|
|
1276
|
-
if (result.value.kind === "noop") {
|
|
1277
|
-
runtime.prompt.outro(pc.yellow(result.value.message));
|
|
1278
|
-
return;
|
|
1279
|
-
}
|
|
1280
|
-
runtime.prompt.outro(pc.green("Done."));
|
|
1281
|
-
} catch (error) {
|
|
1282
|
-
handleAppError(toAppError(error), runtime.prompt.error);
|
|
1283
|
-
runtime.prompt.outro(pc.red("Failed."));
|
|
1284
|
-
process.exitCode = 1;
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
function handleAppError(error, write) {
|
|
1288
|
-
if (error.kind === "UserCancelled") {
|
|
1289
|
-
write(error.message);
|
|
1290
|
-
return;
|
|
1291
|
-
}
|
|
1292
|
-
write(`[${error.kind}] ${error.message}`);
|
|
1293
|
-
}
|
|
1294
|
-
run();
|
|
1295
|
-
|
|
1296
|
-
//#endregion
|
|
1297
|
-
export { };
|
|
16
|
+
`)}async function Oe(){let n=new AbortController,r=e=>{n.signal.aborted||n.abort(e),e instanceof Error&&console.error(t.red(`\n[Fatal Error] ${e.message}`)),process.exit(1)};process.on(`SIGINT`,()=>r()),process.on(`SIGTERM`,()=>r()),process.on(`uncaughtException`,r),process.on(`unhandledRejection`,e=>r(e instanceof Error?e:Error(String(e))));let i=De({signal:n.signal}),a=Ee(process.argv.slice(2)),o=a.positionals[0];if(!o||a.flags.help){Q();return}let s={cwd:a.flags.cwd?e.resolve(process.cwd(),String(a.flags.cwd)):process.cwd(),args:a,runtime:i};i.prompt.intro(t.cyan(`regpick`));try{let e;if(o===`init`)e=await _e(s);else if(o===`list`)e=await be(s);else if(o===`add`)e=await he(s);else if(o===`update`)e=await Se(s);else if(o===`pack`)e=await Z(s);else{i.prompt.error(`Unknown command: ${o}`),Q(),process.exitCode=1;return}if(!e.ok){$(e.error,i.prompt.error),i.prompt.outro(t.red(`Failed.`)),process.exitCode=1;return}if(e.value.kind===`noop`){i.prompt.outro(t.yellow(e.value.message));return}i.prompt.outro(t.green(`Done.`))}catch(e){$(b(e),i.prompt.error),i.prompt.outro(t.red(`Failed.`)),process.exitCode=1}}function $(e,t){if(e.kind===`UserCancelled`){t(e.message);return}t(`[${e.kind}] ${e.message}`)}Oe();export{};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "regpick",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Lightweight CLI for selecting and installing registry entries.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -51,6 +51,6 @@
|
|
|
51
51
|
"tsdown": "^0.21.0-beta.2",
|
|
52
52
|
"tsx": "^4.20.5",
|
|
53
53
|
"typescript": "^5.9.2",
|
|
54
|
-
"vitest": "^
|
|
54
|
+
"vitest": "^4.0.18"
|
|
55
55
|
}
|
|
56
56
|
}
|