@kora-platform/cli 0.8.2-rc3 → 0.9.0-rc2

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.
@@ -1,6 +1,6 @@
1
- import { readOptionalStringFlag, readRequiredStringFlag } from "./command-flags.js";
1
+ import { readOptionalNumberFlag, readOptionalStringFlag, readRequiredStringFlag } from "./command-flags.js";
2
2
  import { genericProblem, usageProblem } from "./cli-errors.js";
3
- import { isZipArchivePath, readArchiveBytes, readJsonInputSpecifier, readPackageFileEntries, readWorkspaceExportMetadata, writeArchiveExport, writePackageExport } from "./files.js";
3
+ import { isZipArchivePath, readArchiveBytes, readJsonInputSpecifier, readJsonValueInputSpecifier, readPackageFileEntries, readWorkspaceExportMetadata, writeArchiveExport, writePackageExport } from "./files.js";
4
4
  import { renderKeyValue, renderPrettyJson, renderSuccess, renderTable } from "./format.js";
5
5
  import { confirmDestructive } from "./interaction.js";
6
6
  import { readCliEnvironmentFlag, readRequiredCliEnvironmentFlag } from "./environment-context.js";
@@ -73,7 +73,7 @@ export async function executeExtensions(parsed, context, api, resolveOrgScope) {
73
73
  : await api.publishExtensionPackage(session, org.id, { files: await readPackageFileEntries(path, parsed.definition.id) });
74
74
  return {
75
75
  data,
76
- human: renderSuccess(`Published extension ${data.package.name} revision ${data.revision.id}.`),
76
+ human: renderSuccess(`Published extension package ${data.package.name}.`),
77
77
  kind: "extensions_publish",
78
78
  meta: withExtensionSummary({ command: parsed.definition.path.join(" "), mutated: true, orgId: org.id }, "Extension published.")
79
79
  };
@@ -85,8 +85,8 @@ export async function executeExtensions(parsed, context, api, resolveOrgScope) {
85
85
  const data = await api.installExtension(session, org.id, {
86
86
  environment,
87
87
  ...(permissions ? { grantedPermissions: permissions } : {}),
88
- packageRevisionId: readRequiredArg(parsed, "package-revision"),
89
- slug: readRequiredArg(parsed, "slug")
88
+ packageArtifactId: readRequiredArg(parsed, "published-package"),
89
+ slug: readRequiredArg(parsed, "name")
90
90
  });
91
91
  return {
92
92
  data,
@@ -98,7 +98,7 @@ export async function executeExtensions(parsed, context, api, resolveOrgScope) {
98
98
  case "extensions.grant": {
99
99
  const environment = readRequiredCliEnvironmentFlag(parsed);
100
100
  const permissions = await readRequiredPermissions(parsed, context);
101
- const installRef = readRequiredArg(parsed, "install");
101
+ const installRef = readRequiredArg(parsed, "name");
102
102
  announceResolvedEnvironment(parsed, context, environment);
103
103
  const data = await api.grantExtensionPermissions(session, org.id, installRef, {
104
104
  environment,
@@ -112,34 +112,6 @@ export async function executeExtensions(parsed, context, api, resolveOrgScope) {
112
112
  meta: withExtensionSummary({ command: parsed.definition.path.join(" "), environment, mutated: true, orgId: org.id }, "Extension grants updated.")
113
113
  };
114
114
  }
115
- case "extensions.update": {
116
- const environment = readRequiredCliEnvironmentFlag(parsed);
117
- const installRef = readRequiredArg(parsed, "install");
118
- const input = {
119
- environment,
120
- ...await readInstallPreconditions(parsed, context, installRef),
121
- packageRevisionId: readRequiredStringFlag(parsed, "revision")
122
- };
123
- if (parsed.flags["dry-run"] === true) {
124
- const data = await api.planExtensionInstallUpdate(session, org.id, installRef, input);
125
- const plan = data.plan;
126
- return {
127
- data,
128
- exitCode: plan.updateAllowed === false ? 1 : 0,
129
- human: renderPrettyJson(data.plan),
130
- kind: "extensions_update_plan",
131
- meta: withExtensionSummary({ command: parsed.definition.path.join(" "), dryRun: true, environment, mutated: false, orgId: org.id }, summarizeExtensionUpdatePlan(plan))
132
- };
133
- }
134
- announceResolvedEnvironment(parsed, context, environment);
135
- const data = await api.updateExtensionInstall(session, org.id, installRef, input);
136
- return {
137
- data,
138
- human: renderSuccess(`Updated extension ${data.install.slug} to ${data.install.packageRevisionId} in ${environment}.`),
139
- kind: "extensions_update",
140
- meta: withExtensionSummary({ command: parsed.definition.path.join(" "), environment, mutated: true, orgId: org.id }, "Extension updated.")
141
- };
142
- }
143
115
  case "extensions.built-ins.list": {
144
116
  const environment = readCliEnvironmentFlag(parsed);
145
117
  const data = await api.listBuiltInExtensions(session, org.id, {
@@ -155,12 +127,12 @@ export async function executeExtensions(parsed, context, api, resolveOrgScope) {
155
127
  case "extensions.built-ins.install": {
156
128
  const environment = readRequiredCliEnvironmentFlag(parsed);
157
129
  const permissions = await readOptionalPermissions(parsed, context);
158
- const slug = readOptionalStringFlag(parsed, "slug");
130
+ const name = readOptionalStringFlag(parsed, "name");
159
131
  announceResolvedEnvironment(parsed, context, environment);
160
132
  const data = await api.installBuiltInExtension(session, org.id, readRequiredArg(parsed, "built-in"), {
161
133
  environment,
162
134
  ...(permissions ? { grantedPermissions: permissions } : {}),
163
- ...(slug ? { slug } : {})
135
+ ...(name ? { slug: name } : {})
164
136
  });
165
137
  return {
166
138
  data,
@@ -169,45 +141,79 @@ export async function executeExtensions(parsed, context, api, resolveOrgScope) {
169
141
  meta: withExtensionSummary({ command: parsed.definition.path.join(" "), environment, mutated: true, orgId: org.id }, "Built-in extension installed.")
170
142
  };
171
143
  }
172
- case "extensions.list": {
144
+ case "extensions.search": {
173
145
  const environment = readCliEnvironmentFlag(parsed);
174
- const data = await api.listExtensionInstalls(session, org.id, {
175
- ...(environment ? { environment } : {})
146
+ const extensionRef = readOptionalArg(parsed, "extension");
147
+ if (extensionRef) {
148
+ const effectiveEnvironment = readRequiredCliEnvironmentFlag(parsed);
149
+ const data = await api.searchExtensionCapabilities(session, org.id, extensionRef, {
150
+ environment: effectiveEnvironment,
151
+ ...readExtensionSearchFilters(parsed, { capabilities: true })
152
+ });
153
+ return {
154
+ data,
155
+ human: renderCapabilitySearchHuman(data.matches, data),
156
+ kind: "extensions_search",
157
+ meta: withExtensionSummary({ command: parsed.definition.path.join(" "), environment: effectiveEnvironment, orgId: org.id }, summarizeSearchResult(data))
158
+ };
159
+ }
160
+ const data = await api.searchExtensions(session, org.id, {
161
+ ...(environment ? { environment } : {}),
162
+ ...readExtensionSearchFilters(parsed, { capabilities: false })
176
163
  });
177
164
  return {
178
165
  data,
179
- human: renderExtensionInstallTable(data.installs),
180
- kind: "extensions_list",
181
- meta: { command: parsed.definition.path.join(" "), ...(environment ? { environment } : {}), orgId: org.id }
166
+ human: renderExtensionSearchHuman(data.matches, data),
167
+ kind: "extensions_search",
168
+ meta: withExtensionSummary({ command: parsed.definition.path.join(" "), ...(environment ? { environment } : {}), orgId: org.id }, summarizeSearchResult(data))
182
169
  };
183
170
  }
184
- case "extensions.inspect": {
171
+ case "extensions.get": {
185
172
  const environment = readRequiredCliEnvironmentFlag(parsed);
186
- const data = await api.getExtensionInstall(session, org.id, readRequiredArg(parsed, "install"), {
187
- environment
188
- });
173
+ const extensionRef = readRequiredArg(parsed, "extension");
174
+ const selectedCapability = readSelectedCapability(parsed);
175
+ if (selectedCapability) {
176
+ const data = await api.getExtensionCapability(session, org.id, extensionRef, selectedCapability.kind, selectedCapability.name, { environment });
177
+ return {
178
+ data,
179
+ human: renderPrettyJson(data.capability),
180
+ kind: "extensions_get",
181
+ meta: withExtensionSummary({ command: parsed.definition.path.join(" "), environment, orgId: org.id }, `${selectedCapability.kind} ${selectedCapability.name} loaded.`)
182
+ };
183
+ }
184
+ const data = await api.getExtensionDiscovery(session, org.id, extensionRef, { environment });
189
185
  return {
190
186
  data,
191
- human: renderPrettyJson(data.install),
192
- kind: "extensions_inspect",
187
+ human: renderPrettyJson(data.extension),
188
+ kind: "extensions_get",
193
189
  meta: { command: parsed.definition.path.join(" "), environment, orgId: org.id }
194
190
  };
195
191
  }
196
- case "extensions.detail": {
192
+ case "extensions.invoke": {
197
193
  const environment = readRequiredCliEnvironmentFlag(parsed);
198
- const data = await api.getExtensionInstallDetail(session, org.id, readRequiredArg(parsed, "install"), {
199
- environment
194
+ const extensionRef = readRequiredArg(parsed, "extension");
195
+ const functionName = readRequiredArg(parsed, "function");
196
+ const invocationInput = await readOptionalInvocationInput(parsed, context);
197
+ await confirmDestructive(parsed, context, `Invoke extension function ${extensionRef}.${functionName} in ${environment}?`);
198
+ const data = await api.invokeExtensionFunction(session, org.id, extensionRef, functionName, {
199
+ environment,
200
+ ...(invocationInput.provided ? { input: invocationInput.value } : {})
200
201
  });
201
202
  return {
202
203
  data,
203
- human: renderExtensionInstallDetailHuman(data.detail),
204
- kind: "extensions_detail",
205
- meta: { command: parsed.definition.path.join(" "), environment, orgId: org.id }
204
+ human: renderExtensionInvocationHuman(data),
205
+ kind: "extensions_invoke",
206
+ meta: withExtensionSummary({
207
+ command: parsed.definition.path.join(" "),
208
+ environment,
209
+ executionId: data.invocation.executionId,
210
+ orgId: org.id
211
+ }, `Extension function ${functionName} invoked.`)
206
212
  };
207
213
  }
208
214
  case "extensions.disable": {
209
215
  const environment = readRequiredCliEnvironmentFlag(parsed);
210
- const installRef = readRequiredArg(parsed, "install");
216
+ const installRef = readRequiredArg(parsed, "name");
211
217
  announceResolvedEnvironment(parsed, context, environment);
212
218
  const data = await api.disableExtensionInstall(session, org.id, installRef, {
213
219
  environment
@@ -221,7 +227,7 @@ export async function executeExtensions(parsed, context, api, resolveOrgScope) {
221
227
  }
222
228
  case "extensions.enable": {
223
229
  const environment = readRequiredCliEnvironmentFlag(parsed);
224
- const installRef = readRequiredArg(parsed, "install");
230
+ const installRef = readRequiredArg(parsed, "name");
225
231
  announceResolvedEnvironment(parsed, context, environment);
226
232
  const data = await api.enableExtensionInstall(session, org.id, installRef, {
227
233
  environment
@@ -235,15 +241,15 @@ export async function executeExtensions(parsed, context, api, resolveOrgScope) {
235
241
  }
236
242
  case "extensions.delete": {
237
243
  const environment = readRequiredCliEnvironmentFlag(parsed);
238
- const installRef = readRequiredArg(parsed, "install");
244
+ const installRef = readRequiredArg(parsed, "name");
239
245
  announceResolvedEnvironment(parsed, context, environment);
240
- await confirmDestructive(parsed, context, `Delete extension install ${installRef} from ${environment}?`);
246
+ await confirmDestructive(parsed, context, `Delete extension ${installRef} from ${environment}?`);
241
247
  await api.deleteExtensionInstall(session, org.id, installRef, {
242
248
  environment
243
249
  });
244
250
  return {
245
251
  data: {},
246
- human: renderSuccess(`Deleted extension install ${installRef} from ${environment}.`),
252
+ human: renderSuccess(`Deleted extension ${installRef} from ${environment}.`),
247
253
  kind: "extensions_delete",
248
254
  meta: { command: parsed.definition.path.join(" "), environment, orgId: org.id }
249
255
  };
@@ -252,6 +258,94 @@ export async function executeExtensions(parsed, context, api, resolveOrgScope) {
252
258
  throw genericProblem(`Unhandled extensions command ${parsed.definition.id}.`, parsed.definition.id);
253
259
  }
254
260
  }
261
+ function readExtensionSearchFilters(parsed, input) {
262
+ const description = readOptionalStringFlag(parsed, "description");
263
+ const kind = readOptionalStringFlag(parsed, "kind");
264
+ const limit = readOptionalNumberFlag(parsed, "limit");
265
+ const name = readOptionalStringFlag(parsed, "name");
266
+ const scope = readOptionalStringFlag(parsed, "scope");
267
+ const title = readOptionalStringFlag(parsed, "title");
268
+ if (input.capabilities && title) {
269
+ throw usageProblem("--title can only be used when searching extensions.", parsed.definition.id);
270
+ }
271
+ if (input.capabilities && scope) {
272
+ throw usageProblem("--scope can only be used when searching extensions.", parsed.definition.id);
273
+ }
274
+ if (!input.capabilities && kind) {
275
+ throw usageProblem("--kind can only be used when searching inside one extension.", parsed.definition.id);
276
+ }
277
+ return {
278
+ ...(description ? { description } : {}),
279
+ ...(input.capabilities && kind ? { kind } : {}),
280
+ ...(limit !== undefined ? { limit } : {}),
281
+ ...(name ? { name } : {}),
282
+ ...(!input.capabilities && scope ? { scope } : {}),
283
+ ...(!input.capabilities && title ? { title } : {})
284
+ };
285
+ }
286
+ function readSelectedCapability(parsed) {
287
+ const selected = [
288
+ { kind: "function", name: readOptionalStringFlag(parsed, "function") },
289
+ { kind: "tool", name: readOptionalStringFlag(parsed, "tool") },
290
+ { kind: "skill", name: readOptionalStringFlag(parsed, "skill") }
291
+ ].filter((entry) => Boolean(entry.name));
292
+ if (selected.length > 1) {
293
+ throw usageProblem("Use only one of --function, --tool, or --skill.", parsed.definition.id);
294
+ }
295
+ return selected[0] ?? null;
296
+ }
297
+ function summarizeSearchResult(input) {
298
+ return input.truncated
299
+ ? `Found ${input.matches.length} of ${input.totalCount} matches. Narrow the search or raise --limit.`
300
+ : `Found ${input.matches.length} match${input.matches.length === 1 ? "" : "es"}.`;
301
+ }
302
+ function renderExtensionSearchHuman(matches, input) {
303
+ const rows = matches.map((match) => ({
304
+ ...match,
305
+ nextCommand: match.nextCommands?.install ?? match.nextCommands?.get ?? "",
306
+ stateLabel: formatExtensionState(match)
307
+ }));
308
+ return [
309
+ renderTable(rows, [
310
+ { key: "name", label: "Name" },
311
+ { key: "status", label: "Status" },
312
+ { key: "stateLabel", label: "State" },
313
+ { key: "title", label: "Title" },
314
+ { key: "description", label: "Description" },
315
+ { key: "nextCommand", label: "Next" }
316
+ ]),
317
+ renderSearchLimitHint(input, "extension")
318
+ ].filter(Boolean).join("\n");
319
+ }
320
+ function renderCapabilitySearchHuman(matches, input) {
321
+ return [
322
+ renderTable(matches, [
323
+ { key: "kind", label: "Kind" },
324
+ { key: "name", label: "Name" },
325
+ { key: "description", label: "Description" },
326
+ { key: "nextCommand", label: "Get" }
327
+ ]),
328
+ renderSearchLimitHint(input, "capability")
329
+ ].filter(Boolean).join("\n");
330
+ }
331
+ function renderSearchLimitHint(input, context) {
332
+ if (!input.truncated)
333
+ return "";
334
+ const filters = context === "extension" ? "--name, --title, --description, --scope" : "--name, --description, --kind";
335
+ return `Too many matches (${input.totalCount}). Narrow the search with ${filters}, or raise --limit.`;
336
+ }
337
+ function formatExtensionState(match) {
338
+ switch (match.state) {
339
+ case "ready":
340
+ return "Ready";
341
+ case "setup_required":
342
+ return "Setup required";
343
+ case "disabled":
344
+ return "Disabled";
345
+ default:
346
+ return "";
347
+ }
348
+ }
255
349
  function withExtensionSummary(meta, summary) {
256
350
  const trimmed = summary?.trim();
257
351
  return trimmed && trimmed.length > 0 ? { ...meta, summary: trimmed } : meta;
@@ -261,14 +355,6 @@ function summarizeExtensionValidation(input) {
261
355
  ? `Extension validation passed${formatExtensionDiagnosticSuffix(input.diagnostics)}.`
262
356
  : `Extension validation failed${formatExtensionDiagnosticSuffix(input.diagnostics)}.`;
263
357
  }
264
- function summarizeExtensionUpdatePlan(plan) {
265
- const diagnostics = Array.isArray(plan.diagnostics)
266
- ? plan.diagnostics.filter((entry) => typeof entry === "object" && entry !== null && !Array.isArray(entry))
267
- : [];
268
- return plan.updateAllowed === false
269
- ? `Extension update dry run blocked${formatExtensionDiagnosticSuffix(diagnostics)}.`
270
- : `Extension update dry run passed${formatExtensionDiagnosticSuffix(diagnostics)}.`;
271
- }
272
358
  function formatExtensionDiagnosticSuffix(diagnostics) {
273
359
  if (diagnostics.length === 0) {
274
360
  return "";
@@ -285,8 +371,7 @@ function renderValidationHuman(input) {
285
371
  const summary = renderKeyValue([
286
372
  { label: "Valid", value: input.ok },
287
373
  { label: "Files", value: input.fileCount },
288
- { label: "Bytes", value: input.totalBytes },
289
- { label: "Revision hash", value: input.revisionHash ?? "" }
374
+ { label: "Bytes", value: input.totalBytes }
290
375
  ]);
291
376
  if (input.diagnostics.length === 0) {
292
377
  return summary;
@@ -301,53 +386,41 @@ function renderValidationHuman(input) {
301
386
  ])
302
387
  ].join("\n");
303
388
  }
304
- function renderExtensionInstallTable(installs) {
305
- return renderTable(installs, [
306
- { key: "slug", label: "Slug" },
307
- { key: "enabled", label: "Enabled" },
308
- { key: "packageRevisionId", label: "Revision" },
309
- { key: "grantedPermissions", label: "Grants" }
310
- ]);
311
- }
312
389
  function renderBuiltInExtensionTable(builtIns) {
313
390
  return renderTable(builtIns, [
314
- { key: "slug", label: "Slug" },
391
+ { key: "slug", label: "Name" },
315
392
  { key: "title", label: "Title" },
316
393
  { key: "availability", label: "Availability" },
317
394
  { key: "installedSlug", label: "Installed" },
318
395
  { key: "description", label: "Description" }
319
396
  ]);
320
397
  }
321
- function renderExtensionInstallDetailHuman(detail) {
322
- const registrations = readRecord(detail.registrations?.registrations);
323
- const functions = Object.keys(readRecord(registrations?.functions) ?? {});
324
- const tools = Object.keys(readRecord(registrations?.tools) ?? {});
325
- return [
398
+ function renderExtensionInvocationHuman(data) {
399
+ const lines = [
326
400
  renderKeyValue([
327
- { label: "Slug", value: detail.install.slug },
328
- { label: "Package", value: detail.package.name },
329
- { label: "Revision", value: detail.install.packageRevisionId },
330
- { label: "Functions", value: functions.join(", ") },
331
- { label: "Tools", value: tools.join(", ") }
332
- ]),
333
- "",
334
- renderPrettyJson(detail)
335
- ].join("\n");
401
+ { label: "Execution", value: data.invocation.executionId },
402
+ { label: "Status", value: data.invocation.status }
403
+ ])
404
+ ];
405
+ if (Object.prototype.hasOwnProperty.call(data.invocation, "output")) {
406
+ lines.push("", "Output:", renderPrettyJson(data.invocation.output));
407
+ }
408
+ return lines.join("\n");
336
409
  }
337
410
  function resolveExtensionExportSelector(parsed) {
338
- const install = readOptionalStringFlag(parsed, "install");
339
- const revision = readOptionalStringFlag(parsed, "revision");
411
+ const install = readOptionalStringFlag(parsed, "name");
412
+ const artifact = readOptionalStringFlag(parsed, "artifact");
340
413
  const packageName = readOptionalStringFlag(parsed, "package");
341
- const selectedCount = [Boolean(install), Boolean(revision), Boolean(packageName)].filter(Boolean).length;
414
+ const selectedCount = [Boolean(install), Boolean(artifact), Boolean(packageName)].filter(Boolean).length;
342
415
  if (selectedCount !== 1) {
343
- throw usageProblem("Use exactly one of --install, --revision, or --package --latest.", parsed.definition.id);
416
+ throw usageProblem("Use exactly one of --name, --artifact, or --package --latest.", parsed.definition.id);
344
417
  }
345
418
  if (install) {
346
419
  const environment = readRequiredCliEnvironmentFlag(parsed);
347
420
  return { install, ...(environment ? { environment } : {}) };
348
421
  }
349
- if (revision) {
350
- return { revision };
422
+ if (artifact) {
423
+ return { artifact };
351
424
  }
352
425
  if (packageName && parsed.flags.latest === true) {
353
426
  return { latest: true, package: packageName };
@@ -356,18 +429,11 @@ function resolveExtensionExportSelector(parsed) {
356
429
  }
357
430
  async function readInstallPreconditions(parsed, context, installRef) {
358
431
  const metadata = await readExtensionInstallExportMetadata(context.cwd, installRef);
359
- const explicitBasePackageRevision = readOptionalStringFlag(parsed, "base-package-revision");
360
432
  const explicitBaseUpdatedAt = readOptionalStringFlag(parsed, "base-updated-at");
361
433
  return {
362
- ...(metadata?.packageRevisionId
363
- ? { basePackageRevisionId: metadata.packageRevisionId }
364
- : {}),
365
434
  ...(metadata?.installUpdatedAt
366
435
  ? { baseUpdatedAt: metadata.installUpdatedAt }
367
436
  : {}),
368
- ...(explicitBasePackageRevision
369
- ? { basePackageRevisionId: explicitBasePackageRevision }
370
- : {}),
371
437
  ...(explicitBaseUpdatedAt
372
438
  ? { baseUpdatedAt: explicitBaseUpdatedAt }
373
439
  : {})
@@ -384,9 +450,6 @@ async function readExtensionInstallExportMetadata(cwd, installRef) {
384
450
  return {
385
451
  ...(typeof metadata.installUpdatedAt === "string"
386
452
  ? { installUpdatedAt: metadata.installUpdatedAt }
387
- : {}),
388
- ...(typeof metadata.packageRevisionId === "string"
389
- ? { packageRevisionId: metadata.packageRevisionId }
390
453
  : {})
391
454
  };
392
455
  }
@@ -401,6 +464,16 @@ async function readRequiredPermissions(parsed, context) {
401
464
  const specifier = readRequiredStringFlag(parsed, "permissions");
402
465
  return normalizePermissions(await readJsonInputSpecifier(specifier, context.stdin, parsed.definition.id, { flag: "permissions" }), parsed.definition.id);
403
466
  }
467
+ async function readOptionalInvocationInput(parsed, context) {
468
+ const specifier = readOptionalStringFlag(parsed, "input");
469
+ if (!specifier) {
470
+ return { provided: false };
471
+ }
472
+ return {
473
+ provided: true,
474
+ value: await readJsonValueInputSpecifier(specifier, context.stdin, parsed.definition.id, { flag: "input" })
475
+ };
476
+ }
404
477
  function normalizePermissions(input, instance) {
405
478
  const permissions = input.grantedPermissions && isRecord(input.grantedPermissions)
406
479
  ? input.grantedPermissions
@@ -429,9 +502,6 @@ function normalizePermissions(input, instance) {
429
502
  function isRecord(value) {
430
503
  return typeof value === "object" && value !== null && !Array.isArray(value);
431
504
  }
432
- function readRecord(value) {
433
- return isRecord(value) ? value : null;
434
- }
435
505
  function announceResolvedEnvironment(parsed, context, environment) {
436
506
  if (!parsed.json) {
437
507
  context.stdout.write(`Environment: ${environment}\n`);
@@ -444,3 +514,7 @@ function readRequiredArg(parsed, name) {
444
514
  }
445
515
  return value;
446
516
  }
517
+ function readOptionalArg(parsed, name) {
518
+ const value = parsed.args[name];
519
+ return value && value.length > 0 ? value : undefined;
520
+ }
package/dist/files.d.ts CHANGED
@@ -4,6 +4,7 @@ interface ReadStructuredInputOptions {
4
4
  flag?: string;
5
5
  }
6
6
  export declare function readJsonInputSpecifier(specifier: string, stdin: Readable, instance: string, options?: ReadStructuredInputOptions): Promise<Record<string, unknown>>;
7
+ export declare function readJsonValueInputSpecifier(specifier: string, stdin: Readable, instance: string, options?: ReadStructuredInputOptions): Promise<unknown>;
7
8
  export declare function readTextInputSpecifier(specifier: string, stdin: Readable, instance: string): Promise<string>;
8
9
  export declare function readImportEntries(pathValue: string): Promise<Array<{
9
10
  content: string;
package/dist/files.js CHANGED
@@ -8,11 +8,22 @@ const MAX_IMPORT_FILE_BYTES = 1_000_000;
8
8
  const MAX_PACKAGE_FILE_BYTES = 2_000_000;
9
9
  const MAX_IMPORT_TOTAL_BYTES = 10_000_000;
10
10
  export async function readJsonInputSpecifier(specifier, stdin, instance, options = {}) {
11
+ const input = await readStructuredJsonInputSource(specifier, stdin, instance, options);
12
+ return readJsonObject(input.text, instance, input.options);
13
+ }
14
+ export async function readJsonValueInputSpecifier(specifier, stdin, instance, options = {}) {
15
+ const input = await readStructuredJsonInputSource(specifier, stdin, instance, options);
16
+ return readJsonValue(input.text, instance, input.options);
17
+ }
18
+ async function readStructuredJsonInputSource(specifier, stdin, instance, options = {}) {
11
19
  if (specifier === "-") {
12
- return readJsonObject(await readStream(stdin), instance, {
13
- source: "stdin",
14
- ...options
15
- });
20
+ return {
21
+ options: {
22
+ source: "stdin",
23
+ ...options
24
+ },
25
+ text: await readStream(stdin)
26
+ };
16
27
  }
17
28
  if (!specifier.startsWith("@")) {
18
29
  throw usageProblem("Structured JSON input must use @file.json or - for stdin.", instance, {
@@ -35,10 +46,13 @@ export async function readJsonInputSpecifier(specifier, stdin, instance, options
35
46
  }
36
47
  throw error;
37
48
  }
38
- return readJsonObject(source, instance, {
39
- filePath,
40
- ...options
41
- });
49
+ return {
50
+ options: {
51
+ filePath,
52
+ ...options
53
+ },
54
+ text: source
55
+ };
42
56
  }
43
57
  export async function readTextInputSpecifier(specifier, stdin, instance) {
44
58
  if (specifier === "-") {
@@ -214,25 +228,27 @@ export async function readPackageFileEntries(pathValue, instance) {
214
228
  return entries;
215
229
  }
216
230
  function readJsonObject(source, instance, options) {
217
- let parsed;
218
- try {
219
- parsed = JSON.parse(source);
220
- }
221
- catch {
231
+ const parsed = readJsonValue(source, instance, options);
232
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
222
233
  const sourceLabel = "filePath" in options ? `file ${options.filePath}` : "from stdin";
223
- throw usageProblem(`Structured JSON input ${sourceLabel} must be valid JSON.`, instance, {
234
+ throw usageProblem(`Structured JSON input ${sourceLabel} must be a JSON object.`, instance, {
224
235
  ...("filePath" in options ? { filePath: options.filePath } : { source: options.source }),
225
236
  ...(options.flag ? { flag: options.flag } : {})
226
237
  });
227
238
  }
228
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
239
+ return parsed;
240
+ }
241
+ function readJsonValue(source, instance, options) {
242
+ try {
243
+ return JSON.parse(source);
244
+ }
245
+ catch {
229
246
  const sourceLabel = "filePath" in options ? `file ${options.filePath}` : "from stdin";
230
- throw usageProblem(`Structured JSON input ${sourceLabel} must be a JSON object.`, instance, {
247
+ throw usageProblem(`Structured JSON input ${sourceLabel} must be valid JSON.`, instance, {
231
248
  ...("filePath" in options ? { filePath: options.filePath } : { source: options.source }),
232
249
  ...(options.flag ? { flag: options.flag } : {})
233
250
  });
234
251
  }
235
- return parsed;
236
252
  }
237
253
  async function readStream(stream) {
238
254
  const chunks = [];