@jskit-ai/jskit-cli 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/jskit.js +5 -0
- package/bundles/auth-base/bundle.descriptor.mjs +10 -0
- package/package.json +37 -0
- package/src/client/index.js +1 -0
- package/src/index.js +1 -0
- package/src/server/cliEntrypoint.js +14 -0
- package/src/server/cliError.js +8 -0
- package/src/server/collectionUtils.js +17 -0
- package/src/server/commandHandlers.js +1657 -0
- package/src/server/index.js +4604 -0
- package/src/server/optionInterpolation.js +408 -0
- package/src/server/outputFormatting.js +103 -0
- package/src/server/pathResolution.js +106 -0
|
@@ -0,0 +1,1657 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import {
|
|
3
|
+
ensureArray,
|
|
4
|
+
ensureObject,
|
|
5
|
+
sortStrings
|
|
6
|
+
} from "./collectionUtils.js";
|
|
7
|
+
|
|
8
|
+
function createCommandHandlers(deps) {
|
|
9
|
+
const {
|
|
10
|
+
createCliError,
|
|
11
|
+
createColorFormatter,
|
|
12
|
+
resolveWrapWidth,
|
|
13
|
+
writeWrappedItems,
|
|
14
|
+
normalizeRelativePath,
|
|
15
|
+
normalizeRelativePosixPath,
|
|
16
|
+
resolveAppRootFromCwd,
|
|
17
|
+
loadLockFile,
|
|
18
|
+
loadPackageRegistry,
|
|
19
|
+
loadBundleRegistry,
|
|
20
|
+
loadAppLocalPackageRegistry,
|
|
21
|
+
mergePackageRegistries,
|
|
22
|
+
resolvePackageIdInput,
|
|
23
|
+
resolveInstalledPackageIdInput,
|
|
24
|
+
resolveInstalledNodeModulePackageEntry,
|
|
25
|
+
hydratePackageRegistryFromInstalledNodeModules,
|
|
26
|
+
validateInlineOptionsForPackage,
|
|
27
|
+
resolveLocalDependencyOrder,
|
|
28
|
+
validatePlannedCapabilityClosure,
|
|
29
|
+
resolvePackageOptions,
|
|
30
|
+
applyPackageInstall,
|
|
31
|
+
applyPackagePositioning,
|
|
32
|
+
adoptAppLocalPackageDependencies,
|
|
33
|
+
loadAppPackageJson,
|
|
34
|
+
resolveLocalPackageId,
|
|
35
|
+
createLocalPackageScaffoldFiles,
|
|
36
|
+
fileExists,
|
|
37
|
+
applyPackageJsonField,
|
|
38
|
+
toFileDependencySpecifier,
|
|
39
|
+
writeJsonFile,
|
|
40
|
+
writeFile,
|
|
41
|
+
mkdir,
|
|
42
|
+
path,
|
|
43
|
+
inspectPackageOfferings,
|
|
44
|
+
buildFileWriteGroups,
|
|
45
|
+
listDeclaredCapabilities,
|
|
46
|
+
buildCapabilityDetailsForPackage,
|
|
47
|
+
formatPackageSubpathImport,
|
|
48
|
+
normalizePlacementOutlets,
|
|
49
|
+
normalizePlacementContributions,
|
|
50
|
+
shouldShowPackageExportTarget,
|
|
51
|
+
classifyExportedSymbols,
|
|
52
|
+
deriveProviderDisplayName,
|
|
53
|
+
restorePackageJsonField,
|
|
54
|
+
readFileBufferIfExists,
|
|
55
|
+
removeEnvValue,
|
|
56
|
+
removeManagedViteProxyEntries,
|
|
57
|
+
hashBuffer,
|
|
58
|
+
rm
|
|
59
|
+
} = deps;
|
|
60
|
+
|
|
61
|
+
function renderResolvedSummary(commandType, targetId, resolvedPackageIds, touchedFiles, appRoot, lockPath, externalDependencies) {
|
|
62
|
+
const lines = [];
|
|
63
|
+
lines.push(`${commandType} ${targetId}.`);
|
|
64
|
+
lines.push(`Resolved packages (${resolvedPackageIds.length}):`);
|
|
65
|
+
for (const packageId of resolvedPackageIds) {
|
|
66
|
+
lines.push(`- ${packageId}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (externalDependencies.length > 0) {
|
|
70
|
+
lines.push(`External dependencies (${externalDependencies.length}):`);
|
|
71
|
+
for (const dependencyId of externalDependencies) {
|
|
72
|
+
lines.push(`- ${dependencyId}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
lines.push(`Touched files (${touchedFiles.length}):`);
|
|
77
|
+
for (const touchedFile of touchedFiles) {
|
|
78
|
+
lines.push(`- ${touchedFile}`);
|
|
79
|
+
}
|
|
80
|
+
lines.push(`Lock file: ${normalizeRelativePath(appRoot, lockPath)}`);
|
|
81
|
+
return lines.join("\n");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function runNpmInstall(appRoot, stderr) {
|
|
85
|
+
await new Promise((resolve, reject) => {
|
|
86
|
+
const child = spawn("npm", ["install"], {
|
|
87
|
+
cwd: appRoot,
|
|
88
|
+
stdio: "inherit"
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
child.on("error", reject);
|
|
92
|
+
child.on("exit", (code) => {
|
|
93
|
+
if (code === 0) {
|
|
94
|
+
resolve();
|
|
95
|
+
} else {
|
|
96
|
+
reject(createCliError(`npm install failed with exit code ${code}.`));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}).catch((error) => {
|
|
100
|
+
stderr.write(`npm install failed: ${error.message}\n`);
|
|
101
|
+
throw error;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getInstalledDependents(lock, packageId, packageRegistry) {
|
|
106
|
+
const dependents = [];
|
|
107
|
+
const installedPackageIds = Object.keys(ensureObject(lock.installedPackages));
|
|
108
|
+
|
|
109
|
+
for (const installedId of installedPackageIds) {
|
|
110
|
+
if (installedId === packageId) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const packageEntry = packageRegistry.get(installedId);
|
|
114
|
+
if (!packageEntry) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const dependencies = ensureArray(packageEntry.descriptor.dependsOn).map((value) => String(value));
|
|
118
|
+
if (dependencies.includes(packageId)) {
|
|
119
|
+
dependents.push(installedId);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return sortStrings(dependents);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function resolvePackageOptionNames(packageEntry) {
|
|
127
|
+
const optionSchemas = ensureObject(packageEntry?.descriptor?.options);
|
|
128
|
+
return Object.keys(optionSchemas);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function resolveBundleInlineOptionsForPackage(packageEntry, inlineOptions) {
|
|
132
|
+
const allowedOptionNames = new Set(resolvePackageOptionNames(packageEntry));
|
|
133
|
+
const resolved = {};
|
|
134
|
+
|
|
135
|
+
for (const [optionName, optionValue] of Object.entries(ensureObject(inlineOptions))) {
|
|
136
|
+
if (!allowedOptionNames.has(optionName)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
resolved[optionName] = String(optionValue || "").trim();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return resolved;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function validateInlineOptionsForBundle({
|
|
146
|
+
bundleId,
|
|
147
|
+
inlineOptions,
|
|
148
|
+
packageIds,
|
|
149
|
+
packageRegistry
|
|
150
|
+
}) {
|
|
151
|
+
const providedOptionNames = Object.keys(ensureObject(inlineOptions));
|
|
152
|
+
if (providedOptionNames.length < 1) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const allowedOptionNames = new Set();
|
|
157
|
+
for (const packageId of ensureArray(packageIds).map((value) => String(value || "").trim()).filter(Boolean)) {
|
|
158
|
+
const packageEntry = packageRegistry.get(packageId);
|
|
159
|
+
if (!packageEntry) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
for (const optionName of resolvePackageOptionNames(packageEntry)) {
|
|
163
|
+
allowedOptionNames.add(optionName);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const unknownOptionNames = providedOptionNames.filter((optionName) => !allowedOptionNames.has(optionName));
|
|
168
|
+
if (unknownOptionNames.length < 1) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const sortedUnknown = sortStrings(unknownOptionNames);
|
|
173
|
+
const sortedAllowed = sortStrings([...allowedOptionNames]);
|
|
174
|
+
const suffix = sortedAllowed.length > 0
|
|
175
|
+
? ` Allowed options: ${sortedAllowed.join(", ")}.`
|
|
176
|
+
: " This bundle does not accept inline options.";
|
|
177
|
+
throw createCliError(`Unknown option(s) for bundle ${bundleId}: ${sortedUnknown.join(", ")}.${suffix}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function commandList({ positional, options, cwd, stdout }) {
|
|
181
|
+
const packageRegistry = await loadPackageRegistry();
|
|
182
|
+
const bundleRegistry = await loadBundleRegistry();
|
|
183
|
+
|
|
184
|
+
const appRoot = await resolveAppRootFromCwd(cwd);
|
|
185
|
+
const appLocalRegistry = await loadAppLocalPackageRegistry(appRoot);
|
|
186
|
+
const { lock } = await loadLockFile(appRoot);
|
|
187
|
+
const installedPackageEntries = ensureObject(lock.installedPackages);
|
|
188
|
+
const installedPackages = new Set(Object.keys(installedPackageEntries));
|
|
189
|
+
const installedUnknownPackageIds = sortStrings(
|
|
190
|
+
[...installedPackages].filter((packageId) => !packageRegistry.has(packageId))
|
|
191
|
+
);
|
|
192
|
+
const installedLocalPackageIds = sortStrings(
|
|
193
|
+
installedUnknownPackageIds.filter((packageId) => {
|
|
194
|
+
const lockEntry = ensureObject(installedPackageEntries[packageId]);
|
|
195
|
+
const sourceType = String(ensureObject(lockEntry.source).type || "").trim();
|
|
196
|
+
return sourceType === "local-package" || sourceType === "app-local-package" || appLocalRegistry.has(packageId);
|
|
197
|
+
})
|
|
198
|
+
);
|
|
199
|
+
const installedExternalPackageIds = sortStrings(
|
|
200
|
+
installedUnknownPackageIds.filter((packageId) => !installedLocalPackageIds.includes(packageId))
|
|
201
|
+
);
|
|
202
|
+
const availableLocalPackageIds = sortStrings(
|
|
203
|
+
[...appLocalRegistry.keys()].filter((packageId) => !installedPackages.has(packageId))
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const mode = String(positional[0] || "").trim();
|
|
207
|
+
const shouldListBundles = !mode || mode === "bundles";
|
|
208
|
+
const shouldListPackages = !mode || mode === "packages";
|
|
209
|
+
|
|
210
|
+
if (!shouldListBundles && !shouldListPackages) {
|
|
211
|
+
throw createCliError(`Unknown list mode: ${mode}`, { showUsage: true });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const color = createColorFormatter(stdout);
|
|
215
|
+
const lines = [];
|
|
216
|
+
if (shouldListBundles) {
|
|
217
|
+
lines.push(color.heading("Available bundles:"));
|
|
218
|
+
const bundleIds = sortStrings([...bundleRegistry.keys()]);
|
|
219
|
+
for (const bundleId of bundleIds) {
|
|
220
|
+
const bundle = bundleRegistry.get(bundleId);
|
|
221
|
+
const packageIds = ensureArray(bundle.packages).map((value) => String(value));
|
|
222
|
+
const isInstalled = packageIds.length > 0 && packageIds.every((packageId) => installedPackages.has(packageId));
|
|
223
|
+
const providerLabel = Number(bundle.provider) === 1 ? " [provider]" : "";
|
|
224
|
+
const installedLabel = isInstalled ? " (installed)" : "";
|
|
225
|
+
lines.push(
|
|
226
|
+
`- ${color.item(bundle.bundleId)} ${color.version(`(${bundle.version})`)}${isInstalled ? color.installed(installedLabel) : installedLabel}${providerLabel ? color.provider(providerLabel) : providerLabel}: ${String(bundle.description || "")}`
|
|
227
|
+
);
|
|
228
|
+
if (options.full || options.expanded) {
|
|
229
|
+
for (const packageId of packageIds) {
|
|
230
|
+
lines.push(` - ${color.dim(packageId)}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (shouldListPackages) {
|
|
237
|
+
if (lines.length > 0) {
|
|
238
|
+
lines.push("");
|
|
239
|
+
}
|
|
240
|
+
lines.push(color.heading("Available packages:"));
|
|
241
|
+
const packageIds = sortStrings([...packageRegistry.keys()]);
|
|
242
|
+
for (const packageId of packageIds) {
|
|
243
|
+
const packageEntry = packageRegistry.get(packageId);
|
|
244
|
+
const installedLabel = installedPackages.has(packageId) ? " (installed)" : "";
|
|
245
|
+
lines.push(
|
|
246
|
+
`- ${color.item(packageId)} ${color.version(`(${packageEntry.version})`)}${installedLabel ? color.installed(installedLabel) : ""}`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (installedLocalPackageIds.length > 0) {
|
|
251
|
+
lines.push("");
|
|
252
|
+
lines.push(color.heading("Installed local packages:"));
|
|
253
|
+
for (const packageId of installedLocalPackageIds) {
|
|
254
|
+
const lockEntry = ensureObject(installedPackageEntries[packageId]);
|
|
255
|
+
const version = String(lockEntry.version || "").trim();
|
|
256
|
+
const versionLabel = version ? ` ${color.version(`(${version})`)}` : "";
|
|
257
|
+
lines.push(`- ${color.item(packageId)}${versionLabel}${color.installed(" (installed)")}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (installedExternalPackageIds.length > 0) {
|
|
262
|
+
lines.push("");
|
|
263
|
+
lines.push(color.heading("Installed external packages:"));
|
|
264
|
+
for (const packageId of installedExternalPackageIds) {
|
|
265
|
+
const lockEntry = ensureObject(installedPackageEntries[packageId]);
|
|
266
|
+
const version = String(lockEntry.version || "").trim();
|
|
267
|
+
const versionLabel = version ? ` ${color.version(`(${version})`)}` : "";
|
|
268
|
+
lines.push(`- ${color.item(packageId)}${versionLabel}${color.installed(" (installed)")}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (availableLocalPackageIds.length > 0) {
|
|
273
|
+
lines.push("");
|
|
274
|
+
lines.push(color.heading("Available local packages (not installed):"));
|
|
275
|
+
for (const packageId of availableLocalPackageIds) {
|
|
276
|
+
const packageEntry = appLocalRegistry.get(packageId);
|
|
277
|
+
const version = String(packageEntry?.version || "").trim();
|
|
278
|
+
const versionLabel = version ? ` ${color.version(`(${version})`)}` : "";
|
|
279
|
+
lines.push(`- ${color.item(packageId)}${versionLabel}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (options.json) {
|
|
285
|
+
const payload = {
|
|
286
|
+
bundles: shouldListBundles
|
|
287
|
+
? sortStrings([...bundleRegistry.keys()]).map((bundleId) => {
|
|
288
|
+
const bundle = bundleRegistry.get(bundleId);
|
|
289
|
+
const packageIds = ensureArray(bundle.packages).map((value) => String(value));
|
|
290
|
+
return {
|
|
291
|
+
bundleId: bundle.bundleId,
|
|
292
|
+
version: bundle.version,
|
|
293
|
+
description: bundle.description || "",
|
|
294
|
+
provider: Number(bundle.provider) === 1,
|
|
295
|
+
installed: packageIds.length > 0 && packageIds.every((packageId) => installedPackages.has(packageId)),
|
|
296
|
+
packages: packageIds
|
|
297
|
+
};
|
|
298
|
+
})
|
|
299
|
+
: [],
|
|
300
|
+
packages: shouldListPackages
|
|
301
|
+
? sortStrings([...packageRegistry.keys()]).map((packageId) => {
|
|
302
|
+
const packageEntry = packageRegistry.get(packageId);
|
|
303
|
+
return {
|
|
304
|
+
packageId,
|
|
305
|
+
version: packageEntry.version,
|
|
306
|
+
installed: installedPackages.has(packageId)
|
|
307
|
+
};
|
|
308
|
+
})
|
|
309
|
+
: [],
|
|
310
|
+
installedLocalPackages: shouldListPackages
|
|
311
|
+
? installedLocalPackageIds.map((packageId) => {
|
|
312
|
+
const lockEntry = ensureObject(installedPackageEntries[packageId]);
|
|
313
|
+
return {
|
|
314
|
+
packageId,
|
|
315
|
+
version: String(lockEntry.version || "").trim()
|
|
316
|
+
};
|
|
317
|
+
})
|
|
318
|
+
: [],
|
|
319
|
+
installedExternalPackages: shouldListPackages
|
|
320
|
+
? installedExternalPackageIds.map((packageId) => {
|
|
321
|
+
const lockEntry = ensureObject(installedPackageEntries[packageId]);
|
|
322
|
+
return {
|
|
323
|
+
packageId,
|
|
324
|
+
version: String(lockEntry.version || "").trim(),
|
|
325
|
+
source: ensureObject(lockEntry.source)
|
|
326
|
+
};
|
|
327
|
+
})
|
|
328
|
+
: [],
|
|
329
|
+
availableLocalPackages: shouldListPackages
|
|
330
|
+
? availableLocalPackageIds.map((packageId) => {
|
|
331
|
+
const packageEntry = appLocalRegistry.get(packageId);
|
|
332
|
+
return {
|
|
333
|
+
packageId,
|
|
334
|
+
version: String(packageEntry?.version || "").trim(),
|
|
335
|
+
packagePath: normalizeRelativePosixPath(String(packageEntry?.relativeDir || ""))
|
|
336
|
+
};
|
|
337
|
+
})
|
|
338
|
+
: []
|
|
339
|
+
};
|
|
340
|
+
stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
341
|
+
} else {
|
|
342
|
+
stdout.write(`${lines.join("\n")}\n`);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return 0;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function commandShow({ positional, options, stdout }) {
|
|
349
|
+
const id = String(positional[0] || "").trim();
|
|
350
|
+
if (!id) {
|
|
351
|
+
throw createCliError("show requires an id.", { showUsage: true });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const packageRegistry = await loadPackageRegistry();
|
|
355
|
+
const bundleRegistry = await loadBundleRegistry();
|
|
356
|
+
const color = createColorFormatter(stdout);
|
|
357
|
+
const writeField = (label, value, formatValue = (raw) => raw) => {
|
|
358
|
+
stdout.write(`${color.dim(`${label}:`)} ${formatValue(String(value || ""))}\n`);
|
|
359
|
+
};
|
|
360
|
+
const writeBindingsSection = (side, bindings) => {
|
|
361
|
+
const sectionSide = String(side || "").trim().toLowerCase();
|
|
362
|
+
const bindingEntries = ensureArray(bindings);
|
|
363
|
+
stdout.write(`${color.heading(`Container bindings ${sectionSide} (${bindingEntries.length}):`)}\n`);
|
|
364
|
+
if (bindingEntries.length < 1) {
|
|
365
|
+
stdout.write(`- ${color.dim("none detected")}\n`);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
for (const bindingRecord of bindingEntries) {
|
|
370
|
+
const binding = ensureObject(bindingRecord);
|
|
371
|
+
const token = String(binding.token || "").trim();
|
|
372
|
+
const tokenExpression = String(binding.tokenExpression || "").trim();
|
|
373
|
+
const tokenLabel = binding.tokenResolved === true
|
|
374
|
+
? token
|
|
375
|
+
: token || tokenExpression;
|
|
376
|
+
const bindingMethod = String(binding.binding || "").trim();
|
|
377
|
+
const providerName = deriveProviderDisplayName(binding);
|
|
378
|
+
const lifecycle = String(binding.lifecycle || "").trim();
|
|
379
|
+
const lifecycleSuffix = lifecycle && lifecycle !== "unknown" ? ` ${color.dim(`(${lifecycle})`)}` : "";
|
|
380
|
+
const unresolvedSuffix = binding.tokenResolved === true ? "" : color.dim(" [unresolved token]");
|
|
381
|
+
stdout.write(
|
|
382
|
+
`- ${color.item(tokenLabel)} ${color.installed(`[${bindingMethod}]`)} ${color.dim("by")} ${color.item(providerName)}${lifecycleSuffix}${unresolvedSuffix}\n`
|
|
383
|
+
);
|
|
384
|
+
if (options.details) {
|
|
385
|
+
const location = String(binding.location || "").trim();
|
|
386
|
+
if (location) {
|
|
387
|
+
stdout.write(` ${color.dim(`source: ${location}`)}\n`);
|
|
388
|
+
}
|
|
389
|
+
const providerLabel = String(binding.provider || "").trim();
|
|
390
|
+
if (providerLabel) {
|
|
391
|
+
stdout.write(` ${color.dim(`provider: ${providerLabel}`)}\n`);
|
|
392
|
+
}
|
|
393
|
+
if (binding.tokenResolved !== true && tokenExpression) {
|
|
394
|
+
stdout.write(` ${color.dim(`token expression: ${tokenExpression}`)}\n`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
const writeRuntimeProviders = (side, providers) => {
|
|
400
|
+
const sectionSide = String(side || "").trim().toLowerCase();
|
|
401
|
+
const providerEntries = ensureArray(providers);
|
|
402
|
+
if (providerEntries.length < 1) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
stdout.write(`${color.heading(`Runtime ${sectionSide} providers (${providerEntries.length}):`)}\n`);
|
|
407
|
+
for (const provider of providerEntries) {
|
|
408
|
+
const record = ensureObject(provider);
|
|
409
|
+
const entrypoint = String(record.entrypoint || "").trim();
|
|
410
|
+
const exportName = String(record.export || "").trim();
|
|
411
|
+
const label = exportName ? `${entrypoint}#${exportName}` : entrypoint;
|
|
412
|
+
stdout.write(`- ${color.item(label)}\n`);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
const resolvedPackageId = resolvePackageIdInput(id, packageRegistry);
|
|
416
|
+
|
|
417
|
+
if (resolvedPackageId) {
|
|
418
|
+
const packageEntry = packageRegistry.get(resolvedPackageId);
|
|
419
|
+
const descriptor = packageEntry.descriptor;
|
|
420
|
+
const fileWriteGroups = buildFileWriteGroups(ensureArray(ensureObject(descriptor.mutations).files));
|
|
421
|
+
const fileWriteCount = fileWriteGroups.reduce((total, group) => total + ensureArray(group.files).length, 0);
|
|
422
|
+
const capabilities = ensureObject(descriptor.capabilities);
|
|
423
|
+
const runtime = ensureObject(descriptor.runtime);
|
|
424
|
+
const metadata = ensureObject(descriptor.metadata);
|
|
425
|
+
const mutations = ensureObject(descriptor.mutations);
|
|
426
|
+
const runtimeMutations = ensureObject(ensureObject(mutations.dependencies).runtime);
|
|
427
|
+
const devMutations = ensureObject(ensureObject(mutations.dependencies).dev);
|
|
428
|
+
const scriptMutations = ensureObject(ensureObject(mutations.packageJson).scripts);
|
|
429
|
+
const textMutations = ensureArray(mutations.text);
|
|
430
|
+
const packageInsights = await inspectPackageOfferings({ packageEntry });
|
|
431
|
+
const payload = {
|
|
432
|
+
kind: "package",
|
|
433
|
+
packageId: descriptor.packageId,
|
|
434
|
+
version: descriptor.version,
|
|
435
|
+
description: String(descriptor.description || ""),
|
|
436
|
+
dependsOn: ensureArray(descriptor.dependsOn).map((value) => String(value)),
|
|
437
|
+
capabilities,
|
|
438
|
+
options: ensureObject(descriptor.options),
|
|
439
|
+
runtime,
|
|
440
|
+
metadata,
|
|
441
|
+
mutations,
|
|
442
|
+
fileWritePlan: {
|
|
443
|
+
groupCount: fileWriteGroups.length,
|
|
444
|
+
fileCount: fileWriteCount,
|
|
445
|
+
groups: fileWriteGroups
|
|
446
|
+
},
|
|
447
|
+
descriptorPath: packageEntry.descriptorRelativePath,
|
|
448
|
+
introspection: {
|
|
449
|
+
available: Boolean(packageInsights.available),
|
|
450
|
+
notes: ensureArray(packageInsights.notes)
|
|
451
|
+
},
|
|
452
|
+
packageExports: ensureArray(packageInsights.packageExports),
|
|
453
|
+
containerBindings: ensureObject(packageInsights.containerBindings),
|
|
454
|
+
exportedSymbols: ensureArray(packageInsights.exportedSymbols)
|
|
455
|
+
};
|
|
456
|
+
const provides = listDeclaredCapabilities(payload.capabilities, "provides");
|
|
457
|
+
const requires = listDeclaredCapabilities(payload.capabilities, "requires");
|
|
458
|
+
const capabilityDetails = options.details
|
|
459
|
+
? buildCapabilityDetailsForPackage({
|
|
460
|
+
packageRegistry,
|
|
461
|
+
packageId: payload.packageId,
|
|
462
|
+
dependsOn: payload.dependsOn,
|
|
463
|
+
provides,
|
|
464
|
+
requires
|
|
465
|
+
})
|
|
466
|
+
: null;
|
|
467
|
+
if (capabilityDetails) {
|
|
468
|
+
payload.capabilityDetails = capabilityDetails;
|
|
469
|
+
}
|
|
470
|
+
if (options.json) {
|
|
471
|
+
stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
472
|
+
} else {
|
|
473
|
+
const runtimeMutationEntries = Object.entries(runtimeMutations);
|
|
474
|
+
const devMutationEntries = Object.entries(devMutations);
|
|
475
|
+
const scriptMutationEntries = Object.entries(scriptMutations);
|
|
476
|
+
const wrapWidth = resolveWrapWidth(stdout, 80);
|
|
477
|
+
const introspection = ensureObject(payload.introspection);
|
|
478
|
+
const introspectionAvailable = introspection.available === true;
|
|
479
|
+
const introspectionNotes = ensureArray(introspection.notes)
|
|
480
|
+
.map((value) => String(value || "").trim())
|
|
481
|
+
.filter(Boolean);
|
|
482
|
+
const metadataApiSummary = ensureObject(ensureObject(payload.metadata).apiSummary);
|
|
483
|
+
const metadataUi = ensureObject(ensureObject(payload.metadata).ui);
|
|
484
|
+
const summarySurfaces = ensureArray(metadataApiSummary.surfaces)
|
|
485
|
+
.map((entry) => {
|
|
486
|
+
const record = ensureObject(entry);
|
|
487
|
+
return {
|
|
488
|
+
subpath: String(record.subpath || "").trim(),
|
|
489
|
+
summary: String(record.summary || "").trim()
|
|
490
|
+
};
|
|
491
|
+
})
|
|
492
|
+
.filter((entry) => entry.subpath && entry.summary);
|
|
493
|
+
const containerTokenSummary = ensureObject(metadataApiSummary.containerTokens);
|
|
494
|
+
const quickServerTokens = ensureArray(containerTokenSummary.server).map((value) => String(value || "").trim()).filter(Boolean);
|
|
495
|
+
const quickClientTokens = ensureArray(containerTokenSummary.client).map((value) => String(value || "").trim()).filter(Boolean);
|
|
496
|
+
const metadataUiPlacements = ensureObject(metadataUi.placements);
|
|
497
|
+
const placementOutlets = normalizePlacementOutlets(metadataUiPlacements.outlets);
|
|
498
|
+
const placementContributions = normalizePlacementContributions(metadataUiPlacements.contributions);
|
|
499
|
+
const packageExports = ensureArray(payload.packageExports);
|
|
500
|
+
const exportedSymbols = ensureArray(payload.exportedSymbols);
|
|
501
|
+
const exportedSymbolsByFile = new Map(
|
|
502
|
+
exportedSymbols
|
|
503
|
+
.map((entry) => ensureObject(entry))
|
|
504
|
+
.map((entry) => {
|
|
505
|
+
const file = normalizeRelativePosixPath(String(entry.file || "").trim());
|
|
506
|
+
return file ? [file, entry] : null;
|
|
507
|
+
})
|
|
508
|
+
.filter(Boolean)
|
|
509
|
+
);
|
|
510
|
+
const bindingSections = ensureObject(payload.containerBindings);
|
|
511
|
+
const serverBindings = ensureArray(bindingSections.server);
|
|
512
|
+
const clientBindings = ensureArray(bindingSections.client);
|
|
513
|
+
stdout.write(`${color.heading("Information")}\n`);
|
|
514
|
+
writeField("Package", payload.packageId, color.item);
|
|
515
|
+
writeField("Version", payload.version, color.installed);
|
|
516
|
+
if (payload.description) {
|
|
517
|
+
writeField("Description", payload.description);
|
|
518
|
+
}
|
|
519
|
+
writeField("Descriptor", payload.descriptorPath, color.dim);
|
|
520
|
+
if (summarySurfaces.length > 0) {
|
|
521
|
+
stdout.write(`${color.heading("Summary:")}\n`);
|
|
522
|
+
for (const summaryEntry of summarySurfaces) {
|
|
523
|
+
const importPath = formatPackageSubpathImport(payload.packageId, summaryEntry.subpath);
|
|
524
|
+
stdout.write(`- ${color.item(`${importPath}:`)}\n`);
|
|
525
|
+
stdout.write(` ${summaryEntry.summary}\n`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (quickServerTokens.length > 0 || quickClientTokens.length > 0) {
|
|
529
|
+
stdout.write(`${color.heading("Container tokens")} ${color.dim("-- app.make('...'):")}\n`);
|
|
530
|
+
if (quickServerTokens.length > 0) {
|
|
531
|
+
stdout.write(`- ${color.installed("server")}: ${quickServerTokens.map((token) => color.item(token)).join(", ")}\n`);
|
|
532
|
+
}
|
|
533
|
+
if (quickClientTokens.length > 0) {
|
|
534
|
+
stdout.write(`- ${color.installed("client")}: ${quickClientTokens.map((token) => color.item(token)).join(", ")}\n`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (placementOutlets.length > 0) {
|
|
538
|
+
stdout.write(`${color.heading(`Placement outlets (accepted host/position pairs) (${placementOutlets.length}):`)}\n`);
|
|
539
|
+
for (const outlet of placementOutlets) {
|
|
540
|
+
const surfaces = ensureArray(outlet.surfaces).map((value) => String(value || "").trim()).filter(Boolean);
|
|
541
|
+
const surfacesLabel = surfaces.length > 0 ? ` ${color.installed(`[surfaces:${surfaces.join(", ")}]`)}` : "";
|
|
542
|
+
const description = String(outlet.description || "").trim();
|
|
543
|
+
const descriptionSuffix = description ? `: ${description}` : "";
|
|
544
|
+
stdout.write(`- ${color.item(`${outlet.host}.${outlet.position}`)}${surfacesLabel}${descriptionSuffix}\n`);
|
|
545
|
+
if (options.details) {
|
|
546
|
+
const sourceLabel = String(outlet.source || "").trim();
|
|
547
|
+
if (sourceLabel) {
|
|
548
|
+
stdout.write(` ${color.dim(`source: ${sourceLabel}`)}\n`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (placementContributions.length > 0) {
|
|
554
|
+
stdout.write(`${color.heading(`Placement contributions (default entries) (${placementContributions.length}):`)}\n`);
|
|
555
|
+
for (const contribution of placementContributions) {
|
|
556
|
+
const surfaces = ensureArray(contribution.surfaces).map((value) => String(value || "").trim()).filter(Boolean);
|
|
557
|
+
const surfacesLabel = surfaces.length > 0 ? surfaces.join(", ") : "*";
|
|
558
|
+
const orderSuffix = Number.isFinite(contribution.order) ? ` ${color.installed(`[order:${contribution.order}]`)}` : "";
|
|
559
|
+
const componentToken = String(contribution.componentToken || "").trim();
|
|
560
|
+
const componentSuffix = componentToken ? ` ${color.dim(`component:${componentToken}`)}` : "";
|
|
561
|
+
const description = String(contribution.description || "").trim();
|
|
562
|
+
const descriptionSuffix = description ? `: ${description}` : "";
|
|
563
|
+
stdout.write(
|
|
564
|
+
`- ${color.item(contribution.id)} ${color.dim("->")} ${color.item(`${contribution.host}.${contribution.position}`)} ${color.installed(`[surfaces:${surfacesLabel}]`)}${orderSuffix}${componentSuffix}${descriptionSuffix}\n`
|
|
565
|
+
);
|
|
566
|
+
if (options.details) {
|
|
567
|
+
const when = String(contribution.when || "").trim();
|
|
568
|
+
if (when) {
|
|
569
|
+
stdout.write(` ${color.dim(`when: ${when}`)}\n`);
|
|
570
|
+
}
|
|
571
|
+
const sourceLabel = String(contribution.source || "").trim();
|
|
572
|
+
if (sourceLabel) {
|
|
573
|
+
stdout.write(` ${color.dim(`source: ${sourceLabel}`)}\n`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (introspectionAvailable) {
|
|
579
|
+
writeBindingsSection("server", serverBindings);
|
|
580
|
+
writeBindingsSection("client", clientBindings);
|
|
581
|
+
}
|
|
582
|
+
if (introspectionAvailable) {
|
|
583
|
+
stdout.write(`${color.heading(`Package exports (${packageExports.length}):`)}\n`);
|
|
584
|
+
if (packageExports.length < 1) {
|
|
585
|
+
stdout.write(`- ${color.dim("none declared")}\n`);
|
|
586
|
+
} else {
|
|
587
|
+
const symbolDetailsShown = new Set();
|
|
588
|
+
for (const packageExport of packageExports) {
|
|
589
|
+
const record = ensureObject(packageExport);
|
|
590
|
+
const subpath = String(record.subpath || ".").trim() || ".";
|
|
591
|
+
const condition = String(record.condition || "default").trim() || "default";
|
|
592
|
+
const target = String(record.target || "").trim();
|
|
593
|
+
const targetType = String(record.targetType || "").trim();
|
|
594
|
+
const conditionSuffix = condition !== "default" ? ` ${color.installed(`[${condition}]`)}` : "";
|
|
595
|
+
const status = targetType === "file"
|
|
596
|
+
? record.targetExists === true
|
|
597
|
+
? color.installed("[ok]")
|
|
598
|
+
: color.provider("[missing]")
|
|
599
|
+
: targetType === "pattern"
|
|
600
|
+
? color.dim("[pattern]")
|
|
601
|
+
: color.dim("[external]");
|
|
602
|
+
const showTarget = shouldShowPackageExportTarget({ subpath, target, targetType });
|
|
603
|
+
const targetSuffix = showTarget ? ` -> ${color.item(target)}` : "";
|
|
604
|
+
const subpathLabel = options.details ? color.white(subpath) : color.item(subpath);
|
|
605
|
+
stdout.write(`- ${subpathLabel}${conditionSuffix}${targetSuffix} ${status}\n`);
|
|
606
|
+
|
|
607
|
+
if (!options.details) {
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (targetType !== "file" || !target.startsWith("./")) {
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const normalizedTarget = normalizeRelativePosixPath(target.slice(2));
|
|
615
|
+
const summary = ensureObject(exportedSymbolsByFile.get(normalizedTarget));
|
|
616
|
+
if (!summary || Object.keys(summary).length < 1) {
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const detailKey = `${subpath}::${normalizedTarget}`;
|
|
621
|
+
if (symbolDetailsShown.has(detailKey)) {
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
symbolDetailsShown.add(detailKey);
|
|
625
|
+
|
|
626
|
+
const symbols = ensureArray(summary.symbols).map((value) => String(value)).filter(Boolean);
|
|
627
|
+
const classifiedSymbols = classifyExportedSymbols(symbols);
|
|
628
|
+
const writeClassifiedSymbols = (label, entries) => {
|
|
629
|
+
const items = ensureArray(entries).map((entry) => String(entry || "").trim()).filter(Boolean);
|
|
630
|
+
if (items.length < 1) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
writeWrappedItems({
|
|
634
|
+
stdout,
|
|
635
|
+
heading: ` ${color.installed(`${label} (${items.length}):`)}`,
|
|
636
|
+
lineIndent: " ",
|
|
637
|
+
wrapWidth,
|
|
638
|
+
items: items.map((symbol) => ({
|
|
639
|
+
text: symbol,
|
|
640
|
+
rendered: color.item(symbol)
|
|
641
|
+
}))
|
|
642
|
+
});
|
|
643
|
+
};
|
|
644
|
+
writeClassifiedSymbols("providers", classifiedSymbols.providers);
|
|
645
|
+
writeClassifiedSymbols("functions/helpers", classifiedSymbols.functions);
|
|
646
|
+
writeClassifiedSymbols("constants", classifiedSymbols.constants);
|
|
647
|
+
writeClassifiedSymbols("classes/types", classifiedSymbols.classesOrTypes);
|
|
648
|
+
writeClassifiedSymbols("internal/test hooks", classifiedSymbols.internals);
|
|
649
|
+
writeClassifiedSymbols("other symbols", classifiedSymbols.others);
|
|
650
|
+
|
|
651
|
+
if (summary.hasDefaultExport === true) {
|
|
652
|
+
stdout.write(` ${color.installed("default export: yes")}\n`);
|
|
653
|
+
}
|
|
654
|
+
const starReExports = ensureArray(summary.starReExports).map((value) => String(value)).filter(Boolean);
|
|
655
|
+
const namedReExports = ensureArray(summary.namedReExports).map((value) => String(value)).filter(Boolean);
|
|
656
|
+
const reExportSummary = [];
|
|
657
|
+
if (namedReExports.length > 0) {
|
|
658
|
+
reExportSummary.push(`named from ${namedReExports.length} files`);
|
|
659
|
+
}
|
|
660
|
+
if (starReExports.length > 0) {
|
|
661
|
+
reExportSummary.push(`star from ${starReExports.length} files`);
|
|
662
|
+
}
|
|
663
|
+
if (options.debugExports && reExportSummary.length > 0) {
|
|
664
|
+
stdout.write(` ${color.dim(`re-export sources: ${reExportSummary.join(", ")}`)}\n`);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (options.debugExports && starReExports.length > 0) {
|
|
668
|
+
writeWrappedItems({
|
|
669
|
+
stdout,
|
|
670
|
+
heading: ` ${color.installed(`star re-exports (${starReExports.length}):`)}`,
|
|
671
|
+
lineIndent: " ",
|
|
672
|
+
wrapWidth,
|
|
673
|
+
items: starReExports.map((specifier) => ({
|
|
674
|
+
text: specifier,
|
|
675
|
+
rendered: color.item(specifier)
|
|
676
|
+
}))
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
if (options.debugExports && namedReExports.length > 0) {
|
|
680
|
+
writeWrappedItems({
|
|
681
|
+
stdout,
|
|
682
|
+
heading: ` ${color.installed(`named re-exports (${namedReExports.length}):`)}`,
|
|
683
|
+
lineIndent: " ",
|
|
684
|
+
wrapWidth,
|
|
685
|
+
items: namedReExports.map((specifier) => ({
|
|
686
|
+
text: specifier,
|
|
687
|
+
rendered: color.item(specifier)
|
|
688
|
+
}))
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
} else {
|
|
694
|
+
stdout.write(`${color.heading("Code introspection:")}\n`);
|
|
695
|
+
stdout.write(`- ${color.dim("Source files unavailable (descriptor metadata only).")}\n`);
|
|
696
|
+
}
|
|
697
|
+
if (payload.dependsOn.length > 0) {
|
|
698
|
+
writeWrappedItems({
|
|
699
|
+
stdout,
|
|
700
|
+
heading: `${color.heading("Depends on")} ${color.installed(`(${payload.dependsOn.length})`)}:`,
|
|
701
|
+
wrapWidth,
|
|
702
|
+
items: payload.dependsOn.map((dependencyId) => {
|
|
703
|
+
const text = String(dependencyId);
|
|
704
|
+
return {
|
|
705
|
+
text,
|
|
706
|
+
rendered: color.item(text)
|
|
707
|
+
};
|
|
708
|
+
})
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
if (runtimeMutationEntries.length > 0) {
|
|
712
|
+
writeWrappedItems({
|
|
713
|
+
stdout,
|
|
714
|
+
heading: color.heading(`Dependency mutations runtime (${runtimeMutationEntries.length}):`),
|
|
715
|
+
wrapWidth,
|
|
716
|
+
items: runtimeMutationEntries.map(([dependencyId, versionSpec]) => {
|
|
717
|
+
const dependencyText = String(dependencyId);
|
|
718
|
+
const versionText = String(versionSpec);
|
|
719
|
+
return {
|
|
720
|
+
text: `${dependencyText} ${versionText}`,
|
|
721
|
+
rendered: `${color.item(dependencyText)} ${color.installed(versionText)}`
|
|
722
|
+
};
|
|
723
|
+
})
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
if (provides.length > 0 || requires.length > 0) {
|
|
728
|
+
stdout.write(`${color.heading("Capabilities:")}\n`);
|
|
729
|
+
if (provides.length > 0) {
|
|
730
|
+
const providesText = provides.map((capabilityId) => color.item(capabilityId)).join(" ");
|
|
731
|
+
stdout.write(`${color.installed("Provides:")} ${providesText}\n`);
|
|
732
|
+
}
|
|
733
|
+
if (requires.length > 0) {
|
|
734
|
+
const requiresText = requires.map((capabilityId) => color.item(capabilityId)).join(" ");
|
|
735
|
+
stdout.write(`${color.installed("Requires:")} ${requiresText}\n`);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (capabilityDetails && (capabilityDetails.provides.length > 0 || capabilityDetails.requires.length > 0)) {
|
|
739
|
+
const formatPackageSummary = (detail) => {
|
|
740
|
+
const packageId = String(detail?.packageId || "").trim();
|
|
741
|
+
const version = String(detail?.version || "").trim();
|
|
742
|
+
const descriptorPath = String(detail?.descriptorPath || "").trim();
|
|
743
|
+
const versionSuffix = version ? `@${version}` : "";
|
|
744
|
+
const pathSuffix = descriptorPath ? ` [${descriptorPath}]` : "";
|
|
745
|
+
return `${packageId}${versionSuffix}${pathSuffix}`;
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
const writeCapabilityRecord = ({ heading, records, includeDependsOnProviders = false }) => {
|
|
749
|
+
if (records.length < 1) {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
stdout.write(`${color.heading(heading)}\n`);
|
|
753
|
+
for (const record of records) {
|
|
754
|
+
const capabilityId = String(record.capabilityId || "").trim();
|
|
755
|
+
stdout.write(`- ${color.item(capabilityId)}\n`);
|
|
756
|
+
|
|
757
|
+
const providerItems = ensureArray(record.providerDetails).map((detail) => ({
|
|
758
|
+
text: formatPackageSummary(detail),
|
|
759
|
+
rendered: color.item(formatPackageSummary(detail))
|
|
760
|
+
}));
|
|
761
|
+
if (providerItems.length > 0) {
|
|
762
|
+
writeWrappedItems({
|
|
763
|
+
stdout,
|
|
764
|
+
heading: ` ${color.installed(`providers (${providerItems.length}):`)}`,
|
|
765
|
+
lineIndent: " ",
|
|
766
|
+
wrapWidth,
|
|
767
|
+
items: providerItems
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
if (includeDependsOnProviders) {
|
|
772
|
+
const providersInDependsOn = ensureArray(record.providersInDependsOn).map((packageId) => ({
|
|
773
|
+
text: String(packageId),
|
|
774
|
+
rendered: color.item(String(packageId))
|
|
775
|
+
}));
|
|
776
|
+
if (providersInDependsOn.length > 0) {
|
|
777
|
+
writeWrappedItems({
|
|
778
|
+
stdout,
|
|
779
|
+
heading: ` ${color.installed(`providers in dependsOn (${providersInDependsOn.length}):`)}`,
|
|
780
|
+
lineIndent: " ",
|
|
781
|
+
wrapWidth,
|
|
782
|
+
items: providersInDependsOn
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
const requirerItems = ensureArray(record.requirerDetails).map((detail) => ({
|
|
788
|
+
text: formatPackageSummary(detail),
|
|
789
|
+
rendered: color.item(formatPackageSummary(detail))
|
|
790
|
+
}));
|
|
791
|
+
if (requirerItems.length > 0) {
|
|
792
|
+
writeWrappedItems({
|
|
793
|
+
stdout,
|
|
794
|
+
heading: ` ${color.installed(`required by (${requirerItems.length}):`)}`,
|
|
795
|
+
lineIndent: " ",
|
|
796
|
+
wrapWidth,
|
|
797
|
+
items: requirerItems
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
stdout.write(`${color.heading("Capability details:")}\n`);
|
|
804
|
+
writeCapabilityRecord({
|
|
805
|
+
heading: `Provides detail (${capabilityDetails.provides.length}):`,
|
|
806
|
+
records: capabilityDetails.provides,
|
|
807
|
+
includeDependsOnProviders: false
|
|
808
|
+
});
|
|
809
|
+
writeCapabilityRecord({
|
|
810
|
+
heading: `Requires detail (${capabilityDetails.requires.length}):`,
|
|
811
|
+
records: capabilityDetails.requires,
|
|
812
|
+
includeDependsOnProviders: true
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const uiRoutes = ensureArray(ensureObject(payload.metadata.ui).routes);
|
|
817
|
+
if (uiRoutes.length > 0) {
|
|
818
|
+
stdout.write(`${color.heading(`UI routes (${uiRoutes.length}):`)}\n`);
|
|
819
|
+
for (const route of uiRoutes) {
|
|
820
|
+
const record = ensureObject(route);
|
|
821
|
+
const routePath = String(record.path || "").trim();
|
|
822
|
+
const scope = String(record.scope || "").trim();
|
|
823
|
+
const routeId = String(record.id || record.name || "").trim();
|
|
824
|
+
const purpose = String(record.purpose || "").trim();
|
|
825
|
+
const modeLabel = record.autoRegister === false ? "advisory" : "auto";
|
|
826
|
+
const scopeLabel = scope ? ` (${scope})` : "";
|
|
827
|
+
const modePart = ` ${color.installed(`[${modeLabel}]`)}`;
|
|
828
|
+
const purposePart = purpose ? ` ${purpose}` : "";
|
|
829
|
+
const idPart = routeId ? ` ${color.installed(`(id:${routeId})`)}` : "";
|
|
830
|
+
stdout.write(`- ${color.item(routePath)}${color.installed(scopeLabel)}${modePart}${purposePart}${idPart}\n`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
const serverRoutes = ensureArray(ensureObject(payload.metadata.server).routes);
|
|
835
|
+
if (serverRoutes.length > 0) {
|
|
836
|
+
stdout.write(`${color.heading(`Server routes (${serverRoutes.length}):`)}\n`);
|
|
837
|
+
for (const route of serverRoutes) {
|
|
838
|
+
const record = ensureObject(route);
|
|
839
|
+
const method = String(record.method || "").trim().toUpperCase();
|
|
840
|
+
const routePath = String(record.path || "").trim();
|
|
841
|
+
const summary = String(record.summary || "").trim();
|
|
842
|
+
const routeLabel = `${method} ${routePath}`.trim();
|
|
843
|
+
const summarySuffix = summary ? `: ${summary}` : "";
|
|
844
|
+
stdout.write(`- ${color.item(routeLabel)}${summarySuffix}\n`);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
const optionNames = Object.keys(payload.options);
|
|
849
|
+
if (optionNames.length > 0) {
|
|
850
|
+
stdout.write(`${color.heading(`Options (${optionNames.length}):`)}\n`);
|
|
851
|
+
for (const optionName of optionNames) {
|
|
852
|
+
const schema = ensureObject(payload.options[optionName]);
|
|
853
|
+
const required = schema.required ? "required" : "optional";
|
|
854
|
+
const defaultSuffix = schema.defaultValue ? ` (default: ${schema.defaultValue})` : "";
|
|
855
|
+
stdout.write(`- ${color.item(optionName)} ${color.installed(`[${required}]`)}${color.dim(defaultSuffix)}\n`);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
if (devMutationEntries.length > 0) {
|
|
860
|
+
writeWrappedItems({
|
|
861
|
+
stdout,
|
|
862
|
+
heading: color.heading(`Dependency mutations dev (${devMutationEntries.length}):`),
|
|
863
|
+
wrapWidth,
|
|
864
|
+
items: devMutationEntries.map(([dependencyId, versionSpec]) => {
|
|
865
|
+
const dependencyText = String(dependencyId);
|
|
866
|
+
const versionText = String(versionSpec);
|
|
867
|
+
return {
|
|
868
|
+
text: `${dependencyText} ${versionText}`,
|
|
869
|
+
rendered: `${color.item(dependencyText)} ${color.installed(versionText)}`
|
|
870
|
+
};
|
|
871
|
+
})
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
if (scriptMutationEntries.length > 0) {
|
|
875
|
+
stdout.write(`${color.heading(`Script mutations (${scriptMutationEntries.length}):`)}\n`);
|
|
876
|
+
for (const [scriptName, scriptValue] of scriptMutationEntries) {
|
|
877
|
+
stdout.write(`- ${color.item(scriptName)}: ${String(scriptValue)}\n`);
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
if (textMutations.length > 0) {
|
|
881
|
+
stdout.write(`${color.heading(`Text mutations (${textMutations.length}):`)}\n`);
|
|
882
|
+
for (const mutation of textMutations) {
|
|
883
|
+
const record = ensureObject(mutation);
|
|
884
|
+
const op = String(record.op || "").trim();
|
|
885
|
+
const file = String(record.file || "").trim();
|
|
886
|
+
const key = String(record.key || "").trim();
|
|
887
|
+
const position = String(record.position || "").trim();
|
|
888
|
+
const reason = String(record.reason || "").trim();
|
|
889
|
+
const reasonSuffix = reason ? `: ${reason}` : "";
|
|
890
|
+
let mutationLabel = `${op} ${file} ${key}`.trim();
|
|
891
|
+
if (op === "append-text") {
|
|
892
|
+
mutationLabel = `${op} ${file}`;
|
|
893
|
+
if (position) {
|
|
894
|
+
mutationLabel = `${mutationLabel} [${position}]`;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
stdout.write(`- ${color.item(mutationLabel)}${reasonSuffix}\n`);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
if (payload.fileWritePlan.fileCount > 0) {
|
|
902
|
+
stdout.write(`${color.heading(`File writes (${payload.fileWritePlan.fileCount}):`)}\n`);
|
|
903
|
+
for (const group of payload.fileWritePlan.groups) {
|
|
904
|
+
const groupId = String(group.id || "").trim();
|
|
905
|
+
const category = String(group.category || "").trim();
|
|
906
|
+
const reason = String(group.reason || "").trim();
|
|
907
|
+
const files = ensureArray(group.files);
|
|
908
|
+
let marker = "";
|
|
909
|
+
if (groupId) {
|
|
910
|
+
marker = `id:${groupId}`;
|
|
911
|
+
} else if (category) {
|
|
912
|
+
marker = `category:${category}`;
|
|
913
|
+
}
|
|
914
|
+
const markerSuffix = marker ? ` (${marker})` : "";
|
|
915
|
+
for (const file of files) {
|
|
916
|
+
const targetPath = String(ensureObject(file).to || "").trim();
|
|
917
|
+
if (!targetPath) {
|
|
918
|
+
continue;
|
|
919
|
+
}
|
|
920
|
+
stdout.write(`- ${color.item(targetPath)}${color.installed(markerSuffix)}:\n`);
|
|
921
|
+
if (reason) {
|
|
922
|
+
stdout.write(` ${reason}\n`);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const serverProviders = ensureArray(ensureObject(payload.runtime.server).providers);
|
|
929
|
+
const clientProviders = ensureArray(ensureObject(payload.runtime.client).providers);
|
|
930
|
+
writeRuntimeProviders("server", serverProviders);
|
|
931
|
+
writeRuntimeProviders("client", clientProviders);
|
|
932
|
+
if (introspectionNotes.length > 0) {
|
|
933
|
+
stdout.write(`${color.heading(`Introspection notes (${introspectionNotes.length}):`)}\n`);
|
|
934
|
+
for (const note of introspectionNotes) {
|
|
935
|
+
stdout.write(`- ${color.dim(note)}\n`);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return 0;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
if (bundleRegistry.has(id)) {
|
|
943
|
+
const bundle = bundleRegistry.get(id);
|
|
944
|
+
const payload = {
|
|
945
|
+
kind: "bundle",
|
|
946
|
+
bundleId: bundle.bundleId,
|
|
947
|
+
version: bundle.version,
|
|
948
|
+
description: String(bundle.description || ""),
|
|
949
|
+
provider: Number(bundle.provider) === 1,
|
|
950
|
+
curated: Number(bundle.curated) === 1,
|
|
951
|
+
packages: ensureArray(bundle.packages).map((value) => String(value))
|
|
952
|
+
};
|
|
953
|
+
if (options.json) {
|
|
954
|
+
stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
955
|
+
} else {
|
|
956
|
+
stdout.write(`${color.heading("Information")}\n`);
|
|
957
|
+
writeField("Bundle", payload.bundleId, color.item);
|
|
958
|
+
writeField("Version", payload.version, color.installed);
|
|
959
|
+
if (payload.description) {
|
|
960
|
+
writeField("Description", payload.description);
|
|
961
|
+
}
|
|
962
|
+
stdout.write(`${color.heading(`Packages (${payload.packages.length}):`)}\n`);
|
|
963
|
+
for (const packageId of payload.packages) {
|
|
964
|
+
stdout.write(`- ${color.item(packageId)}\n`);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return 0;
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
throw createCliError(`Unknown package or bundle: ${id}`);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
async function commandCreate({ positional, options, cwd, io }) {
|
|
974
|
+
const targetType = String(positional[0] || "").trim();
|
|
975
|
+
const rawName = String(positional[1] || "").trim();
|
|
976
|
+
if (targetType !== "package" || !rawName) {
|
|
977
|
+
throw createCliError("create requires: create package <name>", { showUsage: true });
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
const appRoot = await resolveAppRootFromCwd(cwd);
|
|
981
|
+
const { packageJsonPath, packageJson } = await loadAppPackageJson(appRoot);
|
|
982
|
+
const { lockPath, lock } = await loadLockFile(appRoot);
|
|
983
|
+
const installedPackages = ensureObject(lock.installedPackages);
|
|
984
|
+
const dependencies = ensureObject(packageJson.dependencies);
|
|
985
|
+
const devDependencies = ensureObject(packageJson.devDependencies);
|
|
986
|
+
|
|
987
|
+
const { packageId, packageDirName } = resolveLocalPackageId({
|
|
988
|
+
rawName,
|
|
989
|
+
appPackageName: packageJson.name,
|
|
990
|
+
inlineOptions: options.inlineOptions
|
|
991
|
+
});
|
|
992
|
+
const localPackagesRoot = path.join(appRoot, "packages");
|
|
993
|
+
const packageRoot = path.join(localPackagesRoot, packageDirName);
|
|
994
|
+
const packageRelativePath = normalizeRelativePath(appRoot, packageRoot);
|
|
995
|
+
const descriptorRelativePath = `${normalizeRelativePosixPath(packageRelativePath)}/package.descriptor.mjs`;
|
|
996
|
+
const localDependencySpecifier = toFileDependencySpecifier(packageRelativePath);
|
|
997
|
+
const packageDescription = String(options.inlineOptions.description || "").trim() || `App-local package ${packageId}.`;
|
|
998
|
+
|
|
999
|
+
if (await fileExists(packageRoot)) {
|
|
1000
|
+
throw createCliError(`Package directory already exists: ${normalizeRelativePath(appRoot, packageRoot)}`);
|
|
1001
|
+
}
|
|
1002
|
+
if (Object.prototype.hasOwnProperty.call(installedPackages, packageId)) {
|
|
1003
|
+
throw createCliError(`Package is already present in lock file: ${packageId}`);
|
|
1004
|
+
}
|
|
1005
|
+
if (Object.prototype.hasOwnProperty.call(dependencies, packageId)) {
|
|
1006
|
+
throw createCliError(`package.json dependencies already contains ${packageId}.`);
|
|
1007
|
+
}
|
|
1008
|
+
if (Object.prototype.hasOwnProperty.call(devDependencies, packageId)) {
|
|
1009
|
+
throw createCliError(`package.json devDependencies already contains ${packageId}.`);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const scaffoldFiles = createLocalPackageScaffoldFiles({
|
|
1013
|
+
packageId,
|
|
1014
|
+
packageDescription
|
|
1015
|
+
});
|
|
1016
|
+
const touchedFiles = new Set(["package.json", normalizeRelativePath(appRoot, lockPath)]);
|
|
1017
|
+
for (const scaffoldFile of scaffoldFiles) {
|
|
1018
|
+
touchedFiles.add(`${normalizeRelativePosixPath(packageRelativePath)}/${normalizeRelativePosixPath(scaffoldFile.relativePath)}`);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
if (!options.dryRun) {
|
|
1022
|
+
for (const scaffoldFile of scaffoldFiles) {
|
|
1023
|
+
const absoluteFilePath = path.join(packageRoot, scaffoldFile.relativePath);
|
|
1024
|
+
await mkdir(path.dirname(absoluteFilePath), { recursive: true });
|
|
1025
|
+
await writeFile(absoluteFilePath, String(scaffoldFile.content || ""), "utf8");
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
const dependencyApplied = applyPackageJsonField(packageJson, "dependencies", packageId, localDependencySpecifier);
|
|
1030
|
+
const managedRecord = {
|
|
1031
|
+
packageId,
|
|
1032
|
+
version: "0.1.0",
|
|
1033
|
+
source: {
|
|
1034
|
+
type: "local-package",
|
|
1035
|
+
packagePath: normalizeRelativePosixPath(packageRelativePath),
|
|
1036
|
+
descriptorPath: descriptorRelativePath
|
|
1037
|
+
},
|
|
1038
|
+
managed: {
|
|
1039
|
+
packageJson: {
|
|
1040
|
+
dependencies: {},
|
|
1041
|
+
devDependencies: {},
|
|
1042
|
+
scripts: {}
|
|
1043
|
+
},
|
|
1044
|
+
text: {},
|
|
1045
|
+
vite: {},
|
|
1046
|
+
files: [],
|
|
1047
|
+
migrations: []
|
|
1048
|
+
},
|
|
1049
|
+
options: {},
|
|
1050
|
+
installedAt: new Date().toISOString()
|
|
1051
|
+
};
|
|
1052
|
+
if (dependencyApplied.changed) {
|
|
1053
|
+
managedRecord.managed.packageJson.dependencies[packageId] = dependencyApplied.managed;
|
|
1054
|
+
}
|
|
1055
|
+
lock.installedPackages[packageId] = managedRecord;
|
|
1056
|
+
|
|
1057
|
+
const touchedFileList = sortStrings([...touchedFiles]);
|
|
1058
|
+
if (!options.dryRun) {
|
|
1059
|
+
await writeJsonFile(packageJsonPath, packageJson);
|
|
1060
|
+
await writeJsonFile(lockPath, lock);
|
|
1061
|
+
if (!options.noInstall) {
|
|
1062
|
+
await runNpmInstall(appRoot, io.stderr);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if (options.json) {
|
|
1067
|
+
io.stdout.write(
|
|
1068
|
+
`${JSON.stringify(
|
|
1069
|
+
{
|
|
1070
|
+
targetType: "package",
|
|
1071
|
+
packageId,
|
|
1072
|
+
packageDirectory: normalizeRelativePosixPath(packageRelativePath),
|
|
1073
|
+
descriptorPath: descriptorRelativePath,
|
|
1074
|
+
dependency: localDependencySpecifier,
|
|
1075
|
+
touchedFiles: touchedFileList,
|
|
1076
|
+
lockPath: normalizeRelativePath(appRoot, lockPath),
|
|
1077
|
+
dryRun: options.dryRun
|
|
1078
|
+
},
|
|
1079
|
+
null,
|
|
1080
|
+
2
|
|
1081
|
+
)}\n`
|
|
1082
|
+
);
|
|
1083
|
+
} else {
|
|
1084
|
+
io.stdout.write(`Created local package ${packageId}.\n`);
|
|
1085
|
+
io.stdout.write(`Directory: ${normalizeRelativePosixPath(packageRelativePath)}\n`);
|
|
1086
|
+
io.stdout.write(`Dependency: ${packageId} -> ${localDependencySpecifier}\n`);
|
|
1087
|
+
io.stdout.write(`Descriptor: ${descriptorRelativePath}\n`);
|
|
1088
|
+
io.stdout.write(`Touched files (${touchedFileList.length}):\n`);
|
|
1089
|
+
for (const touchedFile of touchedFileList) {
|
|
1090
|
+
io.stdout.write(`- ${touchedFile}\n`);
|
|
1091
|
+
}
|
|
1092
|
+
io.stdout.write(`Lock file: ${normalizeRelativePath(appRoot, lockPath)}\n`);
|
|
1093
|
+
if (options.dryRun) {
|
|
1094
|
+
io.stdout.write("Dry run enabled: no files were written.\n");
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
return 0;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
async function commandAdd({ positional, options, cwd, io }) {
|
|
1102
|
+
const targetType = String(positional[0] || "").trim();
|
|
1103
|
+
const targetId = String(positional[1] || "").trim();
|
|
1104
|
+
|
|
1105
|
+
if (!targetType || !targetId) {
|
|
1106
|
+
throw createCliError("add requires target type and id (add bundle <id> | add package <id>).", {
|
|
1107
|
+
showUsage: true
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
if (targetType !== "bundle" && targetType !== "package") {
|
|
1111
|
+
throw createCliError(`Unsupported add target type: ${targetType}`, { showUsage: true });
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
const appRoot = await resolveAppRootFromCwd(cwd);
|
|
1115
|
+
const packageRegistry = await loadPackageRegistry();
|
|
1116
|
+
const appLocalRegistry = await loadAppLocalPackageRegistry(appRoot);
|
|
1117
|
+
const bundleRegistry = await loadBundleRegistry();
|
|
1118
|
+
const combinedPackageRegistry = mergePackageRegistries(packageRegistry, appLocalRegistry);
|
|
1119
|
+
const { packageJsonPath, packageJson } = await loadAppPackageJson(appRoot);
|
|
1120
|
+
const { lockPath, lock } = await loadLockFile(appRoot);
|
|
1121
|
+
let resolvedTargetPackageId = targetType === "package" ? resolvePackageIdInput(targetId, combinedPackageRegistry) : "";
|
|
1122
|
+
if (targetType === "package" && !resolvedTargetPackageId) {
|
|
1123
|
+
const installedNodeModuleEntry = await resolveInstalledNodeModulePackageEntry({
|
|
1124
|
+
appRoot,
|
|
1125
|
+
packageId: targetId
|
|
1126
|
+
});
|
|
1127
|
+
if (installedNodeModuleEntry) {
|
|
1128
|
+
combinedPackageRegistry.set(installedNodeModuleEntry.packageId, installedNodeModuleEntry);
|
|
1129
|
+
resolvedTargetPackageId = installedNodeModuleEntry.packageId;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
const targetPackageIds = targetType === "bundle"
|
|
1134
|
+
? ensureArray(bundleRegistry.get(targetId)?.packages).map((value) => String(value))
|
|
1135
|
+
: [resolvedTargetPackageId];
|
|
1136
|
+
if (targetType === "bundle" && targetPackageIds.length === 0) {
|
|
1137
|
+
throw createCliError(`Unknown bundle: ${targetId}`);
|
|
1138
|
+
}
|
|
1139
|
+
if (targetType === "package" && !resolvedTargetPackageId) {
|
|
1140
|
+
throw createCliError(
|
|
1141
|
+
`Unknown package: ${targetId}. Install an external module first (npm install ${targetId}) if you want to adopt it into lock.`
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
await hydratePackageRegistryFromInstalledNodeModules({
|
|
1146
|
+
appRoot,
|
|
1147
|
+
packageRegistry: combinedPackageRegistry,
|
|
1148
|
+
seedPackageIds: targetPackageIds
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
if (targetType === "package") {
|
|
1152
|
+
const targetPackageEntry = combinedPackageRegistry.get(resolvedTargetPackageId);
|
|
1153
|
+
if (!targetPackageEntry) {
|
|
1154
|
+
throw createCliError(`Unknown package: ${targetId}`);
|
|
1155
|
+
}
|
|
1156
|
+
validateInlineOptionsForPackage(targetPackageEntry, options.inlineOptions);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
const { ordered: resolvedPackageIds, externalDependencies } = resolveLocalDependencyOrder(
|
|
1160
|
+
targetPackageIds,
|
|
1161
|
+
combinedPackageRegistry
|
|
1162
|
+
);
|
|
1163
|
+
const plannedInstalledPackageIds = sortStrings([
|
|
1164
|
+
...new Set([
|
|
1165
|
+
...Object.keys(ensureObject(lock.installedPackages)).map((value) => String(value || "").trim()).filter(Boolean),
|
|
1166
|
+
...resolvedPackageIds
|
|
1167
|
+
])
|
|
1168
|
+
]);
|
|
1169
|
+
validatePlannedCapabilityClosure(
|
|
1170
|
+
plannedInstalledPackageIds,
|
|
1171
|
+
combinedPackageRegistry,
|
|
1172
|
+
`add ${targetType} ${targetId}`
|
|
1173
|
+
);
|
|
1174
|
+
|
|
1175
|
+
if (targetType === "bundle") {
|
|
1176
|
+
validateInlineOptionsForBundle({
|
|
1177
|
+
bundleId: targetId,
|
|
1178
|
+
inlineOptions: options.inlineOptions,
|
|
1179
|
+
packageIds: resolvedPackageIds,
|
|
1180
|
+
packageRegistry: combinedPackageRegistry
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
const packagesToInstall = [];
|
|
1185
|
+
const resolvedOptionsByPackage = {};
|
|
1186
|
+
const forceReapplyTarget = options?.forceReapplyTarget === true;
|
|
1187
|
+
const hasInlineOptions = Object.keys(ensureObject(options.inlineOptions)).length > 0;
|
|
1188
|
+
for (const packageId of resolvedPackageIds) {
|
|
1189
|
+
const packageEntry = combinedPackageRegistry.get(packageId);
|
|
1190
|
+
const existingInstall = ensureObject(lock.installedPackages[packageId]);
|
|
1191
|
+
const existingVersion = String(existingInstall.version || "").trim();
|
|
1192
|
+
const isDirectTargetPackage = targetType === "package" && packageId === resolvedTargetPackageId;
|
|
1193
|
+
const packageInlineOptions = targetType === "bundle"
|
|
1194
|
+
? resolveBundleInlineOptionsForPackage(packageEntry, options.inlineOptions)
|
|
1195
|
+
: isDirectTargetPackage
|
|
1196
|
+
? ensureObject(options.inlineOptions)
|
|
1197
|
+
: {};
|
|
1198
|
+
const hasPackageInlineOptions = Object.keys(packageInlineOptions).length > 0;
|
|
1199
|
+
const shouldReapplyInstalledPackage =
|
|
1200
|
+
(isDirectTargetPackage && (forceReapplyTarget || hasInlineOptions)) ||
|
|
1201
|
+
(targetType === "bundle" && hasPackageInlineOptions);
|
|
1202
|
+
if (existingVersion && existingVersion === packageEntry.version && !shouldReapplyInstalledPackage) {
|
|
1203
|
+
continue;
|
|
1204
|
+
}
|
|
1205
|
+
packagesToInstall.push(packageId);
|
|
1206
|
+
const lockEntryOptions = ensureObject(existingInstall.options);
|
|
1207
|
+
resolvedOptionsByPackage[packageId] = await resolvePackageOptions(
|
|
1208
|
+
packageEntry,
|
|
1209
|
+
{
|
|
1210
|
+
...lockEntryOptions,
|
|
1211
|
+
...packageInlineOptions
|
|
1212
|
+
},
|
|
1213
|
+
io,
|
|
1214
|
+
{ appRoot }
|
|
1215
|
+
);
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
const touchedFiles = new Set();
|
|
1219
|
+
const installedPackageRecords = [];
|
|
1220
|
+
|
|
1221
|
+
for (const packageId of packagesToInstall) {
|
|
1222
|
+
const packageEntry = combinedPackageRegistry.get(packageId);
|
|
1223
|
+
const managedRecord = await applyPackageInstall({
|
|
1224
|
+
packageEntry,
|
|
1225
|
+
packageOptions: resolvedOptionsByPackage[packageId],
|
|
1226
|
+
appRoot,
|
|
1227
|
+
appPackageJson: packageJson,
|
|
1228
|
+
lock,
|
|
1229
|
+
packageRegistry: combinedPackageRegistry,
|
|
1230
|
+
touchedFiles
|
|
1231
|
+
});
|
|
1232
|
+
installedPackageRecords.push(managedRecord);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
const {
|
|
1236
|
+
appLocalRegistry: refreshedAppLocalRegistry,
|
|
1237
|
+
adoptedPackageIds
|
|
1238
|
+
} = await adoptAppLocalPackageDependencies({
|
|
1239
|
+
appRoot,
|
|
1240
|
+
appPackageJson: packageJson,
|
|
1241
|
+
lock
|
|
1242
|
+
});
|
|
1243
|
+
for (const [packageId, packageEntry] of refreshedAppLocalRegistry.entries()) {
|
|
1244
|
+
combinedPackageRegistry.set(packageId, packageEntry);
|
|
1245
|
+
}
|
|
1246
|
+
if (adoptedPackageIds.length > 0) {
|
|
1247
|
+
const postInstallPackageIds = sortStrings(Object.keys(ensureObject(lock.installedPackages)));
|
|
1248
|
+
validatePlannedCapabilityClosure(
|
|
1249
|
+
postInstallPackageIds,
|
|
1250
|
+
combinedPackageRegistry,
|
|
1251
|
+
`add ${targetType} ${targetId}`
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
const finalResolvedPackageIds = sortStrings([...resolvedPackageIds, ...adoptedPackageIds]);
|
|
1256
|
+
|
|
1257
|
+
const touchedFileList = sortStrings([...touchedFiles]);
|
|
1258
|
+
const successLabel = targetType === "bundle" ? "Added bundle" : "Added package";
|
|
1259
|
+
const installWarnings = installedPackageRecords
|
|
1260
|
+
.flatMap((record) => ensureArray(ensureObject(record).warnings))
|
|
1261
|
+
.map((value) => String(value || "").trim())
|
|
1262
|
+
.filter(Boolean);
|
|
1263
|
+
|
|
1264
|
+
if (!options.dryRun) {
|
|
1265
|
+
await writeJsonFile(packageJsonPath, packageJson);
|
|
1266
|
+
await writeJsonFile(lockPath, lock);
|
|
1267
|
+
if (!options.noInstall) {
|
|
1268
|
+
await runNpmInstall(appRoot, io.stderr);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
if (options.json) {
|
|
1273
|
+
io.stdout.write(`${JSON.stringify({
|
|
1274
|
+
targetType,
|
|
1275
|
+
targetId,
|
|
1276
|
+
resolvedPackages: finalResolvedPackageIds,
|
|
1277
|
+
touchedFiles: touchedFileList,
|
|
1278
|
+
lockPath: normalizeRelativePath(appRoot, lockPath),
|
|
1279
|
+
externalDependencies,
|
|
1280
|
+
dryRun: options.dryRun,
|
|
1281
|
+
installed: installedPackageRecords,
|
|
1282
|
+
warnings: installWarnings
|
|
1283
|
+
}, null, 2)}\n`);
|
|
1284
|
+
} else {
|
|
1285
|
+
io.stdout.write(
|
|
1286
|
+
`${renderResolvedSummary(
|
|
1287
|
+
`${successLabel}`,
|
|
1288
|
+
targetId,
|
|
1289
|
+
finalResolvedPackageIds,
|
|
1290
|
+
touchedFileList,
|
|
1291
|
+
appRoot,
|
|
1292
|
+
lockPath,
|
|
1293
|
+
externalDependencies
|
|
1294
|
+
)}\n`
|
|
1295
|
+
);
|
|
1296
|
+
if (installWarnings.length > 0) {
|
|
1297
|
+
io.stdout.write(`Warnings (${installWarnings.length}):\n`);
|
|
1298
|
+
for (const warning of installWarnings) {
|
|
1299
|
+
io.stdout.write(`- ${warning}\n`);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
if (options.dryRun) {
|
|
1303
|
+
io.stdout.write("Dry run enabled: no files were written.\n");
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
return 0;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
async function commandUpdate({ positional, options, cwd, io }) {
|
|
1311
|
+
const targetType = String(positional[0] || "").trim();
|
|
1312
|
+
const targetId = String(positional[1] || "").trim();
|
|
1313
|
+
if (targetType !== "package" || !targetId) {
|
|
1314
|
+
throw createCliError("update requires: update package <packageId>", { showUsage: true });
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
const appRoot = await resolveAppRootFromCwd(cwd);
|
|
1318
|
+
const { lock } = await loadLockFile(appRoot);
|
|
1319
|
+
const installedPackages = ensureObject(lock.installedPackages);
|
|
1320
|
+
const resolvedTargetId = resolveInstalledPackageIdInput(targetId, installedPackages);
|
|
1321
|
+
if (!resolvedTargetId) {
|
|
1322
|
+
throw createCliError(`Package is not installed: ${targetId}`);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
return commandAdd({
|
|
1326
|
+
positional: ["package", resolvedTargetId],
|
|
1327
|
+
options: {
|
|
1328
|
+
...options,
|
|
1329
|
+
forceReapplyTarget: true
|
|
1330
|
+
},
|
|
1331
|
+
cwd,
|
|
1332
|
+
io
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
async function commandPosition({ positional, options, cwd, io }) {
|
|
1337
|
+
const targetType = String(positional[0] || "").trim();
|
|
1338
|
+
const targetId = String(positional[1] || "").trim();
|
|
1339
|
+
if (targetType !== "element" || !targetId) {
|
|
1340
|
+
throw createCliError("position requires: position element <packageId>", { showUsage: true });
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
const appRoot = await resolveAppRootFromCwd(cwd);
|
|
1344
|
+
const packageRegistry = await loadPackageRegistry();
|
|
1345
|
+
const appLocalRegistry = await loadAppLocalPackageRegistry(appRoot);
|
|
1346
|
+
const combinedPackageRegistry = mergePackageRegistries(packageRegistry, appLocalRegistry);
|
|
1347
|
+
const { lockPath, lock } = await loadLockFile(appRoot);
|
|
1348
|
+
const installedPackages = ensureObject(lock.installedPackages);
|
|
1349
|
+
const resolvedTargetId = resolveInstalledPackageIdInput(targetId, installedPackages);
|
|
1350
|
+
if (!resolvedTargetId) {
|
|
1351
|
+
throw createCliError(`Element is not installed: ${targetId}`);
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
await hydratePackageRegistryFromInstalledNodeModules({
|
|
1355
|
+
appRoot,
|
|
1356
|
+
packageRegistry: combinedPackageRegistry,
|
|
1357
|
+
seedPackageIds: [resolvedTargetId]
|
|
1358
|
+
});
|
|
1359
|
+
const packageEntry = combinedPackageRegistry.get(resolvedTargetId);
|
|
1360
|
+
if (!packageEntry) {
|
|
1361
|
+
throw createCliError(
|
|
1362
|
+
`Installed element descriptor not found: ${resolvedTargetId}. Ensure it exists in catalog, app packages/, or node_modules.`
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
validateInlineOptionsForPackage(packageEntry, options.inlineOptions);
|
|
1366
|
+
|
|
1367
|
+
const installedRecord = ensureObject(installedPackages[resolvedTargetId]);
|
|
1368
|
+
const resolvedOptions = await resolvePackageOptions(
|
|
1369
|
+
packageEntry,
|
|
1370
|
+
{
|
|
1371
|
+
...ensureObject(installedRecord.options),
|
|
1372
|
+
...ensureObject(options.inlineOptions)
|
|
1373
|
+
},
|
|
1374
|
+
io,
|
|
1375
|
+
{ appRoot }
|
|
1376
|
+
);
|
|
1377
|
+
|
|
1378
|
+
const touchedFiles = new Set();
|
|
1379
|
+
const positionedRecord = await applyPackagePositioning({
|
|
1380
|
+
packageEntry,
|
|
1381
|
+
packageOptions: resolvedOptions,
|
|
1382
|
+
appRoot,
|
|
1383
|
+
lock,
|
|
1384
|
+
touchedFiles
|
|
1385
|
+
});
|
|
1386
|
+
const touchedFileList = sortStrings([...touchedFiles]);
|
|
1387
|
+
|
|
1388
|
+
if (!options.dryRun) {
|
|
1389
|
+
await writeJsonFile(lockPath, lock);
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
if (options.json) {
|
|
1393
|
+
io.stdout.write(`${JSON.stringify({
|
|
1394
|
+
targetType: "element",
|
|
1395
|
+
elementId: resolvedTargetId,
|
|
1396
|
+
packageId: resolvedTargetId,
|
|
1397
|
+
touchedFiles: touchedFileList,
|
|
1398
|
+
lockPath: normalizeRelativePath(appRoot, lockPath),
|
|
1399
|
+
dryRun: options.dryRun,
|
|
1400
|
+
positioned: positionedRecord
|
|
1401
|
+
}, null, 2)}\n`);
|
|
1402
|
+
} else {
|
|
1403
|
+
io.stdout.write(`Positioned element ${resolvedTargetId}.\n`);
|
|
1404
|
+
io.stdout.write(`Touched files (${touchedFileList.length}):\n`);
|
|
1405
|
+
for (const touchedFile of touchedFileList) {
|
|
1406
|
+
io.stdout.write(`- ${touchedFile}\n`);
|
|
1407
|
+
}
|
|
1408
|
+
io.stdout.write(`Lock file: ${normalizeRelativePath(appRoot, lockPath)}\n`);
|
|
1409
|
+
if (options.dryRun) {
|
|
1410
|
+
io.stdout.write("Dry run enabled: no files were written.\n");
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
return 0;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
async function commandRemove({ positional, options, cwd, io }) {
|
|
1418
|
+
const targetType = String(positional[0] || "").trim();
|
|
1419
|
+
const targetId = String(positional[1] || "").trim();
|
|
1420
|
+
if (targetType !== "package" || !targetId) {
|
|
1421
|
+
throw createCliError("remove requires: remove package <packageId>", { showUsage: true });
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
const appRoot = await resolveAppRootFromCwd(cwd);
|
|
1425
|
+
const packageRegistry = await loadPackageRegistry();
|
|
1426
|
+
const appLocalRegistry = await loadAppLocalPackageRegistry(appRoot);
|
|
1427
|
+
const combinedPackageRegistry = mergePackageRegistries(packageRegistry, appLocalRegistry);
|
|
1428
|
+
const { packageJsonPath, packageJson } = await loadAppPackageJson(appRoot);
|
|
1429
|
+
const { lockPath, lock } = await loadLockFile(appRoot);
|
|
1430
|
+
const installed = ensureObject(lock.installedPackages);
|
|
1431
|
+
await hydratePackageRegistryFromInstalledNodeModules({
|
|
1432
|
+
appRoot,
|
|
1433
|
+
packageRegistry: combinedPackageRegistry,
|
|
1434
|
+
seedPackageIds: Object.keys(installed)
|
|
1435
|
+
});
|
|
1436
|
+
const resolvedTargetId = resolveInstalledPackageIdInput(targetId, installed);
|
|
1437
|
+
|
|
1438
|
+
if (!resolvedTargetId) {
|
|
1439
|
+
throw createCliError(`Package is not installed: ${targetId}`);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
const dependents = getInstalledDependents(lock, resolvedTargetId, combinedPackageRegistry);
|
|
1443
|
+
if (dependents.length > 0) {
|
|
1444
|
+
throw createCliError(
|
|
1445
|
+
`Cannot remove ${resolvedTargetId}; installed packages depend on it: ${dependents.join(", ")}`
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
const lockEntry = ensureObject(installed[resolvedTargetId]);
|
|
1450
|
+
const managed = ensureObject(lockEntry.managed);
|
|
1451
|
+
const touchedFiles = new Set();
|
|
1452
|
+
|
|
1453
|
+
const managedPackageJson = ensureObject(managed.packageJson);
|
|
1454
|
+
for (const [dependencyId, managedChange] of Object.entries(ensureObject(managedPackageJson.dependencies))) {
|
|
1455
|
+
if (restorePackageJsonField(packageJson, "dependencies", dependencyId, managedChange)) {
|
|
1456
|
+
touchedFiles.add("package.json");
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
for (const [dependencyId, managedChange] of Object.entries(ensureObject(managedPackageJson.devDependencies))) {
|
|
1460
|
+
if (restorePackageJsonField(packageJson, "devDependencies", dependencyId, managedChange)) {
|
|
1461
|
+
touchedFiles.add("package.json");
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
for (const [scriptName, managedChange] of Object.entries(ensureObject(managedPackageJson.scripts))) {
|
|
1465
|
+
if (restorePackageJsonField(packageJson, "scripts", scriptName, managedChange)) {
|
|
1466
|
+
touchedFiles.add("package.json");
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
const managedText = ensureObject(managed.text);
|
|
1471
|
+
for (const change of Object.values(managedText)) {
|
|
1472
|
+
const changeRecord = ensureObject(change);
|
|
1473
|
+
if (String(changeRecord.op || "") !== "upsert-env") {
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
const relativeFile = String(changeRecord.file || "").trim();
|
|
1477
|
+
if (!relativeFile) {
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
const absoluteFile = path.join(appRoot, relativeFile);
|
|
1481
|
+
const existing = await readFileBufferIfExists(absoluteFile);
|
|
1482
|
+
if (!existing.exists) {
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
const updated = removeEnvValue(
|
|
1486
|
+
existing.buffer.toString("utf8"),
|
|
1487
|
+
String(changeRecord.key || ""),
|
|
1488
|
+
String(changeRecord.value || ""),
|
|
1489
|
+
{
|
|
1490
|
+
hadPrevious: Boolean(changeRecord.hadPrevious),
|
|
1491
|
+
previousValue: String(changeRecord.previousValue || "")
|
|
1492
|
+
}
|
|
1493
|
+
);
|
|
1494
|
+
if (updated.changed) {
|
|
1495
|
+
await writeFile(absoluteFile, updated.content, "utf8");
|
|
1496
|
+
touchedFiles.add(normalizeRelativePath(appRoot, absoluteFile));
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
await removeManagedViteProxyEntries({
|
|
1501
|
+
appRoot,
|
|
1502
|
+
packageId: resolvedTargetId,
|
|
1503
|
+
managedViteChanges: ensureObject(managed.vite),
|
|
1504
|
+
touchedFiles
|
|
1505
|
+
});
|
|
1506
|
+
|
|
1507
|
+
for (const fileChange of ensureArray(managed.files)) {
|
|
1508
|
+
const changeRecord = ensureObject(fileChange);
|
|
1509
|
+
if (changeRecord.preserveOnRemove === true) {
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
const relativeFile = String(changeRecord.path || "").trim();
|
|
1513
|
+
if (!relativeFile) {
|
|
1514
|
+
continue;
|
|
1515
|
+
}
|
|
1516
|
+
const absoluteFile = path.join(appRoot, relativeFile);
|
|
1517
|
+
const existing = await readFileBufferIfExists(absoluteFile);
|
|
1518
|
+
if (!existing.exists) {
|
|
1519
|
+
continue;
|
|
1520
|
+
}
|
|
1521
|
+
if (hashBuffer(existing.buffer) !== String(changeRecord.hash || "")) {
|
|
1522
|
+
continue;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
if (changeRecord.hadPrevious) {
|
|
1526
|
+
const previousBuffer = Buffer.from(String(changeRecord.previousContentBase64 || ""), "base64");
|
|
1527
|
+
await writeFile(absoluteFile, previousBuffer);
|
|
1528
|
+
} else {
|
|
1529
|
+
await rm(absoluteFile);
|
|
1530
|
+
}
|
|
1531
|
+
touchedFiles.add(relativeFile);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
delete installed[resolvedTargetId];
|
|
1535
|
+
const touchedFileList = sortStrings([...touchedFiles]);
|
|
1536
|
+
|
|
1537
|
+
if (!options.dryRun) {
|
|
1538
|
+
await writeJsonFile(packageJsonPath, packageJson);
|
|
1539
|
+
await writeJsonFile(lockPath, lock);
|
|
1540
|
+
if (!options.noInstall) {
|
|
1541
|
+
await runNpmInstall(appRoot, io.stderr);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
if (options.json) {
|
|
1546
|
+
io.stdout.write(`${JSON.stringify({
|
|
1547
|
+
removedPackage: resolvedTargetId,
|
|
1548
|
+
touchedFiles: touchedFileList,
|
|
1549
|
+
lockPath: normalizeRelativePath(appRoot, lockPath),
|
|
1550
|
+
dryRun: options.dryRun
|
|
1551
|
+
}, null, 2)}\n`);
|
|
1552
|
+
} else {
|
|
1553
|
+
io.stdout.write(`Removed package ${resolvedTargetId}.\n`);
|
|
1554
|
+
io.stdout.write(`Touched files (${touchedFileList.length}):\n`);
|
|
1555
|
+
for (const touchedFile of touchedFileList) {
|
|
1556
|
+
io.stdout.write(`- ${touchedFile}\n`);
|
|
1557
|
+
}
|
|
1558
|
+
io.stdout.write(`Lock file: ${normalizeRelativePath(appRoot, lockPath)}\n`);
|
|
1559
|
+
if (options.dryRun) {
|
|
1560
|
+
io.stdout.write("Dry run enabled: no files were written.\n");
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
return 0;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
async function commandDoctor({ cwd, options, stdout }) {
|
|
1568
|
+
const appRoot = await resolveAppRootFromCwd(cwd);
|
|
1569
|
+
const { lock } = await loadLockFile(appRoot);
|
|
1570
|
+
const packageRegistry = await loadPackageRegistry();
|
|
1571
|
+
const appLocalRegistry = await loadAppLocalPackageRegistry(appRoot);
|
|
1572
|
+
const combinedPackageRegistry = mergePackageRegistries(packageRegistry, appLocalRegistry);
|
|
1573
|
+
const issues = [];
|
|
1574
|
+
const installed = ensureObject(lock.installedPackages);
|
|
1575
|
+
await hydratePackageRegistryFromInstalledNodeModules({
|
|
1576
|
+
appRoot,
|
|
1577
|
+
packageRegistry: combinedPackageRegistry,
|
|
1578
|
+
seedPackageIds: Object.keys(installed)
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1581
|
+
for (const [packageId, lockEntryValue] of Object.entries(installed)) {
|
|
1582
|
+
const lockEntry = ensureObject(lockEntryValue);
|
|
1583
|
+
if (!combinedPackageRegistry.has(packageId)) {
|
|
1584
|
+
issues.push(`Installed package not found in package registry: ${packageId}`);
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
const managed = ensureObject(lockEntry.managed);
|
|
1589
|
+
for (const fileChange of ensureArray(managed.files)) {
|
|
1590
|
+
const changeRecord = ensureObject(fileChange);
|
|
1591
|
+
const relativePath = String(changeRecord.path || "").trim();
|
|
1592
|
+
const absolutePath = path.join(appRoot, relativePath);
|
|
1593
|
+
if (!(await fileExists(absolutePath))) {
|
|
1594
|
+
issues.push(`${packageId}: managed file missing: ${relativePath}`);
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
const payload = {
|
|
1600
|
+
appRoot,
|
|
1601
|
+
lockVersion: lock.lockVersion,
|
|
1602
|
+
installedPackages: sortStrings(Object.keys(installed)),
|
|
1603
|
+
issues
|
|
1604
|
+
};
|
|
1605
|
+
|
|
1606
|
+
if (options.json) {
|
|
1607
|
+
stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
1608
|
+
} else {
|
|
1609
|
+
stdout.write(`App root: ${appRoot}\n`);
|
|
1610
|
+
stdout.write(`Installed packages: ${payload.installedPackages.length}\n`);
|
|
1611
|
+
if (issues.length === 0) {
|
|
1612
|
+
stdout.write("Doctor status: healthy\n");
|
|
1613
|
+
} else {
|
|
1614
|
+
stdout.write(`Doctor status: unhealthy (${issues.length} issue(s))\n`);
|
|
1615
|
+
for (const issue of issues) {
|
|
1616
|
+
stdout.write(`- ${issue}\n`);
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
return issues.length === 0 ? 0 : 1;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
async function commandLintDescriptors({ options, stdout }) {
|
|
1625
|
+
const packageRegistry = await loadPackageRegistry();
|
|
1626
|
+
const bundleRegistry = await loadBundleRegistry();
|
|
1627
|
+
const payload = {
|
|
1628
|
+
packageCount: packageRegistry.size,
|
|
1629
|
+
bundleCount: bundleRegistry.size,
|
|
1630
|
+
packages: sortStrings([...packageRegistry.keys()]),
|
|
1631
|
+
bundles: sortStrings([...bundleRegistry.keys()])
|
|
1632
|
+
};
|
|
1633
|
+
|
|
1634
|
+
if (options.json) {
|
|
1635
|
+
stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
1636
|
+
} else {
|
|
1637
|
+
stdout.write(`Descriptor lint passed.\n`);
|
|
1638
|
+
stdout.write(`Packages: ${payload.packageCount}\n`);
|
|
1639
|
+
stdout.write(`Bundles: ${payload.bundleCount}\n`);
|
|
1640
|
+
}
|
|
1641
|
+
return 0;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
return {
|
|
1645
|
+
commandList,
|
|
1646
|
+
commandShow,
|
|
1647
|
+
commandCreate,
|
|
1648
|
+
commandAdd,
|
|
1649
|
+
commandPosition,
|
|
1650
|
+
commandUpdate,
|
|
1651
|
+
commandRemove,
|
|
1652
|
+
commandDoctor,
|
|
1653
|
+
commandLintDescriptors
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
export { createCommandHandlers };
|