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.
Files changed (2) hide show
  1. package/dist/index.mjs +3 -1284
  2. package/package.json +2 -2
package/dist/index.mjs CHANGED
@@ -1,1220 +1,5 @@
1
- import path from "node:path";
2
- import pc from "picocolors";
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.6",
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": "^2.1.8"
54
+ "vitest": "^4.0.18"
55
55
  }
56
56
  }