@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,282 @@
1
+ import {
2
+ ensureArray,
3
+ ensureObject,
4
+ sortStrings
5
+ } from "../../shared/collectionUtils.js";
6
+
7
+ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io }) {
8
+ const {
9
+ createCliError,
10
+ normalizeRelativePath,
11
+ resolveAppRootFromCwd,
12
+ loadPackageRegistry,
13
+ loadAppLocalPackageRegistry,
14
+ loadBundleRegistry,
15
+ mergePackageRegistries,
16
+ loadAppPackageJson,
17
+ loadLockFile,
18
+ resolvePackageIdFromRegistryOrNodeModules,
19
+ hydratePackageRegistryFromInstalledNodeModules,
20
+ resolvePackageKind,
21
+ validateInlineOptionsForPackage,
22
+ resolveLocalDependencyOrder,
23
+ validatePlannedCapabilityClosure,
24
+ validateInlineOptionsForBundle,
25
+ resolveBundleInlineOptionsForPackage,
26
+ resolvePackageOptions,
27
+ applyPackageInstall,
28
+ adoptAppLocalPackageDependencies,
29
+ writeJsonFile,
30
+ runNpmInstall,
31
+ renderResolvedSummary
32
+ } = ctx;
33
+
34
+ const invocationMode = options?.commandMode === "generate" ? "generate" : "add";
35
+ const targetType = String(positional[0] || "").trim();
36
+ const targetId = String(positional[1] || "").trim();
37
+
38
+ if (!targetType || !targetId) {
39
+ if (invocationMode === "generate") {
40
+ throw createCliError("generate requires a package id (generate <packageId>).", {
41
+ showUsage: true
42
+ });
43
+ }
44
+ throw createCliError("add requires target type and id (add bundle <id> | add package <id>).", {
45
+ showUsage: true
46
+ });
47
+ }
48
+ if (targetType !== "bundle" && targetType !== "package") {
49
+ throw createCliError(`Unsupported add target type: ${targetType}`, { showUsage: true });
50
+ }
51
+ if (invocationMode === "generate" && targetType !== "package") {
52
+ throw createCliError("generate requires a package id (generate <packageId>).", {
53
+ showUsage: true
54
+ });
55
+ }
56
+
57
+ const appRoot = await resolveAppRootFromCwd(cwd);
58
+ const packageRegistry = await loadPackageRegistry();
59
+ const appLocalRegistry = await loadAppLocalPackageRegistry(appRoot);
60
+ const bundleRegistry = await loadBundleRegistry();
61
+ const combinedPackageRegistry = mergePackageRegistries(packageRegistry, appLocalRegistry);
62
+ const { packageJsonPath, packageJson } = await loadAppPackageJson(appRoot);
63
+ const { lockPath, lock } = await loadLockFile(appRoot);
64
+ const resolvedTargetPackageId = targetType === "package"
65
+ ? await resolvePackageIdFromRegistryOrNodeModules({
66
+ appRoot,
67
+ packageRegistry: combinedPackageRegistry,
68
+ packageIdInput: targetId
69
+ })
70
+ : "";
71
+
72
+ const targetPackageIds = targetType === "bundle"
73
+ ? ensureArray(bundleRegistry.get(targetId)?.packages).map((value) => String(value))
74
+ : [resolvedTargetPackageId];
75
+ if (targetType === "bundle" && targetPackageIds.length === 0) {
76
+ throw createCliError(`Unknown bundle: ${targetId}`);
77
+ }
78
+ if (targetType === "package" && !resolvedTargetPackageId) {
79
+ throw createCliError(
80
+ `Unknown package: ${targetId}. Install an external module first (npm install ${targetId}) if you want to adopt it into lock.`
81
+ );
82
+ }
83
+
84
+ await hydratePackageRegistryFromInstalledNodeModules({
85
+ appRoot,
86
+ packageRegistry: combinedPackageRegistry,
87
+ seedPackageIds: targetPackageIds
88
+ });
89
+
90
+ if (targetType === "package") {
91
+ const targetPackageEntry = combinedPackageRegistry.get(resolvedTargetPackageId);
92
+ if (!targetPackageEntry) {
93
+ throw createCliError(`Unknown package: ${targetId}`);
94
+ }
95
+ const packageKind = resolvePackageKind(targetPackageEntry);
96
+ if (invocationMode === "add" && packageKind === "generator") {
97
+ throw createCliError(
98
+ `Package ${resolvedTargetPackageId} is a generator. Use: jskit generate ${resolvedTargetPackageId}`
99
+ );
100
+ }
101
+ if (invocationMode === "generate" && packageKind !== "generator") {
102
+ throw createCliError(
103
+ `Package ${resolvedTargetPackageId} is a runtime package. Use: jskit add package ${resolvedTargetPackageId}`
104
+ );
105
+ }
106
+ validateInlineOptionsForPackage(targetPackageEntry, options.inlineOptions);
107
+ }
108
+
109
+ const { ordered: resolvedPackageIds, externalDependencies } = resolveLocalDependencyOrder(
110
+ targetPackageIds,
111
+ combinedPackageRegistry
112
+ );
113
+ if (invocationMode === "add" && targetType === "bundle") {
114
+ const bundledGenerators = resolvedPackageIds.filter((packageId) => {
115
+ const packageEntry = combinedPackageRegistry.get(packageId);
116
+ return resolvePackageKind(packageEntry) === "generator";
117
+ });
118
+ if (bundledGenerators.length > 0) {
119
+ throw createCliError(
120
+ `Bundle ${targetId} includes generator package(s): ${bundledGenerators.join(", ")}. Use: jskit generate <packageId>`
121
+ );
122
+ }
123
+ }
124
+ const plannedInstalledPackageIds = sortStrings([
125
+ ...new Set([
126
+ ...Object.keys(ensureObject(lock.installedPackages)).map((value) => String(value || "").trim()).filter(Boolean),
127
+ ...resolvedPackageIds
128
+ ])
129
+ ]);
130
+ validatePlannedCapabilityClosure(
131
+ plannedInstalledPackageIds,
132
+ combinedPackageRegistry,
133
+ `${invocationMode} ${targetType} ${targetId}`
134
+ );
135
+
136
+ if (targetType === "bundle") {
137
+ validateInlineOptionsForBundle({
138
+ bundleId: targetId,
139
+ inlineOptions: options.inlineOptions,
140
+ packageIds: resolvedPackageIds,
141
+ packageRegistry: combinedPackageRegistry
142
+ });
143
+ }
144
+
145
+ const packagesToInstall = [];
146
+ const resolvedOptionsByPackage = {};
147
+ const forceReapplyTarget = options?.forceReapplyTarget === true;
148
+ const hasInlineOptions = Object.keys(ensureObject(options.inlineOptions)).length > 0;
149
+ for (const packageId of resolvedPackageIds) {
150
+ const packageEntry = combinedPackageRegistry.get(packageId);
151
+ const existingInstall = ensureObject(lock.installedPackages[packageId]);
152
+ const existingVersion = String(existingInstall.version || "").trim();
153
+ const isDirectTargetPackage = targetType === "package" && packageId === resolvedTargetPackageId;
154
+ const packageInlineOptions = targetType === "bundle"
155
+ ? resolveBundleInlineOptionsForPackage(packageEntry, options.inlineOptions)
156
+ : isDirectTargetPackage
157
+ ? ensureObject(options.inlineOptions)
158
+ : {};
159
+ const hasPackageInlineOptions = Object.keys(packageInlineOptions).length > 0;
160
+ const shouldReapplyInstalledPackage =
161
+ (isDirectTargetPackage && (forceReapplyTarget || hasInlineOptions)) ||
162
+ (targetType === "bundle" && hasPackageInlineOptions);
163
+ const shouldSkipGenerateDependencyReinstall =
164
+ invocationMode === "generate" &&
165
+ !isDirectTargetPackage &&
166
+ Boolean(existingVersion);
167
+ if (shouldSkipGenerateDependencyReinstall && !shouldReapplyInstalledPackage) {
168
+ continue;
169
+ }
170
+ if (existingVersion && existingVersion === packageEntry.version && !shouldReapplyInstalledPackage) {
171
+ continue;
172
+ }
173
+ packagesToInstall.push(packageId);
174
+ const lockEntryOptions = ensureObject(existingInstall.options);
175
+ resolvedOptionsByPackage[packageId] = await resolvePackageOptions(
176
+ packageEntry,
177
+ {
178
+ ...lockEntryOptions,
179
+ ...packageInlineOptions
180
+ },
181
+ io,
182
+ { appRoot }
183
+ );
184
+ }
185
+
186
+ const touchedFiles = new Set();
187
+ const installedPackageRecords = [];
188
+
189
+ for (const packageId of packagesToInstall) {
190
+ const packageEntry = combinedPackageRegistry.get(packageId);
191
+ const managedRecord = await applyPackageInstall({
192
+ packageEntry,
193
+ packageOptions: resolvedOptionsByPackage[packageId],
194
+ appRoot,
195
+ appPackageJson: packageJson,
196
+ lock,
197
+ packageRegistry: combinedPackageRegistry,
198
+ touchedFiles
199
+ });
200
+ installedPackageRecords.push(managedRecord);
201
+ }
202
+
203
+ const {
204
+ appLocalRegistry: refreshedAppLocalRegistry,
205
+ adoptedPackageIds
206
+ } = await adoptAppLocalPackageDependencies({
207
+ appRoot,
208
+ appPackageJson: packageJson,
209
+ lock
210
+ });
211
+ for (const [packageId, packageEntry] of refreshedAppLocalRegistry.entries()) {
212
+ combinedPackageRegistry.set(packageId, packageEntry);
213
+ }
214
+ if (adoptedPackageIds.length > 0) {
215
+ const postInstallPackageIds = sortStrings(Object.keys(ensureObject(lock.installedPackages)));
216
+ validatePlannedCapabilityClosure(
217
+ postInstallPackageIds,
218
+ combinedPackageRegistry,
219
+ `${invocationMode} ${targetType} ${targetId}`
220
+ );
221
+ }
222
+
223
+ const finalResolvedPackageIds = sortStrings([...resolvedPackageIds, ...adoptedPackageIds]);
224
+
225
+ const touchedFileList = sortStrings([...touchedFiles]);
226
+ const successLabel = invocationMode === "generate"
227
+ ? "Generated with"
228
+ : targetType === "bundle"
229
+ ? "Added bundle"
230
+ : "Added package";
231
+ const installWarnings = installedPackageRecords
232
+ .flatMap((record) => ensureArray(ensureObject(record).warnings))
233
+ .map((value) => String(value || "").trim())
234
+ .filter(Boolean);
235
+
236
+ if (!options.dryRun) {
237
+ await writeJsonFile(packageJsonPath, packageJson);
238
+ await writeJsonFile(lockPath, lock);
239
+ if (options.runNpmInstall) {
240
+ await runNpmInstall(appRoot, io.stderr);
241
+ }
242
+ }
243
+
244
+ if (options.json) {
245
+ io.stdout.write(`${JSON.stringify({
246
+ targetType: invocationMode === "generate" ? "generator" : targetType,
247
+ targetId,
248
+ resolvedPackages: finalResolvedPackageIds,
249
+ touchedFiles: touchedFileList,
250
+ lockPath: normalizeRelativePath(appRoot, lockPath),
251
+ externalDependencies,
252
+ dryRun: options.dryRun,
253
+ installed: installedPackageRecords,
254
+ warnings: installWarnings
255
+ }, null, 2)}\n`);
256
+ } else {
257
+ io.stdout.write(
258
+ `${renderResolvedSummary(
259
+ `${successLabel}`,
260
+ targetId,
261
+ finalResolvedPackageIds,
262
+ touchedFileList,
263
+ appRoot,
264
+ lockPath,
265
+ externalDependencies
266
+ )}\n`
267
+ );
268
+ if (installWarnings.length > 0) {
269
+ io.stdout.write(`Warnings (${installWarnings.length}):\n`);
270
+ for (const warning of installWarnings) {
271
+ io.stdout.write(`- ${warning}\n`);
272
+ }
273
+ }
274
+ if (options.dryRun) {
275
+ io.stdout.write("Dry run enabled: no files were written.\n");
276
+ }
277
+ }
278
+
279
+ return 0;
280
+ }
281
+
282
+ export { runPackageAddCommand };
@@ -0,0 +1,155 @@
1
+ import {
2
+ ensureObject,
3
+ sortStrings
4
+ } from "../../shared/collectionUtils.js";
5
+
6
+ async function runPackageCreateCommand(ctx = {}, { positional, options, cwd, io }) {
7
+ const {
8
+ createCliError,
9
+ normalizeRelativePath,
10
+ normalizeRelativePosixPath,
11
+ resolveAppRootFromCwd,
12
+ loadAppPackageJson,
13
+ loadLockFile,
14
+ resolveLocalPackageId,
15
+ createLocalPackageScaffoldFiles,
16
+ path,
17
+ toFileDependencySpecifier,
18
+ fileExists,
19
+ mkdir,
20
+ writeFile,
21
+ applyPackageJsonField,
22
+ writeJsonFile,
23
+ runNpmInstall
24
+ } = ctx;
25
+
26
+ const targetType = String(positional[0] || "").trim();
27
+ const rawName = String(positional[1] || "").trim();
28
+ if (targetType !== "package" || !rawName) {
29
+ throw createCliError("create requires: create package <name>", { showUsage: true });
30
+ }
31
+
32
+ const appRoot = await resolveAppRootFromCwd(cwd);
33
+ const { packageJsonPath, packageJson } = await loadAppPackageJson(appRoot);
34
+ const { lockPath, lock } = await loadLockFile(appRoot);
35
+ const installedPackages = ensureObject(lock.installedPackages);
36
+ const dependencies = ensureObject(packageJson.dependencies);
37
+ const devDependencies = ensureObject(packageJson.devDependencies);
38
+
39
+ const { packageId, packageDirName } = resolveLocalPackageId({
40
+ rawName,
41
+ appPackageName: packageJson.name,
42
+ inlineOptions: options.inlineOptions
43
+ });
44
+ const localPackagesRoot = path.join(appRoot, "packages");
45
+ const packageRoot = path.join(localPackagesRoot, packageDirName);
46
+ const packageRelativePath = normalizeRelativePath(appRoot, packageRoot);
47
+ const descriptorRelativePath = `${normalizeRelativePosixPath(packageRelativePath)}/package.descriptor.mjs`;
48
+ const localDependencySpecifier = toFileDependencySpecifier(packageRelativePath);
49
+ const packageDescription = String(options.inlineOptions.description || "").trim() || `App-local package ${packageId}.`;
50
+
51
+ if (await fileExists(packageRoot)) {
52
+ throw createCliError(`Package directory already exists: ${normalizeRelativePath(appRoot, packageRoot)}`);
53
+ }
54
+ if (Object.prototype.hasOwnProperty.call(installedPackages, packageId)) {
55
+ throw createCliError(`Package is already present in lock file: ${packageId}`);
56
+ }
57
+ if (Object.prototype.hasOwnProperty.call(dependencies, packageId)) {
58
+ throw createCliError(`package.json dependencies already contains ${packageId}.`);
59
+ }
60
+ if (Object.prototype.hasOwnProperty.call(devDependencies, packageId)) {
61
+ throw createCliError(`package.json devDependencies already contains ${packageId}.`);
62
+ }
63
+
64
+ const scaffoldFiles = createLocalPackageScaffoldFiles({
65
+ packageId,
66
+ packageDescription
67
+ });
68
+ const touchedFiles = new Set(["package.json", normalizeRelativePath(appRoot, lockPath)]);
69
+ for (const scaffoldFile of scaffoldFiles) {
70
+ touchedFiles.add(
71
+ `${normalizeRelativePosixPath(packageRelativePath)}/${normalizeRelativePosixPath(scaffoldFile.relativePath)}`
72
+ );
73
+ }
74
+
75
+ if (!options.dryRun) {
76
+ for (const scaffoldFile of scaffoldFiles) {
77
+ const absoluteFilePath = path.join(packageRoot, scaffoldFile.relativePath);
78
+ await mkdir(path.dirname(absoluteFilePath), { recursive: true });
79
+ await writeFile(absoluteFilePath, String(scaffoldFile.content || ""), "utf8");
80
+ }
81
+ }
82
+
83
+ const dependencyApplied = applyPackageJsonField(packageJson, "dependencies", packageId, localDependencySpecifier);
84
+ const managedRecord = {
85
+ packageId,
86
+ version: "0.1.0",
87
+ source: {
88
+ type: "local-package",
89
+ packagePath: normalizeRelativePosixPath(packageRelativePath),
90
+ descriptorPath: descriptorRelativePath
91
+ },
92
+ managed: {
93
+ packageJson: {
94
+ dependencies: {},
95
+ devDependencies: {},
96
+ scripts: {}
97
+ },
98
+ text: {},
99
+ vite: {},
100
+ files: [],
101
+ migrations: []
102
+ },
103
+ options: {},
104
+ installedAt: new Date().toISOString()
105
+ };
106
+ if (dependencyApplied.changed) {
107
+ managedRecord.managed.packageJson.dependencies[packageId] = dependencyApplied.managed;
108
+ }
109
+ lock.installedPackages[packageId] = managedRecord;
110
+
111
+ const touchedFileList = sortStrings([...touchedFiles]);
112
+ if (!options.dryRun) {
113
+ await writeJsonFile(packageJsonPath, packageJson);
114
+ await writeJsonFile(lockPath, lock);
115
+ if (options.runNpmInstall) {
116
+ await runNpmInstall(appRoot, io.stderr);
117
+ }
118
+ }
119
+
120
+ if (options.json) {
121
+ io.stdout.write(
122
+ `${JSON.stringify(
123
+ {
124
+ targetType: "package",
125
+ packageId,
126
+ packageDirectory: normalizeRelativePosixPath(packageRelativePath),
127
+ descriptorPath: descriptorRelativePath,
128
+ dependency: localDependencySpecifier,
129
+ touchedFiles: touchedFileList,
130
+ lockPath: normalizeRelativePath(appRoot, lockPath),
131
+ dryRun: options.dryRun
132
+ },
133
+ null,
134
+ 2
135
+ )}\n`
136
+ );
137
+ } else {
138
+ io.stdout.write(`Created local package ${packageId}.\n`);
139
+ io.stdout.write(`Directory: ${normalizeRelativePosixPath(packageRelativePath)}\n`);
140
+ io.stdout.write(`Dependency: ${packageId} -> ${localDependencySpecifier}\n`);
141
+ io.stdout.write(`Descriptor: ${descriptorRelativePath}\n`);
142
+ io.stdout.write(`Touched files (${touchedFileList.length}):\n`);
143
+ for (const touchedFile of touchedFileList) {
144
+ io.stdout.write(`- ${touchedFile}\n`);
145
+ }
146
+ io.stdout.write(`Lock file: ${normalizeRelativePath(appRoot, lockPath)}\n`);
147
+ if (options.dryRun) {
148
+ io.stdout.write("Dry run enabled: no files were written.\n");
149
+ }
150
+ }
151
+
152
+ return 0;
153
+ }
154
+
155
+ export { runPackageCreateCommand };
@@ -0,0 +1,116 @@
1
+ async function runPackageGenerateCommand(
2
+ ctx = {},
3
+ { positional, options, cwd, io },
4
+ { runCommandAdd }
5
+ ) {
6
+ const {
7
+ createCliError,
8
+ resolveAppRootFromCwd,
9
+ loadPackageRegistry,
10
+ loadAppLocalPackageRegistry,
11
+ mergePackageRegistries,
12
+ resolvePackageIdFromRegistryOrNodeModules,
13
+ hydratePackageRegistryFromInstalledNodeModules,
14
+ resolvePackageKind,
15
+ resolveGeneratorPrimarySubcommand,
16
+ hasGeneratorSubcommandDefinition,
17
+ runGeneratorSubcommand
18
+ } = ctx;
19
+
20
+ const firstToken = String(positional[0] || "").trim();
21
+ const secondToken = String(positional[1] || "").trim();
22
+ const thirdToken = String(positional[2] || "").trim();
23
+ if (firstToken === "bundle") {
24
+ throw createCliError("generate supports packages only (generate <packageId>).", {
25
+ showUsage: true
26
+ });
27
+ }
28
+ const targetId = firstToken === "package" ? secondToken : firstToken;
29
+ const subcommandName = firstToken === "package" ? thirdToken : secondToken;
30
+ const subcommandArgs = firstToken === "package" ? positional.slice(3) : positional.slice(2);
31
+ if (!targetId) {
32
+ throw createCliError("generate requires a package id (generate <packageId>).", {
33
+ showUsage: true
34
+ });
35
+ }
36
+
37
+ if (subcommandName) {
38
+ const appRoot = await resolveAppRootFromCwd(cwd);
39
+ const packageRegistry = await loadPackageRegistry();
40
+ const appLocalRegistry = await loadAppLocalPackageRegistry(appRoot);
41
+ const combinedPackageRegistry = mergePackageRegistries(packageRegistry, appLocalRegistry);
42
+
43
+ const resolvedPackageId = await resolvePackageIdFromRegistryOrNodeModules({
44
+ appRoot,
45
+ packageRegistry: combinedPackageRegistry,
46
+ packageIdInput: targetId
47
+ });
48
+ if (!resolvedPackageId) {
49
+ throw createCliError(
50
+ `Unknown package: ${targetId}. Install it first (npm install ${targetId}) if you want to run generator subcommands from node_modules.`
51
+ );
52
+ }
53
+
54
+ await hydratePackageRegistryFromInstalledNodeModules({
55
+ appRoot,
56
+ packageRegistry: combinedPackageRegistry,
57
+ seedPackageIds: [resolvedPackageId]
58
+ });
59
+ const packageEntry = combinedPackageRegistry.get(resolvedPackageId);
60
+ if (!packageEntry) {
61
+ throw createCliError(`Unknown package: ${targetId}`);
62
+ }
63
+
64
+ if (resolvePackageKind(packageEntry) !== "generator") {
65
+ throw createCliError(
66
+ `Package ${resolvedPackageId} is a runtime package. Use: jskit add package ${resolvedPackageId}`
67
+ );
68
+ }
69
+
70
+ const normalizedSubcommandName = String(subcommandName || "").trim().toLowerCase();
71
+ const primarySubcommand = resolveGeneratorPrimarySubcommand(packageEntry);
72
+ if (
73
+ normalizedSubcommandName &&
74
+ normalizedSubcommandName === primarySubcommand &&
75
+ !hasGeneratorSubcommandDefinition(packageEntry, normalizedSubcommandName)
76
+ ) {
77
+ if (subcommandArgs.length > 0) {
78
+ throw createCliError(
79
+ `Generator command "${primarySubcommand}" for ${resolvedPackageId} does not accept positional arguments.`
80
+ );
81
+ }
82
+ return runCommandAdd({
83
+ positional: ["package", resolvedPackageId],
84
+ options: {
85
+ ...options,
86
+ commandMode: "generate"
87
+ },
88
+ cwd,
89
+ io
90
+ });
91
+ }
92
+
93
+ return runGeneratorSubcommand({
94
+ packageEntry,
95
+ subcommandName,
96
+ subcommandArgs,
97
+ inlineOptions: options.inlineOptions,
98
+ appRoot,
99
+ io,
100
+ dryRun: options.dryRun,
101
+ json: options.json
102
+ });
103
+ }
104
+
105
+ return runCommandAdd({
106
+ positional: ["package", targetId],
107
+ options: {
108
+ ...options,
109
+ commandMode: "generate"
110
+ },
111
+ cwd,
112
+ io
113
+ });
114
+ }
115
+
116
+ export { runPackageGenerateCommand };
@@ -0,0 +1,155 @@
1
+ import {
2
+ ensureArray,
3
+ ensureObject,
4
+ sortStrings
5
+ } from "../../shared/collectionUtils.js";
6
+
7
+ async function runPackageMigrationsCommand(ctx = {}, { positional, options, cwd, io }) {
8
+ const {
9
+ createCliError,
10
+ normalizeRelativePath,
11
+ resolveAppRootFromCwd,
12
+ loadPackageRegistry,
13
+ loadAppLocalPackageRegistry,
14
+ mergePackageRegistries,
15
+ loadLockFile,
16
+ hydratePackageRegistryFromInstalledNodeModules,
17
+ resolveInstalledPackageIdInput,
18
+ validateInlineOptionsForPackage,
19
+ resolvePackageOptions,
20
+ applyPackageMigrationsOnly,
21
+ writeJsonFile
22
+ } = ctx;
23
+
24
+ const scope = String(positional[0] || "").trim().toLowerCase();
25
+ const targetId = String(positional[1] || "").trim();
26
+ if (!scope || (scope !== "all" && scope !== "changed" && scope !== "package")) {
27
+ throw createCliError("migrations requires: migrations <all|changed|package <packageId>>", {
28
+ showUsage: true
29
+ });
30
+ }
31
+ if (scope === "package" && !targetId) {
32
+ throw createCliError("migrations package requires a package id.", {
33
+ showUsage: true
34
+ });
35
+ }
36
+ if (scope !== "package" && Object.keys(ensureObject(options.inlineOptions)).length > 0) {
37
+ throw createCliError("Inline options are only supported with: migrations package <packageId>.");
38
+ }
39
+
40
+ const appRoot = await resolveAppRootFromCwd(cwd);
41
+ const packageRegistry = await loadPackageRegistry();
42
+ const appLocalRegistry = await loadAppLocalPackageRegistry(appRoot);
43
+ const combinedPackageRegistry = mergePackageRegistries(packageRegistry, appLocalRegistry);
44
+ const { lockPath, lock } = await loadLockFile(appRoot);
45
+ const installedPackages = ensureObject(lock.installedPackages);
46
+ const installedPackageIds = sortStrings(Object.keys(installedPackages));
47
+ await hydratePackageRegistryFromInstalledNodeModules({
48
+ appRoot,
49
+ packageRegistry: combinedPackageRegistry,
50
+ seedPackageIds: installedPackageIds
51
+ });
52
+
53
+ let requestedPackageIds = [];
54
+ if (scope === "all") {
55
+ requestedPackageIds = installedPackageIds;
56
+ } else if (scope === "package") {
57
+ const resolvedTargetId = resolveInstalledPackageIdInput(targetId, installedPackages);
58
+ if (!resolvedTargetId) {
59
+ throw createCliError(`Package is not installed: ${targetId}`);
60
+ }
61
+ requestedPackageIds = [resolvedTargetId];
62
+ } else {
63
+ requestedPackageIds = installedPackageIds.filter((packageId) => {
64
+ const packageEntry = combinedPackageRegistry.get(packageId);
65
+ if (!packageEntry) {
66
+ return true;
67
+ }
68
+ const lockEntry = ensureObject(installedPackages[packageId]);
69
+ const migrationSyncVersion = String(lockEntry.migrationSyncVersion || "").trim();
70
+ return migrationSyncVersion !== String(packageEntry.version || "").trim();
71
+ });
72
+ }
73
+
74
+ const touchedFiles = new Set();
75
+ const migratedRecords = [];
76
+ const migrationWarnings = [];
77
+ for (const packageId of requestedPackageIds) {
78
+ const packageEntry = combinedPackageRegistry.get(packageId);
79
+ if (!packageEntry) {
80
+ throw createCliError(
81
+ `Installed package descriptor not found: ${packageId}. Ensure it is available in catalog, app packages/, or node_modules.`
82
+ );
83
+ }
84
+ validateInlineOptionsForPackage(packageEntry, options.inlineOptions);
85
+ const installedRecord = ensureObject(installedPackages[packageId]);
86
+ const mergedInlineOptions = scope === "package" ? ensureObject(options.inlineOptions) : {};
87
+ const resolvedOptions = await resolvePackageOptions(
88
+ packageEntry,
89
+ {
90
+ ...ensureObject(installedRecord.options),
91
+ ...mergedInlineOptions
92
+ },
93
+ io,
94
+ { appRoot }
95
+ );
96
+
97
+ const managedRecord = await applyPackageMigrationsOnly({
98
+ packageEntry,
99
+ packageOptions: resolvedOptions,
100
+ appRoot,
101
+ lock,
102
+ touchedFiles
103
+ });
104
+ migratedRecords.push(managedRecord);
105
+ for (const warning of ensureArray(ensureObject(managedRecord).warnings)) {
106
+ const normalizedWarning = String(warning || "").trim();
107
+ if (!normalizedWarning) {
108
+ continue;
109
+ }
110
+ migrationWarnings.push(normalizedWarning);
111
+ }
112
+ }
113
+
114
+ const touchedFileList = sortStrings([...touchedFiles]);
115
+ if (!options.dryRun) {
116
+ await writeJsonFile(lockPath, lock);
117
+ }
118
+
119
+ if (options.json) {
120
+ io.stdout.write(`${JSON.stringify({
121
+ targetType: "migrations",
122
+ scope,
123
+ requestedPackages: requestedPackageIds,
124
+ touchedFiles: touchedFileList,
125
+ lockPath: normalizeRelativePath(appRoot, lockPath),
126
+ dryRun: options.dryRun,
127
+ migrated: migratedRecords,
128
+ warnings: migrationWarnings
129
+ }, null, 2)}\n`);
130
+ } else {
131
+ io.stdout.write(`Generated migrations (${scope}).\n`);
132
+ io.stdout.write(`Resolved packages (${requestedPackageIds.length}):\n`);
133
+ for (const packageId of requestedPackageIds) {
134
+ io.stdout.write(`- ${packageId}\n`);
135
+ }
136
+ io.stdout.write(`Touched files (${touchedFileList.length}):\n`);
137
+ for (const touchedFile of touchedFileList) {
138
+ io.stdout.write(`- ${touchedFile}\n`);
139
+ }
140
+ io.stdout.write(`Lock file: ${normalizeRelativePath(appRoot, lockPath)}\n`);
141
+ if (options.verbose && migrationWarnings.length > 0) {
142
+ io.stdout.write(`Warnings (${migrationWarnings.length}):\n`);
143
+ for (const warning of migrationWarnings) {
144
+ io.stdout.write(`- ${warning}\n`);
145
+ }
146
+ }
147
+ if (options.dryRun) {
148
+ io.stdout.write("Dry run enabled: no files were written.\n");
149
+ }
150
+ }
151
+
152
+ return 0;
153
+ }
154
+
155
+ export { runPackageMigrationsCommand };