@jskit-ai/jskit-cli 0.2.26 → 0.2.28
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/package.json +3 -2
- package/src/server/cliRuntime/appState.js +226 -0
- package/src/server/cliRuntime/capabilitySupport.js +194 -0
- package/src/server/cliRuntime/descriptorValidation.js +150 -0
- package/src/server/cliRuntime/ioAndMigrations.js +381 -0
- package/src/server/cliRuntime/localPackageSupport.js +390 -0
- package/src/server/cliRuntime/mutationApplication.js +9 -0
- package/src/server/cliRuntime/mutationWhen.js +285 -0
- package/src/server/cliRuntime/mutations/fileMutations.js +247 -0
- package/src/server/cliRuntime/mutations/installMigrationMutation.js +213 -0
- package/src/server/cliRuntime/mutations/mutationPathUtils.js +12 -0
- package/src/server/cliRuntime/mutations/surfaceTargets.js +155 -0
- package/src/server/cliRuntime/mutations/templateContext.js +171 -0
- package/src/server/cliRuntime/mutations/textMutations.js +250 -0
- package/src/server/cliRuntime/packageInstallFlow.js +489 -0
- package/src/server/cliRuntime/packageIntrospection/exportEntries.js +259 -0
- package/src/server/cliRuntime/packageIntrospection/exportedSymbols.js +216 -0
- package/src/server/cliRuntime/packageIntrospection/placementNormalization.js +98 -0
- package/src/server/cliRuntime/packageIntrospection/providerBindingIntrospection.js +377 -0
- package/src/server/cliRuntime/packageIntrospection.js +137 -0
- package/src/server/cliRuntime/packageOptions.js +299 -0
- package/src/server/cliRuntime/packageRegistries.js +343 -0
- package/src/server/cliRuntime/packageTemplateResolution.js +131 -0
- package/src/server/cliRuntime/viteProxy.js +356 -0
- package/src/server/commandHandlers/health.js +292 -0
- package/src/server/commandHandlers/list.js +292 -0
- package/src/server/commandHandlers/package.js +23 -0
- package/src/server/commandHandlers/packageCommands/add.js +282 -0
- package/src/server/commandHandlers/packageCommands/create.js +155 -0
- package/src/server/commandHandlers/packageCommands/generate.js +116 -0
- package/src/server/commandHandlers/packageCommands/migrations.js +155 -0
- package/src/server/commandHandlers/packageCommands/position.js +103 -0
- package/src/server/commandHandlers/packageCommands/remove.js +181 -0
- package/src/server/commandHandlers/packageCommands/update.js +40 -0
- package/src/server/commandHandlers/shared.js +314 -0
- package/src/server/commandHandlers/show/payloads.js +92 -0
- package/src/server/commandHandlers/show/renderBundleText.js +16 -0
- package/src/server/commandHandlers/show/renderHelpers.js +82 -0
- package/src/server/commandHandlers/show/renderPackageCapabilities.js +124 -0
- package/src/server/commandHandlers/show/renderPackageExports.js +203 -0
- package/src/server/commandHandlers/show/renderPackageText.js +332 -0
- package/src/server/commandHandlers/show.js +114 -0
- package/src/server/core/argParser.js +144 -0
- package/src/server/{runtimeDeps.js → core/buildCommandDeps.js} +2 -1
- package/src/server/core/commandCatalog.js +47 -0
- package/src/server/core/createCliRunner.js +150 -0
- package/src/server/core/createCommandHandlers.js +43 -0
- package/src/server/{runCli.js → core/dispatchCli.js} +14 -1
- package/src/server/core/usageHelp.js +344 -0
- package/src/server/index.js +1 -1
- package/src/server/{optionInterpolation.js → shared/optionInterpolation.js} +12 -1
- package/src/server/{pathResolution.js → shared/pathResolution.js} +1 -1
- package/src/server/argParser.js +0 -206
- package/src/server/cliRuntime.js +0 -4853
- package/src/server/commandHandlers.js +0 -2109
- /package/src/server/{cliError.js → shared/cliError.js} +0 -0
- /package/src/server/{collectionUtils.js → shared/collectionUtils.js} +0 -0
- /package/src/server/{outputFormatting.js → shared/outputFormatting.js} +0 -0
- /package/src/server/{packageIdHelpers.js → shared/packageIdHelpers.js} +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureObject,
|
|
3
|
+
sortStrings
|
|
4
|
+
} from "../../shared/collectionUtils.js";
|
|
5
|
+
|
|
6
|
+
async function runPackagePositionCommand(ctx = {}, { positional, options, cwd, io }) {
|
|
7
|
+
const {
|
|
8
|
+
createCliError,
|
|
9
|
+
normalizeRelativePath,
|
|
10
|
+
resolveAppRootFromCwd,
|
|
11
|
+
loadPackageRegistry,
|
|
12
|
+
loadAppLocalPackageRegistry,
|
|
13
|
+
mergePackageRegistries,
|
|
14
|
+
loadLockFile,
|
|
15
|
+
resolveInstalledPackageIdInput,
|
|
16
|
+
hydratePackageRegistryFromInstalledNodeModules,
|
|
17
|
+
validateInlineOptionsForPackage,
|
|
18
|
+
resolvePackageOptions,
|
|
19
|
+
applyPackagePositioning,
|
|
20
|
+
writeJsonFile
|
|
21
|
+
} = ctx;
|
|
22
|
+
|
|
23
|
+
const targetType = String(positional[0] || "").trim();
|
|
24
|
+
const targetId = String(positional[1] || "").trim();
|
|
25
|
+
if (targetType !== "element" || !targetId) {
|
|
26
|
+
throw createCliError("position requires: position element <packageId>", { showUsage: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const appRoot = await resolveAppRootFromCwd(cwd);
|
|
30
|
+
const packageRegistry = await loadPackageRegistry();
|
|
31
|
+
const appLocalRegistry = await loadAppLocalPackageRegistry(appRoot);
|
|
32
|
+
const combinedPackageRegistry = mergePackageRegistries(packageRegistry, appLocalRegistry);
|
|
33
|
+
const { lockPath, lock } = await loadLockFile(appRoot);
|
|
34
|
+
const installedPackages = ensureObject(lock.installedPackages);
|
|
35
|
+
const resolvedTargetId = resolveInstalledPackageIdInput(targetId, installedPackages);
|
|
36
|
+
if (!resolvedTargetId) {
|
|
37
|
+
throw createCliError(`Element is not installed: ${targetId}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
await hydratePackageRegistryFromInstalledNodeModules({
|
|
41
|
+
appRoot,
|
|
42
|
+
packageRegistry: combinedPackageRegistry,
|
|
43
|
+
seedPackageIds: [resolvedTargetId]
|
|
44
|
+
});
|
|
45
|
+
const packageEntry = combinedPackageRegistry.get(resolvedTargetId);
|
|
46
|
+
if (!packageEntry) {
|
|
47
|
+
throw createCliError(
|
|
48
|
+
`Installed element descriptor not found: ${resolvedTargetId}. Ensure it exists in catalog, app packages/, or node_modules.`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
validateInlineOptionsForPackage(packageEntry, options.inlineOptions);
|
|
52
|
+
|
|
53
|
+
const installedRecord = ensureObject(installedPackages[resolvedTargetId]);
|
|
54
|
+
const resolvedOptions = await resolvePackageOptions(
|
|
55
|
+
packageEntry,
|
|
56
|
+
{
|
|
57
|
+
...ensureObject(installedRecord.options),
|
|
58
|
+
...ensureObject(options.inlineOptions)
|
|
59
|
+
},
|
|
60
|
+
io,
|
|
61
|
+
{ appRoot }
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const touchedFiles = new Set();
|
|
65
|
+
const positionedRecord = await applyPackagePositioning({
|
|
66
|
+
packageEntry,
|
|
67
|
+
packageOptions: resolvedOptions,
|
|
68
|
+
appRoot,
|
|
69
|
+
lock,
|
|
70
|
+
touchedFiles
|
|
71
|
+
});
|
|
72
|
+
const touchedFileList = sortStrings([...touchedFiles]);
|
|
73
|
+
|
|
74
|
+
if (!options.dryRun) {
|
|
75
|
+
await writeJsonFile(lockPath, lock);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (options.json) {
|
|
79
|
+
io.stdout.write(`${JSON.stringify({
|
|
80
|
+
targetType: "element",
|
|
81
|
+
elementId: resolvedTargetId,
|
|
82
|
+
packageId: resolvedTargetId,
|
|
83
|
+
touchedFiles: touchedFileList,
|
|
84
|
+
lockPath: normalizeRelativePath(appRoot, lockPath),
|
|
85
|
+
dryRun: options.dryRun,
|
|
86
|
+
positioned: positionedRecord
|
|
87
|
+
}, null, 2)}\n`);
|
|
88
|
+
} else {
|
|
89
|
+
io.stdout.write(`Positioned element ${resolvedTargetId}.\n`);
|
|
90
|
+
io.stdout.write(`Touched files (${touchedFileList.length}):\n`);
|
|
91
|
+
for (const touchedFile of touchedFileList) {
|
|
92
|
+
io.stdout.write(`- ${touchedFile}\n`);
|
|
93
|
+
}
|
|
94
|
+
io.stdout.write(`Lock file: ${normalizeRelativePath(appRoot, lockPath)}\n`);
|
|
95
|
+
if (options.dryRun) {
|
|
96
|
+
io.stdout.write("Dry run enabled: no files were written.\n");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { runPackagePositionCommand };
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureArray,
|
|
3
|
+
ensureObject,
|
|
4
|
+
sortStrings
|
|
5
|
+
} from "../../shared/collectionUtils.js";
|
|
6
|
+
|
|
7
|
+
async function runPackageRemoveCommand(ctx = {}, { positional, options, cwd, io }) {
|
|
8
|
+
const {
|
|
9
|
+
createCliError,
|
|
10
|
+
normalizeRelativePath,
|
|
11
|
+
resolveAppRootFromCwd,
|
|
12
|
+
loadPackageRegistry,
|
|
13
|
+
loadAppLocalPackageRegistry,
|
|
14
|
+
mergePackageRegistries,
|
|
15
|
+
loadAppPackageJson,
|
|
16
|
+
loadLockFile,
|
|
17
|
+
hydratePackageRegistryFromInstalledNodeModules,
|
|
18
|
+
resolveInstalledPackageIdInput,
|
|
19
|
+
getInstalledDependents,
|
|
20
|
+
restorePackageJsonField,
|
|
21
|
+
path,
|
|
22
|
+
readFileBufferIfExists,
|
|
23
|
+
removeEnvValue,
|
|
24
|
+
writeFile,
|
|
25
|
+
removeManagedViteProxyEntries,
|
|
26
|
+
hashBuffer,
|
|
27
|
+
rm,
|
|
28
|
+
writeJsonFile,
|
|
29
|
+
runNpmInstall
|
|
30
|
+
} = ctx;
|
|
31
|
+
|
|
32
|
+
const targetType = String(positional[0] || "").trim();
|
|
33
|
+
const targetId = String(positional[1] || "").trim();
|
|
34
|
+
if (targetType !== "package" || !targetId) {
|
|
35
|
+
throw createCliError("remove requires: remove package <packageId>", { showUsage: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const appRoot = await resolveAppRootFromCwd(cwd);
|
|
39
|
+
const packageRegistry = await loadPackageRegistry();
|
|
40
|
+
const appLocalRegistry = await loadAppLocalPackageRegistry(appRoot);
|
|
41
|
+
const combinedPackageRegistry = mergePackageRegistries(packageRegistry, appLocalRegistry);
|
|
42
|
+
const { packageJsonPath, packageJson } = await loadAppPackageJson(appRoot);
|
|
43
|
+
const { lockPath, lock } = await loadLockFile(appRoot);
|
|
44
|
+
const installed = ensureObject(lock.installedPackages);
|
|
45
|
+
await hydratePackageRegistryFromInstalledNodeModules({
|
|
46
|
+
appRoot,
|
|
47
|
+
packageRegistry: combinedPackageRegistry,
|
|
48
|
+
seedPackageIds: Object.keys(installed)
|
|
49
|
+
});
|
|
50
|
+
const resolvedTargetId = resolveInstalledPackageIdInput(targetId, installed);
|
|
51
|
+
|
|
52
|
+
if (!resolvedTargetId) {
|
|
53
|
+
throw createCliError(`Package is not installed: ${targetId}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const dependents = getInstalledDependents(lock, resolvedTargetId, combinedPackageRegistry);
|
|
57
|
+
if (dependents.length > 0) {
|
|
58
|
+
throw createCliError(
|
|
59
|
+
`Cannot remove ${resolvedTargetId}; installed packages depend on it: ${dependents.join(", ")}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const lockEntry = ensureObject(installed[resolvedTargetId]);
|
|
64
|
+
const managed = ensureObject(lockEntry.managed);
|
|
65
|
+
const touchedFiles = new Set();
|
|
66
|
+
|
|
67
|
+
const managedPackageJson = ensureObject(managed.packageJson);
|
|
68
|
+
for (const [dependencyId, managedChange] of Object.entries(ensureObject(managedPackageJson.dependencies))) {
|
|
69
|
+
if (restorePackageJsonField(packageJson, "dependencies", dependencyId, managedChange)) {
|
|
70
|
+
touchedFiles.add("package.json");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
for (const [dependencyId, managedChange] of Object.entries(ensureObject(managedPackageJson.devDependencies))) {
|
|
74
|
+
if (restorePackageJsonField(packageJson, "devDependencies", dependencyId, managedChange)) {
|
|
75
|
+
touchedFiles.add("package.json");
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const [scriptName, managedChange] of Object.entries(ensureObject(managedPackageJson.scripts))) {
|
|
79
|
+
if (restorePackageJsonField(packageJson, "scripts", scriptName, managedChange)) {
|
|
80
|
+
touchedFiles.add("package.json");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const managedText = ensureObject(managed.text);
|
|
85
|
+
for (const change of Object.values(managedText)) {
|
|
86
|
+
const changeRecord = ensureObject(change);
|
|
87
|
+
if (String(changeRecord.op || "") !== "upsert-env") {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const relativeFile = String(changeRecord.file || "").trim();
|
|
91
|
+
if (!relativeFile) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const absoluteFile = path.join(appRoot, relativeFile);
|
|
95
|
+
const existing = await readFileBufferIfExists(absoluteFile);
|
|
96
|
+
if (!existing.exists) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const updated = removeEnvValue(
|
|
100
|
+
existing.buffer.toString("utf8"),
|
|
101
|
+
String(changeRecord.key || ""),
|
|
102
|
+
String(changeRecord.value || ""),
|
|
103
|
+
{
|
|
104
|
+
hadPrevious: Boolean(changeRecord.hadPrevious),
|
|
105
|
+
previousValue: String(changeRecord.previousValue || "")
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
if (updated.changed) {
|
|
109
|
+
await writeFile(absoluteFile, updated.content, "utf8");
|
|
110
|
+
touchedFiles.add(normalizeRelativePath(appRoot, absoluteFile));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
await removeManagedViteProxyEntries({
|
|
115
|
+
appRoot,
|
|
116
|
+
packageId: resolvedTargetId,
|
|
117
|
+
managedViteChanges: ensureObject(managed.vite),
|
|
118
|
+
touchedFiles
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
for (const fileChange of ensureArray(managed.files)) {
|
|
122
|
+
const changeRecord = ensureObject(fileChange);
|
|
123
|
+
if (changeRecord.preserveOnRemove === true) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const relativeFile = String(changeRecord.path || "").trim();
|
|
127
|
+
if (!relativeFile) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const absoluteFile = path.join(appRoot, relativeFile);
|
|
131
|
+
const existing = await readFileBufferIfExists(absoluteFile);
|
|
132
|
+
if (!existing.exists) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (hashBuffer(existing.buffer) !== String(changeRecord.hash || "")) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (changeRecord.hadPrevious) {
|
|
140
|
+
const previousBuffer = Buffer.from(String(changeRecord.previousContentBase64 || ""), "base64");
|
|
141
|
+
await writeFile(absoluteFile, previousBuffer);
|
|
142
|
+
} else {
|
|
143
|
+
await rm(absoluteFile);
|
|
144
|
+
}
|
|
145
|
+
touchedFiles.add(relativeFile);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
delete installed[resolvedTargetId];
|
|
149
|
+
const touchedFileList = sortStrings([...touchedFiles]);
|
|
150
|
+
|
|
151
|
+
if (!options.dryRun) {
|
|
152
|
+
await writeJsonFile(packageJsonPath, packageJson);
|
|
153
|
+
await writeJsonFile(lockPath, lock);
|
|
154
|
+
if (options.runNpmInstall) {
|
|
155
|
+
await runNpmInstall(appRoot, io.stderr);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (options.json) {
|
|
160
|
+
io.stdout.write(`${JSON.stringify({
|
|
161
|
+
removedPackage: resolvedTargetId,
|
|
162
|
+
touchedFiles: touchedFileList,
|
|
163
|
+
lockPath: normalizeRelativePath(appRoot, lockPath),
|
|
164
|
+
dryRun: options.dryRun
|
|
165
|
+
}, null, 2)}\n`);
|
|
166
|
+
} else {
|
|
167
|
+
io.stdout.write(`Removed package ${resolvedTargetId}.\n`);
|
|
168
|
+
io.stdout.write(`Touched files (${touchedFileList.length}):\n`);
|
|
169
|
+
for (const touchedFile of touchedFileList) {
|
|
170
|
+
io.stdout.write(`- ${touchedFile}\n`);
|
|
171
|
+
}
|
|
172
|
+
io.stdout.write(`Lock file: ${normalizeRelativePath(appRoot, lockPath)}\n`);
|
|
173
|
+
if (options.dryRun) {
|
|
174
|
+
io.stdout.write("Dry run enabled: no files were written.\n");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export { runPackageRemoveCommand };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ensureObject } from "../../shared/collectionUtils.js";
|
|
2
|
+
|
|
3
|
+
async function runPackageUpdateCommand(
|
|
4
|
+
ctx = {},
|
|
5
|
+
{ positional, options, cwd, io },
|
|
6
|
+
{ runCommandAdd }
|
|
7
|
+
) {
|
|
8
|
+
const {
|
|
9
|
+
createCliError,
|
|
10
|
+
resolveAppRootFromCwd,
|
|
11
|
+
loadLockFile,
|
|
12
|
+
resolveInstalledPackageIdInput
|
|
13
|
+
} = ctx;
|
|
14
|
+
|
|
15
|
+
const targetType = String(positional[0] || "").trim();
|
|
16
|
+
const targetId = String(positional[1] || "").trim();
|
|
17
|
+
if (targetType !== "package" || !targetId) {
|
|
18
|
+
throw createCliError("update requires: update package <packageId>", { showUsage: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const appRoot = await resolveAppRootFromCwd(cwd);
|
|
22
|
+
const { lock } = await loadLockFile(appRoot);
|
|
23
|
+
const installedPackages = ensureObject(lock.installedPackages);
|
|
24
|
+
const resolvedTargetId = resolveInstalledPackageIdInput(targetId, installedPackages);
|
|
25
|
+
if (!resolvedTargetId) {
|
|
26
|
+
throw createCliError(`Package is not installed: ${targetId}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return runCommandAdd({
|
|
30
|
+
positional: ["package", resolvedTargetId],
|
|
31
|
+
options: {
|
|
32
|
+
...options,
|
|
33
|
+
forceReapplyTarget: true
|
|
34
|
+
},
|
|
35
|
+
cwd,
|
|
36
|
+
io
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { runPackageUpdateCommand };
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import {
|
|
4
|
+
ensureArray,
|
|
5
|
+
ensureObject,
|
|
6
|
+
sortStrings
|
|
7
|
+
} from "../shared/collectionUtils.js";
|
|
8
|
+
|
|
9
|
+
function createCommandHandlerShared(ctx = {}) {
|
|
10
|
+
const {
|
|
11
|
+
createCliError,
|
|
12
|
+
normalizeRelativePath,
|
|
13
|
+
resolvePackageIdInput,
|
|
14
|
+
resolveInstalledNodeModulePackageEntry,
|
|
15
|
+
path,
|
|
16
|
+
fileExists
|
|
17
|
+
} = ctx;
|
|
18
|
+
|
|
19
|
+
function renderResolvedSummary(commandType, targetId, resolvedPackageIds, touchedFiles, appRoot, lockPath, externalDependencies) {
|
|
20
|
+
const lines = [];
|
|
21
|
+
lines.push(`${commandType} ${targetId}.`);
|
|
22
|
+
lines.push(`Resolved packages (${resolvedPackageIds.length}):`);
|
|
23
|
+
for (const packageId of resolvedPackageIds) {
|
|
24
|
+
lines.push(`- ${packageId}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (externalDependencies.length > 0) {
|
|
28
|
+
lines.push(`External dependencies (${externalDependencies.length}):`);
|
|
29
|
+
for (const dependencyId of externalDependencies) {
|
|
30
|
+
lines.push(`- ${dependencyId}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
lines.push(`Touched files (${touchedFiles.length}):`);
|
|
35
|
+
for (const touchedFile of touchedFiles) {
|
|
36
|
+
lines.push(`- ${touchedFile}`);
|
|
37
|
+
}
|
|
38
|
+
lines.push(`Lock file: ${normalizeRelativePath(appRoot, lockPath)}`);
|
|
39
|
+
return lines.join("\n");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function runNpmInstall(appRoot, stderr) {
|
|
43
|
+
await new Promise((resolve, reject) => {
|
|
44
|
+
const child = spawn("npm", ["install"], {
|
|
45
|
+
cwd: appRoot,
|
|
46
|
+
stdio: "inherit"
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
child.on("error", reject);
|
|
50
|
+
child.on("exit", (code) => {
|
|
51
|
+
if (code === 0) {
|
|
52
|
+
resolve();
|
|
53
|
+
} else {
|
|
54
|
+
reject(createCliError(`npm install failed with exit code ${code}.`));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}).catch((error) => {
|
|
58
|
+
stderr.write(`npm install failed: ${error.message}\n`);
|
|
59
|
+
throw error;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getInstalledDependents(lock, packageId, packageRegistry) {
|
|
64
|
+
const dependents = [];
|
|
65
|
+
const installedPackageIds = Object.keys(ensureObject(lock.installedPackages));
|
|
66
|
+
|
|
67
|
+
for (const installedId of installedPackageIds) {
|
|
68
|
+
if (installedId === packageId) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const packageEntry = packageRegistry.get(installedId);
|
|
72
|
+
if (!packageEntry) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const dependencies = ensureArray(packageEntry.descriptor.dependsOn).map((value) => String(value));
|
|
76
|
+
if (dependencies.includes(packageId)) {
|
|
77
|
+
dependents.push(installedId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return sortStrings(dependents);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function resolvePackageKind(packageEntry) {
|
|
85
|
+
const descriptor = ensureObject(packageEntry?.descriptor);
|
|
86
|
+
const normalizedKind = String(descriptor.kind || "").trim().toLowerCase();
|
|
87
|
+
if (normalizedKind === "runtime" || normalizedKind === "generator") {
|
|
88
|
+
return normalizedKind;
|
|
89
|
+
}
|
|
90
|
+
const packageId = String(packageEntry?.packageId || descriptor.packageId || "unknown-package").trim();
|
|
91
|
+
throw createCliError(
|
|
92
|
+
`Invalid package descriptor for ${packageId}: missing/invalid kind (expected runtime or generator).`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resolvePackageOptionNames(packageEntry) {
|
|
97
|
+
const optionSchemas = ensureObject(packageEntry?.descriptor?.options);
|
|
98
|
+
return Object.keys(optionSchemas);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function resolveBundleInlineOptionsForPackage(packageEntry, inlineOptions) {
|
|
102
|
+
const allowedOptionNames = new Set(resolvePackageOptionNames(packageEntry));
|
|
103
|
+
const resolved = {};
|
|
104
|
+
|
|
105
|
+
for (const [optionName, optionValue] of Object.entries(ensureObject(inlineOptions))) {
|
|
106
|
+
if (!allowedOptionNames.has(optionName)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
resolved[optionName] = String(optionValue || "").trim();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return resolved;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function resolveGeneratorSubcommands(packageEntry) {
|
|
116
|
+
const descriptor = ensureObject(packageEntry?.descriptor);
|
|
117
|
+
const metadata = ensureObject(descriptor.metadata);
|
|
118
|
+
return ensureObject(metadata.generatorSubcommands || descriptor.generatorSubcommands);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function resolveGeneratorSubcommandDefinition(packageEntry, subcommandName) {
|
|
122
|
+
const subcommands = resolveGeneratorSubcommands(packageEntry);
|
|
123
|
+
const definition = ensureObject(subcommands[subcommandName]);
|
|
124
|
+
const entrypoint = String(definition.entrypoint || "").trim();
|
|
125
|
+
const exportName = String(definition.export || "runGeneratorSubcommand").trim() || "runGeneratorSubcommand";
|
|
126
|
+
if (!entrypoint) {
|
|
127
|
+
throw createCliError(
|
|
128
|
+
`Generator ${packageEntry?.packageId || "unknown"} does not define subcommand "${subcommandName}".`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return Object.freeze({
|
|
133
|
+
entrypoint,
|
|
134
|
+
exportName
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function hasGeneratorSubcommandDefinition(packageEntry, subcommandName) {
|
|
139
|
+
const subcommands = resolveGeneratorSubcommands(packageEntry);
|
|
140
|
+
const normalizedSubcommandName = String(subcommandName || "").trim();
|
|
141
|
+
if (!normalizedSubcommandName) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const definition = ensureObject(subcommands[normalizedSubcommandName]);
|
|
146
|
+
return String(definition.entrypoint || "").trim().length > 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function resolveGeneratorPrimarySubcommand(packageEntry) {
|
|
150
|
+
const descriptor = ensureObject(packageEntry?.descriptor);
|
|
151
|
+
const metadata = ensureObject(descriptor.metadata);
|
|
152
|
+
return String(metadata.generatorPrimarySubcommand || descriptor.generatorPrimarySubcommand || "")
|
|
153
|
+
.trim()
|
|
154
|
+
.toLowerCase();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function resolvePackageIdFromRegistryOrNodeModules({
|
|
158
|
+
appRoot,
|
|
159
|
+
packageRegistry,
|
|
160
|
+
packageIdInput
|
|
161
|
+
}) {
|
|
162
|
+
let resolvedPackageId = resolvePackageIdInput(packageIdInput, packageRegistry);
|
|
163
|
+
const packageIdForNodeModulesLookup = resolvedPackageId || packageIdInput;
|
|
164
|
+
const installedNodeModuleEntry = await resolveInstalledNodeModulePackageEntry({
|
|
165
|
+
appRoot,
|
|
166
|
+
packageId: packageIdForNodeModulesLookup
|
|
167
|
+
});
|
|
168
|
+
if (installedNodeModuleEntry) {
|
|
169
|
+
packageRegistry.set(installedNodeModuleEntry.packageId, installedNodeModuleEntry);
|
|
170
|
+
resolvedPackageId = installedNodeModuleEntry.packageId;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return resolvedPackageId;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function runGeneratorSubcommand({
|
|
177
|
+
packageEntry,
|
|
178
|
+
subcommandName,
|
|
179
|
+
subcommandArgs = [],
|
|
180
|
+
inlineOptions = {},
|
|
181
|
+
appRoot,
|
|
182
|
+
io,
|
|
183
|
+
dryRun = false,
|
|
184
|
+
json = false
|
|
185
|
+
}) {
|
|
186
|
+
const normalizedSubcommandName = String(subcommandName || "").trim();
|
|
187
|
+
if (!normalizedSubcommandName) {
|
|
188
|
+
throw createCliError("Generator subcommand name is required.");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const packageId = String(packageEntry?.packageId || "").trim();
|
|
192
|
+
const packageRoot = String(packageEntry?.rootDir || "").trim();
|
|
193
|
+
if (!packageRoot) {
|
|
194
|
+
throw createCliError(`Could not resolve package root for generator ${packageId || "<unknown>"}.`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const definition = resolveGeneratorSubcommandDefinition(packageEntry, normalizedSubcommandName);
|
|
198
|
+
const entrypointPath = path.resolve(packageRoot, definition.entrypoint);
|
|
199
|
+
if (!(await fileExists(entrypointPath))) {
|
|
200
|
+
throw createCliError(
|
|
201
|
+
`Generator subcommand entrypoint not found: ${normalizeRelativePath(appRoot, entrypointPath)}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let moduleNamespace = null;
|
|
206
|
+
try {
|
|
207
|
+
moduleNamespace = await import(`${pathToFileURL(entrypointPath).href}?t=${Date.now()}_${Math.random()}`);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
throw createCliError(
|
|
210
|
+
`Unable to load generator subcommand entrypoint ${normalizeRelativePath(appRoot, entrypointPath)}: ${String(error?.message || error || "unknown error")}`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const handler =
|
|
215
|
+
(typeof moduleNamespace?.[definition.exportName] === "function" && moduleNamespace[definition.exportName]) ||
|
|
216
|
+
(typeof moduleNamespace?.default === "function" && moduleNamespace.default) ||
|
|
217
|
+
null;
|
|
218
|
+
if (!handler) {
|
|
219
|
+
throw createCliError(
|
|
220
|
+
`Generator subcommand "${normalizedSubcommandName}" export "${definition.exportName}" was not found in ${normalizeRelativePath(appRoot, entrypointPath)}.`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const result = await handler({
|
|
225
|
+
appRoot,
|
|
226
|
+
packageId,
|
|
227
|
+
subcommand: normalizedSubcommandName,
|
|
228
|
+
args: ensureArray(subcommandArgs).map((value) => String(value || "")),
|
|
229
|
+
options: ensureObject(inlineOptions),
|
|
230
|
+
dryRun: dryRun === true
|
|
231
|
+
});
|
|
232
|
+
const payload = ensureObject(result);
|
|
233
|
+
const touchedFiles = sortStrings(
|
|
234
|
+
ensureArray(payload.touchedFiles).map((value) => String(value || "").trim()).filter(Boolean)
|
|
235
|
+
);
|
|
236
|
+
const summary = String(payload.summary || "").trim();
|
|
237
|
+
|
|
238
|
+
if (json) {
|
|
239
|
+
io.stdout.write(`${JSON.stringify({
|
|
240
|
+
targetType: "generator-subcommand",
|
|
241
|
+
packageId,
|
|
242
|
+
subcommand: normalizedSubcommandName,
|
|
243
|
+
touchedFiles,
|
|
244
|
+
summary,
|
|
245
|
+
dryRun: dryRun === true
|
|
246
|
+
}, null, 2)}\n`);
|
|
247
|
+
} else {
|
|
248
|
+
io.stdout.write(`Generated with ${packageId} (${normalizedSubcommandName}).\n`);
|
|
249
|
+
if (summary) {
|
|
250
|
+
io.stdout.write(`${summary}\n`);
|
|
251
|
+
}
|
|
252
|
+
io.stdout.write(`Touched files (${touchedFiles.length}):\n`);
|
|
253
|
+
for (const touchedFile of touchedFiles) {
|
|
254
|
+
io.stdout.write(`- ${touchedFile}\n`);
|
|
255
|
+
}
|
|
256
|
+
if (dryRun) {
|
|
257
|
+
io.stdout.write("Dry run enabled: no files were written.\n");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return 0;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function validateInlineOptionsForBundle({
|
|
265
|
+
bundleId,
|
|
266
|
+
inlineOptions,
|
|
267
|
+
packageIds,
|
|
268
|
+
packageRegistry
|
|
269
|
+
}) {
|
|
270
|
+
const providedOptionNames = Object.keys(ensureObject(inlineOptions));
|
|
271
|
+
if (providedOptionNames.length < 1) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const allowedOptionNames = new Set();
|
|
276
|
+
for (const packageId of ensureArray(packageIds).map((value) => String(value || "").trim()).filter(Boolean)) {
|
|
277
|
+
const packageEntry = packageRegistry.get(packageId);
|
|
278
|
+
if (!packageEntry) {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
for (const optionName of resolvePackageOptionNames(packageEntry)) {
|
|
282
|
+
allowedOptionNames.add(optionName);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const unknownOptionNames = providedOptionNames.filter((optionName) => !allowedOptionNames.has(optionName));
|
|
287
|
+
if (unknownOptionNames.length < 1) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const sortedUnknown = sortStrings(unknownOptionNames);
|
|
292
|
+
const sortedAllowed = sortStrings([...allowedOptionNames]);
|
|
293
|
+
const suffix = sortedAllowed.length > 0
|
|
294
|
+
? ` Allowed options: ${sortedAllowed.join(", ")}.`
|
|
295
|
+
: " This bundle does not accept inline options.";
|
|
296
|
+
throw createCliError(`Unknown option(s) for bundle ${bundleId}: ${sortedUnknown.join(", ")}.${suffix}`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
renderResolvedSummary,
|
|
301
|
+
runNpmInstall,
|
|
302
|
+
getInstalledDependents,
|
|
303
|
+
resolvePackageKind,
|
|
304
|
+
resolveBundleInlineOptionsForPackage,
|
|
305
|
+
resolveGeneratorSubcommandDefinition,
|
|
306
|
+
hasGeneratorSubcommandDefinition,
|
|
307
|
+
resolveGeneratorPrimarySubcommand,
|
|
308
|
+
resolvePackageIdFromRegistryOrNodeModules,
|
|
309
|
+
runGeneratorSubcommand,
|
|
310
|
+
validateInlineOptionsForBundle
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export { createCommandHandlerShared };
|