@rooaak/cli 0.1.0-beta.1
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 +121 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/src/commands/auth-login.d.ts +3 -0
- package/dist/src/commands/auth-login.d.ts.map +1 -0
- package/dist/src/commands/auth-login.js +97 -0
- package/dist/src/commands/config-profile-use.d.ts +3 -0
- package/dist/src/commands/config-profile-use.d.ts.map +1 -0
- package/dist/src/commands/config-profile-use.js +88 -0
- package/dist/src/commands/config-whoami.d.ts +3 -0
- package/dist/src/commands/config-whoami.d.ts.map +1 -0
- package/dist/src/commands/config-whoami.js +101 -0
- package/dist/src/commands/help.d.ts +3 -0
- package/dist/src/commands/help.d.ts.map +1 -0
- package/dist/src/commands/help.js +61 -0
- package/dist/src/commands/init/index.d.ts +3 -0
- package/dist/src/commands/init/index.d.ts.map +1 -0
- package/dist/src/commands/init/index.js +756 -0
- package/dist/src/commands/init/prompting.d.ts +16 -0
- package/dist/src/commands/init/prompting.d.ts.map +1 -0
- package/dist/src/commands/init/prompting.js +63 -0
- package/dist/src/commands/init/wait-for-message.d.ts +45 -0
- package/dist/src/commands/init/wait-for-message.d.ts.map +1 -0
- package/dist/src/commands/init/wait-for-message.js +27 -0
- package/dist/src/commands/version.d.ts +3 -0
- package/dist/src/commands/version.d.ts.map +1 -0
- package/dist/src/commands/version.js +14 -0
- package/dist/src/generated/handlers.d.ts +5 -0
- package/dist/src/generated/handlers.d.ts.map +1 -0
- package/dist/src/generated/handlers.js +17 -0
- package/dist/src/generated/manifest.d.ts +3 -0
- package/dist/src/generated/manifest.d.ts.map +1 -0
- package/dist/src/generated/manifest.js +56 -0
- package/dist/src/lib/argv.d.ts +16 -0
- package/dist/src/lib/argv.d.ts.map +1 -0
- package/dist/src/lib/argv.js +68 -0
- package/dist/src/lib/base-url.d.ts +7 -0
- package/dist/src/lib/base-url.d.ts.map +1 -0
- package/dist/src/lib/base-url.js +6 -0
- package/dist/src/lib/exit-codes.d.ts +11 -0
- package/dist/src/lib/exit-codes.d.ts.map +1 -0
- package/dist/src/lib/exit-codes.js +29 -0
- package/dist/src/lib/generated-command.d.ts +25 -0
- package/dist/src/lib/generated-command.d.ts.map +1 -0
- package/dist/src/lib/generated-command.js +357 -0
- package/dist/src/lib/normalize-error.d.ts +45 -0
- package/dist/src/lib/normalize-error.d.ts.map +1 -0
- package/dist/src/lib/normalize-error.js +128 -0
- package/dist/src/lib/options.d.ts +12 -0
- package/dist/src/lib/options.d.ts.map +1 -0
- package/dist/src/lib/options.js +49 -0
- package/dist/src/lib/package-version.d.ts +2 -0
- package/dist/src/lib/package-version.d.ts.map +1 -0
- package/dist/src/lib/package-version.js +22 -0
- package/dist/src/lib/paths.d.ts +2 -0
- package/dist/src/lib/paths.d.ts.map +1 -0
- package/dist/src/lib/paths.js +21 -0
- package/dist/src/lib/profile-name.d.ts +2 -0
- package/dist/src/lib/profile-name.d.ts.map +1 -0
- package/dist/src/lib/profile-name.js +4 -0
- package/dist/src/lib/profile-store.d.ts +31 -0
- package/dist/src/lib/profile-store.d.ts.map +1 -0
- package/dist/src/lib/profile-store.js +179 -0
- package/dist/src/lib/renderer.d.ts +10 -0
- package/dist/src/lib/renderer.d.ts.map +1 -0
- package/dist/src/lib/renderer.js +25 -0
- package/dist/src/lib/run.d.ts +12 -0
- package/dist/src/lib/run.d.ts.map +1 -0
- package/dist/src/lib/run.js +108 -0
- package/dist/src/lib/transport.d.ts +37 -0
- package/dist/src/lib/transport.d.ts.map +1 -0
- package/dist/src/lib/transport.js +65 -0
- package/dist/src/lib/types.d.ts +22 -0
- package/dist/src/lib/types.d.ts.map +1 -0
- package/dist/src/lib/types.js +1 -0
- package/package.json +46 -0
- package/rooaak.js +39 -0
- package/schemas/openapi.v1.json +6013 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { resolveBaseUrl } from "./base-url.js";
|
|
2
|
+
import { ExitCode } from "./exit-codes.js";
|
|
3
|
+
import { normalizeLocalUsageError, exitCodeForError, toJsonErrorEnvelope, toJsonOkEnvelope } from "./normalize-error.js";
|
|
4
|
+
import { parseOptions } from "./options.js";
|
|
5
|
+
import { createProfileStore } from "./profile-store.js";
|
|
6
|
+
import { createRenderer } from "./renderer.js";
|
|
7
|
+
import { createTransport } from "./transport.js";
|
|
8
|
+
function jsonTypeFromFlag(flag) {
|
|
9
|
+
if (flag.valueType === "boolean")
|
|
10
|
+
return "boolean";
|
|
11
|
+
if (flag.valueType === "number")
|
|
12
|
+
return "number";
|
|
13
|
+
if (flag.valueType === "json")
|
|
14
|
+
return "json";
|
|
15
|
+
return "string";
|
|
16
|
+
}
|
|
17
|
+
function coerceFlagValue(flag, raw) {
|
|
18
|
+
if (flag.valueType === "boolean") {
|
|
19
|
+
if (typeof raw === "boolean")
|
|
20
|
+
return raw;
|
|
21
|
+
const v = raw.toLowerCase();
|
|
22
|
+
if (v === "true")
|
|
23
|
+
return true;
|
|
24
|
+
if (v === "false")
|
|
25
|
+
return false;
|
|
26
|
+
throw new Error(`Expected boolean for ${flag.name} (true|false)`);
|
|
27
|
+
}
|
|
28
|
+
if (flag.valueType === "number") {
|
|
29
|
+
if (typeof raw !== "string")
|
|
30
|
+
throw new Error(`Expected number for ${flag.name}`);
|
|
31
|
+
const n = Number(raw);
|
|
32
|
+
if (!Number.isFinite(n))
|
|
33
|
+
throw new Error(`Expected number for ${flag.name}`);
|
|
34
|
+
return n;
|
|
35
|
+
}
|
|
36
|
+
if (flag.valueType === "json") {
|
|
37
|
+
if (typeof raw !== "string")
|
|
38
|
+
throw new Error(`Expected JSON for ${flag.name}`);
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(raw);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw new Error(`Expected valid JSON for ${flag.name}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (typeof raw !== "string")
|
|
47
|
+
throw new Error(`Expected string for ${flag.name}`);
|
|
48
|
+
return raw;
|
|
49
|
+
}
|
|
50
|
+
function getProfileOrUsageError(ctx) {
|
|
51
|
+
const r = createRenderer(ctx);
|
|
52
|
+
const store = createProfileStore({ configPath: ctx.profileConfigPath });
|
|
53
|
+
const cfgRes = store.readConfig();
|
|
54
|
+
if (cfgRes.kind === "missing") {
|
|
55
|
+
const err = {
|
|
56
|
+
code: "auth.not_logged_in",
|
|
57
|
+
message: "Not logged in. Run `rooaak auth login --api-key ...`.",
|
|
58
|
+
details: {},
|
|
59
|
+
meta: {},
|
|
60
|
+
};
|
|
61
|
+
if (ctx.flags.json)
|
|
62
|
+
r.json(toJsonErrorEnvelope(err));
|
|
63
|
+
else
|
|
64
|
+
r.error(err.message);
|
|
65
|
+
return { ok: false, exit: exitCodeForError(err) };
|
|
66
|
+
}
|
|
67
|
+
if (cfgRes.kind === "corrupt") {
|
|
68
|
+
const err = {
|
|
69
|
+
code: "config.corrupt",
|
|
70
|
+
message: cfgRes.message,
|
|
71
|
+
details: { path: cfgRes.path },
|
|
72
|
+
meta: {},
|
|
73
|
+
};
|
|
74
|
+
if (ctx.flags.json)
|
|
75
|
+
r.json(toJsonErrorEnvelope(err));
|
|
76
|
+
else
|
|
77
|
+
r.error(err.message);
|
|
78
|
+
return { ok: false, exit: exitCodeForError(err) };
|
|
79
|
+
}
|
|
80
|
+
const config = cfgRes.config;
|
|
81
|
+
const profileName = config.currentProfile;
|
|
82
|
+
const profile = config.profiles[profileName];
|
|
83
|
+
if (!profile?.apiKey) {
|
|
84
|
+
const err = {
|
|
85
|
+
code: "auth.not_logged_in",
|
|
86
|
+
message: "Not logged in. Run `rooaak auth login --api-key ...`.",
|
|
87
|
+
details: {},
|
|
88
|
+
meta: {},
|
|
89
|
+
};
|
|
90
|
+
if (ctx.flags.json)
|
|
91
|
+
r.json(toJsonErrorEnvelope(err));
|
|
92
|
+
else
|
|
93
|
+
r.error(err.message);
|
|
94
|
+
return { ok: false, exit: exitCodeForError(err) };
|
|
95
|
+
}
|
|
96
|
+
const baseUrl = resolveBaseUrl({
|
|
97
|
+
cliFlag: ctx.flags.baseUrl,
|
|
98
|
+
env: ctx.env.ROOAAK_BASE_URL,
|
|
99
|
+
profile: profile.baseUrl,
|
|
100
|
+
});
|
|
101
|
+
return { ok: true, apiKey: profile.apiKey, baseUrl, profileName };
|
|
102
|
+
}
|
|
103
|
+
export function renderGeneratedCommandHelp(ctx, spec) {
|
|
104
|
+
const r = createRenderer(ctx);
|
|
105
|
+
const usage = `rooaak ${spec.group} ${spec.action} [flags]`;
|
|
106
|
+
const flags = [...spec.flags].sort((a, b) => {
|
|
107
|
+
// Required first, then by source, then by name.
|
|
108
|
+
const req = Number(b.required) - Number(a.required);
|
|
109
|
+
if (req !== 0)
|
|
110
|
+
return req;
|
|
111
|
+
const order = { path: 0, query: 1, header: 2, body: 3 };
|
|
112
|
+
const src = order[a.source] - order[b.source];
|
|
113
|
+
if (src !== 0)
|
|
114
|
+
return src;
|
|
115
|
+
return a.name.localeCompare(b.name);
|
|
116
|
+
});
|
|
117
|
+
if (ctx.flags.json) {
|
|
118
|
+
r.json(toJsonOkEnvelope({
|
|
119
|
+
type: "help",
|
|
120
|
+
usage,
|
|
121
|
+
operationId: spec.operationId,
|
|
122
|
+
summary: spec.summary,
|
|
123
|
+
description: spec.description,
|
|
124
|
+
method: spec.method,
|
|
125
|
+
path: spec.pathTemplate,
|
|
126
|
+
flags: flags.map((f) => ({
|
|
127
|
+
name: f.name,
|
|
128
|
+
required: f.required,
|
|
129
|
+
type: jsonTypeFromFlag(f),
|
|
130
|
+
source: f.source,
|
|
131
|
+
description: f.description,
|
|
132
|
+
})),
|
|
133
|
+
}));
|
|
134
|
+
return ExitCode.Success;
|
|
135
|
+
}
|
|
136
|
+
const lines = [];
|
|
137
|
+
lines.push("Usage:");
|
|
138
|
+
lines.push(` ${usage}`);
|
|
139
|
+
lines.push("");
|
|
140
|
+
lines.push("Endpoint:");
|
|
141
|
+
lines.push(` ${spec.method} ${spec.pathTemplate}`);
|
|
142
|
+
if (spec.summary)
|
|
143
|
+
lines.push(` ${spec.summary}`);
|
|
144
|
+
lines.push("");
|
|
145
|
+
lines.push("Flags:");
|
|
146
|
+
if (flags.length === 0) {
|
|
147
|
+
lines.push(" (none)");
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
for (const f of flags) {
|
|
151
|
+
const req = f.required ? "required" : "optional";
|
|
152
|
+
const type = jsonTypeFromFlag(f);
|
|
153
|
+
const desc = f.description ? ` - ${f.description}` : "";
|
|
154
|
+
lines.push(` ${f.name} <${type}> (${req}, ${f.source})${desc}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
lines.push("");
|
|
158
|
+
ctx.stdout.write(lines.join("\n") + "\n");
|
|
159
|
+
return ExitCode.Success;
|
|
160
|
+
}
|
|
161
|
+
export async function runGeneratedCommand(ctx, spec, argv) {
|
|
162
|
+
const r = createRenderer(ctx);
|
|
163
|
+
const optionSpecs = spec.flags.map((f) => ({ name: f.name, takesValue: f.takesValue }));
|
|
164
|
+
const parsed = parseOptions(argv, optionSpecs);
|
|
165
|
+
if (parsed.unknownOption) {
|
|
166
|
+
const err = normalizeLocalUsageError(`Unknown option: ${parsed.unknownOption}`);
|
|
167
|
+
if (ctx.flags.json)
|
|
168
|
+
r.json(toJsonErrorEnvelope(err));
|
|
169
|
+
else
|
|
170
|
+
r.error(err.message);
|
|
171
|
+
return ExitCode.Usage;
|
|
172
|
+
}
|
|
173
|
+
if (parsed.missingValueOption) {
|
|
174
|
+
const err = normalizeLocalUsageError(`Missing value for: ${parsed.missingValueOption}`);
|
|
175
|
+
if (ctx.flags.json)
|
|
176
|
+
r.json(toJsonErrorEnvelope(err));
|
|
177
|
+
else
|
|
178
|
+
r.error(err.message);
|
|
179
|
+
return ExitCode.Usage;
|
|
180
|
+
}
|
|
181
|
+
if (parsed.positionals.length > 0) {
|
|
182
|
+
const err = normalizeLocalUsageError("Unexpected extra arguments", { argc: parsed.positionals.length });
|
|
183
|
+
if (ctx.flags.json)
|
|
184
|
+
r.json(toJsonErrorEnvelope(err));
|
|
185
|
+
else
|
|
186
|
+
r.error(err.message);
|
|
187
|
+
return ExitCode.Usage;
|
|
188
|
+
}
|
|
189
|
+
// Parse `--body` early so we can treat it as an escape hatch for required body fields.
|
|
190
|
+
let bodyOverridePresent = false;
|
|
191
|
+
let bodyOverride = undefined;
|
|
192
|
+
const bodyExplicit = parsed.values["--body"];
|
|
193
|
+
if (typeof bodyExplicit === "string") {
|
|
194
|
+
bodyOverridePresent = true;
|
|
195
|
+
try {
|
|
196
|
+
bodyOverride = JSON.parse(bodyExplicit);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
const err = normalizeLocalUsageError("Invalid JSON for --body");
|
|
200
|
+
if (ctx.flags.json)
|
|
201
|
+
r.json(toJsonErrorEnvelope(err));
|
|
202
|
+
else
|
|
203
|
+
r.error(err.message);
|
|
204
|
+
return ExitCode.Usage;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
for (const f of spec.flags) {
|
|
208
|
+
if (!f.required)
|
|
209
|
+
continue;
|
|
210
|
+
if (bodyOverridePresent && f.source === "body" && f.name !== "--body")
|
|
211
|
+
continue;
|
|
212
|
+
const v = parsed.values[f.name];
|
|
213
|
+
if (v === undefined || v === false || v === "") {
|
|
214
|
+
const err = normalizeLocalUsageError(`Missing required flag: ${f.name}`);
|
|
215
|
+
if (ctx.flags.json)
|
|
216
|
+
r.json(toJsonErrorEnvelope(err));
|
|
217
|
+
else
|
|
218
|
+
r.error(err.message);
|
|
219
|
+
return ExitCode.Usage;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const auth = getProfileOrUsageError(ctx);
|
|
223
|
+
if (!auth.ok)
|
|
224
|
+
return auth.exit;
|
|
225
|
+
const transport = createTransport({
|
|
226
|
+
baseUrl: auth.baseUrl,
|
|
227
|
+
apiKey: auth.apiKey,
|
|
228
|
+
fetchFn: ctx.fetchFn,
|
|
229
|
+
});
|
|
230
|
+
let path = spec.pathTemplate;
|
|
231
|
+
const query = new URLSearchParams();
|
|
232
|
+
const headers = {};
|
|
233
|
+
let body = undefined;
|
|
234
|
+
const bodyObj = {};
|
|
235
|
+
let sawBodyField = false;
|
|
236
|
+
// Always bind path/query/header flags regardless of whether `--body` is present.
|
|
237
|
+
// Only the body-building portion is conditional: `--body` overrides individual body flags.
|
|
238
|
+
for (const f of spec.flags) {
|
|
239
|
+
const raw = parsed.values[f.name];
|
|
240
|
+
if (raw === undefined)
|
|
241
|
+
continue;
|
|
242
|
+
if (f.source === "path") {
|
|
243
|
+
if (typeof raw !== "string") {
|
|
244
|
+
const err = normalizeLocalUsageError(`Invalid value for ${f.name}`);
|
|
245
|
+
if (ctx.flags.json)
|
|
246
|
+
r.json(toJsonErrorEnvelope(err));
|
|
247
|
+
else
|
|
248
|
+
r.error(err.message);
|
|
249
|
+
return ExitCode.Usage;
|
|
250
|
+
}
|
|
251
|
+
const token = `{${f.wireName}}`;
|
|
252
|
+
if (!path.includes(token)) {
|
|
253
|
+
const err = normalizeLocalUsageError(`Unable to bind path param for ${f.name}`);
|
|
254
|
+
if (ctx.flags.json)
|
|
255
|
+
r.json(toJsonErrorEnvelope(err));
|
|
256
|
+
else
|
|
257
|
+
r.error(err.message);
|
|
258
|
+
return ExitCode.Usage;
|
|
259
|
+
}
|
|
260
|
+
path = path.replaceAll(token, encodeURIComponent(raw));
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (f.source === "query") {
|
|
264
|
+
if (typeof raw !== "string" && typeof raw !== "boolean") {
|
|
265
|
+
const err = normalizeLocalUsageError(`Invalid value for ${f.name}`);
|
|
266
|
+
if (ctx.flags.json)
|
|
267
|
+
r.json(toJsonErrorEnvelope(err));
|
|
268
|
+
else
|
|
269
|
+
r.error(err.message);
|
|
270
|
+
return ExitCode.Usage;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
const v = coerceFlagValue(f, raw);
|
|
274
|
+
query.set(f.wireName, typeof v === "string" ? v : JSON.stringify(v));
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
const err = normalizeLocalUsageError(e instanceof Error ? e.message : `Invalid value for ${f.name}`);
|
|
278
|
+
if (ctx.flags.json)
|
|
279
|
+
r.json(toJsonErrorEnvelope(err));
|
|
280
|
+
else
|
|
281
|
+
r.error(err.message);
|
|
282
|
+
return ExitCode.Usage;
|
|
283
|
+
}
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (f.source === "header") {
|
|
287
|
+
if (typeof raw !== "string" && typeof raw !== "boolean") {
|
|
288
|
+
const err = normalizeLocalUsageError(`Invalid value for ${f.name}`);
|
|
289
|
+
if (ctx.flags.json)
|
|
290
|
+
r.json(toJsonErrorEnvelope(err));
|
|
291
|
+
else
|
|
292
|
+
r.error(err.message);
|
|
293
|
+
return ExitCode.Usage;
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
const v = coerceFlagValue(f, raw);
|
|
297
|
+
headers[f.wireName] = typeof v === "string" ? v : JSON.stringify(v);
|
|
298
|
+
}
|
|
299
|
+
catch (e) {
|
|
300
|
+
const err = normalizeLocalUsageError(e instanceof Error ? e.message : `Invalid value for ${f.name}`);
|
|
301
|
+
if (ctx.flags.json)
|
|
302
|
+
r.json(toJsonErrorEnvelope(err));
|
|
303
|
+
else
|
|
304
|
+
r.error(err.message);
|
|
305
|
+
return ExitCode.Usage;
|
|
306
|
+
}
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (f.source === "body") {
|
|
310
|
+
if (f.name === "--body")
|
|
311
|
+
continue;
|
|
312
|
+
if (bodyOverridePresent)
|
|
313
|
+
continue;
|
|
314
|
+
sawBodyField = true;
|
|
315
|
+
try {
|
|
316
|
+
bodyObj[f.wireName] = coerceFlagValue(f, raw);
|
|
317
|
+
}
|
|
318
|
+
catch (e) {
|
|
319
|
+
const err = normalizeLocalUsageError(e instanceof Error ? e.message : `Invalid value for ${f.name}`);
|
|
320
|
+
if (ctx.flags.json)
|
|
321
|
+
r.json(toJsonErrorEnvelope(err));
|
|
322
|
+
else
|
|
323
|
+
r.error(err.message);
|
|
324
|
+
return ExitCode.Usage;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (bodyOverridePresent)
|
|
329
|
+
body = bodyOverride;
|
|
330
|
+
else if (sawBodyField)
|
|
331
|
+
body = bodyObj;
|
|
332
|
+
const qs = query.toString();
|
|
333
|
+
const fullPath = qs.length > 0 ? `${path}?${qs}` : path;
|
|
334
|
+
r.debug(`${spec.method} ${auth.baseUrl}${fullPath}`);
|
|
335
|
+
const httpRes = await transport.requestJson({
|
|
336
|
+
method: spec.method,
|
|
337
|
+
path: fullPath,
|
|
338
|
+
headers: Object.keys(headers).length > 0 ? headers : undefined,
|
|
339
|
+
body,
|
|
340
|
+
});
|
|
341
|
+
if (!httpRes.ok) {
|
|
342
|
+
const err = httpRes.error;
|
|
343
|
+
if (ctx.flags.json)
|
|
344
|
+
r.json(toJsonErrorEnvelope(err));
|
|
345
|
+
else
|
|
346
|
+
r.error(err.message);
|
|
347
|
+
return exitCodeForError(err);
|
|
348
|
+
}
|
|
349
|
+
const meta = { status: httpRes.status, requestId: httpRes.requestId };
|
|
350
|
+
if (ctx.flags.json) {
|
|
351
|
+
r.json(toJsonOkEnvelope(httpRes.data, meta));
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
ctx.stdout.write(JSON.stringify(httpRes.data, null, 2) + "\n");
|
|
355
|
+
}
|
|
356
|
+
return ExitCode.Success;
|
|
357
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ExitCode } from "./exit-codes.js";
|
|
2
|
+
export type JsonErrorEnvelope = {
|
|
3
|
+
ok: false;
|
|
4
|
+
error: {
|
|
5
|
+
code: string;
|
|
6
|
+
message: string;
|
|
7
|
+
details: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
meta: {
|
|
10
|
+
status?: number;
|
|
11
|
+
requestId?: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export type JsonOkEnvelope<T> = {
|
|
15
|
+
ok: true;
|
|
16
|
+
data: T;
|
|
17
|
+
meta: {
|
|
18
|
+
status?: number;
|
|
19
|
+
requestId?: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
export type JsonEnvelope<T> = JsonOkEnvelope<T> | JsonErrorEnvelope;
|
|
23
|
+
export type NormalizedError = {
|
|
24
|
+
code: string;
|
|
25
|
+
message: string;
|
|
26
|
+
details: Record<string, unknown>;
|
|
27
|
+
meta: {
|
|
28
|
+
status?: number;
|
|
29
|
+
requestId?: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export declare function getRequestIdFromHeaders(headers: Headers): string | undefined;
|
|
33
|
+
export declare function normalizeHttpError(input: {
|
|
34
|
+
status: number;
|
|
35
|
+
requestId?: string;
|
|
36
|
+
body: unknown;
|
|
37
|
+
}): NormalizedError;
|
|
38
|
+
export declare function normalizeLocalUsageError(message: string, details?: Record<string, unknown>): NormalizedError;
|
|
39
|
+
export declare function exitCodeForError(err: NormalizedError): ExitCode;
|
|
40
|
+
export declare function toJsonErrorEnvelope(err: NormalizedError): JsonErrorEnvelope;
|
|
41
|
+
export declare function toJsonOkEnvelope<T>(data: T, meta?: {
|
|
42
|
+
status?: number;
|
|
43
|
+
requestId?: string;
|
|
44
|
+
}): JsonOkEnvelope<T>;
|
|
45
|
+
//# sourceMappingURL=normalize-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize-error.d.ts","sourceRoot":"","sources":["../../../src/lib/normalize-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAElE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAClC,CAAC;IACF,IAAI,EAAE;QACJ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,CAAC,CAAC;IACR,IAAI,EAAE;QACJ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC;AAEpE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE;QACJ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH,CAAC;AAMF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAO5E;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,OAAO,CAAC;CACf,GAAG,eAAe,CAqElB;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACpC,eAAe,CAOjB;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,GAAG,QAAQ,CAY/D;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,eAAe,GAAG,iBAAiB,CAa3E;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,cAAc,CAAC,CAAC,CAAC,CAS9G"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { exitCodeForHttpStatus, ExitCode } from "./exit-codes.js";
|
|
2
|
+
function isPlainObject(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
export function getRequestIdFromHeaders(headers) {
|
|
6
|
+
return (headers.get("x-request-id") ||
|
|
7
|
+
headers.get("x-correlation-id") ||
|
|
8
|
+
headers.get("x-correlation-id".toLowerCase()) ||
|
|
9
|
+
undefined);
|
|
10
|
+
}
|
|
11
|
+
export function normalizeHttpError(input) {
|
|
12
|
+
const status = input.status;
|
|
13
|
+
const serverCode = isPlainObject(input.body) &&
|
|
14
|
+
isPlainObject(input.body.error) &&
|
|
15
|
+
typeof input.body.error.code === "string"
|
|
16
|
+
? input.body.error.code
|
|
17
|
+
: undefined;
|
|
18
|
+
const serverMessage = isPlainObject(input.body) &&
|
|
19
|
+
isPlainObject(input.body.error) &&
|
|
20
|
+
typeof input.body.error.message === "string"
|
|
21
|
+
? input.body.error.message
|
|
22
|
+
: undefined;
|
|
23
|
+
const serverDetails = isPlainObject(input.body) &&
|
|
24
|
+
isPlainObject(input.body.error) &&
|
|
25
|
+
isPlainObject(input.body.error.details)
|
|
26
|
+
? input.body.error.details
|
|
27
|
+
: {};
|
|
28
|
+
let code = "server.error";
|
|
29
|
+
let message = serverMessage || `Request failed (${status})`;
|
|
30
|
+
if (status === 401) {
|
|
31
|
+
if (serverMessage?.toLowerCase().includes("invalid api key")) {
|
|
32
|
+
code = "auth.invalid_api_key";
|
|
33
|
+
message = "Invalid API key";
|
|
34
|
+
}
|
|
35
|
+
else if (serverMessage?.toLowerCase().includes("missing api key")) {
|
|
36
|
+
code = "auth.missing_api_key";
|
|
37
|
+
message = "Missing API key";
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
code = "auth.unauthorized";
|
|
41
|
+
message = "Unauthorized";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (status === 403) {
|
|
45
|
+
code = "permission.forbidden";
|
|
46
|
+
message = serverMessage || "Forbidden";
|
|
47
|
+
}
|
|
48
|
+
else if (status === 404) {
|
|
49
|
+
code = "not_found";
|
|
50
|
+
message = serverMessage || "Not found";
|
|
51
|
+
}
|
|
52
|
+
else if (status === 409) {
|
|
53
|
+
code = "conflict";
|
|
54
|
+
message = serverMessage || "Conflict";
|
|
55
|
+
}
|
|
56
|
+
else if (status === 400) {
|
|
57
|
+
code = "request.bad_request";
|
|
58
|
+
message = serverMessage || "Bad request";
|
|
59
|
+
}
|
|
60
|
+
else if (status === 422) {
|
|
61
|
+
code = "request.validation_error";
|
|
62
|
+
message = serverMessage || "Validation error";
|
|
63
|
+
}
|
|
64
|
+
else if (status >= 500) {
|
|
65
|
+
code = "server.error";
|
|
66
|
+
message = "Server error";
|
|
67
|
+
}
|
|
68
|
+
const details = {
|
|
69
|
+
...(serverCode ? { serverCode } : {}),
|
|
70
|
+
...(Object.keys(serverDetails).length > 0 ? { serverDetails } : {}),
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
code,
|
|
74
|
+
message,
|
|
75
|
+
details,
|
|
76
|
+
meta: { status, requestId: input.requestId },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export function normalizeLocalUsageError(message, details = {}) {
|
|
80
|
+
return {
|
|
81
|
+
code: "usage.validation_error",
|
|
82
|
+
message,
|
|
83
|
+
details,
|
|
84
|
+
meta: {},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function exitCodeForError(err) {
|
|
88
|
+
const status = err.meta.status;
|
|
89
|
+
if (typeof status === "number")
|
|
90
|
+
return exitCodeForHttpStatus(status);
|
|
91
|
+
if (err.code.startsWith("usage."))
|
|
92
|
+
return ExitCode.Usage;
|
|
93
|
+
if (err.code.startsWith("auth."))
|
|
94
|
+
return ExitCode.Auth;
|
|
95
|
+
if (err.code.startsWith("permission."))
|
|
96
|
+
return ExitCode.Permission;
|
|
97
|
+
if (err.code.startsWith("not_found"))
|
|
98
|
+
return ExitCode.NotFound;
|
|
99
|
+
if (err.code.startsWith("conflict"))
|
|
100
|
+
return ExitCode.Conflict;
|
|
101
|
+
if (err.code.startsWith("config."))
|
|
102
|
+
return ExitCode.Usage;
|
|
103
|
+
return ExitCode.Transient;
|
|
104
|
+
}
|
|
105
|
+
export function toJsonErrorEnvelope(err) {
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
error: {
|
|
109
|
+
code: err.code,
|
|
110
|
+
message: err.message,
|
|
111
|
+
details: err.details,
|
|
112
|
+
},
|
|
113
|
+
meta: {
|
|
114
|
+
status: err.meta.status,
|
|
115
|
+
requestId: err.meta.requestId,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export function toJsonOkEnvelope(data, meta) {
|
|
120
|
+
return {
|
|
121
|
+
ok: true,
|
|
122
|
+
data,
|
|
123
|
+
meta: {
|
|
124
|
+
status: meta?.status,
|
|
125
|
+
requestId: meta?.requestId,
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type OptionSpec = {
|
|
2
|
+
name: string;
|
|
3
|
+
takesValue: boolean;
|
|
4
|
+
};
|
|
5
|
+
export type ParsedOptions = {
|
|
6
|
+
positionals: string[];
|
|
7
|
+
values: Record<string, string | boolean>;
|
|
8
|
+
unknownOption?: string;
|
|
9
|
+
missingValueOption?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function parseOptions(argv: string[], specs: OptionSpec[]): ParsedOptions;
|
|
12
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/lib/options.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IACzC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,aAAa,CAwD/E"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function parseOptions(argv, specs) {
|
|
2
|
+
const specMap = new Map();
|
|
3
|
+
for (const spec of specs)
|
|
4
|
+
specMap.set(spec.name, spec);
|
|
5
|
+
const values = {};
|
|
6
|
+
const positionals = [];
|
|
7
|
+
for (let i = 0; i < argv.length; i++) {
|
|
8
|
+
const arg = argv[i];
|
|
9
|
+
if (arg === "--") {
|
|
10
|
+
positionals.push(...argv.slice(i + 1));
|
|
11
|
+
break;
|
|
12
|
+
}
|
|
13
|
+
if (!arg.startsWith("-")) {
|
|
14
|
+
positionals.push(...argv.slice(i));
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
if (arg.startsWith("--")) {
|
|
18
|
+
const [name, inline] = arg.split("=", 2);
|
|
19
|
+
const spec = specMap.get(name);
|
|
20
|
+
if (!spec)
|
|
21
|
+
return { positionals: [], values, unknownOption: name };
|
|
22
|
+
if (!spec.takesValue) {
|
|
23
|
+
values[name] = true;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (inline !== undefined) {
|
|
27
|
+
if (inline.length === 0)
|
|
28
|
+
return { positionals: [], values, missingValueOption: name };
|
|
29
|
+
values[name] = inline;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const value = argv[i + 1];
|
|
33
|
+
if (!value || value === "--") {
|
|
34
|
+
return { positionals: [], values, missingValueOption: name };
|
|
35
|
+
}
|
|
36
|
+
// Only treat the next token as "missing" when it is a known option. This allows legitimate values
|
|
37
|
+
// that start with `-` (e.g. negative numbers, free-form strings like "-hello", or "--not-an-option").
|
|
38
|
+
const [nextName] = value.split("=", 2);
|
|
39
|
+
if (nextName.startsWith("--") && specMap.has(nextName)) {
|
|
40
|
+
return { positionals: [], values, missingValueOption: name };
|
|
41
|
+
}
|
|
42
|
+
values[name] = value;
|
|
43
|
+
i++;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
return { positionals: [], values, unknownOption: arg };
|
|
47
|
+
}
|
|
48
|
+
return { positionals, values };
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-version.d.ts","sourceRoot":"","sources":["../../../src/lib/package-version.ts"],"names":[],"mappings":"AAMA,wBAAgB,iBAAiB,IAAI,MAAM,CAiB1C"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
let cached = null;
|
|
5
|
+
export function getPackageVersion() {
|
|
6
|
+
if (cached)
|
|
7
|
+
return cached;
|
|
8
|
+
try {
|
|
9
|
+
// Compiled output lives at `dist/**`; package.json is one directory up from `dist`.
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
const pkgJsonPath = path.resolve(__dirname, "..", "..", "..", "package.json");
|
|
13
|
+
const raw = fs.readFileSync(pkgJsonPath, "utf8");
|
|
14
|
+
const parsed = JSON.parse(raw);
|
|
15
|
+
cached = typeof parsed.version === "string" ? parsed.version : "0.0.0";
|
|
16
|
+
return cached;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
cached = "0.0.0";
|
|
20
|
+
return cached;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../../src/lib/paths.ts"],"names":[],"mappings":"AAOA,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAmB3D"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
function assertNever(x) {
|
|
4
|
+
throw new Error(`Unexpected platform: ${String(x)}`);
|
|
5
|
+
}
|
|
6
|
+
export function getDefaultConfigDir(appName) {
|
|
7
|
+
const platform = process.platform;
|
|
8
|
+
const home = os.homedir();
|
|
9
|
+
if (platform === "darwin") {
|
|
10
|
+
return path.join(home, "Library", "Application Support", appName);
|
|
11
|
+
}
|
|
12
|
+
if (platform === "win32") {
|
|
13
|
+
const appData = process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
14
|
+
return path.join(appData, appName);
|
|
15
|
+
}
|
|
16
|
+
if (platform === "linux") {
|
|
17
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME || path.join(home, ".config");
|
|
18
|
+
return path.join(xdgConfigHome, appName);
|
|
19
|
+
}
|
|
20
|
+
assertNever(platform);
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile-name.d.ts","sourceRoot":"","sources":["../../../src/lib/profile-name.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGxD"}
|