zinkee 0.1.44 → 0.1.45
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 +4 -0
- package/dist/chunk-MHEVRMDX.js +1230 -0
- package/dist/chunk-MHEVRMDX.js.map +1 -0
- package/{src/utils/examples.ts → dist/examples-E6FFEVBE.js} +856 -1141
- package/dist/examples-E6FFEVBE.js.map +1 -0
- package/dist/index.js +1145 -8456
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
- package/.github/workflows/npm-publish.yml +0 -77
- package/.github/workflows/pr-checks.yml +0 -36
- package/AGENTS.md +0 -110
- package/docs/cli-contract.md +0 -41
- package/docs/npm-release.md +0 -64
- package/docs/superpowers/plans/2026-03-24-zinkee-cli-implementation.md +0 -837
- package/docs/superpowers/plans/2026-03-25-cli-backend-error-contract.md +0 -503
- package/docs/superpowers/plans/2026-03-26-display-freeform-create.md +0 -389
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-backend-blockers.md +0 -172
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-design.md +0 -1576
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-checklist.md +0 -215
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-design.md +0 -492
- package/docs/superpowers/specs/2026-03-24-zinkee-cli-e2e-status.md +0 -307
- package/docs/superpowers/specs/2026-07-30-cli-pr-checks-design.md +0 -37
- package/src/api/automations.ts +0 -404
- package/src/api/comments.ts +0 -51
- package/src/api/displays.ts +0 -337
- package/src/api/document-templates.ts +0 -110
- package/src/api/files.ts +0 -50
- package/src/api/formulas.test.ts +0 -65
- package/src/api/formulas.ts +0 -67
- package/src/api/logs.ts +0 -34
- package/src/api/navigation.ts +0 -70
- package/src/api/records.ts +0 -184
- package/src/api/schemas.ts +0 -148
- package/src/api/teamspace.ts +0 -110
- package/src/cli-examples.ts +0 -130
- package/src/cli-runner.ts +0 -95
- package/src/client.test.ts +0 -189
- package/src/client.ts +0 -269
- package/src/command-registry.ts +0 -882
- package/src/commands/automations.test.ts +0 -1030
- package/src/commands/automations.ts +0 -2102
- package/src/commands/comments.test.ts +0 -214
- package/src/commands/comments.ts +0 -303
- package/src/commands/config.test.ts +0 -81
- package/src/commands/config.ts +0 -150
- package/src/commands/displays.test.ts +0 -1105
- package/src/commands/displays.ts +0 -1442
- package/src/commands/document-templates.test.ts +0 -569
- package/src/commands/document-templates.ts +0 -563
- package/src/commands/files.test.ts +0 -284
- package/src/commands/files.ts +0 -280
- package/src/commands/formulas.test.ts +0 -194
- package/src/commands/formulas.ts +0 -243
- package/src/commands/logs.test.ts +0 -123
- package/src/commands/logs.ts +0 -159
- package/src/commands/navigation.test.ts +0 -211
- package/src/commands/navigation.ts +0 -348
- package/src/commands/profiles.test.ts +0 -191
- package/src/commands/profiles.ts +0 -303
- package/src/commands/records.test.ts +0 -860
- package/src/commands/records.ts +0 -883
- package/src/commands/schemas.test.ts +0 -1252
- package/src/commands/schemas.ts +0 -890
- package/src/commands/teamspace.test.ts +0 -229
- package/src/commands/teamspace.ts +0 -546
- package/src/completion/engine.test.ts +0 -138
- package/src/completion/engine.ts +0 -168
- package/src/completion/install.test.ts +0 -179
- package/src/completion/install.ts +0 -260
- package/src/completion/runtime.ts +0 -150
- package/src/completion/scripts.ts +0 -91
- package/src/config.test.ts +0 -362
- package/src/config.ts +0 -294
- package/src/index.test.ts +0 -217
- package/src/index.ts +0 -8
- package/src/parsers/expressions.test.ts +0 -95
- package/src/parsers/expressions.ts +0 -128
- package/src/parsers/kv.test.ts +0 -35
- package/src/parsers/kv.ts +0 -49
- package/src/parsers/selectors.test.ts +0 -23
- package/src/parsers/selectors.ts +0 -29
- package/src/program.ts +0 -64
- package/src/runtime-context.ts +0 -103
- package/src/types.ts +0 -76
- package/src/utils/argv-rewrite.test.ts +0 -149
- package/src/utils/argv-rewrite.ts +0 -269
- package/src/utils/errors.test.ts +0 -85
- package/src/utils/errors.ts +0 -191
- package/src/utils/examples.test.ts +0 -374
- package/src/utils/output.test.ts +0 -65
- package/src/utils/output.ts +0 -154
- package/src/utils/schema-fields.ts +0 -1016
- package/tsconfig.json +0 -20
- package/tsup.config.ts +0 -13
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
2
|
-
|
|
3
|
-
export const HOISTABLE_GLOBAL_FLAGS = {
|
|
4
|
-
"--profile": "value",
|
|
5
|
-
"--base-url": "value",
|
|
6
|
-
"--api-key": "value",
|
|
7
|
-
"--read-only": "boolean",
|
|
8
|
-
"--json": "boolean",
|
|
9
|
-
} as const satisfies Record<string, "value" | "boolean">;
|
|
10
|
-
|
|
11
|
-
type HoistableFlagName = keyof typeof HOISTABLE_GLOBAL_FLAGS;
|
|
12
|
-
|
|
13
|
-
function isHoistableFlag(flagName: string): flagName is HoistableFlagName {
|
|
14
|
-
return Object.hasOwn(HOISTABLE_GLOBAL_FLAGS, flagName);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface OptionMetadata {
|
|
18
|
-
valueFlags: Set<string>;
|
|
19
|
-
booleanFlags: Set<string>;
|
|
20
|
-
localOptionsByCommand: Map<string, Set<string>>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function getCommandPath(command: Command): string {
|
|
24
|
-
const parts: string[] = [];
|
|
25
|
-
let cursor: Command | null = command;
|
|
26
|
-
while (cursor && cursor.parent) {
|
|
27
|
-
parts.unshift(cursor.name());
|
|
28
|
-
cursor = cursor.parent;
|
|
29
|
-
}
|
|
30
|
-
return parts.join(" ");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function buildOptionMetadata(program: Command): OptionMetadata {
|
|
34
|
-
const valueFlags = new Set<string>();
|
|
35
|
-
const booleanFlags = new Set<string>();
|
|
36
|
-
const localOptionsByCommand = new Map<string, Set<string>>();
|
|
37
|
-
|
|
38
|
-
function visit(command: Command, isRoot: boolean): void {
|
|
39
|
-
const localFlags = new Set<string>();
|
|
40
|
-
|
|
41
|
-
for (const option of command.options) {
|
|
42
|
-
if (!option.long) continue;
|
|
43
|
-
|
|
44
|
-
if (option.required || option.optional) {
|
|
45
|
-
valueFlags.add(option.long);
|
|
46
|
-
} else {
|
|
47
|
-
booleanFlags.add(option.long);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (!isRoot) {
|
|
51
|
-
localFlags.add(option.long);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!isRoot) {
|
|
56
|
-
localOptionsByCommand.set(getCommandPath(command), localFlags);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
for (const sub of command.commands) {
|
|
60
|
-
visit(sub, false);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
visit(program, true);
|
|
65
|
-
return { valueFlags, booleanFlags, localOptionsByCommand };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function createDefaultOptionMetadata(): OptionMetadata {
|
|
69
|
-
return {
|
|
70
|
-
valueFlags: new Set([
|
|
71
|
-
"--profile",
|
|
72
|
-
"--base-url",
|
|
73
|
-
"--api-key",
|
|
74
|
-
"--install-completion",
|
|
75
|
-
"--show-completion",
|
|
76
|
-
]),
|
|
77
|
-
booleanFlags: new Set([
|
|
78
|
-
"--read-only",
|
|
79
|
-
"--json",
|
|
80
|
-
"--example",
|
|
81
|
-
"--help",
|
|
82
|
-
]),
|
|
83
|
-
localOptionsByCommand: new Map(),
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
interface FlagSplit {
|
|
88
|
-
flagName: string;
|
|
89
|
-
inlineValue: string | undefined;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function isFlagToken(token: string): boolean {
|
|
93
|
-
return token.length > 1 && token.startsWith("-");
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function splitFlagToken(token: string): FlagSplit {
|
|
97
|
-
if (token.startsWith("--") && token.includes("=")) {
|
|
98
|
-
const eqIdx = token.indexOf("=");
|
|
99
|
-
return {
|
|
100
|
-
flagName: token.slice(0, eqIdx),
|
|
101
|
-
inlineValue: token.slice(eqIdx + 1),
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
return { flagName: token, inlineValue: undefined };
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function flagConsumesValue(flagName: string, optionMeta: OptionMetadata): boolean {
|
|
108
|
-
if (optionMeta.valueFlags.has(flagName)) {
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
if (optionMeta.booleanFlags.has(flagName)) {
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
return true;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
interface OperandPosition {
|
|
118
|
-
index: number;
|
|
119
|
-
token: string;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function collectOperandTokens(
|
|
123
|
-
userArgs: readonly string[],
|
|
124
|
-
optionMeta: OptionMetadata,
|
|
125
|
-
options: { skipFlag?: string } = {},
|
|
126
|
-
): OperandPosition[] {
|
|
127
|
-
const operands: OperandPosition[] = [];
|
|
128
|
-
let i = 0;
|
|
129
|
-
while (i < userArgs.length) {
|
|
130
|
-
const token = userArgs[i] as string;
|
|
131
|
-
|
|
132
|
-
if (token === "--") {
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (isFlagToken(token)) {
|
|
137
|
-
const { flagName, inlineValue } = splitFlagToken(token);
|
|
138
|
-
|
|
139
|
-
if (options.skipFlag !== undefined && flagName === options.skipFlag) {
|
|
140
|
-
i += 1;
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (inlineValue !== undefined) {
|
|
145
|
-
i += 1;
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const consumes = flagConsumesValue(flagName, optionMeta);
|
|
150
|
-
const next = userArgs[i + 1];
|
|
151
|
-
if (consumes && next !== undefined && !isFlagToken(next)) {
|
|
152
|
-
i += 2;
|
|
153
|
-
} else {
|
|
154
|
-
i += 1;
|
|
155
|
-
}
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
operands.push({ index: i, token });
|
|
160
|
-
i += 1;
|
|
161
|
-
}
|
|
162
|
-
return operands;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export interface CommandPathMatch {
|
|
166
|
-
commandName: string;
|
|
167
|
-
firstArgvIndex: number;
|
|
168
|
-
lastArgvIndex: number;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export function findCommandPath(
|
|
172
|
-
operands: readonly OperandPosition[],
|
|
173
|
-
registry: Record<string, unknown>,
|
|
174
|
-
): CommandPathMatch | null {
|
|
175
|
-
if (operands.length === 0) return null;
|
|
176
|
-
|
|
177
|
-
const commandsByLength = Object.keys(registry).sort(
|
|
178
|
-
(left, right) => right.split(" ").length - left.split(" ").length,
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
for (let start = 0; start < operands.length; start += 1) {
|
|
182
|
-
for (const commandName of commandsByLength) {
|
|
183
|
-
const parts = commandName.split(" ");
|
|
184
|
-
if (start + parts.length > operands.length) continue;
|
|
185
|
-
|
|
186
|
-
let matches = true;
|
|
187
|
-
for (let j = 0; j < parts.length; j += 1) {
|
|
188
|
-
if (operands[start + j]!.token !== parts[j]) {
|
|
189
|
-
matches = false;
|
|
190
|
-
break;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (matches) {
|
|
195
|
-
return {
|
|
196
|
-
commandName,
|
|
197
|
-
firstArgvIndex: operands[start]!.index,
|
|
198
|
-
lastArgvIndex: operands[start + parts.length - 1]!.index,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export function hoistGlobalOptions(
|
|
208
|
-
argv: readonly string[],
|
|
209
|
-
optionMeta: OptionMetadata,
|
|
210
|
-
registry: Record<string, unknown>,
|
|
211
|
-
): string[] {
|
|
212
|
-
if (argv.length < 3) return [...argv];
|
|
213
|
-
|
|
214
|
-
const userArgs = argv.slice(2);
|
|
215
|
-
const operands = collectOperandTokens(userArgs, optionMeta);
|
|
216
|
-
const cmdPath = findCommandPath(operands, registry);
|
|
217
|
-
|
|
218
|
-
if (!cmdPath) return [...argv];
|
|
219
|
-
|
|
220
|
-
const localOptions =
|
|
221
|
-
optionMeta.localOptionsByCommand.get(cmdPath.commandName) ?? new Set<string>();
|
|
222
|
-
|
|
223
|
-
const hoisted: string[] = [];
|
|
224
|
-
const remaining: string[] = [];
|
|
225
|
-
let i = 0;
|
|
226
|
-
|
|
227
|
-
while (i < userArgs.length) {
|
|
228
|
-
const token = userArgs[i] as string;
|
|
229
|
-
|
|
230
|
-
if (token === "--") {
|
|
231
|
-
remaining.push(...userArgs.slice(i));
|
|
232
|
-
break;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (!isFlagToken(token)) {
|
|
236
|
-
remaining.push(token);
|
|
237
|
-
i += 1;
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const { flagName, inlineValue } = splitFlagToken(token);
|
|
242
|
-
|
|
243
|
-
if (!isHoistableFlag(flagName)) {
|
|
244
|
-
remaining.push(token);
|
|
245
|
-
i += 1;
|
|
246
|
-
continue;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const isAfterCommandPath = i > cmdPath.lastArgvIndex;
|
|
250
|
-
const collidesWithLocal = localOptions.has(flagName);
|
|
251
|
-
|
|
252
|
-
const target = isAfterCommandPath && collidesWithLocal ? remaining : hoisted;
|
|
253
|
-
target.push(token);
|
|
254
|
-
|
|
255
|
-
const spec = HOISTABLE_GLOBAL_FLAGS[flagName];
|
|
256
|
-
if (spec === "value" && inlineValue === undefined) {
|
|
257
|
-
const next = userArgs[i + 1];
|
|
258
|
-
if (next !== undefined && !isFlagToken(next)) {
|
|
259
|
-
target.push(next);
|
|
260
|
-
i += 2;
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
i += 1;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return [argv[0] as string, argv[1] as string, ...hoisted, ...remaining];
|
|
269
|
-
}
|
package/src/utils/errors.test.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { ApiClientError } from "../client.js";
|
|
4
|
-
|
|
5
|
-
import { formatCliErrorJson } from "./errors.js";
|
|
6
|
-
|
|
7
|
-
describe("formatCliErrorJson", () => {
|
|
8
|
-
it("preserves backend reason, action, requestId, and status details", () => {
|
|
9
|
-
const error = new ApiClientError(
|
|
10
|
-
"http_error",
|
|
11
|
-
"Request failed with status 409 for PATCH https://api.example.com/api/v2/automations/1.",
|
|
12
|
-
{
|
|
13
|
-
url: "https://api.example.com/api/v2/automations/1",
|
|
14
|
-
method: "PATCH",
|
|
15
|
-
status: 409,
|
|
16
|
-
responseBody: {
|
|
17
|
-
error: {
|
|
18
|
-
reason: "schema_locked",
|
|
19
|
-
action: "retry_later",
|
|
20
|
-
message: "Schema is locked.",
|
|
21
|
-
},
|
|
22
|
-
meta: {
|
|
23
|
-
requestId: "req_123",
|
|
24
|
-
},
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
expect(JSON.parse(formatCliErrorJson(error, { exitCode: 1 }))).toEqual({
|
|
30
|
-
error: {
|
|
31
|
-
code: "http_error",
|
|
32
|
-
message: "Schema is locked.",
|
|
33
|
-
exitCode: 1,
|
|
34
|
-
details: {
|
|
35
|
-
reason: "schema_locked",
|
|
36
|
-
action: "retry_later",
|
|
37
|
-
requestId: "req_123",
|
|
38
|
-
status: 409,
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
meta: {
|
|
42
|
-
requestId: "req_123",
|
|
43
|
-
status: 409,
|
|
44
|
-
exitCode: 1,
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("preserves network_error code for transport-layer failures", () => {
|
|
50
|
-
const error = new ApiClientError(
|
|
51
|
-
"network_error",
|
|
52
|
-
"Request failed for GET https://api.example.com/api/v2/schemas: getaddrinfo ENOTFOUND api.example.com.",
|
|
53
|
-
{
|
|
54
|
-
url: "https://api.example.com/api/v2/schemas",
|
|
55
|
-
method: "GET",
|
|
56
|
-
cause: new Error("getaddrinfo ENOTFOUND api.example.com"),
|
|
57
|
-
},
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
expect(JSON.parse(formatCliErrorJson(error)).error.code).toBe("network_error");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("preserves timeout code for aborted requests", () => {
|
|
64
|
-
const error = new ApiClientError("timeout", "Request timed out after 10ms.", {
|
|
65
|
-
url: "https://api.example.com/api/v2/schemas",
|
|
66
|
-
method: "GET",
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
expect(JSON.parse(formatCliErrorJson(error)).error.code).toBe("timeout");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("preserves invalid_json code for unparseable responses", () => {
|
|
73
|
-
const error = new ApiClientError(
|
|
74
|
-
"invalid_json",
|
|
75
|
-
"Failed to parse JSON response from GET https://api.example.com/api/v2/schemas.",
|
|
76
|
-
{
|
|
77
|
-
url: "https://api.example.com/api/v2/schemas",
|
|
78
|
-
method: "GET",
|
|
79
|
-
responseText: "<html>oops</html>",
|
|
80
|
-
},
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
expect(JSON.parse(formatCliErrorJson(error)).error.code).toBe("invalid_json");
|
|
84
|
-
});
|
|
85
|
-
});
|
package/src/utils/errors.ts
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import { ApiClientError } from "../client.js";
|
|
2
|
-
import type { JsonEnvelopeMeta, JsonErrorEnvelope } from "../types.js";
|
|
3
|
-
|
|
4
|
-
import { buildJsonError } from "./output.js";
|
|
5
|
-
|
|
6
|
-
export type CliErrorCode =
|
|
7
|
-
| "unknown_error"
|
|
8
|
-
| "invalid_cli_usage"
|
|
9
|
-
| "configuration_error"
|
|
10
|
-
| "command_not_allowed_in_read_only_mode"
|
|
11
|
-
| "network_error"
|
|
12
|
-
| "http_error"
|
|
13
|
-
| "timeout"
|
|
14
|
-
| "invalid_json";
|
|
15
|
-
|
|
16
|
-
export const cliExitCodeByErrorCode = {
|
|
17
|
-
unknown_error: 1,
|
|
18
|
-
invalid_cli_usage: 2,
|
|
19
|
-
configuration_error: 2,
|
|
20
|
-
command_not_allowed_in_read_only_mode: 7,
|
|
21
|
-
network_error: 1,
|
|
22
|
-
http_error: 1,
|
|
23
|
-
timeout: 1,
|
|
24
|
-
invalid_json: 1,
|
|
25
|
-
} as const satisfies Record<CliErrorCode, number>;
|
|
26
|
-
|
|
27
|
-
export interface CliErrorDetails {
|
|
28
|
-
[key: string]: unknown;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface CliErrorPayload {
|
|
32
|
-
code: CliErrorCode;
|
|
33
|
-
message: string;
|
|
34
|
-
exitCode: number;
|
|
35
|
-
details?: CliErrorDetails;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const errorCodeByName: Record<string, CliErrorCode> = {
|
|
39
|
-
ProfileResolutionError: "configuration_error",
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export class CliError extends Error {
|
|
43
|
-
override name = "CliError";
|
|
44
|
-
|
|
45
|
-
readonly code: CliErrorCode;
|
|
46
|
-
|
|
47
|
-
readonly exitCode: number;
|
|
48
|
-
|
|
49
|
-
readonly details?: CliErrorDetails;
|
|
50
|
-
|
|
51
|
-
constructor(
|
|
52
|
-
code: CliErrorCode,
|
|
53
|
-
message: string,
|
|
54
|
-
details: CliErrorDetails = {},
|
|
55
|
-
exitCode: number = cliExitCodeByErrorCode[code],
|
|
56
|
-
) {
|
|
57
|
-
super(message);
|
|
58
|
-
this.code = code;
|
|
59
|
-
this.exitCode = exitCode;
|
|
60
|
-
|
|
61
|
-
if (Object.keys(details).length > 0) {
|
|
62
|
-
this.details = details;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
toJSON(): CliErrorPayload {
|
|
67
|
-
const payload: CliErrorPayload = {
|
|
68
|
-
code: this.code,
|
|
69
|
-
message: this.message,
|
|
70
|
-
exitCode: this.exitCode,
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
if (this.details) {
|
|
74
|
-
payload.details = this.details;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return payload;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function isCliError(error: unknown): error is CliError {
|
|
82
|
-
return error instanceof CliError;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
86
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function readString(value: unknown): string | undefined {
|
|
90
|
-
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function readNumber(value: unknown): number | undefined {
|
|
94
|
-
return typeof value === "number" ? value : undefined;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function extractApiErrorDetails(error: ApiClientError): {
|
|
98
|
-
message: string;
|
|
99
|
-
details: CliErrorDetails;
|
|
100
|
-
meta: JsonEnvelopeMeta;
|
|
101
|
-
} {
|
|
102
|
-
const details: CliErrorDetails = {};
|
|
103
|
-
const meta: JsonEnvelopeMeta = {};
|
|
104
|
-
const responseBody = isRecord(error.responseBody) ? error.responseBody : undefined;
|
|
105
|
-
const errorBody = responseBody?.error;
|
|
106
|
-
const backendError = isRecord(errorBody) ? errorBody : responseBody;
|
|
107
|
-
const backendMeta = isRecord(responseBody?.meta) ? responseBody.meta : undefined;
|
|
108
|
-
const reason =
|
|
109
|
-
readString(backendError?.reason) ??
|
|
110
|
-
readString(backendError?.code);
|
|
111
|
-
const action = readString(backendError?.action);
|
|
112
|
-
const requestId =
|
|
113
|
-
readString(backendMeta?.requestId) ??
|
|
114
|
-
readString(responseBody?.requestId) ??
|
|
115
|
-
readString(backendError?.requestId);
|
|
116
|
-
const status =
|
|
117
|
-
readNumber(backendError?.status) ??
|
|
118
|
-
readNumber(responseBody?.status) ??
|
|
119
|
-
error.status;
|
|
120
|
-
const backendMessage = readString(backendError?.message);
|
|
121
|
-
|
|
122
|
-
if (reason !== undefined) {
|
|
123
|
-
details.reason = reason;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (action !== undefined) {
|
|
127
|
-
details.action = action;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (requestId !== undefined) {
|
|
131
|
-
details.requestId = requestId;
|
|
132
|
-
meta.requestId = requestId;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (status !== undefined) {
|
|
136
|
-
details.status = status;
|
|
137
|
-
meta.status = status;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
message: backendMessage ?? error.message,
|
|
142
|
-
details,
|
|
143
|
-
meta,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function toCliError(error: unknown): CliError {
|
|
148
|
-
if (error instanceof CliError) {
|
|
149
|
-
return error;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (error instanceof ApiClientError) {
|
|
153
|
-
const apiError = extractApiErrorDetails(error);
|
|
154
|
-
|
|
155
|
-
return new CliError(error.code, apiError.message, apiError.details);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (error instanceof Error) {
|
|
159
|
-
return new CliError(errorCodeByName[error.name] ?? "unknown_error", error.message);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return new CliError("unknown_error", "Unknown error");
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export function getExitCode(error: unknown): number {
|
|
166
|
-
return toCliError(error).exitCode;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export function buildJsonCliError<TMeta extends JsonEnvelopeMeta = JsonEnvelopeMeta>(
|
|
170
|
-
error: unknown,
|
|
171
|
-
meta: TMeta = {} as TMeta,
|
|
172
|
-
): JsonErrorEnvelope<CliErrorPayload, TMeta> {
|
|
173
|
-
const cliError = toCliError(error);
|
|
174
|
-
const apiErrorMeta =
|
|
175
|
-
error instanceof ApiClientError
|
|
176
|
-
? extractApiErrorDetails(error).meta
|
|
177
|
-
: {};
|
|
178
|
-
|
|
179
|
-
return buildJsonError(cliError.toJSON(), { ...apiErrorMeta, ...meta } as TMeta);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export function formatCliErrorText(error: unknown): string {
|
|
183
|
-
return toCliError(error).message;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export function formatCliErrorJson<TMeta extends JsonEnvelopeMeta = JsonEnvelopeMeta>(
|
|
187
|
-
error: unknown,
|
|
188
|
-
meta: TMeta = {} as TMeta,
|
|
189
|
-
): string {
|
|
190
|
-
return JSON.stringify(buildJsonCliError(error, meta));
|
|
191
|
-
}
|