@kora-platform/cli 0.7.0-rc1 → 0.8.0-rc10
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 +274 -106
- package/dist/api-client.js +192 -167
- package/dist/api-types.d.ts +301 -163
- 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 +177 -4
- 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 +195 -32
- package/dist/cli-errors.d.ts +7 -1
- package/dist/cli-errors.js +12 -1
- package/dist/command-builders.d.ts +1 -0
- package/dist/command-builders.js +1 -0
- package/dist/command-flags.d.ts +1 -0
- package/dist/command-flags.js +7 -0
- package/dist/command-groups.js +10 -12
- package/dist/command-registry.js +595 -277
- package/dist/commands.js +728 -636
- package/dist/environment-context.d.ts +9 -0
- package/dist/environment-context.js +32 -0
- package/dist/error-code.d.ts +2 -0
- package/dist/error-code.js +9 -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 +44 -4
- package/dist/files.js +349 -26
- package/dist/format.d.ts +6 -0
- package/dist/format.js +83 -1
- package/dist/runner.js +28 -10
- package/dist/schema-registry-data.d.ts +318 -571
- package/dist/schema-registry-data.js +356 -698
- package/dist/session-store.js +80 -0
- package/dist/session.d.ts +1 -0
- package/dist/transport-refresh.d.ts +10 -0
- package/dist/transport-refresh.js +51 -0
- package/dist/transport.d.ts +31 -0
- package/dist/transport.js +102 -36
- 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/dotenv.d.ts +0 -1
- package/dist/dotenv.js +0 -26
- 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,16 +1,44 @@
|
|
|
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
|
-
import {
|
|
6
|
-
|
|
5
|
+
import { shouldIgnoreWorkspacePath } from "./workspace-source.js";
|
|
6
|
+
const MAX_IMPORT_FILE_COUNT = 500;
|
|
7
|
+
const MAX_IMPORT_FILE_BYTES = 1_000_000;
|
|
8
|
+
const MAX_PACKAGE_FILE_BYTES = 2_000_000;
|
|
9
|
+
const MAX_IMPORT_TOTAL_BYTES = 10_000_000;
|
|
10
|
+
export async function readJsonInputSpecifier(specifier, stdin, instance, options = {}) {
|
|
7
11
|
if (specifier === "-") {
|
|
8
|
-
return readJsonObject(await readStream(stdin), instance
|
|
12
|
+
return readJsonObject(await readStream(stdin), instance, {
|
|
13
|
+
source: "stdin",
|
|
14
|
+
...options
|
|
15
|
+
});
|
|
9
16
|
}
|
|
10
17
|
if (!specifier.startsWith("@")) {
|
|
11
|
-
throw usageProblem("Structured JSON input must use @file.json or - for stdin.", instance
|
|
18
|
+
throw usageProblem("Structured JSON input must use @file.json or - for stdin.", instance, {
|
|
19
|
+
...(options.flag ? { flag: options.flag } : {})
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const filePath = resolve(specifier.slice(1));
|
|
23
|
+
let source;
|
|
24
|
+
try {
|
|
25
|
+
source = await readFile(filePath, "utf8");
|
|
12
26
|
}
|
|
13
|
-
|
|
27
|
+
catch (error) {
|
|
28
|
+
const nativeCode = readNodeErrorCode(error);
|
|
29
|
+
if (nativeCode && isLocalPathReadErrorCode(nativeCode)) {
|
|
30
|
+
throw usageProblem(`Structured JSON input file ${filePath} could not be read: ${nativeCode}.`, instance, {
|
|
31
|
+
filePath,
|
|
32
|
+
...(options.flag ? { flag: options.flag } : {}),
|
|
33
|
+
nativeCode
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
return readJsonObject(source, instance, {
|
|
39
|
+
filePath,
|
|
40
|
+
...options
|
|
41
|
+
});
|
|
14
42
|
}
|
|
15
43
|
export async function readTextInputSpecifier(specifier, stdin, instance) {
|
|
16
44
|
if (specifier === "-") {
|
|
@@ -22,36 +50,187 @@ export async function readTextInputSpecifier(specifier, stdin, instance) {
|
|
|
22
50
|
return readFile(specifier.slice(1), "utf8");
|
|
23
51
|
}
|
|
24
52
|
export async function readImportEntries(pathValue) {
|
|
53
|
+
return await readUtf8Entries(pathValue, {
|
|
54
|
+
instance: "org.import",
|
|
55
|
+
maxFileBytes: MAX_IMPORT_FILE_BYTES
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export function isZipArchivePath(pathValue) {
|
|
59
|
+
return extname(pathValue).toLowerCase() === ".zip";
|
|
60
|
+
}
|
|
61
|
+
export async function readArchiveBytes(pathValue, instance) {
|
|
62
|
+
const file = await readLocalFileBytes(pathValue, instance, {
|
|
63
|
+
regularFileMessage: "Archive path must be a regular .zip file, not a directory or symbolic link."
|
|
64
|
+
});
|
|
65
|
+
return file.bytes;
|
|
66
|
+
}
|
|
67
|
+
export async function readLocalFileBytes(pathValue, instance, options = {}) {
|
|
25
68
|
const absolutePath = resolve(pathValue);
|
|
26
|
-
const pathStat = await
|
|
69
|
+
const pathStat = await readLocalPathStat(absolutePath, instance);
|
|
70
|
+
if (pathStat.isSymbolicLink() || !pathStat.isFile()) {
|
|
71
|
+
throw usageProblem(options.regularFileMessage ?? "Path must be a regular file, not a directory or symbolic link.", instance);
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
return {
|
|
75
|
+
absolutePath,
|
|
76
|
+
bytes: await readFile(absolutePath)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
throwLocalPathReadProblem(error, absolutePath, instance);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export async function readWorkspaceTestEntries(pathValue) {
|
|
84
|
+
return await readUtf8Entries(pathValue, {
|
|
85
|
+
instance: "test node",
|
|
86
|
+
maxFileBytes: MAX_PACKAGE_FILE_BYTES
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async function readUtf8Entries(pathValue, options) {
|
|
90
|
+
const absolutePath = resolve(pathValue);
|
|
91
|
+
const pathStat = await readLocalPathStat(absolutePath, options.instance);
|
|
92
|
+
if (pathStat.isSymbolicLink()) {
|
|
93
|
+
throw usageProblem("Import path must be a regular file or directory, not a symbolic link.", options.instance);
|
|
94
|
+
}
|
|
27
95
|
if (pathStat.isFile()) {
|
|
96
|
+
const pathName = basename(absolutePath);
|
|
97
|
+
if (shouldIgnoreSourceImportPath(pathName)) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
const content = await readLimitedUtf8File(absolutePath, pathName, {
|
|
101
|
+
fileCount: 0,
|
|
102
|
+
totalBytes: 0
|
|
103
|
+
}, options);
|
|
28
104
|
return [{
|
|
29
|
-
content:
|
|
105
|
+
content: content.content,
|
|
30
106
|
path: basename(absolutePath)
|
|
31
107
|
}];
|
|
32
108
|
}
|
|
33
109
|
const entries = [];
|
|
34
|
-
await walkImportDirectory(absolutePath, absolutePath, entries
|
|
110
|
+
await walkImportDirectory(absolutePath, absolutePath, entries, {
|
|
111
|
+
fileCount: 0,
|
|
112
|
+
totalBytes: 0
|
|
113
|
+
}, options);
|
|
35
114
|
entries.sort((left, right) => left.path.localeCompare(right.path));
|
|
36
115
|
return entries;
|
|
37
116
|
}
|
|
38
|
-
export async function
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
117
|
+
export async function readWorkspaceExportMetadata(pathValue) {
|
|
118
|
+
const absolutePath = resolve(pathValue);
|
|
119
|
+
try {
|
|
120
|
+
const metadata = await readFile(join(absolutePath, ".kora", "export.json"), "utf8");
|
|
121
|
+
const parsed = JSON.parse(metadata);
|
|
122
|
+
return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
|
|
123
|
+
? parsed
|
|
124
|
+
: null;
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (isNodeErrorWithCode(error, "ENOENT") || isNodeErrorWithCode(error, "ENOTDIR")) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
export async function writeWorkspaceExport(outPath, envelope) {
|
|
134
|
+
const absolutePath = resolve(outPath);
|
|
135
|
+
await assertExportOutputIsEmpty(absolutePath, "org.export");
|
|
136
|
+
await mkdir(absolutePath, { recursive: true });
|
|
137
|
+
for (const file of envelope.files) {
|
|
138
|
+
const targetPath = resolveExportTargetPath(absolutePath, file.path, "org.export");
|
|
139
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
140
|
+
await writeFile(targetPath, file.content, "utf8");
|
|
141
|
+
}
|
|
142
|
+
const metadataPath = join(absolutePath, ".kora", "export.json");
|
|
143
|
+
await mkdir(dirname(metadataPath), { recursive: true });
|
|
144
|
+
await writeFile(metadataPath, `${JSON.stringify(envelope.metadata, null, 2)}\n`, "utf8");
|
|
145
|
+
}
|
|
146
|
+
export async function writeReleaseSourceFiles(outPath, envelope) {
|
|
147
|
+
const absolutePath = resolve(outPath);
|
|
148
|
+
await assertExportOutputIsEmpty(absolutePath, "release source", "Release source");
|
|
149
|
+
await mkdir(absolutePath, { recursive: true });
|
|
150
|
+
const seenPaths = new Set();
|
|
151
|
+
for (const file of envelope.files) {
|
|
152
|
+
if (seenPaths.has(file.path)) {
|
|
153
|
+
throw usageProblem(`Release source contains duplicate file path '${file.path}'.`, "release source");
|
|
154
|
+
}
|
|
155
|
+
seenPaths.add(file.path);
|
|
156
|
+
const targetPath = resolveExportTargetPath(absolutePath, file.path, "release source");
|
|
157
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
158
|
+
await writeFile(targetPath, file.content, "utf8");
|
|
159
|
+
}
|
|
160
|
+
const metadataPath = join(absolutePath, ".kora", "release-source.json");
|
|
161
|
+
await mkdir(dirname(metadataPath), { recursive: true });
|
|
162
|
+
await writeFile(metadataPath, `${JSON.stringify(envelope.metadata, null, 2)}\n`, "utf8");
|
|
44
163
|
}
|
|
45
|
-
function
|
|
164
|
+
export async function writeArchiveExport(outPath, archive, instance) {
|
|
165
|
+
const absolutePath = resolve(outPath);
|
|
166
|
+
await mkdir(dirname(absolutePath), { recursive: true });
|
|
167
|
+
try {
|
|
168
|
+
await writeFile(absolutePath, archive, { flag: "wx" });
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
if (isNodeErrorWithCode(error, "EEXIST")) {
|
|
172
|
+
throw usageProblem("Export output file already exists.", instance);
|
|
173
|
+
}
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
export async function writePackageExport(outPath, envelope) {
|
|
178
|
+
const absolutePath = resolve(outPath);
|
|
179
|
+
await assertExportOutputIsEmpty(absolutePath, "extensions.export");
|
|
180
|
+
await mkdir(absolutePath, { recursive: true });
|
|
181
|
+
for (const file of envelope.files) {
|
|
182
|
+
const targetPath = resolveExportTargetPath(absolutePath, file.path, "extensions.export");
|
|
183
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
184
|
+
await writeFile(targetPath, Buffer.from(file.contentBase64, "base64"));
|
|
185
|
+
}
|
|
186
|
+
const metadataPath = join(absolutePath, ".kora", "export.json");
|
|
187
|
+
await mkdir(dirname(metadataPath), { recursive: true });
|
|
188
|
+
await writeFile(metadataPath, `${JSON.stringify(envelope.metadata, null, 2)}\n`, "utf8");
|
|
189
|
+
}
|
|
190
|
+
export async function readPackageFileEntries(pathValue, instance) {
|
|
191
|
+
const absolutePath = resolve(pathValue);
|
|
192
|
+
const pathStat = await readLocalPathStat(absolutePath, instance);
|
|
193
|
+
if (pathStat.isSymbolicLink()) {
|
|
194
|
+
throw usageProblem("Package path must be a regular file or directory, not a symbolic link.", instance);
|
|
195
|
+
}
|
|
196
|
+
if (pathStat.isFile()) {
|
|
197
|
+
assertPackageFileCanBeRead(pathStat.size, basename(absolutePath), {
|
|
198
|
+
fileCount: 0,
|
|
199
|
+
totalBytes: 0
|
|
200
|
+
}, instance);
|
|
201
|
+
return [{
|
|
202
|
+
contentBase64: (await readLocalFileBytes(absolutePath, instance, {
|
|
203
|
+
regularFileMessage: "Package path must be a regular file or directory, not a symbolic link."
|
|
204
|
+
})).bytes.toString("base64"),
|
|
205
|
+
path: basename(absolutePath)
|
|
206
|
+
}];
|
|
207
|
+
}
|
|
208
|
+
const entries = [];
|
|
209
|
+
await walkPackageDirectory(absolutePath, absolutePath, entries, {
|
|
210
|
+
fileCount: 0,
|
|
211
|
+
totalBytes: 0
|
|
212
|
+
}, instance);
|
|
213
|
+
entries.sort((left, right) => left.path.localeCompare(right.path));
|
|
214
|
+
return entries;
|
|
215
|
+
}
|
|
216
|
+
function readJsonObject(source, instance, options) {
|
|
46
217
|
let parsed;
|
|
47
218
|
try {
|
|
48
219
|
parsed = JSON.parse(source);
|
|
49
220
|
}
|
|
50
221
|
catch {
|
|
51
|
-
|
|
222
|
+
const sourceLabel = "filePath" in options ? `file ${options.filePath}` : "from stdin";
|
|
223
|
+
throw usageProblem(`Structured JSON input ${sourceLabel} must be valid JSON.`, instance, {
|
|
224
|
+
...("filePath" in options ? { filePath: options.filePath } : { source: options.source }),
|
|
225
|
+
...(options.flag ? { flag: options.flag } : {})
|
|
226
|
+
});
|
|
52
227
|
}
|
|
53
228
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
54
|
-
|
|
229
|
+
const sourceLabel = "filePath" in options ? `file ${options.filePath}` : "from stdin";
|
|
230
|
+
throw usageProblem(`Structured JSON input ${sourceLabel} must be a JSON object.`, instance, {
|
|
231
|
+
...("filePath" in options ? { filePath: options.filePath } : { source: options.source }),
|
|
232
|
+
...(options.flag ? { flag: options.flag } : {})
|
|
233
|
+
});
|
|
55
234
|
}
|
|
56
235
|
return parsed;
|
|
57
236
|
}
|
|
@@ -62,24 +241,168 @@ async function readStream(stream) {
|
|
|
62
241
|
}
|
|
63
242
|
return Buffer.concat(chunks).toString("utf8");
|
|
64
243
|
}
|
|
65
|
-
async function walkImportDirectory(root, current, entries) {
|
|
244
|
+
async function walkImportDirectory(root, current, entries, counters, options) {
|
|
66
245
|
const children = await readdir(current, { withFileTypes: true });
|
|
67
246
|
children.sort((left, right) => left.name.localeCompare(right.name));
|
|
68
247
|
for (const child of children) {
|
|
69
|
-
|
|
248
|
+
const childPath = join(current, child.name);
|
|
249
|
+
const relativePath = relative(root, childPath).replaceAll("\\", "/");
|
|
250
|
+
if (shouldIgnoreSourceImportPath(relativePath)) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (child.isDirectory()) {
|
|
254
|
+
await walkImportDirectory(root, childPath, entries, counters, options);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (!child.isFile()) {
|
|
70
258
|
continue;
|
|
71
259
|
}
|
|
260
|
+
const content = await readLimitedUtf8File(childPath, relativePath, counters, options);
|
|
261
|
+
entries.push({
|
|
262
|
+
content: content.content,
|
|
263
|
+
path: relativePath
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async function walkPackageDirectory(root, current, entries, counters, instance) {
|
|
268
|
+
const children = await readdir(current, { withFileTypes: true });
|
|
269
|
+
children.sort((left, right) => left.name.localeCompare(right.name));
|
|
270
|
+
for (const child of children) {
|
|
72
271
|
const childPath = join(current, child.name);
|
|
272
|
+
const relativePath = relative(root, childPath).replaceAll("\\", "/");
|
|
273
|
+
if (shouldIgnoreImportPath(relativePath)) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
73
276
|
if (child.isDirectory()) {
|
|
74
|
-
await
|
|
277
|
+
await walkPackageDirectory(root, childPath, entries, counters, instance);
|
|
75
278
|
continue;
|
|
76
279
|
}
|
|
280
|
+
if (!child.isFile()) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const childStat = await readLocalPathStat(childPath, instance);
|
|
284
|
+
assertPackageFileCanBeRead(childStat.size, relativePath, counters, instance);
|
|
77
285
|
entries.push({
|
|
78
|
-
|
|
79
|
-
path:
|
|
286
|
+
contentBase64: (await readLocalFileBytes(childPath, instance)).bytes.toString("base64"),
|
|
287
|
+
path: relativePath
|
|
80
288
|
});
|
|
81
289
|
}
|
|
82
290
|
}
|
|
83
|
-
function
|
|
84
|
-
return
|
|
291
|
+
function shouldIgnoreImportPath(pathValue) {
|
|
292
|
+
return shouldIgnoreWorkspacePath(pathValue);
|
|
293
|
+
}
|
|
294
|
+
function shouldIgnoreSourceImportPath(pathValue) {
|
|
295
|
+
return shouldIgnoreImportPath(pathValue) || isRemovedRuntimeEnvironmentPath(pathValue);
|
|
296
|
+
}
|
|
297
|
+
function isRemovedRuntimeEnvironmentPath(pathValue) {
|
|
298
|
+
return pathValue === ".env" || pathValue === ".env.example";
|
|
299
|
+
}
|
|
300
|
+
async function assertExportOutputIsEmpty(absolutePath, instance, outputLabel = "Export") {
|
|
301
|
+
try {
|
|
302
|
+
const pathStat = await lstat(absolutePath);
|
|
303
|
+
if (pathStat.isSymbolicLink()) {
|
|
304
|
+
throw usageProblem(`${outputLabel} output path must not be a symbolic link.`, instance);
|
|
305
|
+
}
|
|
306
|
+
if (!pathStat.isDirectory()) {
|
|
307
|
+
throw usageProblem(`${outputLabel} output path must be a directory.`, instance);
|
|
308
|
+
}
|
|
309
|
+
const children = await readdir(absolutePath);
|
|
310
|
+
if (children.length > 0) {
|
|
311
|
+
throw usageProblem(`${outputLabel} output directory must be empty or not exist.`, instance);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
if (isNodeErrorWithCode(error, "ENOENT")) {
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function resolveExportTargetPath(root, filePath, instance) {
|
|
322
|
+
const normalizedPath = filePath.replaceAll("\\", "/");
|
|
323
|
+
const segments = normalizedPath.split("/");
|
|
324
|
+
if (normalizedPath.trim().length === 0 ||
|
|
325
|
+
isAbsolute(normalizedPath) ||
|
|
326
|
+
segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
|
|
327
|
+
throw usageProblem("Export file paths must be relative paths inside the output directory.", instance);
|
|
328
|
+
}
|
|
329
|
+
const targetPath = resolve(root, normalizedPath);
|
|
330
|
+
const relativeTarget = relative(root, targetPath);
|
|
331
|
+
if (relativeTarget === "" || relativeTarget.startsWith("..") || isAbsolute(relativeTarget)) {
|
|
332
|
+
throw usageProblem("Export file paths must be relative paths inside the output directory.", instance);
|
|
333
|
+
}
|
|
334
|
+
return targetPath;
|
|
335
|
+
}
|
|
336
|
+
async function readLimitedUtf8File(filePath, displayPath, counters, options) {
|
|
337
|
+
if (counters.fileCount >= MAX_IMPORT_FILE_COUNT) {
|
|
338
|
+
throw usageProblem(`Import has more than ${String(MAX_IMPORT_FILE_COUNT)} files.`, options.instance);
|
|
339
|
+
}
|
|
340
|
+
let content;
|
|
341
|
+
try {
|
|
342
|
+
content = await readFile(filePath, "utf8");
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
throwLocalPathReadProblem(error, filePath, options.instance);
|
|
346
|
+
}
|
|
347
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
348
|
+
if (bytes > options.maxFileBytes) {
|
|
349
|
+
throw usageProblem(`Import file ${displayPath} exceeds the per-file byte limit.`, options.instance);
|
|
350
|
+
}
|
|
351
|
+
if (counters.totalBytes + bytes > MAX_IMPORT_TOTAL_BYTES) {
|
|
352
|
+
throw usageProblem(`Import exceeds the ${String(MAX_IMPORT_TOTAL_BYTES)} byte limit.`, options.instance);
|
|
353
|
+
}
|
|
354
|
+
counters.fileCount += 1;
|
|
355
|
+
counters.totalBytes += bytes;
|
|
356
|
+
return { content };
|
|
357
|
+
}
|
|
358
|
+
function assertPackageFileCanBeRead(bytes, displayPath, counters, instance) {
|
|
359
|
+
if (counters.fileCount >= MAX_IMPORT_FILE_COUNT) {
|
|
360
|
+
throw usageProblem(`Package has more than ${String(MAX_IMPORT_FILE_COUNT)} files.`, instance);
|
|
361
|
+
}
|
|
362
|
+
if (bytes > MAX_PACKAGE_FILE_BYTES) {
|
|
363
|
+
throw usageProblem(`Package file ${displayPath} exceeds the per-file byte limit.`, instance);
|
|
364
|
+
}
|
|
365
|
+
if (counters.totalBytes + bytes > MAX_IMPORT_TOTAL_BYTES) {
|
|
366
|
+
throw usageProblem(`Package exceeds the ${String(MAX_IMPORT_TOTAL_BYTES)} byte limit.`, instance);
|
|
367
|
+
}
|
|
368
|
+
counters.fileCount += 1;
|
|
369
|
+
counters.totalBytes += bytes;
|
|
370
|
+
}
|
|
371
|
+
function isNodeErrorWithCode(error, code) {
|
|
372
|
+
return typeof error === "object" &&
|
|
373
|
+
error !== null &&
|
|
374
|
+
"code" in error &&
|
|
375
|
+
error.code === code;
|
|
376
|
+
}
|
|
377
|
+
function readNodeErrorCode(error) {
|
|
378
|
+
if (typeof error !== "object" || error === null || !("code" in error)) {
|
|
379
|
+
return undefined;
|
|
380
|
+
}
|
|
381
|
+
const code = error.code;
|
|
382
|
+
return typeof code === "string" && code.length > 0 ? code : undefined;
|
|
383
|
+
}
|
|
384
|
+
function isLocalPathReadErrorCode(code) {
|
|
385
|
+
return code === "EACCES" ||
|
|
386
|
+
code === "EISDIR" ||
|
|
387
|
+
code === "ENOENT" ||
|
|
388
|
+
code === "ENOTDIR" ||
|
|
389
|
+
code === "EPERM";
|
|
390
|
+
}
|
|
391
|
+
async function readLocalPathStat(absolutePath, instance) {
|
|
392
|
+
try {
|
|
393
|
+
return await lstat(absolutePath);
|
|
394
|
+
}
|
|
395
|
+
catch (error) {
|
|
396
|
+
throwLocalPathReadProblem(error, absolutePath, instance);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function throwLocalPathReadProblem(error, absolutePath, instance) {
|
|
400
|
+
const nativeCode = readNodeErrorCode(error);
|
|
401
|
+
if (nativeCode && isLocalPathReadErrorCode(nativeCode)) {
|
|
402
|
+
throw usageProblem(`Local path ${absolutePath} could not be read: ${nativeCode}.`, instance, {
|
|
403
|
+
nativeCode,
|
|
404
|
+
path: absolutePath
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
throw error;
|
|
85
408
|
}
|
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>;
|
|
@@ -9,7 +10,9 @@ export declare function renderJsonEnvelope(input: {
|
|
|
9
10
|
meta?: Record<string, unknown>;
|
|
10
11
|
}): string;
|
|
11
12
|
export declare function renderProblemJson(input: {
|
|
13
|
+
code: string;
|
|
12
14
|
detail: string;
|
|
15
|
+
details?: Record<string, unknown>;
|
|
13
16
|
instance: string;
|
|
14
17
|
status: number;
|
|
15
18
|
title: string;
|
|
@@ -27,12 +30,15 @@ export declare function renderPrettyJson(value: unknown): string;
|
|
|
27
30
|
export declare function renderVerboseHuman(base: string, data: unknown): string;
|
|
28
31
|
export declare function renderVerboseProblem(input: {
|
|
29
32
|
detail: string;
|
|
33
|
+
details?: Record<string, unknown>;
|
|
30
34
|
instance: string;
|
|
31
35
|
rawDetail?: string;
|
|
32
36
|
title: string;
|
|
33
37
|
type: string;
|
|
34
38
|
}): string;
|
|
35
39
|
export declare function renderSuccess(message: string): string;
|
|
40
|
+
export declare function formatProblemDetail(detail: string, details?: Record<string, unknown>): string;
|
|
41
|
+
export declare function formatProblemDetailsSummary(details: Record<string, unknown> | undefined): string | null;
|
|
36
42
|
export declare function renderDiffSummary(input: {
|
|
37
43
|
added: string[];
|
|
38
44
|
changed: string[];
|
package/dist/format.js
CHANGED
|
@@ -6,7 +6,19 @@ 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
|
+
error: {
|
|
11
|
+
code: input.code,
|
|
12
|
+
details: {
|
|
13
|
+
...(input.details ?? {}),
|
|
14
|
+
instance: input.instance,
|
|
15
|
+
status: input.status,
|
|
16
|
+
title: input.title,
|
|
17
|
+
type: input.type
|
|
18
|
+
},
|
|
19
|
+
message: input.detail
|
|
20
|
+
}
|
|
21
|
+
}, null, 2)}\n`;
|
|
10
22
|
}
|
|
11
23
|
export function renderTable(rows, columns) {
|
|
12
24
|
if (rows.length === 0) {
|
|
@@ -38,8 +50,10 @@ export function renderVerboseHuman(base, data) {
|
|
|
38
50
|
return `${base}\n\nDetails:\n${detail}`;
|
|
39
51
|
}
|
|
40
52
|
export function renderVerboseProblem(input) {
|
|
53
|
+
const detailsSummary = formatProblemDetailsSummary(input.details);
|
|
41
54
|
const lines = [
|
|
42
55
|
`${input.title}: ${input.detail}`,
|
|
56
|
+
...(detailsSummary ? ["", detailsSummary] : []),
|
|
43
57
|
"",
|
|
44
58
|
"Details:",
|
|
45
59
|
`Type: ${input.type}`,
|
|
@@ -48,11 +62,79 @@ export function renderVerboseProblem(input) {
|
|
|
48
62
|
if (input.rawDetail && input.rawDetail !== input.detail) {
|
|
49
63
|
lines.push(`Raw backend message: ${input.rawDetail}`);
|
|
50
64
|
}
|
|
65
|
+
if (input.details) {
|
|
66
|
+
lines.push(`Structured details: ${renderPrettyJson(input.details)}`);
|
|
67
|
+
}
|
|
51
68
|
return lines.join("\n");
|
|
52
69
|
}
|
|
53
70
|
export function renderSuccess(message) {
|
|
54
71
|
return message;
|
|
55
72
|
}
|
|
73
|
+
export function formatProblemDetail(detail, details) {
|
|
74
|
+
const detailsSummary = formatProblemDetailsSummary(details);
|
|
75
|
+
return detailsSummary ? `${detail}\n${detailsSummary}` : detail;
|
|
76
|
+
}
|
|
77
|
+
export function formatProblemDetailsSummary(details) {
|
|
78
|
+
if (!details) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const diagnostics = formatProblemDetailsList(details.diagnostics, "Diagnostics", formatDiagnosticLikeEntry);
|
|
82
|
+
if (diagnostics) {
|
|
83
|
+
return diagnostics;
|
|
84
|
+
}
|
|
85
|
+
const errors = formatProblemDetailsList(details.errors, "Errors", formatUnknownDetailEntry);
|
|
86
|
+
if (errors) {
|
|
87
|
+
return errors;
|
|
88
|
+
}
|
|
89
|
+
return formatProblemDetailsList(details.issues, "Issues", formatUnknownDetailEntry);
|
|
90
|
+
}
|
|
91
|
+
function formatProblemDetailsList(value, label, formatter) {
|
|
92
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const lines = value
|
|
96
|
+
.map(formatter)
|
|
97
|
+
.filter((entry) => typeof entry === "string" && entry.trim().length > 0);
|
|
98
|
+
if (lines.length === 0) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
return [label, ...lines.slice(0, 5).map((entry) => `- ${entry}`)].join("\n");
|
|
102
|
+
}
|
|
103
|
+
function formatDiagnosticLikeEntry(entry) {
|
|
104
|
+
if (!isRecord(entry)) {
|
|
105
|
+
return formatUnknownDetailEntry(entry);
|
|
106
|
+
}
|
|
107
|
+
const message = readString(entry.message);
|
|
108
|
+
if (!message) {
|
|
109
|
+
return formatUnknownDetailEntry(entry);
|
|
110
|
+
}
|
|
111
|
+
const path = readString(entry.path) ?? readString(entry.filePath) ?? readString(entry.instancePath);
|
|
112
|
+
const code = readString(entry.code);
|
|
113
|
+
return [
|
|
114
|
+
path ? `${path}:` : null,
|
|
115
|
+
message,
|
|
116
|
+
code && !path ? `(${code})` : null
|
|
117
|
+
].filter((part) => typeof part === "string" && part.length > 0).join(" ");
|
|
118
|
+
}
|
|
119
|
+
function formatUnknownDetailEntry(entry) {
|
|
120
|
+
if (typeof entry === "string") {
|
|
121
|
+
return entry.trim() || null;
|
|
122
|
+
}
|
|
123
|
+
if (isRecord(entry)) {
|
|
124
|
+
const message = readString(entry.message);
|
|
125
|
+
if (message) {
|
|
126
|
+
const path = readString(entry.path) ?? readString(entry.instancePath);
|
|
127
|
+
return path ? `${path}: ${message}` : message;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
function isRecord(value) {
|
|
133
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
134
|
+
}
|
|
135
|
+
function readString(value) {
|
|
136
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
137
|
+
}
|
|
56
138
|
export function renderDiffSummary(input) {
|
|
57
139
|
const lines = [
|
|
58
140
|
`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,18 @@ 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({
|
|
92
|
+
code: problem.code,
|
|
90
93
|
detail: problem.detail,
|
|
94
|
+
...(details ? { details } : {}),
|
|
91
95
|
instance: problem.instance,
|
|
92
96
|
status: problem.status,
|
|
93
97
|
title: problem.title,
|
|
@@ -137,6 +141,9 @@ function resolveHelpInvocation(tokens, commandFilter) {
|
|
|
137
141
|
function hasRawDetail(problem) {
|
|
138
142
|
return "rawDetail" in problem && typeof problem.rawDetail === "string";
|
|
139
143
|
}
|
|
144
|
+
function hasProblemDetails(problem) {
|
|
145
|
+
return "details" in problem && typeof problem.details === "object" && problem.details !== null && !Array.isArray(problem.details);
|
|
146
|
+
}
|
|
140
147
|
function normalizeHelpPath(tokens, commandFilter) {
|
|
141
148
|
if (tokens.length === 0) {
|
|
142
149
|
return [];
|
|
@@ -213,7 +220,10 @@ function parseInvocation(tokens, commandFilter) {
|
|
|
213
220
|
throw usageProblem(withHelpSuggestion(`Unknown command: ${tokens.join(" ")}.`, resolved.commandPath, commandFilter), tokens.join(" "));
|
|
214
221
|
}
|
|
215
222
|
const definition = resolved.definition;
|
|
216
|
-
const
|
|
223
|
+
const visibleFlags = commandFilter
|
|
224
|
+
? definition.flags.filter((entry) => !entry.hiddenWhenCommandFiltered)
|
|
225
|
+
: definition.flags;
|
|
226
|
+
const flagDefinitions = new Map(visibleFlags.map((entry) => [entry.name, entry]));
|
|
217
227
|
const positionals = [];
|
|
218
228
|
const flags = {};
|
|
219
229
|
for (let index = 0; index < resolved.remainder.length; index += 1) {
|
|
@@ -222,7 +232,10 @@ function parseInvocation(tokens, commandFilter) {
|
|
|
222
232
|
positionals.push(token);
|
|
223
233
|
continue;
|
|
224
234
|
}
|
|
225
|
-
const
|
|
235
|
+
const rawFlag = token.slice(2);
|
|
236
|
+
const valueSeparatorIndex = rawFlag.indexOf("=");
|
|
237
|
+
const rawName = valueSeparatorIndex === -1 ? rawFlag : rawFlag.slice(0, valueSeparatorIndex);
|
|
238
|
+
const maybeValue = valueSeparatorIndex === -1 ? undefined : rawFlag.slice(valueSeparatorIndex + 1);
|
|
226
239
|
const flag = flagDefinitions.get(rawName);
|
|
227
240
|
if (!flag) {
|
|
228
241
|
const detail = rawName === "output"
|
|
@@ -237,11 +250,16 @@ function parseInvocation(tokens, commandFilter) {
|
|
|
237
250
|
flags[rawName] = true;
|
|
238
251
|
continue;
|
|
239
252
|
}
|
|
240
|
-
|
|
241
|
-
if (
|
|
242
|
-
|
|
253
|
+
let valueSource;
|
|
254
|
+
if (maybeValue !== undefined) {
|
|
255
|
+
valueSource = maybeValue;
|
|
243
256
|
}
|
|
244
|
-
|
|
257
|
+
else {
|
|
258
|
+
const nextValue = resolved.remainder[index + 1];
|
|
259
|
+
if (!nextValue || nextValue.startsWith("--")) {
|
|
260
|
+
throw usageProblem(`Flag '--${rawName}' requires a value.`, definition.path.join(" "));
|
|
261
|
+
}
|
|
262
|
+
valueSource = nextValue;
|
|
245
263
|
index += 1;
|
|
246
264
|
}
|
|
247
265
|
const coercedValue = coerceFlagValue(flag.valueType ?? "string", valueSource, rawName, definition.path.join(" "));
|
|
@@ -254,7 +272,7 @@ function parseInvocation(tokens, commandFilter) {
|
|
|
254
272
|
if (positionals.length > definition.args.length) {
|
|
255
273
|
throw usageProblem(`Too many positional arguments for '${definition.path.join(" ")}'.`, definition.path.join(" "));
|
|
256
274
|
}
|
|
257
|
-
for (const flag of
|
|
275
|
+
for (const flag of visibleFlags) {
|
|
258
276
|
if (flag.required && flags[flag.name] === undefined) {
|
|
259
277
|
throw usageProblem(`Missing required flag '--${flag.name}'.`, definition.path.join(" "));
|
|
260
278
|
}
|