@kora-platform/cli 0.7.0-rc1 → 0.8.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.
- package/README.md +21 -0
- package/dist/api-client.d.ts +250 -93
- package/dist/api-client.js +187 -163
- package/dist/api-types.d.ts +280 -162
- package/dist/artifact-api-client.d.ts +28 -1
- package/dist/artifact-api-client.js +33 -0
- package/dist/artifact-commands.d.ts +5 -0
- package/dist/artifact-commands.js +172 -1
- package/dist/audit-commands.d.ts +12 -0
- package/dist/audit-commands.js +74 -0
- package/dist/auth-commands.d.ts +1 -0
- package/dist/auth-commands.js +116 -29
- package/dist/command-builders.d.ts +1 -0
- package/dist/command-builders.js +1 -0
- package/dist/command-groups.js +10 -12
- package/dist/command-registry.js +548 -243
- package/dist/commands.js +652 -602
- package/dist/environment-context.d.ts +9 -0
- package/dist/environment-context.js +32 -0
- package/dist/{integration-commands.d.ts → extension-commands.d.ts} +3 -2
- package/dist/extension-commands.js +446 -0
- package/dist/files.d.ts +33 -0
- package/dist/files.js +247 -12
- package/dist/format.d.ts +5 -0
- package/dist/format.js +78 -1
- package/dist/runner.js +14 -5
- package/dist/schema-registry-data.d.ts +272 -569
- package/dist/schema-registry-data.js +307 -700
- package/dist/session.d.ts +1 -0
- package/dist/transport.d.ts +10 -0
- package/dist/transport.js +22 -0
- package/dist/types.d.ts +2 -1
- package/dist/workspace-source.d.ts +1 -0
- package/dist/workspace-source.js +13 -0
- package/package.json +2 -1
- package/dist/integration-api-client.d.ts +0 -29
- package/dist/integration-api-client.js +0 -50
- package/dist/integration-commands.js +0 -208
package/dist/files.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
-
import { readdir, readFile,
|
|
3
|
-
import { basename, join, relative, resolve } from "node:path";
|
|
2
|
+
import { lstat, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { usageProblem } from "./cli-errors.js";
|
|
5
5
|
import { parseDotEnv } from "./dotenv.js";
|
|
6
|
+
import { shouldIgnoreWorkspacePath } from "./workspace-source.js";
|
|
7
|
+
const MAX_IMPORT_FILE_COUNT = 500;
|
|
8
|
+
const MAX_IMPORT_FILE_BYTES = 1_000_000;
|
|
9
|
+
const MAX_PACKAGE_FILE_BYTES = 2_000_000;
|
|
10
|
+
const MAX_IMPORT_TOTAL_BYTES = 10_000_000;
|
|
6
11
|
export async function readJsonInputSpecifier(specifier, stdin, instance) {
|
|
7
12
|
if (specifier === "-") {
|
|
8
13
|
return readJsonObject(await readStream(stdin), instance);
|
|
@@ -22,16 +27,146 @@ export async function readTextInputSpecifier(specifier, stdin, instance) {
|
|
|
22
27
|
return readFile(specifier.slice(1), "utf8");
|
|
23
28
|
}
|
|
24
29
|
export async function readImportEntries(pathValue) {
|
|
30
|
+
return await readUtf8Entries(pathValue, {
|
|
31
|
+
instance: "org.import",
|
|
32
|
+
maxFileBytes: MAX_IMPORT_FILE_BYTES
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export function isZipArchivePath(pathValue) {
|
|
36
|
+
return extname(pathValue).toLowerCase() === ".zip";
|
|
37
|
+
}
|
|
38
|
+
export async function readArchiveBytes(pathValue, instance) {
|
|
39
|
+
const absolutePath = resolve(pathValue);
|
|
40
|
+
const pathStat = await lstat(absolutePath);
|
|
41
|
+
if (pathStat.isSymbolicLink() || !pathStat.isFile()) {
|
|
42
|
+
throw usageProblem("Archive path must be a regular .zip file, not a directory or symbolic link.", instance);
|
|
43
|
+
}
|
|
44
|
+
return await readFile(absolutePath);
|
|
45
|
+
}
|
|
46
|
+
export async function readWorkspaceTestEntries(pathValue) {
|
|
47
|
+
return await readUtf8Entries(pathValue, {
|
|
48
|
+
instance: "test node",
|
|
49
|
+
maxFileBytes: MAX_PACKAGE_FILE_BYTES
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function readUtf8Entries(pathValue, options) {
|
|
25
53
|
const absolutePath = resolve(pathValue);
|
|
26
|
-
const pathStat = await
|
|
54
|
+
const pathStat = await lstat(absolutePath);
|
|
55
|
+
if (pathStat.isSymbolicLink()) {
|
|
56
|
+
throw usageProblem("Import path must be a regular file or directory, not a symbolic link.", options.instance);
|
|
57
|
+
}
|
|
27
58
|
if (pathStat.isFile()) {
|
|
59
|
+
const content = await readLimitedUtf8File(absolutePath, basename(absolutePath), {
|
|
60
|
+
fileCount: 0,
|
|
61
|
+
totalBytes: 0
|
|
62
|
+
}, options);
|
|
28
63
|
return [{
|
|
29
|
-
content:
|
|
64
|
+
content: content.content,
|
|
30
65
|
path: basename(absolutePath)
|
|
31
66
|
}];
|
|
32
67
|
}
|
|
33
68
|
const entries = [];
|
|
34
|
-
await walkImportDirectory(absolutePath, absolutePath, entries
|
|
69
|
+
await walkImportDirectory(absolutePath, absolutePath, entries, {
|
|
70
|
+
fileCount: 0,
|
|
71
|
+
totalBytes: 0
|
|
72
|
+
}, options);
|
|
73
|
+
entries.sort((left, right) => left.path.localeCompare(right.path));
|
|
74
|
+
return entries;
|
|
75
|
+
}
|
|
76
|
+
export async function readWorkspaceExportMetadata(pathValue) {
|
|
77
|
+
const absolutePath = resolve(pathValue);
|
|
78
|
+
try {
|
|
79
|
+
const metadata = await readFile(join(absolutePath, ".kora", "export.json"), "utf8");
|
|
80
|
+
const parsed = JSON.parse(metadata);
|
|
81
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
|
82
|
+
? parsed
|
|
83
|
+
: null;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (isNodeErrorWithCode(error, "ENOENT") || isNodeErrorWithCode(error, "ENOTDIR")) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export async function writeWorkspaceExport(outPath, envelope) {
|
|
93
|
+
const absolutePath = resolve(outPath);
|
|
94
|
+
await assertExportOutputIsEmpty(absolutePath, "org.export");
|
|
95
|
+
await mkdir(absolutePath, { recursive: true });
|
|
96
|
+
for (const file of envelope.files) {
|
|
97
|
+
const targetPath = resolveExportTargetPath(absolutePath, file.path, "org.export");
|
|
98
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
99
|
+
await writeFile(targetPath, file.content, "utf8");
|
|
100
|
+
}
|
|
101
|
+
const metadataPath = join(absolutePath, ".kora", "export.json");
|
|
102
|
+
await mkdir(dirname(metadataPath), { recursive: true });
|
|
103
|
+
await writeFile(metadataPath, `${JSON.stringify(envelope.metadata, null, 2)}\n`, "utf8");
|
|
104
|
+
}
|
|
105
|
+
export async function writeReleaseSourceFiles(outPath, envelope) {
|
|
106
|
+
const absolutePath = resolve(outPath);
|
|
107
|
+
await assertExportOutputIsEmpty(absolutePath, "release source", "Release source");
|
|
108
|
+
await mkdir(absolutePath, { recursive: true });
|
|
109
|
+
const seenPaths = new Set();
|
|
110
|
+
for (const file of envelope.files) {
|
|
111
|
+
if (seenPaths.has(file.path)) {
|
|
112
|
+
throw usageProblem(`Release source contains duplicate file path '${file.path}'.`, "release source");
|
|
113
|
+
}
|
|
114
|
+
seenPaths.add(file.path);
|
|
115
|
+
const targetPath = resolveExportTargetPath(absolutePath, file.path, "release source");
|
|
116
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
117
|
+
await writeFile(targetPath, file.content, "utf8");
|
|
118
|
+
}
|
|
119
|
+
const metadataPath = join(absolutePath, ".kora", "release-source.json");
|
|
120
|
+
await mkdir(dirname(metadataPath), { recursive: true });
|
|
121
|
+
await writeFile(metadataPath, `${JSON.stringify(envelope.metadata, null, 2)}\n`, "utf8");
|
|
122
|
+
}
|
|
123
|
+
export async function writeArchiveExport(outPath, archive, instance) {
|
|
124
|
+
const absolutePath = resolve(outPath);
|
|
125
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
126
|
+
try {
|
|
127
|
+
await writeFile(absolutePath, archive, { flag: "wx" });
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
if (isNodeErrorWithCode(error, "EEXIST")) {
|
|
131
|
+
throw usageProblem("Export output file already exists.", instance);
|
|
132
|
+
}
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export async function writePackageExport(outPath, envelope) {
|
|
137
|
+
const absolutePath = resolve(outPath);
|
|
138
|
+
await assertExportOutputIsEmpty(absolutePath, "extensions.export");
|
|
139
|
+
await mkdir(absolutePath, { recursive: true });
|
|
140
|
+
for (const file of envelope.files) {
|
|
141
|
+
const targetPath = resolveExportTargetPath(absolutePath, file.path, "extensions.export");
|
|
142
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
143
|
+
await writeFile(targetPath, Buffer.from(file.contentBase64, "base64"));
|
|
144
|
+
}
|
|
145
|
+
const metadataPath = join(absolutePath, ".kora", "export.json");
|
|
146
|
+
await mkdir(dirname(metadataPath), { recursive: true });
|
|
147
|
+
await writeFile(metadataPath, `${JSON.stringify(envelope.metadata, null, 2)}\n`, "utf8");
|
|
148
|
+
}
|
|
149
|
+
export async function readPackageFileEntries(pathValue, instance) {
|
|
150
|
+
const absolutePath = resolve(pathValue);
|
|
151
|
+
const pathStat = await lstat(absolutePath);
|
|
152
|
+
if (pathStat.isSymbolicLink()) {
|
|
153
|
+
throw usageProblem("Package path must be a regular file or directory, not a symbolic link.", instance);
|
|
154
|
+
}
|
|
155
|
+
if (pathStat.isFile()) {
|
|
156
|
+
assertPackageFileCanBeRead(pathStat.size, basename(absolutePath), {
|
|
157
|
+
fileCount: 0,
|
|
158
|
+
totalBytes: 0
|
|
159
|
+
}, instance);
|
|
160
|
+
return [{
|
|
161
|
+
contentBase64: (await readFile(absolutePath)).toString("base64"),
|
|
162
|
+
path: basename(absolutePath)
|
|
163
|
+
}];
|
|
164
|
+
}
|
|
165
|
+
const entries = [];
|
|
166
|
+
await walkPackageDirectory(absolutePath, absolutePath, entries, {
|
|
167
|
+
fileCount: 0,
|
|
168
|
+
totalBytes: 0
|
|
169
|
+
}, instance);
|
|
35
170
|
entries.sort((left, right) => left.path.localeCompare(right.path));
|
|
36
171
|
return entries;
|
|
37
172
|
}
|
|
@@ -62,24 +197,124 @@ async function readStream(stream) {
|
|
|
62
197
|
}
|
|
63
198
|
return Buffer.concat(chunks).toString("utf8");
|
|
64
199
|
}
|
|
65
|
-
async function walkImportDirectory(root, current, entries) {
|
|
200
|
+
async function walkImportDirectory(root, current, entries, counters, options) {
|
|
66
201
|
const children = await readdir(current, { withFileTypes: true });
|
|
67
202
|
children.sort((left, right) => left.name.localeCompare(right.name));
|
|
68
203
|
for (const child of children) {
|
|
69
|
-
|
|
204
|
+
const childPath = join(current, child.name);
|
|
205
|
+
const relativePath = relative(root, childPath).replaceAll("\\", "/");
|
|
206
|
+
if (shouldIgnoreImportPath(relativePath)) {
|
|
70
207
|
continue;
|
|
71
208
|
}
|
|
209
|
+
if (child.isDirectory()) {
|
|
210
|
+
await walkImportDirectory(root, childPath, entries, counters, options);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (!child.isFile()) {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const content = await readLimitedUtf8File(childPath, relativePath, counters, options);
|
|
217
|
+
entries.push({
|
|
218
|
+
content: content.content,
|
|
219
|
+
path: relativePath
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async function walkPackageDirectory(root, current, entries, counters, instance) {
|
|
224
|
+
const children = await readdir(current, { withFileTypes: true });
|
|
225
|
+
children.sort((left, right) => left.name.localeCompare(right.name));
|
|
226
|
+
for (const child of children) {
|
|
72
227
|
const childPath = join(current, child.name);
|
|
228
|
+
const relativePath = relative(root, childPath).replaceAll("\\", "/");
|
|
229
|
+
if (shouldIgnoreImportPath(relativePath)) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
73
232
|
if (child.isDirectory()) {
|
|
74
|
-
await
|
|
233
|
+
await walkPackageDirectory(root, childPath, entries, counters, instance);
|
|
75
234
|
continue;
|
|
76
235
|
}
|
|
236
|
+
if (!child.isFile()) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const childStat = await lstat(childPath);
|
|
240
|
+
assertPackageFileCanBeRead(childStat.size, relativePath, counters, instance);
|
|
77
241
|
entries.push({
|
|
78
|
-
|
|
79
|
-
path:
|
|
242
|
+
contentBase64: (await readFile(childPath)).toString("base64"),
|
|
243
|
+
path: relativePath
|
|
80
244
|
});
|
|
81
245
|
}
|
|
82
246
|
}
|
|
83
|
-
function
|
|
84
|
-
return
|
|
247
|
+
function shouldIgnoreImportPath(pathValue) {
|
|
248
|
+
return shouldIgnoreWorkspacePath(pathValue);
|
|
249
|
+
}
|
|
250
|
+
async function assertExportOutputIsEmpty(absolutePath, instance, outputLabel = "Export") {
|
|
251
|
+
try {
|
|
252
|
+
const pathStat = await lstat(absolutePath);
|
|
253
|
+
if (pathStat.isSymbolicLink()) {
|
|
254
|
+
throw usageProblem(`${outputLabel} output path must not be a symbolic link.`, instance);
|
|
255
|
+
}
|
|
256
|
+
if (!pathStat.isDirectory()) {
|
|
257
|
+
throw usageProblem(`${outputLabel} output path must be a directory.`, instance);
|
|
258
|
+
}
|
|
259
|
+
const children = await readdir(absolutePath);
|
|
260
|
+
if (children.length > 0) {
|
|
261
|
+
throw usageProblem(`${outputLabel} output directory must be empty or not exist.`, instance);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
if (isNodeErrorWithCode(error, "ENOENT")) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
function resolveExportTargetPath(root, filePath, instance) {
|
|
272
|
+
const normalizedPath = filePath.replaceAll("\\", "/");
|
|
273
|
+
const segments = normalizedPath.split("/");
|
|
274
|
+
if (normalizedPath.trim().length === 0 ||
|
|
275
|
+
isAbsolute(normalizedPath) ||
|
|
276
|
+
segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
|
|
277
|
+
throw usageProblem("Export file paths must be relative paths inside the output directory.", instance);
|
|
278
|
+
}
|
|
279
|
+
const targetPath = resolve(root, normalizedPath);
|
|
280
|
+
const relativeTarget = relative(root, targetPath);
|
|
281
|
+
if (relativeTarget === "" || relativeTarget.startsWith("..") || isAbsolute(relativeTarget)) {
|
|
282
|
+
throw usageProblem("Export file paths must be relative paths inside the output directory.", instance);
|
|
283
|
+
}
|
|
284
|
+
return targetPath;
|
|
285
|
+
}
|
|
286
|
+
async function readLimitedUtf8File(filePath, displayPath, counters, options) {
|
|
287
|
+
if (counters.fileCount >= MAX_IMPORT_FILE_COUNT) {
|
|
288
|
+
throw usageProblem(`Import has more than ${String(MAX_IMPORT_FILE_COUNT)} files.`, options.instance);
|
|
289
|
+
}
|
|
290
|
+
const content = await readFile(filePath, "utf8");
|
|
291
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
292
|
+
if (bytes > options.maxFileBytes) {
|
|
293
|
+
throw usageProblem(`Import file ${displayPath} exceeds the per-file byte limit.`, options.instance);
|
|
294
|
+
}
|
|
295
|
+
if (counters.totalBytes + bytes > MAX_IMPORT_TOTAL_BYTES) {
|
|
296
|
+
throw usageProblem(`Import exceeds the ${String(MAX_IMPORT_TOTAL_BYTES)} byte limit.`, options.instance);
|
|
297
|
+
}
|
|
298
|
+
counters.fileCount += 1;
|
|
299
|
+
counters.totalBytes += bytes;
|
|
300
|
+
return { content };
|
|
301
|
+
}
|
|
302
|
+
function assertPackageFileCanBeRead(bytes, displayPath, counters, instance) {
|
|
303
|
+
if (counters.fileCount >= MAX_IMPORT_FILE_COUNT) {
|
|
304
|
+
throw usageProblem(`Package has more than ${String(MAX_IMPORT_FILE_COUNT)} files.`, instance);
|
|
305
|
+
}
|
|
306
|
+
if (bytes > MAX_PACKAGE_FILE_BYTES) {
|
|
307
|
+
throw usageProblem(`Package file ${displayPath} exceeds the per-file byte limit.`, instance);
|
|
308
|
+
}
|
|
309
|
+
if (counters.totalBytes + bytes > MAX_IMPORT_TOTAL_BYTES) {
|
|
310
|
+
throw usageProblem(`Package exceeds the ${String(MAX_IMPORT_TOTAL_BYTES)} byte limit.`, instance);
|
|
311
|
+
}
|
|
312
|
+
counters.fileCount += 1;
|
|
313
|
+
counters.totalBytes += bytes;
|
|
314
|
+
}
|
|
315
|
+
function isNodeErrorWithCode(error, code) {
|
|
316
|
+
return typeof error === "object" &&
|
|
317
|
+
error !== null &&
|
|
318
|
+
"code" in error &&
|
|
319
|
+
error.code === code;
|
|
85
320
|
}
|
package/dist/format.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export interface CommandOutput {
|
|
2
|
+
exitCode?: number;
|
|
2
3
|
human: string;
|
|
3
4
|
kind: string;
|
|
4
5
|
meta?: Record<string, unknown>;
|
|
@@ -10,6 +11,7 @@ export declare function renderJsonEnvelope(input: {
|
|
|
10
11
|
}): string;
|
|
11
12
|
export declare function renderProblemJson(input: {
|
|
12
13
|
detail: string;
|
|
14
|
+
details?: Record<string, unknown>;
|
|
13
15
|
instance: string;
|
|
14
16
|
status: number;
|
|
15
17
|
title: string;
|
|
@@ -27,12 +29,15 @@ export declare function renderPrettyJson(value: unknown): string;
|
|
|
27
29
|
export declare function renderVerboseHuman(base: string, data: unknown): string;
|
|
28
30
|
export declare function renderVerboseProblem(input: {
|
|
29
31
|
detail: string;
|
|
32
|
+
details?: Record<string, unknown>;
|
|
30
33
|
instance: string;
|
|
31
34
|
rawDetail?: string;
|
|
32
35
|
title: string;
|
|
33
36
|
type: string;
|
|
34
37
|
}): string;
|
|
35
38
|
export declare function renderSuccess(message: string): string;
|
|
39
|
+
export declare function formatProblemDetail(detail: string, details?: Record<string, unknown>): string;
|
|
40
|
+
export declare function formatProblemDetailsSummary(details: Record<string, unknown> | undefined): string | null;
|
|
36
41
|
export declare function renderDiffSummary(input: {
|
|
37
42
|
added: string[];
|
|
38
43
|
changed: string[];
|
package/dist/format.js
CHANGED
|
@@ -6,7 +6,14 @@ export function renderJsonEnvelope(input) {
|
|
|
6
6
|
}, null, 2)}\n`;
|
|
7
7
|
}
|
|
8
8
|
export function renderProblemJson(input) {
|
|
9
|
-
return `${JSON.stringify(
|
|
9
|
+
return `${JSON.stringify({
|
|
10
|
+
detail: input.detail,
|
|
11
|
+
...(input.details ? { details: input.details } : {}),
|
|
12
|
+
instance: input.instance,
|
|
13
|
+
status: input.status,
|
|
14
|
+
title: input.title,
|
|
15
|
+
type: input.type
|
|
16
|
+
}, null, 2)}\n`;
|
|
10
17
|
}
|
|
11
18
|
export function renderTable(rows, columns) {
|
|
12
19
|
if (rows.length === 0) {
|
|
@@ -38,8 +45,10 @@ export function renderVerboseHuman(base, data) {
|
|
|
38
45
|
return `${base}\n\nDetails:\n${detail}`;
|
|
39
46
|
}
|
|
40
47
|
export function renderVerboseProblem(input) {
|
|
48
|
+
const detailsSummary = formatProblemDetailsSummary(input.details);
|
|
41
49
|
const lines = [
|
|
42
50
|
`${input.title}: ${input.detail}`,
|
|
51
|
+
...(detailsSummary ? ["", detailsSummary] : []),
|
|
43
52
|
"",
|
|
44
53
|
"Details:",
|
|
45
54
|
`Type: ${input.type}`,
|
|
@@ -48,11 +57,79 @@ export function renderVerboseProblem(input) {
|
|
|
48
57
|
if (input.rawDetail && input.rawDetail !== input.detail) {
|
|
49
58
|
lines.push(`Raw backend message: ${input.rawDetail}`);
|
|
50
59
|
}
|
|
60
|
+
if (input.details) {
|
|
61
|
+
lines.push(`Structured details: ${renderPrettyJson(input.details)}`);
|
|
62
|
+
}
|
|
51
63
|
return lines.join("\n");
|
|
52
64
|
}
|
|
53
65
|
export function renderSuccess(message) {
|
|
54
66
|
return message;
|
|
55
67
|
}
|
|
68
|
+
export function formatProblemDetail(detail, details) {
|
|
69
|
+
const detailsSummary = formatProblemDetailsSummary(details);
|
|
70
|
+
return detailsSummary ? `${detail}\n${detailsSummary}` : detail;
|
|
71
|
+
}
|
|
72
|
+
export function formatProblemDetailsSummary(details) {
|
|
73
|
+
if (!details) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const diagnostics = formatProblemDetailsList(details.diagnostics, "Diagnostics", formatDiagnosticLikeEntry);
|
|
77
|
+
if (diagnostics) {
|
|
78
|
+
return diagnostics;
|
|
79
|
+
}
|
|
80
|
+
const errors = formatProblemDetailsList(details.errors, "Errors", formatUnknownDetailEntry);
|
|
81
|
+
if (errors) {
|
|
82
|
+
return errors;
|
|
83
|
+
}
|
|
84
|
+
return formatProblemDetailsList(details.issues, "Issues", formatUnknownDetailEntry);
|
|
85
|
+
}
|
|
86
|
+
function formatProblemDetailsList(value, label, formatter) {
|
|
87
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const lines = value
|
|
91
|
+
.map(formatter)
|
|
92
|
+
.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
93
|
+
if (lines.length === 0) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return [label, ...lines.slice(0, 5).map((entry) => `- ${entry}`)].join("\n");
|
|
97
|
+
}
|
|
98
|
+
function formatDiagnosticLikeEntry(entry) {
|
|
99
|
+
if (!isRecord(entry)) {
|
|
100
|
+
return formatUnknownDetailEntry(entry);
|
|
101
|
+
}
|
|
102
|
+
const message = readString(entry.message);
|
|
103
|
+
if (!message) {
|
|
104
|
+
return formatUnknownDetailEntry(entry);
|
|
105
|
+
}
|
|
106
|
+
const path = readString(entry.path) ?? readString(entry.filePath) ?? readString(entry.instancePath);
|
|
107
|
+
const code = readString(entry.code);
|
|
108
|
+
return [
|
|
109
|
+
path ? `${path}:` : null,
|
|
110
|
+
message,
|
|
111
|
+
code && !path ? `(${code})` : null
|
|
112
|
+
].filter((part) => typeof part === "string" && part.length > 0).join(" ");
|
|
113
|
+
}
|
|
114
|
+
function formatUnknownDetailEntry(entry) {
|
|
115
|
+
if (typeof entry === "string") {
|
|
116
|
+
return entry.trim() || null;
|
|
117
|
+
}
|
|
118
|
+
if (isRecord(entry)) {
|
|
119
|
+
const message = readString(entry.message);
|
|
120
|
+
if (message) {
|
|
121
|
+
const path = readString(entry.path) ?? readString(entry.instancePath);
|
|
122
|
+
return path ? `${path}: ${message}` : message;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
function isRecord(value) {
|
|
128
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
129
|
+
}
|
|
130
|
+
function readString(value) {
|
|
131
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
132
|
+
}
|
|
56
133
|
export function renderDiffSummary(input) {
|
|
57
134
|
const lines = [
|
|
58
135
|
`Add: ${input.added.length}`,
|
package/dist/runner.js
CHANGED
|
@@ -2,7 +2,7 @@ import { defaultGlobalConfigPath, loadCliConfig } from "./config.js";
|
|
|
2
2
|
import { authProblem, exitCodeFromError, toProblem, usageProblem } from "./cli-errors.js";
|
|
3
3
|
import { buildHelpJson, findCommandDefinition, listSubcommands, resolveCommandAliases } from "./command-registry.js";
|
|
4
4
|
import { executeParsedCommand } from "./commands.js";
|
|
5
|
-
import { renderJsonEnvelope, renderProblemJson, renderVerboseHuman, renderVerboseProblem } from "./format.js";
|
|
5
|
+
import { formatProblemDetail, renderJsonEnvelope, renderProblemJson, renderVerboseHuman, renderVerboseProblem } from "./format.js";
|
|
6
6
|
import { createFileSessionStore, createMemorySessionStore } from "./session-store.js";
|
|
7
7
|
import { defaultSessionPath, readSessionFromEnv } from "./session.js";
|
|
8
8
|
const VISIBLE_COMMAND_LABELS_ENV = "KORA_VISIBLE_COMMAND_LABELS";
|
|
@@ -57,7 +57,7 @@ export async function runCli(argv, input = {}) {
|
|
|
57
57
|
stdout
|
|
58
58
|
});
|
|
59
59
|
return {
|
|
60
|
-
exitCode: 0,
|
|
60
|
+
exitCode: executed.exitCode ?? 0,
|
|
61
61
|
stderr: "",
|
|
62
62
|
stdout: parsed.json
|
|
63
63
|
? renderJsonEnvelope({
|
|
@@ -72,6 +72,7 @@ export async function runCli(argv, input = {}) {
|
|
|
72
72
|
const wantsJson = tokens.includes("--json");
|
|
73
73
|
const wantsVerbose = tokens.includes("--verbose");
|
|
74
74
|
const problem = toProblem(error, tokens.join(" "));
|
|
75
|
+
const details = hasProblemDetails(problem) ? problem.details : undefined;
|
|
75
76
|
return {
|
|
76
77
|
exitCode: exitCodeFromError(problem),
|
|
77
78
|
stderr: wantsJson
|
|
@@ -79,15 +80,17 @@ export async function runCli(argv, input = {}) {
|
|
|
79
80
|
: `${wantsVerbose
|
|
80
81
|
? renderVerboseProblem({
|
|
81
82
|
detail: problem.detail,
|
|
83
|
+
...(details ? { details } : {}),
|
|
82
84
|
instance: problem.instance,
|
|
83
85
|
...(hasRawDetail(problem) ? { rawDetail: problem.rawDetail } : {}),
|
|
84
86
|
title: problem.title,
|
|
85
87
|
type: problem.type
|
|
86
88
|
})
|
|
87
|
-
: `${problem.title}: ${problem.detail}`}\n`,
|
|
89
|
+
: `${problem.title}: ${formatProblemDetail(problem.detail, details)}`}\n`,
|
|
88
90
|
stdout: wantsJson
|
|
89
91
|
? renderProblemJson({
|
|
90
92
|
detail: problem.detail,
|
|
93
|
+
...(details ? { details } : {}),
|
|
91
94
|
instance: problem.instance,
|
|
92
95
|
status: problem.status,
|
|
93
96
|
title: problem.title,
|
|
@@ -137,6 +140,9 @@ function resolveHelpInvocation(tokens, commandFilter) {
|
|
|
137
140
|
function hasRawDetail(problem) {
|
|
138
141
|
return "rawDetail" in problem && typeof problem.rawDetail === "string";
|
|
139
142
|
}
|
|
143
|
+
function hasProblemDetails(problem) {
|
|
144
|
+
return "details" in problem && typeof problem.details === "object" && problem.details !== null && !Array.isArray(problem.details);
|
|
145
|
+
}
|
|
140
146
|
function normalizeHelpPath(tokens, commandFilter) {
|
|
141
147
|
if (tokens.length === 0) {
|
|
142
148
|
return [];
|
|
@@ -213,7 +219,10 @@ function parseInvocation(tokens, commandFilter) {
|
|
|
213
219
|
throw usageProblem(withHelpSuggestion(`Unknown command: ${tokens.join(" ")}.`, resolved.commandPath, commandFilter), tokens.join(" "));
|
|
214
220
|
}
|
|
215
221
|
const definition = resolved.definition;
|
|
216
|
-
const
|
|
222
|
+
const visibleFlags = commandFilter
|
|
223
|
+
? definition.flags.filter((entry) => !entry.hiddenWhenCommandFiltered)
|
|
224
|
+
: definition.flags;
|
|
225
|
+
const flagDefinitions = new Map(visibleFlags.map((entry) => [entry.name, entry]));
|
|
217
226
|
const positionals = [];
|
|
218
227
|
const flags = {};
|
|
219
228
|
for (let index = 0; index < resolved.remainder.length; index += 1) {
|
|
@@ -254,7 +263,7 @@ function parseInvocation(tokens, commandFilter) {
|
|
|
254
263
|
if (positionals.length > definition.args.length) {
|
|
255
264
|
throw usageProblem(`Too many positional arguments for '${definition.path.join(" ")}'.`, definition.path.join(" "));
|
|
256
265
|
}
|
|
257
|
-
for (const flag of
|
|
266
|
+
for (const flag of visibleFlags) {
|
|
258
267
|
if (flag.required && flags[flag.name] === undefined) {
|
|
259
268
|
throw usageProblem(`Missing required flag '--${flag.name}'.`, definition.path.join(" "));
|
|
260
269
|
}
|