workos 0.13.3 → 0.14.0
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 +39 -3
- package/dist/bin.js +100 -12
- package/dist/bin.js.map +1 -1
- package/dist/commands/api/catalog.d.ts +23 -0
- package/dist/commands/api/catalog.js +97 -0
- package/dist/commands/api/catalog.js.map +1 -0
- package/dist/commands/api/format.d.ts +5 -0
- package/dist/commands/api/format.js +46 -0
- package/dist/commands/api/format.js.map +1 -0
- package/dist/commands/api/index.d.ts +15 -0
- package/dist/commands/api/index.js +200 -0
- package/dist/commands/api/index.js.map +1 -0
- package/dist/commands/api/interactive.d.ts +3 -0
- package/dist/commands/api/interactive.js +127 -0
- package/dist/commands/api/interactive.js.map +1 -0
- package/dist/commands/api/request.d.ts +14 -0
- package/dist/commands/api/request.js +38 -0
- package/dist/commands/api/request.js.map +1 -0
- package/dist/commands/claim.js +21 -2
- package/dist/commands/claim.js.map +1 -1
- package/dist/commands/connection.js +5 -3
- package/dist/commands/connection.js.map +1 -1
- package/dist/commands/debug.js +5 -4
- package/dist/commands/debug.js.map +1 -1
- package/dist/commands/directory.js +5 -3
- package/dist/commands/directory.js.map +1 -1
- package/dist/commands/env.js +13 -3
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/login.js +20 -4
- package/dist/commands/login.js.map +1 -1
- package/dist/doctor/checks/auth-patterns.js +12 -1
- package/dist/doctor/checks/auth-patterns.js.map +1 -1
- package/dist/doctor/checks/host-execution.d.ts +2 -0
- package/dist/doctor/checks/host-execution.js +21 -0
- package/dist/doctor/checks/host-execution.js.map +1 -0
- package/dist/doctor/index.js +9 -1
- package/dist/doctor/index.js.map +1 -1
- package/dist/doctor/issues.d.ts +5 -0
- package/dist/doctor/issues.js +12 -0
- package/dist/doctor/issues.js.map +1 -1
- package/dist/doctor/output.d.ts +2 -0
- package/dist/doctor/output.js +42 -0
- package/dist/doctor/output.js.map +1 -1
- package/dist/doctor/types.d.ts +16 -0
- package/dist/doctor/types.js.map +1 -1
- package/dist/lib/config-store.js +51 -7
- package/dist/lib/config-store.js.map +1 -1
- package/dist/lib/credential-proxy.js +14 -1
- package/dist/lib/credential-proxy.js.map +1 -1
- package/dist/lib/credential-store.js +51 -7
- package/dist/lib/credential-store.js.map +1 -1
- package/dist/lib/ensure-auth.d.ts +0 -10
- package/dist/lib/ensure-auth.js +27 -9
- package/dist/lib/ensure-auth.js.map +1 -1
- package/dist/lib/host-probe.d.ts +28 -0
- package/dist/lib/host-probe.js +154 -0
- package/dist/lib/host-probe.js.map +1 -0
- package/dist/lib/run-with-core.js +26 -7
- package/dist/lib/run-with-core.js.map +1 -1
- package/dist/lib/workos-client.js +6 -3
- package/dist/lib/workos-client.js.map +1 -1
- package/dist/utils/cli-symbols.d.ts +1 -1
- package/dist/utils/command-invocation.d.ts +2 -0
- package/dist/utils/command-invocation.js +9 -0
- package/dist/utils/command-invocation.js.map +1 -1
- package/dist/utils/debug.d.ts +1 -0
- package/dist/utils/debug.js +10 -2
- package/dist/utils/debug.js.map +1 -1
- package/dist/utils/environment.d.ts +6 -0
- package/dist/utils/environment.js +8 -16
- package/dist/utils/environment.js.map +1 -1
- package/dist/utils/exit-codes.d.ts +9 -5
- package/dist/utils/exit-codes.js +10 -2
- package/dist/utils/exit-codes.js.map +1 -1
- package/dist/utils/help-json.d.ts +7 -0
- package/dist/utils/help-json.js +101 -0
- package/dist/utils/help-json.js.map +1 -1
- package/dist/utils/interaction-mode.d.ts +25 -0
- package/dist/utils/interaction-mode.js +102 -0
- package/dist/utils/interaction-mode.js.map +1 -0
- package/dist/utils/output.d.ts +20 -12
- package/dist/utils/output.js +16 -4
- package/dist/utils/output.js.map +1 -1
- package/dist/utils/recovery-hints.d.ts +37 -0
- package/dist/utils/recovery-hints.js +80 -0
- package/dist/utils/recovery-hints.js.map +1 -0
- package/package.json +4 -3
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface Param {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
required: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface EndpointInfo {
|
|
7
|
+
method: string;
|
|
8
|
+
path: string;
|
|
9
|
+
summary: string;
|
|
10
|
+
tag: string;
|
|
11
|
+
operationId: string;
|
|
12
|
+
pathParams: Param[];
|
|
13
|
+
queryParams: Param[];
|
|
14
|
+
hasRequestBody: boolean;
|
|
15
|
+
requestBodyRequired: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface Catalog {
|
|
18
|
+
endpoints: EndpointInfo[];
|
|
19
|
+
tags: string[];
|
|
20
|
+
}
|
|
21
|
+
export declare function parseSpec(yamlText: string): Catalog;
|
|
22
|
+
export declare function loadCatalog(): Promise<Catalog>;
|
|
23
|
+
export declare function endpointsByTag(endpoints: EndpointInfo[]): Map<string, EndpointInfo[]>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { parse as parseYaml } from 'yaml';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
const HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'];
|
|
5
|
+
/**
|
|
6
|
+
* Resolve an OpenAPI 3.x parameter object that may itself be a $ref pointing
|
|
7
|
+
* into components.parameters. Returns undefined if the ref can't be resolved
|
|
8
|
+
* (so the parameter is skipped instead of producing a {param} placeholder
|
|
9
|
+
* that leaks into request URLs).
|
|
10
|
+
*/
|
|
11
|
+
function resolveParam(param, componentParams) {
|
|
12
|
+
if (!param || typeof param !== 'object')
|
|
13
|
+
return undefined;
|
|
14
|
+
if (typeof param.$ref === 'string') {
|
|
15
|
+
const match = /^#\/components\/parameters\/(.+)$/.exec(param.$ref);
|
|
16
|
+
if (!match)
|
|
17
|
+
return undefined;
|
|
18
|
+
const target = componentParams[match[1]];
|
|
19
|
+
if (!target)
|
|
20
|
+
return undefined;
|
|
21
|
+
// Recurse so a chain of $refs still resolves to a concrete definition.
|
|
22
|
+
return resolveParam(target, componentParams);
|
|
23
|
+
}
|
|
24
|
+
return param;
|
|
25
|
+
}
|
|
26
|
+
export function parseSpec(yamlText) {
|
|
27
|
+
const spec = parseYaml(yamlText);
|
|
28
|
+
const endpoints = [];
|
|
29
|
+
const componentParams = spec.components?.parameters ?? {};
|
|
30
|
+
for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {
|
|
31
|
+
const pathObj = pathItem;
|
|
32
|
+
for (const method of HTTP_METHODS) {
|
|
33
|
+
const operation = pathObj[method];
|
|
34
|
+
if (!operation || typeof operation !== 'object')
|
|
35
|
+
continue;
|
|
36
|
+
const op = operation;
|
|
37
|
+
const tag = (op.tags ?? ['other'])[0] ?? 'other';
|
|
38
|
+
// Resolve $ref and merge path-level + operation-level params.
|
|
39
|
+
// Operation-level params override path-level ones with the same (name, in)
|
|
40
|
+
// pair, per the OpenAPI 3.x spec.
|
|
41
|
+
const rawPathLevel = pathObj.parameters ?? [];
|
|
42
|
+
const rawOpLevel = op.parameters ?? [];
|
|
43
|
+
const merged = new Map();
|
|
44
|
+
for (const raw of [...rawPathLevel, ...rawOpLevel]) {
|
|
45
|
+
const resolved = resolveParam(raw, componentParams);
|
|
46
|
+
if (!resolved || !resolved.name || !resolved.in)
|
|
47
|
+
continue;
|
|
48
|
+
merged.set(`${resolved.in}:${resolved.name}`, resolved);
|
|
49
|
+
}
|
|
50
|
+
const allParams = [...merged.values()];
|
|
51
|
+
const pathParams = allParams
|
|
52
|
+
.filter((p) => p.in === 'path')
|
|
53
|
+
.map((p) => ({ name: p.name, description: p.description ?? '', required: p.required ?? true }));
|
|
54
|
+
const queryParams = allParams
|
|
55
|
+
.filter((p) => p.in === 'query')
|
|
56
|
+
.map((p) => ({ name: p.name, description: p.description ?? '', required: p.required ?? false }));
|
|
57
|
+
const reqBody = op.requestBody;
|
|
58
|
+
endpoints.push({
|
|
59
|
+
method: method.toUpperCase(),
|
|
60
|
+
path,
|
|
61
|
+
summary: op.summary ?? '',
|
|
62
|
+
tag,
|
|
63
|
+
operationId: op.operationId ?? '',
|
|
64
|
+
pathParams,
|
|
65
|
+
queryParams,
|
|
66
|
+
hasRequestBody: !!reqBody,
|
|
67
|
+
requestBodyRequired: !!reqBody?.required,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const tags = [...new Set(endpoints.map((e) => e.tag))].sort();
|
|
72
|
+
return { endpoints, tags };
|
|
73
|
+
}
|
|
74
|
+
let cachedCatalog;
|
|
75
|
+
export function loadCatalog() {
|
|
76
|
+
// Cache the in-flight Promise (not just the resolved value) so concurrent
|
|
77
|
+
// callers reuse the same readFile/parse pass — see request.ts callers.
|
|
78
|
+
if (cachedCatalog)
|
|
79
|
+
return cachedCatalog;
|
|
80
|
+
cachedCatalog = (async () => {
|
|
81
|
+
const require = createRequire(import.meta.url);
|
|
82
|
+
const specPath = require.resolve('@workos/openapi-spec/spec');
|
|
83
|
+
const yamlText = await readFile(specPath, 'utf-8');
|
|
84
|
+
return parseSpec(yamlText);
|
|
85
|
+
})();
|
|
86
|
+
return cachedCatalog;
|
|
87
|
+
}
|
|
88
|
+
export function endpointsByTag(endpoints) {
|
|
89
|
+
const grouped = new Map();
|
|
90
|
+
for (const ep of endpoints) {
|
|
91
|
+
const list = grouped.get(ep.tag) ?? [];
|
|
92
|
+
list.push(ep);
|
|
93
|
+
grouped.set(ep.tag, list);
|
|
94
|
+
}
|
|
95
|
+
return grouped;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../../src/commands/api/catalog.ts"],"names":[],"mappings":"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","sourcesContent":["import { parse as parseYaml } from 'yaml';\nimport { createRequire } from 'node:module';\nimport { readFile } from 'node:fs/promises';\n\nexport interface Param {\n name: string;\n description: string;\n required: boolean;\n}\n\nexport interface EndpointInfo {\n method: string;\n path: string;\n summary: string;\n tag: string;\n operationId: string;\n pathParams: Param[];\n queryParams: Param[];\n hasRequestBody: boolean;\n requestBodyRequired: boolean;\n}\n\nexport interface Catalog {\n endpoints: EndpointInfo[];\n tags: string[];\n}\n\nconst HTTP_METHODS = ['get', 'post', 'put', 'patch', 'delete'] as const;\n\ninterface RawParam {\n name?: string;\n in?: string;\n description?: string;\n required?: boolean;\n $ref?: string;\n}\n\n/**\n * Resolve an OpenAPI 3.x parameter object that may itself be a $ref pointing\n * into components.parameters. Returns undefined if the ref can't be resolved\n * (so the parameter is skipped instead of producing a {param} placeholder\n * that leaks into request URLs).\n */\nfunction resolveParam(param: RawParam, componentParams: Record<string, RawParam>): RawParam | undefined {\n if (!param || typeof param !== 'object') return undefined;\n if (typeof param.$ref === 'string') {\n const match = /^#\\/components\\/parameters\\/(.+)$/.exec(param.$ref);\n if (!match) return undefined;\n const target = componentParams[match[1]!];\n if (!target) return undefined;\n // Recurse so a chain of $refs still resolves to a concrete definition.\n return resolveParam(target, componentParams);\n }\n return param;\n}\n\nexport function parseSpec(yamlText: string): Catalog {\n const spec = parseYaml(yamlText) as {\n paths?: Record<string, unknown>;\n components?: { parameters?: Record<string, RawParam> };\n };\n const endpoints: EndpointInfo[] = [];\n const componentParams = spec.components?.parameters ?? {};\n\n for (const [path, pathItem] of Object.entries(spec.paths ?? {})) {\n const pathObj = pathItem as Record<string, unknown>;\n\n for (const method of HTTP_METHODS) {\n const operation = pathObj[method];\n if (!operation || typeof operation !== 'object') continue;\n\n const op = operation as Record<string, unknown>;\n const tag = ((op.tags as string[]) ?? ['other'])[0] ?? 'other';\n\n // Resolve $ref and merge path-level + operation-level params.\n // Operation-level params override path-level ones with the same (name, in)\n // pair, per the OpenAPI 3.x spec.\n const rawPathLevel = (pathObj.parameters as RawParam[] | undefined) ?? [];\n const rawOpLevel = (op.parameters as RawParam[] | undefined) ?? [];\n const merged = new Map<string, RawParam>();\n for (const raw of [...rawPathLevel, ...rawOpLevel]) {\n const resolved = resolveParam(raw, componentParams);\n if (!resolved || !resolved.name || !resolved.in) continue;\n merged.set(`${resolved.in}:${resolved.name}`, resolved);\n }\n const allParams = [...merged.values()];\n\n const pathParams: Param[] = allParams\n .filter((p) => p.in === 'path')\n .map((p) => ({ name: p.name!, description: p.description ?? '', required: p.required ?? true }));\n\n const queryParams: Param[] = allParams\n .filter((p) => p.in === 'query')\n .map((p) => ({ name: p.name!, description: p.description ?? '', required: p.required ?? false }));\n\n const reqBody = op.requestBody as Record<string, unknown> | undefined;\n endpoints.push({\n method: method.toUpperCase(),\n path,\n summary: (op.summary as string) ?? '',\n tag,\n operationId: (op.operationId as string) ?? '',\n pathParams,\n queryParams,\n hasRequestBody: !!reqBody,\n requestBodyRequired: !!reqBody?.required,\n });\n }\n }\n\n const tags = [...new Set(endpoints.map((e) => e.tag))].sort();\n return { endpoints, tags };\n}\n\nlet cachedCatalog: Promise<Catalog> | undefined;\n\nexport function loadCatalog(): Promise<Catalog> {\n // Cache the in-flight Promise (not just the resolved value) so concurrent\n // callers reuse the same readFile/parse pass — see request.ts callers.\n if (cachedCatalog) return cachedCatalog;\n\n cachedCatalog = (async () => {\n const require = createRequire(import.meta.url);\n const specPath = require.resolve('@workos/openapi-spec/spec');\n const yamlText = await readFile(specPath, 'utf-8');\n return parseSpec(yamlText);\n })();\n\n return cachedCatalog;\n}\n\nexport function endpointsByTag(endpoints: EndpointInfo[]): Map<string, EndpointInfo[]> {\n const grouped = new Map<string, EndpointInfo[]>();\n for (const ep of endpoints) {\n const list = grouped.get(ep.tag) ?? [];\n list.push(ep);\n grouped.set(ep.tag, list);\n }\n return grouped;\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { isJsonMode, outputJson } from '../../utils/output.js';
|
|
3
|
+
export function colorMethod(method) {
|
|
4
|
+
switch (method) {
|
|
5
|
+
case 'GET':
|
|
6
|
+
return chalk.green(method);
|
|
7
|
+
case 'POST':
|
|
8
|
+
return chalk.blue(method);
|
|
9
|
+
case 'PUT':
|
|
10
|
+
case 'PATCH':
|
|
11
|
+
return chalk.yellow(method);
|
|
12
|
+
case 'DELETE':
|
|
13
|
+
return chalk.red(method);
|
|
14
|
+
default:
|
|
15
|
+
return method;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function printResponse(response, { includeStatus = false } = {}) {
|
|
19
|
+
if (isJsonMode()) {
|
|
20
|
+
if (includeStatus) {
|
|
21
|
+
const headers = {};
|
|
22
|
+
response.headers.forEach((value, key) => {
|
|
23
|
+
headers[key] = value;
|
|
24
|
+
});
|
|
25
|
+
outputJson({ status: response.status, headers, body: response.body });
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
outputJson(response.body);
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (includeStatus) {
|
|
33
|
+
console.log(chalk.dim(`HTTP ${response.status}`));
|
|
34
|
+
response.headers.forEach((value, key) => {
|
|
35
|
+
console.log(chalk.dim(`${key}: ${value}`));
|
|
36
|
+
});
|
|
37
|
+
console.log();
|
|
38
|
+
}
|
|
39
|
+
if (typeof response.body === 'object' && response.body !== null) {
|
|
40
|
+
console.log(JSON.stringify(response.body, null, 2));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.log(response.rawBody);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../../src/commands/api/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAG/D,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,KAAK;YACR,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC7B,KAAK,MAAM;YACT,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5B,KAAK,KAAK,CAAC;QACX,KAAK,OAAO;YACV,OAAO,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,KAAK,QAAQ;YACX,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC3B;YACE,OAAO,MAAM,CAAC;IAClB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,QAAqB,EACrB,EAAE,aAAa,GAAG,KAAK,KAAkC,EAAE;IAE3D,IAAI,UAAU,EAAE,EAAE,CAAC;QACjB,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,OAAO,GAA2B,EAAE,CAAC;YAC3C,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;gBACtC,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACvB,CAAC,CAAC,CAAC;YACH,UAAU,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QACxE,CAAC;aAAM,CAAC;YACN,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;QACD,OAAO;IACT,CAAC;IAED,IAAI,aAAa,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAClD,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YACtC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,GAAG,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,EAAE,CAAC;IAChB,CAAC;IAED,IAAI,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QAChE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;AACH,CAAC","sourcesContent":["import chalk from 'chalk';\nimport { isJsonMode, outputJson } from '../../utils/output.js';\nimport type { ApiResponse } from './request.js';\n\nexport function colorMethod(method: string): string {\n switch (method) {\n case 'GET':\n return chalk.green(method);\n case 'POST':\n return chalk.blue(method);\n case 'PUT':\n case 'PATCH':\n return chalk.yellow(method);\n case 'DELETE':\n return chalk.red(method);\n default:\n return method;\n }\n}\n\nexport function printResponse(\n response: ApiResponse,\n { includeStatus = false }: { includeStatus?: boolean } = {},\n): void {\n if (isJsonMode()) {\n if (includeStatus) {\n const headers: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n headers[key] = value;\n });\n outputJson({ status: response.status, headers, body: response.body });\n } else {\n outputJson(response.body);\n }\n return;\n }\n\n if (includeStatus) {\n console.log(chalk.dim(`HTTP ${response.status}`));\n response.headers.forEach((value, key) => {\n console.log(chalk.dim(`${key}: ${value}`));\n });\n console.log();\n }\n\n if (typeof response.body === 'object' && response.body !== null) {\n console.log(JSON.stringify(response.body, null, 2));\n } else {\n console.log(response.rawBody);\n }\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { colorMethod } from './format.js';
|
|
2
|
+
export interface ApiCommandOptions {
|
|
3
|
+
method?: string;
|
|
4
|
+
data?: string;
|
|
5
|
+
file?: string;
|
|
6
|
+
include?: boolean;
|
|
7
|
+
apiKey?: string;
|
|
8
|
+
dryRun?: boolean;
|
|
9
|
+
yes?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare function runApiInteractive(options?: {
|
|
12
|
+
apiKey?: string;
|
|
13
|
+
}): Promise<void>;
|
|
14
|
+
export declare function runApiLs(filter?: string): Promise<void>;
|
|
15
|
+
export declare function runApiRequest(endpoint: string, options: ApiCommandOptions): Promise<void>;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { loadCatalog, endpointsByTag } from './catalog.js';
|
|
4
|
+
import { apiRequest } from './request.js';
|
|
5
|
+
import { resolveApiBaseUrl } from '../../lib/api-key.js';
|
|
6
|
+
import { exitWithError, isJsonMode, outputJson } from '../../utils/output.js';
|
|
7
|
+
import { isCiMode, isPromptAllowed } from '../../utils/interaction-mode.js';
|
|
8
|
+
import { confirmationRecovery } from '../../utils/recovery-hints.js';
|
|
9
|
+
import { formatWorkOSCommandArgs } from '../../utils/command-invocation.js';
|
|
10
|
+
import { colorMethod, printResponse } from './format.js';
|
|
11
|
+
export { colorMethod } from './format.js';
|
|
12
|
+
const MUTATING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);
|
|
13
|
+
export async function runApiInteractive(options) {
|
|
14
|
+
// Interactive mode is inherently human-oriented (clack prompts, preview text,
|
|
15
|
+
// etc.). Refuse to enter it whenever JSON output was requested, regardless of
|
|
16
|
+
// TTY status, so stdout stays machine-readable.
|
|
17
|
+
if (isJsonMode()) {
|
|
18
|
+
exitWithError({
|
|
19
|
+
code: 'tty_required',
|
|
20
|
+
message: 'Interactive mode is not available with --json. Provide an endpoint or use `workos api ls`.',
|
|
21
|
+
details: {
|
|
22
|
+
usage: ['workos api <endpoint>', 'workos api ls [filter]'],
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (!isPromptAllowed()) {
|
|
27
|
+
exitWithError({
|
|
28
|
+
code: 'tty_required',
|
|
29
|
+
message: 'Interactive API mode requires human mode. Usage: workos api <endpoint> or workos api ls [filter]. Example: workos api /user_management/users',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const { apiInteractive } = await import('./interactive.js');
|
|
33
|
+
await apiInteractive({ apiKey: options?.apiKey });
|
|
34
|
+
}
|
|
35
|
+
export async function runApiLs(filter) {
|
|
36
|
+
const catalog = await loadCatalog();
|
|
37
|
+
let endpoints = catalog.endpoints;
|
|
38
|
+
if (filter) {
|
|
39
|
+
const lower = filter.toLowerCase();
|
|
40
|
+
endpoints = endpoints.filter((e) => e.path.toLowerCase().includes(lower) ||
|
|
41
|
+
e.tag.toLowerCase().includes(lower) ||
|
|
42
|
+
e.summary.toLowerCase().includes(lower) ||
|
|
43
|
+
e.operationId.toLowerCase().includes(lower));
|
|
44
|
+
}
|
|
45
|
+
if (isJsonMode()) {
|
|
46
|
+
outputJson({
|
|
47
|
+
data: endpoints.map((e) => ({
|
|
48
|
+
method: e.method,
|
|
49
|
+
path: e.path,
|
|
50
|
+
summary: e.summary,
|
|
51
|
+
tag: e.tag,
|
|
52
|
+
})),
|
|
53
|
+
});
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (endpoints.length === 0) {
|
|
57
|
+
console.log(filter ? `No endpoints matching "${filter}".` : 'No endpoints found.');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const grouped = endpointsByTag(endpoints);
|
|
61
|
+
for (const [tag, eps] of grouped) {
|
|
62
|
+
console.log(`\n${chalk.bold(tag)}`);
|
|
63
|
+
for (const ep of eps) {
|
|
64
|
+
const method = colorMethod(ep.method).padEnd(18);
|
|
65
|
+
console.log(` ${method} ${ep.path} ${chalk.dim(ep.summary)}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
console.log();
|
|
69
|
+
}
|
|
70
|
+
export async function runApiRequest(endpoint, options) {
|
|
71
|
+
const body = await resolveBody(options);
|
|
72
|
+
const hasBody = body !== undefined;
|
|
73
|
+
const method = (options.method ?? (hasBody ? 'POST' : 'GET')).toUpperCase();
|
|
74
|
+
const baseUrl = resolveApiBaseUrl();
|
|
75
|
+
if (options.dryRun) {
|
|
76
|
+
if (isJsonMode()) {
|
|
77
|
+
let parsedBody;
|
|
78
|
+
if (hasBody) {
|
|
79
|
+
try {
|
|
80
|
+
parsedBody = JSON.parse(body);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
exitWithError({ code: 'invalid_json_body', message: 'Request body is not valid JSON.' });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
outputJson({
|
|
87
|
+
dryRun: true,
|
|
88
|
+
method,
|
|
89
|
+
url: `${baseUrl}${normalizePath(endpoint)}`,
|
|
90
|
+
body: parsedBody,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
console.log(`${chalk.dim('[dry-run]')} ${method} ${baseUrl}${normalizePath(endpoint)}`);
|
|
95
|
+
if (hasBody)
|
|
96
|
+
prettyPrint(body);
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (MUTATING_METHODS.has(method) && !options.yes) {
|
|
101
|
+
const confirmCommand = buildConfirmationCommand(endpoint, method, options);
|
|
102
|
+
if (!isPromptAllowed()) {
|
|
103
|
+
exitWithError({
|
|
104
|
+
code: 'confirmation_required',
|
|
105
|
+
message: isCiMode()
|
|
106
|
+
? `Mutating requests in CI mode require --yes. Refusing to ${method} ${endpoint}.`
|
|
107
|
+
: `Mutating requests in agent mode require --yes. Refusing to ${method} ${endpoint}.`,
|
|
108
|
+
recovery: confirmationRecovery(confirmCommand),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (isJsonMode()) {
|
|
112
|
+
exitWithError({
|
|
113
|
+
code: 'confirmation_required',
|
|
114
|
+
message: 'Mutating requests in JSON mode require --yes to keep stdout machine-readable.',
|
|
115
|
+
recovery: confirmationRecovery(confirmCommand),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const clack = (await import('../../utils/clack.js')).default;
|
|
119
|
+
console.log(`\n${chalk.yellow('About to')} ${method} ${endpoint}`);
|
|
120
|
+
if (hasBody)
|
|
121
|
+
prettyPrint(body);
|
|
122
|
+
const ok = await clack.confirm({ message: 'Proceed?' });
|
|
123
|
+
if (!ok || clack.isCancel(ok)) {
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const response = await apiRequest({
|
|
128
|
+
method,
|
|
129
|
+
path: normalizePath(endpoint),
|
|
130
|
+
apiKey: options.apiKey,
|
|
131
|
+
body,
|
|
132
|
+
baseUrl,
|
|
133
|
+
});
|
|
134
|
+
printResponse(response, { includeStatus: options.include });
|
|
135
|
+
if (response.status >= 400) {
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function normalizePath(path) {
|
|
140
|
+
if (!path.startsWith('/'))
|
|
141
|
+
return `/${path}`;
|
|
142
|
+
return path;
|
|
143
|
+
}
|
|
144
|
+
function buildConfirmationCommand(endpoint, method, options) {
|
|
145
|
+
if (options.apiKey || options.file === '-') {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
const args = ['api', endpoint, '--method', method];
|
|
149
|
+
if (options.data !== undefined) {
|
|
150
|
+
args.push(`--data=${options.data}`);
|
|
151
|
+
}
|
|
152
|
+
if (options.file) {
|
|
153
|
+
args.push(`--file=${options.file}`);
|
|
154
|
+
}
|
|
155
|
+
if (options.include) {
|
|
156
|
+
args.push('--include');
|
|
157
|
+
}
|
|
158
|
+
args.push('--yes');
|
|
159
|
+
return formatWorkOSCommandArgs(args);
|
|
160
|
+
}
|
|
161
|
+
async function resolveBody(options) {
|
|
162
|
+
if (options.data !== undefined)
|
|
163
|
+
return options.data;
|
|
164
|
+
if (options.file) {
|
|
165
|
+
if (options.file === '-') {
|
|
166
|
+
const chunks = [];
|
|
167
|
+
for await (const chunk of process.stdin) {
|
|
168
|
+
chunks.push(chunk);
|
|
169
|
+
}
|
|
170
|
+
const stdinBody = Buffer.concat(chunks).toString('utf-8');
|
|
171
|
+
if (stdinBody.length === 0) {
|
|
172
|
+
exitWithError({
|
|
173
|
+
code: 'empty_stdin_body',
|
|
174
|
+
message: 'Reading request body from stdin (--file -) yielded no data. Pipe data into the command or pass --data instead.',
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return stdinBody;
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
180
|
+
return await readFile(options.file, 'utf-8');
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
184
|
+
exitWithError({
|
|
185
|
+
code: 'file_read_error',
|
|
186
|
+
message: `Could not read request body file "${options.file}": ${message}`,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
function prettyPrint(jsonString) {
|
|
193
|
+
try {
|
|
194
|
+
console.log(JSON.stringify(JSON.parse(jsonString), null, 2));
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
console.log(jsonString);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/api/index.ts"],"names":[],"mappings":"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","sourcesContent":["import chalk from 'chalk';\nimport { readFile } from 'node:fs/promises';\nimport { loadCatalog, endpointsByTag } from './catalog.js';\nimport { apiRequest } from './request.js';\nimport { resolveApiBaseUrl } from '../../lib/api-key.js';\nimport { exitWithError, isJsonMode, outputJson } from '../../utils/output.js';\nimport { isCiMode, isPromptAllowed } from '../../utils/interaction-mode.js';\nimport { confirmationRecovery } from '../../utils/recovery-hints.js';\nimport { formatWorkOSCommandArgs } from '../../utils/command-invocation.js';\nimport { colorMethod, printResponse } from './format.js';\n\nexport { colorMethod } from './format.js';\n\nexport interface ApiCommandOptions {\n method?: string;\n data?: string;\n file?: string;\n include?: boolean;\n apiKey?: string;\n dryRun?: boolean;\n yes?: boolean;\n}\n\nconst MUTATING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);\n\nexport async function runApiInteractive(options?: { apiKey?: string }): Promise<void> {\n // Interactive mode is inherently human-oriented (clack prompts, preview text,\n // etc.). Refuse to enter it whenever JSON output was requested, regardless of\n // TTY status, so stdout stays machine-readable.\n if (isJsonMode()) {\n exitWithError({\n code: 'tty_required',\n message: 'Interactive mode is not available with --json. Provide an endpoint or use `workos api ls`.',\n details: {\n usage: ['workos api <endpoint>', 'workos api ls [filter]'],\n },\n });\n }\n\n if (!isPromptAllowed()) {\n exitWithError({\n code: 'tty_required',\n message:\n 'Interactive API mode requires human mode. Usage: workos api <endpoint> or workos api ls [filter]. Example: workos api /user_management/users',\n });\n }\n\n const { apiInteractive } = await import('./interactive.js');\n await apiInteractive({ apiKey: options?.apiKey });\n}\n\nexport async function runApiLs(filter?: string): Promise<void> {\n const catalog = await loadCatalog();\n let endpoints = catalog.endpoints;\n\n if (filter) {\n const lower = filter.toLowerCase();\n endpoints = endpoints.filter(\n (e) =>\n e.path.toLowerCase().includes(lower) ||\n e.tag.toLowerCase().includes(lower) ||\n e.summary.toLowerCase().includes(lower) ||\n e.operationId.toLowerCase().includes(lower),\n );\n }\n\n if (isJsonMode()) {\n outputJson({\n data: endpoints.map((e) => ({\n method: e.method,\n path: e.path,\n summary: e.summary,\n tag: e.tag,\n })),\n });\n return;\n }\n\n if (endpoints.length === 0) {\n console.log(filter ? `No endpoints matching \"${filter}\".` : 'No endpoints found.');\n return;\n }\n\n const grouped = endpointsByTag(endpoints);\n\n for (const [tag, eps] of grouped) {\n console.log(`\\n${chalk.bold(tag)}`);\n for (const ep of eps) {\n const method = colorMethod(ep.method).padEnd(18);\n console.log(` ${method} ${ep.path} ${chalk.dim(ep.summary)}`);\n }\n }\n console.log();\n}\n\nexport async function runApiRequest(endpoint: string, options: ApiCommandOptions): Promise<void> {\n const body = await resolveBody(options);\n const hasBody = body !== undefined;\n const method = (options.method ?? (hasBody ? 'POST' : 'GET')).toUpperCase();\n const baseUrl = resolveApiBaseUrl();\n\n if (options.dryRun) {\n if (isJsonMode()) {\n let parsedBody: unknown;\n if (hasBody) {\n try {\n parsedBody = JSON.parse(body);\n } catch {\n exitWithError({ code: 'invalid_json_body', message: 'Request body is not valid JSON.' });\n }\n }\n outputJson({\n dryRun: true,\n method,\n url: `${baseUrl}${normalizePath(endpoint)}`,\n body: parsedBody,\n });\n } else {\n console.log(`${chalk.dim('[dry-run]')} ${method} ${baseUrl}${normalizePath(endpoint)}`);\n if (hasBody) prettyPrint(body);\n }\n return;\n }\n\n if (MUTATING_METHODS.has(method) && !options.yes) {\n const confirmCommand = buildConfirmationCommand(endpoint, method, options);\n if (!isPromptAllowed()) {\n exitWithError({\n code: 'confirmation_required',\n message: isCiMode()\n ? `Mutating requests in CI mode require --yes. Refusing to ${method} ${endpoint}.`\n : `Mutating requests in agent mode require --yes. Refusing to ${method} ${endpoint}.`,\n recovery: confirmationRecovery(confirmCommand),\n });\n }\n if (isJsonMode()) {\n exitWithError({\n code: 'confirmation_required',\n message: 'Mutating requests in JSON mode require --yes to keep stdout machine-readable.',\n recovery: confirmationRecovery(confirmCommand),\n });\n }\n const clack = (await import('../../utils/clack.js')).default;\n console.log(`\\n${chalk.yellow('About to')} ${method} ${endpoint}`);\n if (hasBody) prettyPrint(body);\n const ok = await clack.confirm({ message: 'Proceed?' });\n if (!ok || clack.isCancel(ok)) {\n process.exit(0);\n }\n }\n\n const response = await apiRequest({\n method,\n path: normalizePath(endpoint),\n apiKey: options.apiKey,\n body,\n baseUrl,\n });\n\n printResponse(response, { includeStatus: options.include });\n\n if (response.status >= 400) {\n process.exit(1);\n }\n}\n\nfunction normalizePath(path: string): string {\n if (!path.startsWith('/')) return `/${path}`;\n return path;\n}\n\nfunction buildConfirmationCommand(endpoint: string, method: string, options: ApiCommandOptions): string | undefined {\n if (options.apiKey || options.file === '-') {\n return undefined;\n }\n\n const args = ['api', endpoint, '--method', method];\n if (options.data !== undefined) {\n args.push(`--data=${options.data}`);\n }\n if (options.file) {\n args.push(`--file=${options.file}`);\n }\n if (options.include) {\n args.push('--include');\n }\n args.push('--yes');\n return formatWorkOSCommandArgs(args);\n}\n\nasync function resolveBody(options: ApiCommandOptions): Promise<string | undefined> {\n if (options.data !== undefined) return options.data;\n if (options.file) {\n if (options.file === '-') {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n const stdinBody = Buffer.concat(chunks).toString('utf-8');\n if (stdinBody.length === 0) {\n exitWithError({\n code: 'empty_stdin_body',\n message:\n 'Reading request body from stdin (--file -) yielded no data. Pipe data into the command or pass --data instead.',\n });\n }\n return stdinBody;\n }\n try {\n return await readFile(options.file, 'utf-8');\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n exitWithError({\n code: 'file_read_error',\n message: `Could not read request body file \"${options.file}\": ${message}`,\n });\n }\n }\n return undefined;\n}\n\nfunction prettyPrint(jsonString: string): void {\n try {\n console.log(JSON.stringify(JSON.parse(jsonString), null, 2));\n } catch {\n console.log(jsonString);\n }\n}\n"]}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import clack from '../../utils/clack.js';
|
|
2
|
+
import { loadCatalog, endpointsByTag } from './catalog.js';
|
|
3
|
+
import { apiRequest } from './request.js';
|
|
4
|
+
import { colorMethod, printResponse } from './format.js';
|
|
5
|
+
import { resolveApiKey, resolveApiBaseUrl } from '../../lib/api-key.js';
|
|
6
|
+
function assertNotCancelled(value) {
|
|
7
|
+
if (clack.isCancel(value))
|
|
8
|
+
process.exit(0);
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
export async function apiInteractive(options) {
|
|
12
|
+
const catalog = await loadCatalog();
|
|
13
|
+
const grouped = endpointsByTag(catalog.endpoints);
|
|
14
|
+
const tag = assertNotCancelled(await clack.select({
|
|
15
|
+
message: 'Select a category:',
|
|
16
|
+
options: catalog.tags.map((t) => {
|
|
17
|
+
const count = grouped.get(t)?.length ?? 0;
|
|
18
|
+
return { value: t, label: `${t} (${count})` };
|
|
19
|
+
}),
|
|
20
|
+
}));
|
|
21
|
+
const endpoints = grouped.get(tag);
|
|
22
|
+
const ep = assertNotCancelled(await clack.select({
|
|
23
|
+
message: 'Select an endpoint:',
|
|
24
|
+
options: endpoints.map((e) => ({
|
|
25
|
+
value: e,
|
|
26
|
+
label: `${colorMethod(e.method).padEnd(18)} ${e.path}`,
|
|
27
|
+
hint: e.summary,
|
|
28
|
+
})),
|
|
29
|
+
}));
|
|
30
|
+
let resolvedPath = ep.path;
|
|
31
|
+
for (const param of ep.pathParams) {
|
|
32
|
+
const value = assertNotCancelled(await clack.text({
|
|
33
|
+
message: `${param.name}:`,
|
|
34
|
+
placeholder: param.description || undefined,
|
|
35
|
+
validate: (v) => {
|
|
36
|
+
if (!v?.trim())
|
|
37
|
+
return `${param.name} is required`;
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
resolvedPath = resolvedPath.replaceAll(`{${param.name}}`, encodeURIComponent(value.trim()));
|
|
41
|
+
}
|
|
42
|
+
let queryString = '';
|
|
43
|
+
if (ep.queryParams.length > 0) {
|
|
44
|
+
const requiredParams = ep.queryParams.filter((qp) => qp.required);
|
|
45
|
+
const optionalParams = ep.queryParams.filter((qp) => !qp.required);
|
|
46
|
+
const params = [];
|
|
47
|
+
for (const qp of requiredParams) {
|
|
48
|
+
const value = assertNotCancelled(await clack.text({
|
|
49
|
+
message: `${qp.name} (required):`,
|
|
50
|
+
placeholder: qp.description || undefined,
|
|
51
|
+
validate: (v) => {
|
|
52
|
+
if (!v?.trim())
|
|
53
|
+
return `${qp.name} is required`;
|
|
54
|
+
},
|
|
55
|
+
}));
|
|
56
|
+
params.push(`${encodeURIComponent(qp.name)}=${encodeURIComponent(value.trim())}`);
|
|
57
|
+
}
|
|
58
|
+
if (optionalParams.length > 0) {
|
|
59
|
+
const wantsOptional = assertNotCancelled(await clack.confirm({
|
|
60
|
+
message: `Add optional query parameters? (${optionalParams.length} available)`,
|
|
61
|
+
initialValue: false,
|
|
62
|
+
}));
|
|
63
|
+
if (wantsOptional) {
|
|
64
|
+
for (const qp of optionalParams) {
|
|
65
|
+
const value = assertNotCancelled(await clack.text({
|
|
66
|
+
message: `${qp.name}:`,
|
|
67
|
+
placeholder: qp.description || undefined,
|
|
68
|
+
}));
|
|
69
|
+
const trimmed = value.trim();
|
|
70
|
+
if (trimmed) {
|
|
71
|
+
params.push(`${encodeURIComponent(qp.name)}=${encodeURIComponent(trimmed)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (params.length > 0) {
|
|
77
|
+
queryString = `?${params.join('&')}`;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
let body;
|
|
81
|
+
if (ep.hasRequestBody) {
|
|
82
|
+
let collectBody = ep.requestBodyRequired;
|
|
83
|
+
if (!collectBody) {
|
|
84
|
+
collectBody = assertNotCancelled(await clack.confirm({
|
|
85
|
+
message: 'Provide a request body?',
|
|
86
|
+
initialValue: ep.method === 'POST' || ep.method === 'PUT',
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
if (collectBody) {
|
|
90
|
+
body = assertNotCancelled(await clack.text({
|
|
91
|
+
message: 'Request body (JSON):',
|
|
92
|
+
placeholder: '{"key": "value"}',
|
|
93
|
+
validate: (v) => {
|
|
94
|
+
if (!v?.trim())
|
|
95
|
+
return 'Body cannot be empty';
|
|
96
|
+
try {
|
|
97
|
+
JSON.parse(v);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return 'Invalid JSON';
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
})).trim();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const fullPath = `${resolvedPath}${queryString}`;
|
|
107
|
+
console.log(`\n ${colorMethod(ep.method)} ${fullPath}`);
|
|
108
|
+
if (body) {
|
|
109
|
+
console.log(JSON.stringify(JSON.parse(body), null, 2));
|
|
110
|
+
}
|
|
111
|
+
console.log();
|
|
112
|
+
const ok = assertNotCancelled(await clack.confirm({ message: 'Execute this request?' }));
|
|
113
|
+
if (!ok)
|
|
114
|
+
process.exit(0);
|
|
115
|
+
const response = await apiRequest({
|
|
116
|
+
method: ep.method,
|
|
117
|
+
path: fullPath,
|
|
118
|
+
apiKey: options?.apiKey ?? resolveApiKey(),
|
|
119
|
+
baseUrl: resolveApiBaseUrl(),
|
|
120
|
+
body,
|
|
121
|
+
});
|
|
122
|
+
printResponse(response, { includeStatus: true });
|
|
123
|
+
if (response.status >= 400) {
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=interactive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interactive.js","sourceRoot":"","sources":["../../../src/commands/api/interactive.ts"],"names":[],"mappings":"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","sourcesContent":["import clack from '../../utils/clack.js';\nimport { loadCatalog, endpointsByTag, type EndpointInfo } from './catalog.js';\nimport { apiRequest } from './request.js';\nimport { colorMethod, printResponse } from './format.js';\nimport { resolveApiKey, resolveApiBaseUrl } from '../../lib/api-key.js';\n\nfunction assertNotCancelled<T>(value: T | symbol): T {\n if (clack.isCancel(value)) process.exit(0);\n return value as T;\n}\n\nexport async function apiInteractive(options?: { apiKey?: string }): Promise<void> {\n const catalog = await loadCatalog();\n const grouped = endpointsByTag(catalog.endpoints);\n\n const tag = assertNotCancelled(\n await clack.select({\n message: 'Select a category:',\n options: catalog.tags.map((t) => {\n const count = grouped.get(t)?.length ?? 0;\n return { value: t, label: `${t} (${count})` };\n }),\n }),\n );\n\n const endpoints = grouped.get(tag)!;\n const ep = assertNotCancelled(\n await clack.select<EndpointInfo>({\n message: 'Select an endpoint:',\n options: endpoints.map((e) => ({\n value: e,\n label: `${colorMethod(e.method).padEnd(18)} ${e.path}`,\n hint: e.summary,\n })),\n }),\n );\n\n let resolvedPath = ep.path;\n for (const param of ep.pathParams) {\n const value = assertNotCancelled(\n await clack.text({\n message: `${param.name}:`,\n placeholder: param.description || undefined,\n validate: (v) => {\n if (!v?.trim()) return `${param.name} is required`;\n },\n }),\n );\n resolvedPath = resolvedPath.replaceAll(`{${param.name}}`, encodeURIComponent(value.trim()));\n }\n\n let queryString = '';\n if (ep.queryParams.length > 0) {\n const requiredParams = ep.queryParams.filter((qp) => qp.required);\n const optionalParams = ep.queryParams.filter((qp) => !qp.required);\n const params: string[] = [];\n\n for (const qp of requiredParams) {\n const value = assertNotCancelled(\n await clack.text({\n message: `${qp.name} (required):`,\n placeholder: qp.description || undefined,\n validate: (v) => {\n if (!v?.trim()) return `${qp.name} is required`;\n },\n }),\n );\n params.push(`${encodeURIComponent(qp.name)}=${encodeURIComponent(value.trim())}`);\n }\n\n if (optionalParams.length > 0) {\n const wantsOptional = assertNotCancelled(\n await clack.confirm({\n message: `Add optional query parameters? (${optionalParams.length} available)`,\n initialValue: false,\n }),\n );\n\n if (wantsOptional) {\n for (const qp of optionalParams) {\n const value = assertNotCancelled(\n await clack.text({\n message: `${qp.name}:`,\n placeholder: qp.description || undefined,\n }),\n );\n const trimmed = value.trim();\n if (trimmed) {\n params.push(`${encodeURIComponent(qp.name)}=${encodeURIComponent(trimmed)}`);\n }\n }\n }\n }\n\n if (params.length > 0) {\n queryString = `?${params.join('&')}`;\n }\n }\n\n let body: string | undefined;\n if (ep.hasRequestBody) {\n let collectBody = ep.requestBodyRequired;\n if (!collectBody) {\n collectBody = assertNotCancelled(\n await clack.confirm({\n message: 'Provide a request body?',\n initialValue: ep.method === 'POST' || ep.method === 'PUT',\n }),\n );\n }\n\n if (collectBody) {\n body = assertNotCancelled(\n await clack.text({\n message: 'Request body (JSON):',\n placeholder: '{\"key\": \"value\"}',\n validate: (v) => {\n if (!v?.trim()) return 'Body cannot be empty';\n try {\n JSON.parse(v);\n } catch {\n return 'Invalid JSON';\n }\n },\n }),\n ).trim();\n }\n }\n\n const fullPath = `${resolvedPath}${queryString}`;\n\n console.log(`\\n ${colorMethod(ep.method)} ${fullPath}`);\n if (body) {\n console.log(JSON.stringify(JSON.parse(body), null, 2));\n }\n console.log();\n\n const ok = assertNotCancelled(await clack.confirm({ message: 'Execute this request?' }));\n if (!ok) process.exit(0);\n\n const response = await apiRequest({\n method: ep.method,\n path: fullPath,\n apiKey: options?.apiKey ?? resolveApiKey(),\n baseUrl: resolveApiBaseUrl(),\n body,\n });\n\n printResponse(response, { includeStatus: true });\n\n if (response.status >= 400) {\n process.exit(1);\n }\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ApiRequestOptions {
|
|
2
|
+
method: string;
|
|
3
|
+
path: string;
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
body?: string;
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ApiResponse {
|
|
9
|
+
status: number;
|
|
10
|
+
headers: Headers;
|
|
11
|
+
body: unknown;
|
|
12
|
+
rawBody: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function apiRequest(options: ApiRequestOptions): Promise<ApiResponse>;
|