@jskit-ai/jskit-cli 0.2.27 → 0.2.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/package.json +3 -2
  2. package/src/server/cliRuntime/appState.js +226 -0
  3. package/src/server/cliRuntime/capabilitySupport.js +194 -0
  4. package/src/server/cliRuntime/descriptorValidation.js +150 -0
  5. package/src/server/cliRuntime/ioAndMigrations.js +381 -0
  6. package/src/server/cliRuntime/localPackageSupport.js +390 -0
  7. package/src/server/cliRuntime/mutationApplication.js +9 -0
  8. package/src/server/cliRuntime/mutationWhen.js +285 -0
  9. package/src/server/cliRuntime/mutations/fileMutations.js +247 -0
  10. package/src/server/cliRuntime/mutations/installMigrationMutation.js +213 -0
  11. package/src/server/cliRuntime/mutations/mutationPathUtils.js +12 -0
  12. package/src/server/cliRuntime/mutations/surfaceTargets.js +155 -0
  13. package/src/server/cliRuntime/mutations/templateContext.js +171 -0
  14. package/src/server/cliRuntime/mutations/textMutations.js +250 -0
  15. package/src/server/cliRuntime/packageInstallFlow.js +489 -0
  16. package/src/server/cliRuntime/packageIntrospection/exportEntries.js +259 -0
  17. package/src/server/cliRuntime/packageIntrospection/exportedSymbols.js +216 -0
  18. package/src/server/cliRuntime/packageIntrospection/placementNormalization.js +98 -0
  19. package/src/server/cliRuntime/packageIntrospection/providerBindingIntrospection.js +377 -0
  20. package/src/server/cliRuntime/packageIntrospection.js +137 -0
  21. package/src/server/cliRuntime/packageOptions.js +299 -0
  22. package/src/server/cliRuntime/packageRegistries.js +343 -0
  23. package/src/server/cliRuntime/packageTemplateResolution.js +131 -0
  24. package/src/server/cliRuntime/viteProxy.js +356 -0
  25. package/src/server/commandHandlers/health.js +292 -0
  26. package/src/server/commandHandlers/list.js +292 -0
  27. package/src/server/commandHandlers/package.js +23 -0
  28. package/src/server/commandHandlers/packageCommands/add.js +282 -0
  29. package/src/server/commandHandlers/packageCommands/create.js +155 -0
  30. package/src/server/commandHandlers/packageCommands/generate.js +116 -0
  31. package/src/server/commandHandlers/packageCommands/migrations.js +155 -0
  32. package/src/server/commandHandlers/packageCommands/position.js +103 -0
  33. package/src/server/commandHandlers/packageCommands/remove.js +181 -0
  34. package/src/server/commandHandlers/packageCommands/update.js +40 -0
  35. package/src/server/commandHandlers/shared.js +314 -0
  36. package/src/server/commandHandlers/show/payloads.js +92 -0
  37. package/src/server/commandHandlers/show/renderBundleText.js +16 -0
  38. package/src/server/commandHandlers/show/renderHelpers.js +82 -0
  39. package/src/server/commandHandlers/show/renderPackageCapabilities.js +124 -0
  40. package/src/server/commandHandlers/show/renderPackageExports.js +203 -0
  41. package/src/server/commandHandlers/show/renderPackageText.js +332 -0
  42. package/src/server/commandHandlers/show.js +114 -0
  43. package/src/server/core/argParser.js +144 -0
  44. package/src/server/{runtimeDeps.js → core/buildCommandDeps.js} +2 -1
  45. package/src/server/core/commandCatalog.js +47 -0
  46. package/src/server/core/createCliRunner.js +150 -0
  47. package/src/server/core/createCommandHandlers.js +43 -0
  48. package/src/server/{runCli.js → core/dispatchCli.js} +14 -1
  49. package/src/server/core/usageHelp.js +344 -0
  50. package/src/server/index.js +1 -1
  51. package/src/server/{optionInterpolation.js → shared/optionInterpolation.js} +12 -1
  52. package/src/server/{pathResolution.js → shared/pathResolution.js} +1 -1
  53. package/src/server/argParser.js +0 -206
  54. package/src/server/cliRuntime.js +0 -4956
  55. package/src/server/commandHandlers.js +0 -2109
  56. /package/src/server/{cliError.js → shared/cliError.js} +0 -0
  57. /package/src/server/{collectionUtils.js → shared/collectionUtils.js} +0 -0
  58. /package/src/server/{outputFormatting.js → shared/outputFormatting.js} +0 -0
  59. /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 };