@jskit-ai/jskit-cli 0.2.40 → 0.2.42

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/jskit-cli",
3
- "version": "0.2.40",
3
+ "version": "0.2.42",
4
4
  "description": "Bundle and package orchestration CLI for JSKIT apps.",
5
5
  "type": "module",
6
6
  "files": [
@@ -20,8 +20,8 @@
20
20
  "test": "node --test"
21
21
  },
22
22
  "dependencies": {
23
- "@jskit-ai/jskit-catalog": "0.1.40",
24
- "@jskit-ai/kernel": "0.1.32"
23
+ "@jskit-ai/jskit-catalog": "0.1.41",
24
+ "@jskit-ai/kernel": "0.1.33"
25
25
  },
26
26
  "engines": {
27
27
  "node": "20.x"
@@ -118,25 +118,30 @@ async function applyTextMutations(packageEntry, appRoot, textMutations, options,
118
118
  const operation = String(mutation?.op || "").trim();
119
119
  if (operation === "upsert-env") {
120
120
  const relativeFile = String(mutation?.file || "").trim();
121
- const key = String(mutation?.key || "").trim();
122
- if (!relativeFile || !key) {
121
+ const rawKey = String(mutation?.key || "").trim();
122
+ if (!relativeFile || !rawKey) {
123
123
  throw createCliError(`Invalid upsert-env mutation in ${packageEntry.packageId}: "file" and "key" are required.`);
124
124
  }
125
125
 
126
+ const resolvedKey = interpolateOptionValue(rawKey, options, packageEntry.packageId, `${rawKey}.key`).trim();
127
+ if (!resolvedKey) {
128
+ throw createCliError(`Invalid upsert-env mutation in ${packageEntry.packageId}: resolved key is empty.`);
129
+ }
130
+
126
131
  const absoluteFile = path.join(appRoot, relativeFile);
127
132
  const previous = await readFileBufferIfExists(absoluteFile);
128
133
  const previousContent = previous.exists ? previous.buffer.toString("utf8") : "";
129
- const resolvedValue = interpolateOptionValue(mutation?.value || "", options, packageEntry.packageId, key);
130
- const upserted = upsertEnvValue(previousContent, key, resolvedValue);
134
+ const resolvedValue = interpolateOptionValue(mutation?.value || "", options, packageEntry.packageId, resolvedKey);
135
+ const upserted = upsertEnvValue(previousContent, resolvedKey, resolvedValue);
131
136
 
132
137
  await mkdir(path.dirname(absoluteFile), { recursive: true });
133
138
  await writeFile(absoluteFile, upserted.content, "utf8");
134
139
 
135
- const recordKey = `${relativeFile}::${String(mutation?.id || key)}`;
140
+ const recordKey = `${relativeFile}::${String(mutation?.id || resolvedKey)}`;
136
141
  managedText[recordKey] = {
137
142
  file: relativeFile,
138
143
  op: "upsert-env",
139
- key,
144
+ key: resolvedKey,
140
145
  value: resolvedValue,
141
146
  hadPrevious: upserted.hadPrevious,
142
147
  previousValue: upserted.previousValue,
@@ -275,7 +275,8 @@ async function applyPackageInstall({
275
275
  appPackageJson,
276
276
  lock,
277
277
  packageRegistry,
278
- touchedFiles
278
+ touchedFiles,
279
+ reportTemplateFetchStatus = null
279
280
  }) {
280
281
  const existingInstall = ensureObject(lock.installedPackages[packageEntry.packageId]);
281
282
  const existingManaged = ensureObject(existingInstall.managed);
@@ -292,7 +293,11 @@ async function applyPackageInstall({
292
293
  const mutationWarnings = [];
293
294
  const mutations = ensureObject(packageEntry.descriptor.mutations);
294
295
  const fileMutations = ensureArray(mutations.files);
295
- const templateRoot = await resolvePackageTemplateRoot({ packageEntry, appRoot });
296
+ const templateRoot = await resolvePackageTemplateRoot({
297
+ packageEntry,
298
+ appRoot,
299
+ reportTemplateFetchStatus
300
+ });
296
301
  const packageEntryForMutations =
297
302
  templateRoot === packageEntry.rootDir
298
303
  ? packageEntry
@@ -91,6 +91,7 @@ function resolveSurfaceDefinitionsForOptionPolicy(configContext = {}) {
91
91
 
92
92
  normalizedDefinitions[definitionId] = Object.freeze({
93
93
  id: definitionId,
94
+ label: String(definition.label || "").trim(),
94
95
  enabled: definition.enabled !== false,
95
96
  requiresWorkspace: definition.requiresWorkspace === true
96
97
  });
@@ -251,6 +252,23 @@ async function validateResolvedOptionPolicies({
251
252
  });
252
253
  }
253
254
 
255
+ function resolvePromptChoicesForOption({ schema = {}, configContext = {} } = {}) {
256
+ const validationType = normalizeResolvedOptionValue(schema.validationType);
257
+ if (validationType !== OPTION_VALIDATION_ENABLED_SURFACE_ID) {
258
+ return [];
259
+ }
260
+
261
+ const surfaceDefinitions = resolveSurfaceDefinitionsForOptionPolicy(configContext);
262
+ return Object.values(surfaceDefinitions)
263
+ .filter((entry) => entry.enabled === true)
264
+ .map((entry) => Object.freeze({
265
+ value: entry.id,
266
+ label: entry.label && entry.label.toLowerCase() !== entry.id.toLowerCase()
267
+ ? `${entry.id} (${entry.label})`
268
+ : entry.id
269
+ }));
270
+ }
271
+
254
272
  async function validateOptionValuesForPackage({
255
273
  packageEntry,
256
274
  resolvedOptions = {},
@@ -379,11 +397,13 @@ async function resolvePackageOptions(packageEntry, inlineOptions, io, { appRoot
379
397
  }
380
398
 
381
399
  if (schema.required) {
400
+ const promptConfigContext = appRoot ? await loadConfigContext() : {};
382
401
  assignResolvedOption(await promptForRequiredOption({
383
402
  ownerType: "package",
384
403
  ownerId: packageEntry.packageId,
385
404
  optionName,
386
405
  optionSchema: schema,
406
+ promptChoices: resolvePromptChoicesForOption({ schema, configContext: promptConfigContext }),
387
407
  stdin: io.stdin,
388
408
  stdout: io.stdout
389
409
  }));
@@ -68,7 +68,10 @@ async function ensureMaterializedInstallWorkspace(installRoot) {
68
68
  );
69
69
  }
70
70
 
71
- async function installCatalogPackageIntoCache({ installRoot, packageEntry }) {
71
+ async function installCatalogPackageIntoCache({
72
+ installRoot,
73
+ packageEntry
74
+ }) {
72
75
  const packageId = String(packageEntry?.packageId || "").trim();
73
76
  const version = String(packageEntry?.version || "").trim();
74
77
  const packageSpec = version ? `${packageId}@${version}` : packageId;
@@ -124,6 +127,7 @@ async function installCatalogPackageIntoCache({ installRoot, packageEntry }) {
124
127
  async function materializeCatalogPackageRoot({
125
128
  packageEntry,
126
129
  appRoot,
130
+ reportTemplateFetchStatus = null,
127
131
  installCatalogPackage = installCatalogPackageIntoCache
128
132
  } = {}) {
129
133
  if (!isInternalCatalogPackageEntry(packageEntry)) {
@@ -139,12 +143,20 @@ async function materializeCatalogPackageRoot({
139
143
  const installRoot = buildMaterializedInstallRoot({ appRoot, packageEntry });
140
144
  const candidateRoot = path.join(installRoot, "node_modules", ...packageId.split("/"));
141
145
  const descriptorPath = path.join(candidateRoot, "package.descriptor.mjs");
146
+ let didInstallPackage = false;
142
147
 
143
148
  if (!(await fileExists(descriptorPath))) {
149
+ if (typeof reportTemplateFetchStatus === "function") {
150
+ reportTemplateFetchStatus({
151
+ packageEntry,
152
+ state: "start"
153
+ });
154
+ }
144
155
  await installCatalogPackage({
145
156
  installRoot,
146
157
  packageEntry
147
158
  });
159
+ didInstallPackage = true;
148
160
  }
149
161
 
150
162
  if (!(await fileExists(descriptorPath))) {
@@ -153,6 +165,13 @@ async function materializeCatalogPackageRoot({
153
165
  );
154
166
  }
155
167
 
168
+ if (didInstallPackage && typeof reportTemplateFetchStatus === "function") {
169
+ reportTemplateFetchStatus({
170
+ packageEntry,
171
+ state: "complete"
172
+ });
173
+ }
174
+
156
175
  MATERIALIZED_PACKAGE_ROOTS.set(cacheKey, candidateRoot);
157
176
  return candidateRoot;
158
177
  }
@@ -243,6 +262,7 @@ async function resolvePackageRootFromLocalWorkspace({ packageId }) {
243
262
  async function resolvePackageTemplateRoot({
244
263
  packageEntry,
245
264
  appRoot,
265
+ reportTemplateFetchStatus = null,
246
266
  materializeCatalogRoot = materializeCatalogPackageRoot
247
267
  } = {}) {
248
268
  const packageRoot = String(packageEntry?.rootDir || "").trim();
@@ -267,7 +287,8 @@ async function resolvePackageTemplateRoot({
267
287
 
268
288
  const materializedCatalogPackageRoot = await materializeCatalogRoot({
269
289
  packageEntry,
270
- appRoot
290
+ appRoot,
291
+ reportTemplateFetchStatus
271
292
  });
272
293
  if (materializedCatalogPackageRoot) {
273
294
  return materializedCatalogPackageRoot;
@@ -38,7 +38,8 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
38
38
  adoptAppLocalPackageDependencies,
39
39
  writeJsonFile,
40
40
  runNpmInstall,
41
- renderResolvedSummary
41
+ renderResolvedSummary,
42
+ createCatalogFetchStatusReporter = () => () => {}
42
43
  } = ctx;
43
44
 
44
45
  const invocationMode = options?.commandMode === "generate" ? "generate" : "add";
@@ -242,6 +243,9 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
242
243
 
243
244
  const packagesToInstall = [];
244
245
  const resolvedOptionsByPackage = {};
246
+ const reportTemplateFetchStatus = createCatalogFetchStatusReporter(io, {
247
+ enabled: options.json !== true
248
+ });
245
249
  const forceReapplyTarget = options?.forceReapplyTarget === true;
246
250
  const hasInlineOptions = Object.keys(ensureObject(options.inlineOptions)).length > 0;
247
251
  for (const packageId of resolvedPackageIds) {
@@ -293,7 +297,8 @@ async function runPackageAddCommand(ctx = {}, { positional, options, cwd, io })
293
297
  appPackageJson: packageJson,
294
298
  lock,
295
299
  packageRegistry: combinedPackageRegistry,
296
- touchedFiles
300
+ touchedFiles,
301
+ reportTemplateFetchStatus
297
302
  });
298
303
  installedPackageRecords.push(managedRecord);
299
304
  }
@@ -73,40 +73,11 @@ function mapDescriptorBackedSubcommandArgsToInlineOptions(
73
73
  }
74
74
 
75
75
  function resolveSubcommandRequiresInput(packageEntry = {}, subcommandName = "") {
76
- const descriptor = packageEntry?.descriptor && typeof packageEntry.descriptor === "object"
77
- ? packageEntry.descriptor
78
- : {};
79
- const optionSchemas = descriptor?.options && typeof descriptor.options === "object"
80
- ? descriptor.options
81
- : {};
82
76
  const subcommandDefinition = resolveGeneratorSubcommandDefinitionMetadata(packageEntry, subcommandName);
83
77
  const positionalArgs = Array.isArray(subcommandDefinition?.positionalArgs)
84
78
  ? subcommandDefinition.positionalArgs
85
79
  : [];
86
- if (positionalArgs.length > 0) {
87
- return true;
88
- }
89
- const requiredOptionNames = Array.isArray(subcommandDefinition?.requiredOptionNames)
90
- ? subcommandDefinition.requiredOptionNames
91
- : [];
92
- if (requiredOptionNames.some((optionName) => String(optionName || "").trim().length > 0)) {
93
- return true;
94
- }
95
-
96
- const optionNames = Array.isArray(subcommandDefinition?.optionNames) && subcommandDefinition.optionNames.length > 0
97
- ? subcommandDefinition.optionNames
98
- : Object.keys(optionSchemas);
99
- for (const optionName of optionNames) {
100
- const normalizedOptionName = String(optionName || "").trim();
101
- if (!normalizedOptionName) {
102
- continue;
103
- }
104
- const schema = optionSchemas[normalizedOptionName];
105
- if (schema && typeof schema === "object" && schema.required === true) {
106
- return true;
107
- }
108
- }
109
- return false;
80
+ return positionalArgs.some((arg) => arg && typeof arg === "object" && arg.required !== false);
110
81
  }
111
82
 
112
83
  function collectUnexpectedGeneratorSubcommandOptionNames(packageEntry = {}, subcommandName = "", inlineOptions = {}) {
@@ -230,7 +201,8 @@ async function runPackageGenerateCommand(
230
201
  hasGeneratorSubcommandDefinition,
231
202
  readdir,
232
203
  validateInlineOptionValuesForPackage,
233
- runGeneratorSubcommand
204
+ runGeneratorSubcommand,
205
+ createCatalogFetchStatusReporter = () => () => {}
234
206
  } = ctx;
235
207
 
236
208
  const firstToken = String(positional[0] || "").trim();
@@ -244,6 +216,9 @@ async function runPackageGenerateCommand(
244
216
  const targetId = firstToken === "package" ? secondToken : firstToken;
245
217
  const subcommandName = firstToken === "package" ? thirdToken : secondToken;
246
218
  const subcommandArgs = firstToken === "package" ? positional.slice(3) : positional.slice(2);
219
+ const reportTemplateFetchStatus = createCatalogFetchStatusReporter(io, {
220
+ enabled: options.json !== true
221
+ });
247
222
 
248
223
  async function resolveGeneratorPackageEntry(packageIdInput = "") {
249
224
  const appRoot = await resolveAppRootFromCwd(cwd);
@@ -425,7 +400,8 @@ async function runPackageGenerateCommand(
425
400
 
426
401
  const templateRoot = await resolvePackageTemplateRoot({
427
402
  packageEntry,
428
- appRoot
403
+ appRoot,
404
+ reportTemplateFetchStatus
429
405
  });
430
406
  const executablePackageEntry =
431
407
  templateRoot === packageEntry.rootDir
@@ -39,6 +39,44 @@ function createCommandHandlerShared(ctx = {}) {
39
39
  return lines.join("\n");
40
40
  }
41
41
 
42
+ function createCatalogFetchStatusReporter(io = {}, { enabled = true } = {}) {
43
+ if (enabled !== true) {
44
+ return () => {};
45
+ }
46
+
47
+ const stdout = io?.stdout;
48
+ if (!stdout || typeof stdout.write !== "function") {
49
+ return () => {};
50
+ }
51
+
52
+ const activeFetchLabels = new Set();
53
+ return ({ packageEntry, state } = {}) => {
54
+ const packageId = String(packageEntry?.packageId || "").trim();
55
+ const version = String(packageEntry?.version || "").trim();
56
+ const packageLabel = version ? `${packageId}@${version}` : packageId;
57
+ if (!packageLabel) {
58
+ return;
59
+ }
60
+
61
+ if (state === "start") {
62
+ if (activeFetchLabels.has(packageLabel)) {
63
+ return;
64
+ }
65
+ activeFetchLabels.add(packageLabel);
66
+ stdout.write(`Fetching ${packageLabel}...\n`);
67
+ return;
68
+ }
69
+
70
+ if (state === "complete") {
71
+ if (!activeFetchLabels.has(packageLabel)) {
72
+ return;
73
+ }
74
+ activeFetchLabels.delete(packageLabel);
75
+ stdout.write(`Fetching ${packageLabel}... done!\n`);
76
+ }
77
+ };
78
+ }
79
+
42
80
  async function runNpmInstall(appRoot, stderr) {
43
81
  await new Promise((resolve, reject) => {
44
82
  const child = spawn("npm", ["install"], {
@@ -298,6 +336,7 @@ function createCommandHandlerShared(ctx = {}) {
298
336
 
299
337
  return {
300
338
  renderResolvedSummary,
339
+ createCatalogFetchStatusReporter,
301
340
  runNpmInstall,
302
341
  getInstalledDependents,
303
342
  resolvePackageKind,
@@ -120,8 +120,9 @@ function parseArgs(argv, { createCliError } = {}) {
120
120
  if (hasInlineValue) {
121
121
  optionValueRaw = withoutPrefix.slice(withoutPrefix.indexOf("=") + 1);
122
122
  } else {
123
- const nextToken = typeof args[0] === "string" ? String(args[0]) : "";
124
- if (nextToken && !nextToken.startsWith("-")) {
123
+ const hasNextStringToken = typeof args[0] === "string";
124
+ const nextToken = hasNextStringToken ? String(args[0]) : "";
125
+ if (hasNextStringToken && !nextToken.startsWith("-")) {
125
126
  optionValueRaw = args.shift();
126
127
  }
127
128
  }