@eve-horizon/cli 0.2.12 → 0.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,350 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleApi = handleApi;
4
- exports.handleSpec = handleSpec;
5
- exports.fetchApiSpec = fetchApiSpec;
6
- const args_1 = require("../lib/args");
7
- const client_1 = require("../lib/client");
8
- const output_1 = require("../lib/output");
9
- const node_fs_1 = require("node:fs");
10
- const node_path_1 = require("node:path");
11
- const node_child_process_1 = require("node:child_process");
12
- async function handleApi(subcommand, positionals, flags, context) {
13
- const jsonOutput = Boolean(flags.json);
14
- switch (subcommand) {
15
- case 'list':
16
- return handleList(positionals, flags, context, jsonOutput);
17
- case 'show':
18
- return handleShow(positionals, flags, context, jsonOutput);
19
- case 'spec':
20
- return handleSpec(positionals, flags, context, jsonOutput);
21
- case 'refresh':
22
- return handleRefresh(positionals, flags, context, jsonOutput);
23
- case 'examples':
24
- return handleExamples(positionals, flags, context, jsonOutput);
25
- case 'call':
26
- return handleCall(positionals, flags, context);
27
- default:
28
- throw new Error('Usage: eve api <list|show|spec|refresh|examples|call>\n' +
29
- ' list [project] - list API sources\n' +
30
- ' show <name> [project] - show a single API source\n' +
31
- ' spec <name> [project] - show cached API spec\n' +
32
- ' refresh <name> [project] - refresh cached API spec\n' +
33
- ' examples <name> [project] - print curl examples from spec\n' +
34
- ' call <name> <method> <path> [options] - call an API with Eve auth');
35
- }
36
- }
37
- async function handleList(positionals, flags, context, jsonOutput) {
38
- const projectId = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
39
- if (!projectId) {
40
- throw new Error('Usage: eve api list [project] [--project=<id>] [--env=<name>]');
41
- }
42
- const query = buildQuery({
43
- env: (0, args_1.getStringFlag)(flags, ['env']),
44
- });
45
- const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/apis${query}`);
46
- if (jsonOutput) {
47
- (0, output_1.outputJson)(response, jsonOutput);
48
- return;
49
- }
50
- if (!response.data || response.data.length === 0) {
51
- console.log('No API sources found.');
52
- return;
53
- }
54
- formatApiSourcesTable(response.data);
55
- }
56
- async function handleShow(positionals, flags, context, jsonOutput) {
57
- const name = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['name']);
58
- const projectId = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
59
- const env = (0, args_1.getStringFlag)(flags, ['env']);
60
- if (!name || !projectId) {
61
- throw new Error('Usage: eve api show <name> [project] [--project=<id>] [--env=<name>]');
62
- }
63
- const query = buildQuery({ env });
64
- const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/apis/${name}${query}`);
65
- (0, output_1.outputJson)(response, jsonOutput);
66
- }
67
- async function handleSpec(positionals, flags, context, jsonOutput) {
68
- const name = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['name']);
69
- const projectId = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
70
- const env = (0, args_1.getStringFlag)(flags, ['env']);
71
- if (!name || !projectId) {
72
- throw new Error('Usage: eve api spec <name> [project] [--project=<id>] [--env=<name>]');
73
- }
74
- const spec = await fetchApiSpec(context, projectId, name, env);
75
- if (typeof spec.data === 'string') {
76
- (0, output_1.outputJson)(spec.data, jsonOutput, spec.raw);
77
- return;
78
- }
79
- (0, output_1.outputJson)(spec.data, jsonOutput);
80
- }
81
- async function handleRefresh(positionals, flags, context, jsonOutput) {
82
- const name = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['name']);
83
- const projectId = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
84
- const env = (0, args_1.getStringFlag)(flags, ['env']);
85
- if (!name || !projectId) {
86
- throw new Error('Usage: eve api refresh <name> [project] [--project=<id>] [--env=<name>]');
87
- }
88
- const query = buildQuery({ env });
89
- const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/apis/${name}/refresh${query}`, {
90
- method: 'POST',
91
- });
92
- (0, output_1.outputJson)(response, jsonOutput);
93
- }
94
- async function handleExamples(positionals, flags, context, jsonOutput) {
95
- const name = positionals[0] ?? (0, args_1.getStringFlag)(flags, ['name']);
96
- const projectId = positionals[1] ?? (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
97
- const env = (0, args_1.getStringFlag)(flags, ['env']);
98
- if (!name || !projectId) {
99
- throw new Error('Usage: eve api examples <name> [project] [--project=<id>] [--env=<name>]');
100
- }
101
- const query = buildQuery({ env });
102
- const api = await (0, client_1.requestJson)(context, `/projects/${projectId}/apis/${name}${query}`);
103
- const spec = await fetchApiSpec(context, projectId, name, env);
104
- if (jsonOutput) {
105
- (0, output_1.outputJson)({ api, spec: spec.data }, jsonOutput);
106
- return;
107
- }
108
- if (!spec.data || typeof spec.data !== 'object') {
109
- throw new Error('API spec is not JSON; examples require a JSON OpenAPI spec.');
110
- }
111
- const examples = buildCurlExamples(api, spec.data);
112
- if (examples.length === 0) {
113
- console.log('No endpoints found in spec.');
114
- return;
115
- }
116
- console.log(`API examples for ${api.name} (${api.type})`);
117
- console.log('');
118
- examples.forEach((example) => {
119
- console.log(example);
120
- });
121
- }
122
- async function handleCall(positionals, flags, context) {
123
- const name = positionals[0];
124
- const methodInput = positionals[1];
125
- const pathInput = positionals[2];
126
- if (!name || !methodInput || !pathInput) {
127
- throw new Error('Usage: eve api call <name> <method> <path> [--json <payload>] [--jq <expr>]');
128
- }
129
- const projectId = (0, args_1.getStringFlag)(flags, ['project']) ?? context.projectId;
130
- const env = (0, args_1.getStringFlag)(flags, ['env']);
131
- if (!projectId) {
132
- throw new Error('Missing project id. Provide --project or set a profile default.');
133
- }
134
- const query = buildQuery({ env });
135
- const api = await (0, client_1.requestJson)(context, `/projects/${projectId}/apis/${name}${query}`);
136
- const jqExpr = (0, args_1.getStringFlag)(flags, ['jq']);
137
- const printCurl = Boolean(flags['print-curl']);
138
- const jsonPayloadFlag = flags.json;
139
- if (jsonPayloadFlag === true) {
140
- throw new Error('Usage: --json <payload|@file>');
141
- }
142
- const graphqlFlag = flags.graphql;
143
- const graphqlQuery = graphqlFlag === true ? undefined : (0, args_1.getStringFlag)(flags, ['graphql']);
144
- if (graphqlFlag === true && !graphqlQuery) {
145
- throw new Error('Usage: --graphql <query|@file>');
146
- }
147
- if (graphqlQuery && jsonPayloadFlag) {
148
- throw new Error('Use --variables for GraphQL variables; --json is for JSON bodies.');
149
- }
150
- const variablesFlag = (0, args_1.getStringFlag)(flags, ['variables']);
151
- const variables = variablesFlag ? resolveJsonInput(variablesFlag) : undefined;
152
- const jsonBody = typeof jsonPayloadFlag === 'string' ? resolveJsonInput(jsonPayloadFlag) : undefined;
153
- const graphqlBody = graphqlQuery
154
- ? {
155
- query: resolveTextInput(graphqlQuery),
156
- ...(variables ? { variables } : {}),
157
- }
158
- : undefined;
159
- const body = graphqlBody ?? jsonBody;
160
- const method = methodInput.toUpperCase();
161
- const path = resolveApiPath(api.base_url, pathInput);
162
- const tokenOverride = (0, args_1.getStringFlag)(flags, ['token']);
163
- const authToken = tokenOverride ?? process.env.EVE_JOB_TOKEN ?? context.token;
164
- if (printCurl) {
165
- const curl = buildCurlCommand({
166
- method,
167
- url: path,
168
- authToken,
169
- jsonBody: body,
170
- authHint: tokenOverride ? 'override' : (process.env.EVE_JOB_TOKEN ? 'job' : 'user'),
171
- });
172
- console.log(curl);
173
- return;
174
- }
175
- const response = await fetch(path, {
176
- method,
177
- headers: buildApiHeaders(authToken, body),
178
- body: body ? JSON.stringify(body) : undefined,
179
- });
180
- const text = await response.text();
181
- if (!response.ok) {
182
- throw new Error(`HTTP ${response.status}: ${text}`);
183
- }
184
- const { data, jsonText } = parseResponse(text);
185
- if (jqExpr) {
186
- if (!jsonText) {
187
- throw new Error('Cannot apply --jq to a non-JSON response.');
188
- }
189
- const output = runJq(jqExpr, jsonText);
190
- process.stdout.write(output);
191
- return;
192
- }
193
- if (data !== undefined) {
194
- console.log(JSON.stringify(data, null, 2));
195
- return;
196
- }
197
- process.stdout.write(text);
198
- }
199
- function buildQuery(params) {
200
- const query = Object.entries(params)
201
- .filter(([, value]) => value !== undefined)
202
- .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
203
- .join('&');
204
- return query ? `?${query}` : '';
205
- }
206
- function formatApiSourcesTable(apis) {
207
- const nameWidth = Math.max(4, ...apis.map((api) => api.name.length));
208
- const typeWidth = Math.max(4, ...apis.map((api) => api.type.length));
209
- console.log(`${'NAME'.padEnd(nameWidth)} ${'TYPE'.padEnd(typeWidth)} BASE URL`);
210
- console.log(`${'-'.repeat(nameWidth)} ${'-'.repeat(typeWidth)} --------`);
211
- apis.forEach((api) => {
212
- console.log(`${api.name.padEnd(nameWidth)} ${api.type.padEnd(typeWidth)} ${api.base_url}`);
213
- });
214
- }
215
- async function fetchApiSpec(context, projectId, name, env) {
216
- const query = buildQuery({ env });
217
- const response = await (0, client_1.requestJson)(context, `/projects/${projectId}/apis/${name}/spec${query}`);
218
- if (response && typeof response === 'object' && 'schema' in response) {
219
- const schema = response.schema;
220
- if (typeof schema === 'string') {
221
- return { data: schema.trim(), raw: schema };
222
- }
223
- return { data: schema, raw: JSON.stringify(schema) };
224
- }
225
- if (typeof response === 'string') {
226
- return { data: response.trim(), raw: response };
227
- }
228
- return { data: response, raw: JSON.stringify(response) };
229
- }
230
- function buildCurlExamples(api, spec) {
231
- const paths = spec.paths ?? {};
232
- const examples = [];
233
- Object.entries(paths).forEach(([path, methods]) => {
234
- Object.entries(methods).forEach(([method, operation]) => {
235
- const lower = method.toLowerCase();
236
- if (!['get', 'post', 'put', 'patch', 'delete'].includes(lower)) {
237
- return;
238
- }
239
- const requestBody = operation.requestBody?.content?.['application/json'];
240
- const examplePayload = extractExamplePayload(requestBody);
241
- const needsBody = ['post', 'put', 'patch'].includes(lower);
242
- const curl = buildCurlCommand({
243
- method: lower.toUpperCase(),
244
- url: resolveApiPath(api.base_url, path),
245
- authToken: api.auth_mode === 'eve' ? '$EVE_JOB_TOKEN' : undefined,
246
- jsonBody: needsBody ? (examplePayload ?? {}) : undefined,
247
- authHint: 'job',
248
- });
249
- const summary = operation.summary ?? operation.description;
250
- if (summary) {
251
- examples.push(`# ${summary}`);
252
- }
253
- examples.push(curl);
254
- examples.push('');
255
- });
256
- });
257
- return examples;
258
- }
259
- function extractExamplePayload(requestBody) {
260
- if (!requestBody)
261
- return undefined;
262
- if (requestBody.example !== undefined)
263
- return requestBody.example;
264
- const examples = requestBody.examples ? Object.values(requestBody.examples) : [];
265
- if (examples.length === 0)
266
- return undefined;
267
- return examples[0]?.value;
268
- }
269
- function resolveApiPath(baseUrl, path) {
270
- if (path.startsWith('http://') || path.startsWith('https://')) {
271
- return path;
272
- }
273
- if (!baseUrl) {
274
- return path;
275
- }
276
- const trimmedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
277
- const trimmedPath = path.startsWith('/') ? path : `/${path}`;
278
- return `${trimmedBase}${trimmedPath}`;
279
- }
280
- function buildApiHeaders(authToken, body) {
281
- const headers = {};
282
- if (authToken) {
283
- headers.Authorization = `Bearer ${authToken}`;
284
- }
285
- if (body !== undefined) {
286
- headers['Content-Type'] = 'application/json';
287
- }
288
- return headers;
289
- }
290
- function resolveJsonInput(value) {
291
- const text = resolveTextInput(value);
292
- try {
293
- return JSON.parse(text);
294
- }
295
- catch (error) {
296
- throw new Error(`Invalid JSON payload: ${error.message}`);
297
- }
298
- }
299
- function resolveTextInput(value) {
300
- if (value.startsWith('@')) {
301
- const filePath = (0, node_path_1.resolve)(value.slice(1));
302
- return (0, node_fs_1.readFileSync)(filePath, 'utf-8');
303
- }
304
- if (value.trim().startsWith('{') || value.trim().startsWith('[')) {
305
- return value;
306
- }
307
- if ((0, node_fs_1.existsSync)(value)) {
308
- return (0, node_fs_1.readFileSync)((0, node_path_1.resolve)(value), 'utf-8');
309
- }
310
- return value;
311
- }
312
- function parseResponse(text) {
313
- if (!text)
314
- return { data: undefined, jsonText: undefined };
315
- try {
316
- const data = JSON.parse(text);
317
- return { data, jsonText: JSON.stringify(data) };
318
- }
319
- catch {
320
- return { data: undefined, jsonText: undefined };
321
- }
322
- }
323
- function runJq(expression, jsonText) {
324
- const result = (0, node_child_process_1.spawnSync)('jq', [expression], {
325
- input: jsonText,
326
- encoding: 'utf-8',
327
- });
328
- if (result.error) {
329
- if (result.error.code === 'ENOENT') {
330
- throw new Error('jq is not installed. Install jq to use --jq output filtering.');
331
- }
332
- throw result.error;
333
- }
334
- if (result.status !== 0) {
335
- throw new Error(result.stderr || 'jq failed to process the response.');
336
- }
337
- return result.stdout ?? '';
338
- }
339
- function buildCurlCommand(options) {
340
- const parts = ['curl', '-sS', '-X', options.method, `'${options.url}'`];
341
- if (options.authToken) {
342
- const tokenValue = options.authHint === 'job' ? '$EVE_JOB_TOKEN' : options.authToken;
343
- parts.push(`-H 'Authorization: Bearer ${tokenValue}'`);
344
- }
345
- if (options.jsonBody !== undefined) {
346
- parts.push("-H 'Content-Type: application/json'");
347
- parts.push(`-d '${JSON.stringify(options.jsonBody)}'`);
348
- }
349
- return parts.join(' ');
350
- }