create-better-fullstack 2.1.5 → 2.1.6
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/{add-handler-BNSL6HdM.mjs → add-handler-CuPmSJRM.mjs} +13 -5
- package/dist/{addons-setup-CyrP1IV-.mjs → addons-setup-C_lxdJqU.mjs} +8 -1
- package/dist/addons-setup-DEPfsn6z.mjs +6 -0
- package/dist/{errors-Cyol8zbN.mjs → bts-config-BceXPcpI.mjs} +3 -84
- package/dist/cli.mjs +2 -2
- package/dist/{templates-CnTOtKjm.mjs → config-processing-B_1wTe3g.mjs} +57 -2
- package/dist/{doctor-DBoq7bZ9.mjs → doctor-DucDyWfl.mjs} +3 -2
- package/dist/errors-ns_o2OKg.mjs +86 -0
- package/dist/{file-formatter-B3dsev2l.mjs → file-formatter-XU6ti05V.mjs} +66 -6
- package/dist/gen-DWx3Xu_K.mjs +274 -0
- package/dist/{generated-checks-C8hn9w2i.mjs → generated-checks-Dt4Xqp1x.mjs} +1 -1
- package/dist/index.d.mts +171 -79
- package/dist/index.mjs +15 -8
- package/dist/{install-dependencies-CgNh-aOy.mjs → install-dependencies-DHoYa3P-.mjs} +75 -21
- package/dist/mcp-D9O5zgAA.mjs +8 -0
- package/dist/mcp-entry.mjs +153 -16
- package/dist/registry-CxeEOPot.mjs +394 -0
- package/dist/run-3AkXloH1.mjs +13 -0
- package/dist/{run-BYse4yJy.mjs → run-D80ZtSO8.mjs} +361 -88
- package/dist/scaffold-manifest-GV1fbhpD.mjs +123 -0
- package/dist/update-C9_x2yBF.mjs +401 -0
- package/package.json +3 -3
- package/dist/addons-setup-DyMm42a5.mjs +0 -5
- package/dist/mcp-CsW3i66c.mjs +0 -6
- package/dist/run-_cf_sFwM.mjs +0 -10
- /package/dist/{update-deps-D5OG0KmJ.mjs → update-deps-DLZAuT3V.mjs} +0 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { r as readBtsConfig, y as types_exports } from "./bts-config-BceXPcpI.mjs";
|
|
3
|
+
import { t as renderTitle } from "./render-title-zvyKC1ej.mjs";
|
|
4
|
+
import { t as CLIError } from "./errors-ns_o2OKg.mjs";
|
|
5
|
+
import { intro, log, outro } from "@clack/prompts";
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
import fs from "fs-extra";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import * as JSONC from "jsonc-parser";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { VirtualFileSystem, processTemplateString } from "@better-fullstack/template-generator";
|
|
12
|
+
import { writeTreeToFilesystem } from "@better-fullstack/template-generator/fs-writer";
|
|
13
|
+
|
|
14
|
+
//#region src/utils/registry-bts.ts
|
|
15
|
+
const BTS_CONFIG_FILE = "bts.jsonc";
|
|
16
|
+
/**
|
|
17
|
+
* Additively records an installed capability pack in the project's bts.jsonc
|
|
18
|
+
* under a dedicated `capabilityPacks` array (list of "name@version"), plus any
|
|
19
|
+
* addon ids the pack declares under `capabilityPackAddons`.
|
|
20
|
+
*
|
|
21
|
+
* This is intentionally self-contained: it does NOT flow through
|
|
22
|
+
* `updateBtsConfig` (whose typed signature is limited to addons/webDeploy/
|
|
23
|
+
* serverDeploy and would rewrite the derived stack graph). It edits the two
|
|
24
|
+
* dedicated keys in place with JSONC.modify so comments and formatting survive.
|
|
25
|
+
*/
|
|
26
|
+
async function recordPackInBtsConfig(projectDir, manifest) {
|
|
27
|
+
const configPath = path.join(projectDir, BTS_CONFIG_FILE);
|
|
28
|
+
if (!await fs.pathExists(configPath)) return;
|
|
29
|
+
let content = await fs.readFile(configPath, "utf-8");
|
|
30
|
+
const errors = [];
|
|
31
|
+
const parsed = JSONC.parse(content, errors, {
|
|
32
|
+
allowTrailingComma: true,
|
|
33
|
+
disallowComments: false
|
|
34
|
+
});
|
|
35
|
+
if (errors.length > 0 || typeof parsed !== "object" || parsed === null) return;
|
|
36
|
+
const packRef = `${manifest.name}@${manifest.version}`;
|
|
37
|
+
const nextPacks = [...(Array.isArray(parsed.capabilityPacks) ? parsed.capabilityPacks : []).filter((entry) => !entry.startsWith(`${manifest.name}@`)), packRef];
|
|
38
|
+
const existingAddons = Array.isArray(parsed.capabilityPackAddons) ? parsed.capabilityPackAddons : [];
|
|
39
|
+
const nextAddons = [...new Set([...existingAddons, ...manifest.addons ?? []])];
|
|
40
|
+
const formattingOptions = {
|
|
41
|
+
tabSize: 2,
|
|
42
|
+
insertSpaces: true,
|
|
43
|
+
eol: "\n"
|
|
44
|
+
};
|
|
45
|
+
const packEdit = JSONC.modify(content, ["capabilityPacks"], nextPacks, { formattingOptions });
|
|
46
|
+
content = JSONC.applyEdits(content, packEdit);
|
|
47
|
+
if (nextAddons.length > 0) {
|
|
48
|
+
const addonEdit = JSONC.modify(content, ["capabilityPackAddons"], nextAddons, { formattingOptions });
|
|
49
|
+
content = JSONC.applyEdits(content, addonEdit);
|
|
50
|
+
}
|
|
51
|
+
await fs.writeFile(configPath, content, "utf-8");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/helpers/core/registry-handler.ts
|
|
56
|
+
const LOCK_DIR = ".better-fullstack";
|
|
57
|
+
const LOCK_FILE = "registry.json";
|
|
58
|
+
const MANIFEST_FILE = "registry.json";
|
|
59
|
+
/** Preference order for the .env.example a pack's env vars are appended to. */
|
|
60
|
+
const ENV_EXAMPLE_CANDIDATES = ["apps/server/.env.example", ".env.example"];
|
|
61
|
+
/** Formats a ZodError's issues into a single readable message. */
|
|
62
|
+
function formatManifestIssues(error) {
|
|
63
|
+
return error.issues.map((issue) => {
|
|
64
|
+
return ` - ${issue.path.length > 0 ? issue.path.join(".") : "(root)"}: ${issue.message}`;
|
|
65
|
+
}).join("\n");
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolves a pack source (local path or file:// URL) to its registry.json.
|
|
69
|
+
* The `https` scheme is reserved but intentionally unsupported in the MVP so
|
|
70
|
+
* pack installs stay offline and deterministic.
|
|
71
|
+
*/
|
|
72
|
+
async function resolvePackSource(source) {
|
|
73
|
+
if (/^https?:\/\//i.test(source)) throw new CLIError(`Remote pack sources are not yet supported (got '${source}'). Use a local path or a file:// URL.`);
|
|
74
|
+
let candidate;
|
|
75
|
+
if (source.startsWith("file://")) candidate = fileURLToPath(source);
|
|
76
|
+
else candidate = path.resolve(source);
|
|
77
|
+
if (!await fs.pathExists(candidate)) throw new CLIError(`Pack source not found: ${source}`);
|
|
78
|
+
const manifestPath = (await fs.stat(candidate)).isDirectory() ? path.join(candidate, MANIFEST_FILE) : candidate;
|
|
79
|
+
if (!await fs.pathExists(manifestPath)) throw new CLIError(`No ${MANIFEST_FILE} found at ${manifestPath}. A capability pack must ship a ${MANIFEST_FILE} manifest.`);
|
|
80
|
+
return {
|
|
81
|
+
manifestPath,
|
|
82
|
+
packDir: path.dirname(manifestPath),
|
|
83
|
+
resolvedSource: manifestPath
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** Reads and validates a pack manifest, raising a clear CLIError on any failure. */
|
|
87
|
+
async function loadPackManifest(manifestPath) {
|
|
88
|
+
let raw;
|
|
89
|
+
try {
|
|
90
|
+
raw = await fs.readFile(manifestPath, "utf-8");
|
|
91
|
+
} catch (error) {
|
|
92
|
+
throw new CLIError(`Failed to read pack manifest ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
93
|
+
}
|
|
94
|
+
let json;
|
|
95
|
+
try {
|
|
96
|
+
json = JSON.parse(raw);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
throw new CLIError(`Pack manifest ${manifestPath} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
99
|
+
}
|
|
100
|
+
const parsed = types_exports.CapabilityPackManifestSchema.safeParse(json);
|
|
101
|
+
if (!parsed.success) throw new CLIError(`Invalid capability pack manifest (${manifestPath}):\n${formatManifestIssues(parsed.error)}`);
|
|
102
|
+
return parsed.data;
|
|
103
|
+
}
|
|
104
|
+
function lockPath(projectDir) {
|
|
105
|
+
return path.join(projectDir, LOCK_DIR, LOCK_FILE);
|
|
106
|
+
}
|
|
107
|
+
/** Reads the per-project registry lockfile, tolerating a missing/empty file. */
|
|
108
|
+
async function readRegistryLock(projectDir) {
|
|
109
|
+
const file = lockPath(projectDir);
|
|
110
|
+
if (!await fs.pathExists(file)) return {
|
|
111
|
+
version: types_exports.REGISTRY_LOCK_VERSION,
|
|
112
|
+
packs: []
|
|
113
|
+
};
|
|
114
|
+
try {
|
|
115
|
+
const raw = await fs.readFile(file, "utf-8");
|
|
116
|
+
const parsed = types_exports.RegistryLockSchema.safeParse(JSON.parse(raw));
|
|
117
|
+
if (!parsed.success) return {
|
|
118
|
+
version: types_exports.REGISTRY_LOCK_VERSION,
|
|
119
|
+
packs: []
|
|
120
|
+
};
|
|
121
|
+
return parsed.data;
|
|
122
|
+
} catch {
|
|
123
|
+
return {
|
|
124
|
+
version: types_exports.REGISTRY_LOCK_VERSION,
|
|
125
|
+
packs: []
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function writeRegistryLock(projectDir, lock) {
|
|
130
|
+
const file = lockPath(projectDir);
|
|
131
|
+
await fs.ensureDir(path.dirname(file));
|
|
132
|
+
await fs.writeFile(file, `${JSON.stringify(lock, null, 2)}\n`, "utf-8");
|
|
133
|
+
}
|
|
134
|
+
/** Lists packs recorded as installed in the per-project lockfile. */
|
|
135
|
+
async function listInstalledPacks(projectDir) {
|
|
136
|
+
return (await readRegistryLock(projectDir)).packs;
|
|
137
|
+
}
|
|
138
|
+
function parseEnvKeys(content) {
|
|
139
|
+
const keys = /* @__PURE__ */ new Set();
|
|
140
|
+
for (const line of content.split("\n")) {
|
|
141
|
+
const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
142
|
+
if (match?.[1]) keys.add(match[1]);
|
|
143
|
+
}
|
|
144
|
+
return keys;
|
|
145
|
+
}
|
|
146
|
+
/** Appends any missing env vars (with optional comment/value) to existing content. */
|
|
147
|
+
function appendEnvVars(content, vars) {
|
|
148
|
+
const existing = parseEnvKeys(content);
|
|
149
|
+
const missing = vars.filter((entry) => !existing.has(entry.key));
|
|
150
|
+
if (missing.length === 0) return {
|
|
151
|
+
content,
|
|
152
|
+
keys: []
|
|
153
|
+
};
|
|
154
|
+
return {
|
|
155
|
+
content: `${content}${content.trim().length === 0 ? "" : content.endsWith("\n") ? "\n" : "\n\n"}${missing.map((entry) => {
|
|
156
|
+
return `${entry.description ? `# ${entry.description}\n` : ""}${entry.key}=${entry.value ?? ""}`;
|
|
157
|
+
}).join("\n")}\n`,
|
|
158
|
+
keys: missing.map((entry) => entry.key)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
async function resolveEnvExamplePath(projectDir) {
|
|
162
|
+
for (const candidate of ENV_EXAMPLE_CANDIDATES) if (await fs.pathExists(path.join(projectDir, candidate))) return candidate;
|
|
163
|
+
return ".env.example";
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Rejects a capability pack whose resolved target path escapes the project
|
|
167
|
+
* directory (path traversal via `..` segments or an absolute path in the
|
|
168
|
+
* manifest). Applied to both file writes and dependency-map target dirs so a
|
|
169
|
+
* pack can never write outside / mutate sibling projects.
|
|
170
|
+
*/
|
|
171
|
+
function assertPathInsideProject(projectDir, targetAbs, label) {
|
|
172
|
+
const rel = path.relative(projectDir, targetAbs);
|
|
173
|
+
if (rel === "" || rel.startsWith("..") || path.isAbsolute(rel)) throw new CLIError(`Capability pack ${label} escapes the project directory and was rejected: ${targetAbs}`);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Merges a pack's dependencies/devDependencies into the target package.json
|
|
177
|
+
* files. Pack dependency versions are arbitrary name->version pairs, so they
|
|
178
|
+
* are merged directly (they cannot flow through addPackageDependency, which is
|
|
179
|
+
* restricted to the AvailableDependencies enum).
|
|
180
|
+
*/
|
|
181
|
+
async function planDependencyChanges(projectDir, manifest) {
|
|
182
|
+
const changes = [];
|
|
183
|
+
const writes = /* @__PURE__ */ new Map();
|
|
184
|
+
const groups = [{
|
|
185
|
+
map: manifest.dependencies,
|
|
186
|
+
dev: false
|
|
187
|
+
}, {
|
|
188
|
+
map: manifest.devDependencies,
|
|
189
|
+
dev: true
|
|
190
|
+
}];
|
|
191
|
+
for (const { map, dev } of groups) {
|
|
192
|
+
if (!map) continue;
|
|
193
|
+
for (const [dir, deps] of Object.entries(map)) {
|
|
194
|
+
if (Object.keys(deps).length === 0) continue;
|
|
195
|
+
const pkgRelPath = path.join(dir === "." ? "" : dir, "package.json");
|
|
196
|
+
const pkgAbsPath = path.join(projectDir, pkgRelPath);
|
|
197
|
+
assertPathInsideProject(projectDir, pkgAbsPath, `dependency target "${dir}"`);
|
|
198
|
+
if (!await fs.pathExists(pkgAbsPath)) throw new CLIError(`Pack targets ${pkgRelPath} which does not exist in this project. Cannot merge dependencies.`);
|
|
199
|
+
const pkgJson = writes.get(pkgAbsPath) ?? await fs.readJson(pkgAbsPath);
|
|
200
|
+
const field = dev ? "devDependencies" : "dependencies";
|
|
201
|
+
const bucket = { ...pkgJson[field] };
|
|
202
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
203
|
+
bucket[name] = version;
|
|
204
|
+
changes.push({
|
|
205
|
+
dir,
|
|
206
|
+
name,
|
|
207
|
+
version,
|
|
208
|
+
dev
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
pkgJson[field] = bucket;
|
|
212
|
+
writes.set(pkgAbsPath, pkgJson);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
changes,
|
|
217
|
+
writes
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Installs a capability pack into an existing Better Fullstack project.
|
|
222
|
+
*
|
|
223
|
+
* Reuses the same building blocks as the `add` handler: readBtsConfig to assert
|
|
224
|
+
* a real project, the template-generator VFS + writeTreeToFilesystem to stage
|
|
225
|
+
* and write files, and processTemplateString to render templated files.
|
|
226
|
+
*/
|
|
227
|
+
async function addPack(options) {
|
|
228
|
+
const projectDir = path.resolve(options.projectDir);
|
|
229
|
+
const dryRun = options.dryRun ?? false;
|
|
230
|
+
const btsConfig = await readBtsConfig(projectDir);
|
|
231
|
+
if (!btsConfig) throw new CLIError(`No Better Fullstack project found in ${projectDir}. Make sure bts.jsonc exists.`);
|
|
232
|
+
const { manifestPath, resolvedSource } = await resolvePackSource(options.source);
|
|
233
|
+
const manifest = await loadPackManifest(manifestPath);
|
|
234
|
+
const templateContext = btsConfig;
|
|
235
|
+
const vfs = new VirtualFileSystem();
|
|
236
|
+
const filesWritten = [];
|
|
237
|
+
const filesSkipped = [];
|
|
238
|
+
for (const file of manifest.files) {
|
|
239
|
+
const normalized = file.path.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
240
|
+
const targetAbs = path.join(projectDir, normalized);
|
|
241
|
+
assertPathInsideProject(projectDir, targetAbs, `file path "${file.path}"`);
|
|
242
|
+
if (!file.overwrite && await fs.pathExists(targetAbs)) {
|
|
243
|
+
filesSkipped.push(normalized);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const content = file.template ? processTemplateString(file.content, templateContext) : file.content;
|
|
247
|
+
vfs.writeFile(normalized, content);
|
|
248
|
+
filesWritten.push(normalized);
|
|
249
|
+
}
|
|
250
|
+
const { changes: dependencies, writes: packageJsonWrites } = await planDependencyChanges(projectDir, manifest);
|
|
251
|
+
let envFile;
|
|
252
|
+
let envKeys = [];
|
|
253
|
+
let envContent;
|
|
254
|
+
if (manifest.env.length > 0) {
|
|
255
|
+
envFile = await resolveEnvExamplePath(projectDir);
|
|
256
|
+
const envAbs = path.join(projectDir, envFile);
|
|
257
|
+
const merged = appendEnvVars(await fs.pathExists(envAbs) ? await fs.readFile(envAbs, "utf-8") : "", manifest.env);
|
|
258
|
+
envKeys = merged.keys;
|
|
259
|
+
if (merged.keys.length > 0) envContent = merged.content;
|
|
260
|
+
else envFile = void 0;
|
|
261
|
+
}
|
|
262
|
+
if (dryRun) return {
|
|
263
|
+
pack: {
|
|
264
|
+
name: manifest.name,
|
|
265
|
+
version: manifest.version
|
|
266
|
+
},
|
|
267
|
+
source: resolvedSource,
|
|
268
|
+
filesWritten,
|
|
269
|
+
filesSkipped,
|
|
270
|
+
dependencies,
|
|
271
|
+
envKeys,
|
|
272
|
+
envFile,
|
|
273
|
+
dryRun: true
|
|
274
|
+
};
|
|
275
|
+
if (filesWritten.length > 0) await writeTreeToFilesystem({
|
|
276
|
+
root: vfs.toTree(manifest.name),
|
|
277
|
+
fileCount: vfs.getFileCount(),
|
|
278
|
+
directoryCount: vfs.getDirectoryCount(),
|
|
279
|
+
config: templateContext
|
|
280
|
+
}, projectDir);
|
|
281
|
+
for (const [pkgAbsPath, pkgJson] of packageJsonWrites) await fs.writeJson(pkgAbsPath, pkgJson, { spaces: 2 });
|
|
282
|
+
if (envFile && envContent !== void 0) await fs.writeFile(path.join(projectDir, envFile), envContent, "utf-8");
|
|
283
|
+
const lock = await readRegistryLock(projectDir);
|
|
284
|
+
const installed = {
|
|
285
|
+
name: manifest.name,
|
|
286
|
+
version: manifest.version,
|
|
287
|
+
source: resolvedSource,
|
|
288
|
+
files: filesWritten,
|
|
289
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
290
|
+
};
|
|
291
|
+
lock.version = types_exports.REGISTRY_LOCK_VERSION;
|
|
292
|
+
lock.packs = [...lock.packs.filter((pack) => pack.name !== manifest.name), installed];
|
|
293
|
+
await writeRegistryLock(projectDir, lock);
|
|
294
|
+
await recordPackInBtsConfig(projectDir, manifest);
|
|
295
|
+
return {
|
|
296
|
+
pack: {
|
|
297
|
+
name: manifest.name,
|
|
298
|
+
version: manifest.version
|
|
299
|
+
},
|
|
300
|
+
source: resolvedSource,
|
|
301
|
+
filesWritten,
|
|
302
|
+
filesSkipped,
|
|
303
|
+
dependencies,
|
|
304
|
+
envKeys,
|
|
305
|
+
envFile,
|
|
306
|
+
dryRun: false
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/commands/registry.ts
|
|
312
|
+
function formatCount(count, noun) {
|
|
313
|
+
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
314
|
+
}
|
|
315
|
+
function reportAddResult(result) {
|
|
316
|
+
const label = result.dryRun ? "[dry run] Would install" : "Installed";
|
|
317
|
+
log.info(pc.cyan(`${label} ${result.pack.name}@${result.pack.version}`));
|
|
318
|
+
const fileLabel = result.dryRun ? "would write" : "wrote";
|
|
319
|
+
log.message(pc.dim(`Files: ${fileLabel} ${formatCount(result.filesWritten.length, "file")}` + (result.filesSkipped.length > 0 ? `, skipped ${formatCount(result.filesSkipped.length, "existing file")}` : "")));
|
|
320
|
+
for (const file of result.filesWritten) log.message(pc.dim(` + ${file}`));
|
|
321
|
+
for (const file of result.filesSkipped) log.message(pc.dim(` = ${file} (exists, not overwritten)`));
|
|
322
|
+
if (result.dependencies.length > 0) {
|
|
323
|
+
log.message(pc.dim(`Dependencies: ${formatCount(result.dependencies.length, "change")}`));
|
|
324
|
+
for (const dep of result.dependencies) log.message(pc.dim(` + ${dep.dir}: ${dep.name}@${dep.version}${dep.dev ? " (dev)" : ""}`));
|
|
325
|
+
}
|
|
326
|
+
if (result.envKeys.length > 0 && result.envFile) {
|
|
327
|
+
log.message(pc.dim(`Env: ${formatCount(result.envKeys.length, "var")} -> ${result.envFile}`));
|
|
328
|
+
for (const key of result.envKeys) log.message(pc.dim(` + ${key}`));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function errorMessage(error) {
|
|
332
|
+
return error instanceof Error ? error.message : String(error);
|
|
333
|
+
}
|
|
334
|
+
function reportJsonError(error) {
|
|
335
|
+
console.log(JSON.stringify({
|
|
336
|
+
ok: false,
|
|
337
|
+
error: errorMessage(error)
|
|
338
|
+
}, null, 2));
|
|
339
|
+
process.exitCode = 1;
|
|
340
|
+
}
|
|
341
|
+
async function registryHandler(input) {
|
|
342
|
+
const projectDir = path.resolve(input.projectDir || process.cwd());
|
|
343
|
+
if (input.action === "list") {
|
|
344
|
+
const packs = await listInstalledPacks(projectDir);
|
|
345
|
+
if (input.json) {
|
|
346
|
+
console.log(JSON.stringify(packs, null, 2));
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
renderTitle();
|
|
350
|
+
intro(pc.magenta("Installed capability packs"));
|
|
351
|
+
if (packs.length === 0) {
|
|
352
|
+
log.info(pc.dim("No capability packs installed. Add one with `registry add <source>`."));
|
|
353
|
+
outro(pc.magenta("Nothing installed."));
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
for (const pack of packs) log.message(`${pc.bold(pack.name)}@${pack.version} ${pc.dim(`(${formatCount(pack.files.length, "file")}, from ${pack.source})`)}`);
|
|
357
|
+
outro(pc.magenta(`${formatCount(packs.length, "pack")} installed.`));
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (!input.source) {
|
|
361
|
+
const error = new CLIError("registry add requires a <source> (a local path or file:// URL to a capability pack).");
|
|
362
|
+
if (input.json) {
|
|
363
|
+
reportJsonError(error);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
const dryRun = input.dryRun ?? false;
|
|
369
|
+
let result;
|
|
370
|
+
try {
|
|
371
|
+
result = await addPack({
|
|
372
|
+
projectDir,
|
|
373
|
+
source: input.source,
|
|
374
|
+
dryRun
|
|
375
|
+
});
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (input.json) {
|
|
378
|
+
reportJsonError(error);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
if (input.json) {
|
|
384
|
+
console.log(JSON.stringify(result, null, 2));
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
renderTitle();
|
|
388
|
+
intro(pc.magenta(dryRun ? "Preview capability pack install" : "Install capability pack"));
|
|
389
|
+
reportAddResult(result);
|
|
390
|
+
outro(pc.magenta(dryRun ? "Dry run complete. No files were written." : "Capability pack installed."));
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
//#endregion
|
|
394
|
+
export { registryHandler };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "./bts-config-BceXPcpI.mjs";
|
|
3
|
+
import { a as createBtsCli, c as history, d as telemetry, f as update, i as create, l as router, n as builder, o as docs, r as check, s as doctor, t as add, u as sponsors } from "./run-D80ZtSO8.mjs";
|
|
4
|
+
import "./render-title-zvyKC1ej.mjs";
|
|
5
|
+
import "./errors-ns_o2OKg.mjs";
|
|
6
|
+
import "./addons-setup-C_lxdJqU.mjs";
|
|
7
|
+
import "./file-formatter-XU6ti05V.mjs";
|
|
8
|
+
import "./install-dependencies-DHoYa3P-.mjs";
|
|
9
|
+
import "./generated-checks-Dt4Xqp1x.mjs";
|
|
10
|
+
import "./config-processing-B_1wTe3g.mjs";
|
|
11
|
+
import "./scaffold-manifest-GV1fbhpD.mjs";
|
|
12
|
+
|
|
13
|
+
export { createBtsCli };
|