specli 0.0.4 → 0.0.7
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/cli.ts +13 -4
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +2331 -0
- package/dist/cli.js.map +53 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2032 -0
- package/dist/index.js.map +48 -0
- package/dist/src/ai/tools.d.ts +139 -0
- package/dist/src/ai/tools.d.ts.map +1 -0
- package/dist/src/ai/tools.js +1656 -0
- package/dist/src/ai/tools.js.map +45 -0
- package/dist/src/cli/auth-requirements.d.ts +10 -0
- package/dist/src/cli/auth-requirements.d.ts.map +1 -0
- package/dist/src/cli/auth-requirements.js +66 -0
- package/dist/src/cli/auth-requirements.js.map +10 -0
- package/dist/src/cli/auth-schemes.d.ts +22 -0
- package/dist/src/cli/auth-schemes.d.ts.map +1 -0
- package/dist/src/cli/auth-schemes.js +116 -0
- package/dist/src/cli/auth-schemes.js.map +11 -0
- package/dist/src/cli/capabilities.d.ts +32 -0
- package/dist/src/cli/capabilities.d.ts.map +1 -0
- package/dist/src/cli/capabilities.js +45 -0
- package/dist/src/cli/capabilities.js.map +10 -0
- package/dist/src/cli/command-id.d.ts +8 -0
- package/dist/src/cli/command-id.d.ts.map +1 -0
- package/dist/src/cli/command-id.js +18 -0
- package/dist/src/cli/command-id.js.map +11 -0
- package/dist/src/cli/command-index.d.ts +6 -0
- package/dist/src/cli/command-index.d.ts.map +1 -0
- package/dist/src/cli/command-index.js +15 -0
- package/dist/src/cli/command-index.js.map +10 -0
- package/dist/src/cli/command-model.d.ts +40 -0
- package/dist/src/cli/command-model.d.ts.map +1 -0
- package/dist/src/cli/command-model.js +274 -0
- package/dist/src/cli/command-model.js.map +18 -0
- package/dist/src/cli/compile.d.ts +15 -0
- package/dist/src/cli/compile.d.ts.map +1 -0
- package/dist/src/cli/compile.js +146 -0
- package/dist/src/cli/compile.js.map +11 -0
- package/dist/src/cli/crypto.d.ts +2 -0
- package/dist/src/cli/crypto.d.ts.map +1 -0
- package/dist/src/cli/crypto.js +15 -0
- package/dist/src/cli/crypto.js.map +10 -0
- package/dist/src/cli/derive-name.d.ts +9 -0
- package/dist/src/cli/derive-name.d.ts.map +1 -0
- package/dist/src/cli/derive-name.js +70 -0
- package/dist/src/cli/derive-name.js.map +10 -0
- package/dist/src/cli/exec.d.ts +14 -0
- package/dist/src/cli/exec.d.ts.map +1 -0
- package/dist/src/cli/exec.js +2077 -0
- package/dist/src/cli/exec.js.map +49 -0
- package/dist/src/cli/main.d.ts +10 -0
- package/dist/src/cli/main.d.ts.map +1 -0
- package/dist/src/cli/main.js +2032 -0
- package/dist/src/cli/main.js.map +48 -0
- package/dist/src/cli/naming.d.ts +12 -0
- package/dist/src/cli/naming.d.ts.map +1 -0
- package/dist/src/cli/naming.js +216 -0
- package/dist/src/cli/naming.js.map +12 -0
- package/dist/src/cli/operations.d.ts +3 -0
- package/dist/src/cli/operations.d.ts.map +1 -0
- package/dist/src/cli/operations.js +103 -0
- package/dist/src/cli/operations.js.map +10 -0
- package/dist/src/cli/params.d.ts +19 -0
- package/dist/src/cli/params.d.ts.map +1 -0
- package/dist/src/cli/params.js +79 -0
- package/dist/src/cli/params.js.map +12 -0
- package/dist/src/cli/pluralize.d.ts +2 -0
- package/dist/src/cli/pluralize.d.ts.map +1 -0
- package/dist/src/cli/pluralize.js +43 -0
- package/dist/src/cli/pluralize.js.map +10 -0
- package/dist/src/cli/positional.d.ts +19 -0
- package/dist/src/cli/positional.d.ts.map +1 -0
- package/dist/src/cli/positional.js +39 -0
- package/dist/src/cli/positional.js.map +10 -0
- package/dist/src/cli/request-body.d.ts +20 -0
- package/dist/src/cli/request-body.d.ts.map +1 -0
- package/dist/src/cli/request-body.js +82 -0
- package/dist/src/cli/request-body.js.map +12 -0
- package/dist/src/cli/runtime/argv.d.ts +3 -0
- package/dist/src/cli/runtime/argv.d.ts.map +1 -0
- package/dist/src/cli/runtime/argv.js +22 -0
- package/dist/src/cli/runtime/argv.js.map +10 -0
- package/dist/src/cli/runtime/auth/resolve.d.ts +9 -0
- package/dist/src/cli/runtime/auth/resolve.d.ts.map +1 -0
- package/dist/src/cli/runtime/auth/resolve.js +38 -0
- package/dist/src/cli/runtime/auth/resolve.js.map +10 -0
- package/dist/src/cli/runtime/body-flags.d.ts +41 -0
- package/dist/src/cli/runtime/body-flags.d.ts.map +1 -0
- package/dist/src/cli/runtime/body-flags.js +86 -0
- package/dist/src/cli/runtime/body-flags.js.map +10 -0
- package/dist/src/cli/runtime/body.d.ts +15 -0
- package/dist/src/cli/runtime/body.d.ts.map +1 -0
- package/dist/src/cli/runtime/body.js +40 -0
- package/dist/src/cli/runtime/body.js.map +11 -0
- package/dist/src/cli/runtime/collect.d.ts +2 -0
- package/dist/src/cli/runtime/collect.d.ts.map +1 -0
- package/dist/src/cli/runtime/collect.js +9 -0
- package/dist/src/cli/runtime/collect.js.map +10 -0
- package/dist/src/cli/runtime/compat.d.ts +35 -0
- package/dist/src/cli/runtime/compat.d.ts.map +1 -0
- package/dist/src/cli/runtime/compat.js +62 -0
- package/dist/src/cli/runtime/compat.js.map +10 -0
- package/dist/src/cli/runtime/context.d.ts +16 -0
- package/dist/src/cli/runtime/context.d.ts.map +1 -0
- package/dist/src/cli/runtime/context.js +936 -0
- package/dist/src/cli/runtime/context.js.map +32 -0
- package/dist/src/cli/runtime/execute.d.ts +33 -0
- package/dist/src/cli/runtime/execute.d.ts.map +1 -0
- package/dist/src/cli/runtime/execute.js +670 -0
- package/dist/src/cli/runtime/execute.js.map +22 -0
- package/dist/src/cli/runtime/generated.d.ts +14 -0
- package/dist/src/cli/runtime/generated.d.ts.map +1 -0
- package/dist/src/cli/runtime/generated.js +869 -0
- package/dist/src/cli/runtime/generated.js.map +23 -0
- package/dist/src/cli/runtime/headers.d.ts +9 -0
- package/dist/src/cli/runtime/headers.d.ts.map +1 -0
- package/dist/src/cli/runtime/headers.js +36 -0
- package/dist/src/cli/runtime/headers.js.map +10 -0
- package/dist/src/cli/runtime/index.d.ts +4 -0
- package/dist/src/cli/runtime/index.d.ts.map +1 -0
- package/dist/src/cli/runtime/index.js +1808 -0
- package/dist/src/cli/runtime/index.js.map +46 -0
- package/dist/src/cli/runtime/profile/secrets.d.ts +25 -0
- package/dist/src/cli/runtime/profile/secrets.d.ts.map +1 -0
- package/dist/src/cli/runtime/profile/secrets.js +51 -0
- package/dist/src/cli/runtime/profile/secrets.js.map +11 -0
- package/dist/src/cli/runtime/profile/store.d.ts +15 -0
- package/dist/src/cli/runtime/profile/store.d.ts.map +1 -0
- package/dist/src/cli/runtime/profile/store.js +102 -0
- package/dist/src/cli/runtime/profile/store.js.map +11 -0
- package/dist/src/cli/runtime/request.d.ts +36 -0
- package/dist/src/cli/runtime/request.d.ts.map +1 -0
- package/dist/src/cli/runtime/request.js +571 -0
- package/dist/src/cli/runtime/request.js.map +21 -0
- package/dist/src/cli/runtime/server-url.d.ts +8 -0
- package/dist/src/cli/runtime/server-url.d.ts.map +1 -0
- package/dist/src/cli/runtime/server-url.js +55 -0
- package/dist/src/cli/runtime/server-url.js.map +11 -0
- package/dist/src/cli/runtime/template.d.ts +5 -0
- package/dist/src/cli/runtime/template.d.ts.map +1 -0
- package/dist/src/cli/runtime/template.js +29 -0
- package/dist/src/cli/runtime/template.js.map +10 -0
- package/dist/src/cli/runtime/validate/ajv.d.ts +3 -0
- package/dist/src/cli/runtime/validate/ajv.d.ts.map +1 -0
- package/dist/src/cli/runtime/validate/ajv.js +17 -0
- package/dist/src/cli/runtime/validate/ajv.js.map +10 -0
- package/dist/src/cli/runtime/validate/coerce.d.ts +4 -0
- package/dist/src/cli/runtime/validate/coerce.d.ts.map +1 -0
- package/dist/src/cli/runtime/validate/coerce.js +60 -0
- package/dist/src/cli/runtime/validate/coerce.js.map +10 -0
- package/dist/src/cli/runtime/validate/error.d.ts +3 -0
- package/dist/src/cli/runtime/validate/error.d.ts.map +1 -0
- package/dist/src/cli/runtime/validate/error.js +21 -0
- package/dist/src/cli/runtime/validate/error.js.map +10 -0
- package/dist/src/cli/runtime/validate/index.d.ts +5 -0
- package/dist/src/cli/runtime/validate/index.d.ts.map +1 -0
- package/dist/src/cli/runtime/validate/index.js +122 -0
- package/dist/src/cli/runtime/validate/index.js.map +13 -0
- package/dist/src/cli/runtime/validate/schema.d.ts +9 -0
- package/dist/src/cli/runtime/validate/schema.d.ts.map +1 -0
- package/dist/src/cli/runtime/validate/schema.js +36 -0
- package/dist/src/cli/runtime/validate/schema.js.map +10 -0
- package/dist/src/cli/schema-shape.d.ts +5 -0
- package/dist/src/cli/schema-shape.d.ts.map +1 -0
- package/dist/src/cli/schema-shape.js +41 -0
- package/dist/src/cli/schema-shape.js.map +10 -0
- package/dist/src/cli/schema.d.ts +30 -0
- package/dist/src/cli/schema.d.ts.map +1 -0
- package/dist/src/cli/schema.js +38 -0
- package/dist/src/cli/schema.js.map +10 -0
- package/dist/src/cli/server.d.ts +16 -0
- package/dist/src/cli/server.d.ts.map +1 -0
- package/dist/src/cli/server.js +64 -0
- package/dist/src/cli/server.js.map +11 -0
- package/dist/src/cli/spec-id.d.ts +3 -0
- package/dist/src/cli/spec-id.d.ts.map +1 -0
- package/dist/src/cli/spec-id.js +21 -0
- package/dist/src/cli/spec-id.js.map +11 -0
- package/dist/src/cli/spec-loader.d.ts +7 -0
- package/dist/src/cli/spec-loader.d.ts.map +1 -0
- package/dist/src/cli/spec-loader.js +110 -0
- package/dist/src/cli/spec-loader.js.map +15 -0
- package/dist/src/cli/stable-json.d.ts +4 -0
- package/dist/src/cli/stable-json.d.ts.map +1 -0
- package/dist/src/cli/stable-json.js +35 -0
- package/dist/src/cli/stable-json.js.map +10 -0
- package/dist/src/cli/strings.d.ts +3 -0
- package/dist/src/cli/strings.d.ts.map +1 -0
- package/dist/src/cli/strings.js +16 -0
- package/dist/src/cli/strings.js.map +10 -0
- package/dist/src/cli/types.d.ts +53 -0
- package/dist/src/cli/types.d.ts.map +1 -0
- package/dist/src/cli/types.js +9 -0
- package/dist/src/cli/types.js.map +10 -0
- package/package.json +31 -4
- package/src/ai/tools.ts +211 -0
- package/src/cli/main.ts +73 -163
- package/src/cli/runtime/auth/resolve.ts +20 -0
- package/src/cli/runtime/body.ts +3 -3
- package/src/cli/runtime/compat.ts +89 -0
- package/src/cli/runtime/execute.ts +98 -39
- package/src/cli/runtime/generated.ts +111 -4
- package/src/cli/runtime/profile/secrets.ts +42 -1
- package/src/cli/runtime/profile/store.ts +15 -13
- package/src/cli/runtime/request.ts +22 -11
- package/src/cli/spec-loader.ts +2 -2
|
@@ -0,0 +1,936 @@
|
|
|
1
|
+
// src/cli/strings.ts
|
|
2
|
+
function kebabCase(input) {
|
|
3
|
+
const trimmed = input.trim();
|
|
4
|
+
if (!trimmed)
|
|
5
|
+
return "";
|
|
6
|
+
return trimmed.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[\s_.:/]+/g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// src/cli/auth-schemes.ts
|
|
10
|
+
function parseOAuthFlow(flow) {
|
|
11
|
+
if (!flow)
|
|
12
|
+
return;
|
|
13
|
+
const scopesObj = flow.scopes;
|
|
14
|
+
const scopes = scopesObj && typeof scopesObj === "object" && !Array.isArray(scopesObj) ? Object.keys(scopesObj) : [];
|
|
15
|
+
return {
|
|
16
|
+
authorizationUrl: typeof flow.authorizationUrl === "string" ? flow.authorizationUrl : undefined,
|
|
17
|
+
tokenUrl: typeof flow.tokenUrl === "string" ? flow.tokenUrl : undefined,
|
|
18
|
+
refreshUrl: typeof flow.refreshUrl === "string" ? flow.refreshUrl : undefined,
|
|
19
|
+
scopes: scopes.sort()
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function parseOAuthFlows(flows) {
|
|
23
|
+
if (!flows)
|
|
24
|
+
return;
|
|
25
|
+
const out = {};
|
|
26
|
+
const implicit = parseOAuthFlow(flows.implicit);
|
|
27
|
+
if (implicit)
|
|
28
|
+
out.implicit = implicit;
|
|
29
|
+
const password = parseOAuthFlow(flows.password);
|
|
30
|
+
if (password)
|
|
31
|
+
out.password = password;
|
|
32
|
+
const clientCredentials = parseOAuthFlow(flows.clientCredentials);
|
|
33
|
+
if (clientCredentials)
|
|
34
|
+
out.clientCredentials = clientCredentials;
|
|
35
|
+
const authorizationCode = parseOAuthFlow(flows.authorizationCode);
|
|
36
|
+
if (authorizationCode)
|
|
37
|
+
out.authorizationCode = authorizationCode;
|
|
38
|
+
return Object.keys(out).length ? out : undefined;
|
|
39
|
+
}
|
|
40
|
+
function listAuthSchemes(doc) {
|
|
41
|
+
const schemes = doc.components?.securitySchemes;
|
|
42
|
+
if (!schemes || typeof schemes !== "object")
|
|
43
|
+
return [];
|
|
44
|
+
const out = [];
|
|
45
|
+
for (const [key, raw] of Object.entries(schemes)) {
|
|
46
|
+
if (!raw || typeof raw !== "object")
|
|
47
|
+
continue;
|
|
48
|
+
const s = raw;
|
|
49
|
+
const type = s.type;
|
|
50
|
+
if (type === "http") {
|
|
51
|
+
const scheme = (s.scheme ?? "").toLowerCase();
|
|
52
|
+
if (scheme === "bearer") {
|
|
53
|
+
out.push({
|
|
54
|
+
key,
|
|
55
|
+
kind: "http-bearer",
|
|
56
|
+
scheme,
|
|
57
|
+
bearerFormat: s.bearerFormat,
|
|
58
|
+
description: s.description
|
|
59
|
+
});
|
|
60
|
+
} else if (scheme === "basic") {
|
|
61
|
+
out.push({
|
|
62
|
+
key,
|
|
63
|
+
kind: "http-basic",
|
|
64
|
+
scheme,
|
|
65
|
+
description: s.description
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
out.push({
|
|
69
|
+
key,
|
|
70
|
+
kind: "unknown",
|
|
71
|
+
scheme: s.scheme,
|
|
72
|
+
description: s.description
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (type === "apiKey") {
|
|
78
|
+
const where = s.in;
|
|
79
|
+
const loc = where === "header" || where === "query" || where === "cookie" ? where : undefined;
|
|
80
|
+
out.push({
|
|
81
|
+
key,
|
|
82
|
+
kind: "api-key",
|
|
83
|
+
name: s.name,
|
|
84
|
+
in: loc,
|
|
85
|
+
description: s.description
|
|
86
|
+
});
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (type === "oauth2") {
|
|
90
|
+
out.push({
|
|
91
|
+
key,
|
|
92
|
+
kind: "oauth2",
|
|
93
|
+
description: s.description,
|
|
94
|
+
oauthFlows: parseOAuthFlows(s.flows)
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (type === "openIdConnect") {
|
|
99
|
+
out.push({
|
|
100
|
+
key,
|
|
101
|
+
kind: "openIdConnect",
|
|
102
|
+
description: s.description,
|
|
103
|
+
openIdConnectUrl: s.openIdConnectUrl
|
|
104
|
+
});
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
out.push({ key, kind: "unknown", description: s.description });
|
|
108
|
+
}
|
|
109
|
+
out.sort((a, b) => kebabCase(a.key).localeCompare(kebabCase(b.key)));
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/cli/capabilities.ts
|
|
114
|
+
function uniqueSorted(items, compare) {
|
|
115
|
+
const out = [...items];
|
|
116
|
+
out.sort(compare);
|
|
117
|
+
return out.filter((v, i) => i === 0 || compare(out[i - 1], v) !== 0);
|
|
118
|
+
}
|
|
119
|
+
function hasSecurity(requirements) {
|
|
120
|
+
if (!requirements?.length)
|
|
121
|
+
return false;
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
function deriveCapabilities(input) {
|
|
125
|
+
const serverHasVars = input.servers.some((s) => s.variableNames.length > 0);
|
|
126
|
+
const authKinds = uniqueSorted(input.authSchemes.map((s) => s.kind), (a, b) => a.localeCompare(b));
|
|
127
|
+
const hasSecurityRequirements = hasSecurity(input.doc.security) || input.operations.some((op) => hasSecurity(op.security));
|
|
128
|
+
const opHasBodies = input.operations.some((op) => Boolean(op.requestBody));
|
|
129
|
+
const cmdResources = input.commands?.resources ?? [];
|
|
130
|
+
const cmdActions = cmdResources.flatMap((r) => r.actions);
|
|
131
|
+
const cmdHasBodies = cmdActions.some((a) => Boolean(a.requestBody));
|
|
132
|
+
return {
|
|
133
|
+
servers: {
|
|
134
|
+
count: input.servers.length,
|
|
135
|
+
hasVariables: serverHasVars
|
|
136
|
+
},
|
|
137
|
+
auth: {
|
|
138
|
+
count: input.authSchemes.length,
|
|
139
|
+
kinds: authKinds,
|
|
140
|
+
hasSecurityRequirements
|
|
141
|
+
},
|
|
142
|
+
operations: {
|
|
143
|
+
count: input.operations.length,
|
|
144
|
+
hasRequestBodies: opHasBodies
|
|
145
|
+
},
|
|
146
|
+
commands: {
|
|
147
|
+
countResources: cmdResources.length,
|
|
148
|
+
countActions: cmdActions.length,
|
|
149
|
+
hasRequestBodies: cmdHasBodies
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/cli/command-index.ts
|
|
155
|
+
function buildCommandsIndex(commands) {
|
|
156
|
+
const byId = {};
|
|
157
|
+
for (const resource of commands?.resources ?? []) {
|
|
158
|
+
for (const action of resource.actions) {
|
|
159
|
+
byId[action.id] = action;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { byId };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/cli/auth-requirements.ts
|
|
166
|
+
function isSecurityRequirement(value) {
|
|
167
|
+
if (!value || typeof value !== "object")
|
|
168
|
+
return false;
|
|
169
|
+
if (Array.isArray(value))
|
|
170
|
+
return false;
|
|
171
|
+
for (const [k, v] of Object.entries(value)) {
|
|
172
|
+
if (typeof k !== "string")
|
|
173
|
+
return false;
|
|
174
|
+
if (!Array.isArray(v))
|
|
175
|
+
return false;
|
|
176
|
+
if (!v.every((s) => typeof s === "string"))
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
function normalizeSecurity(value) {
|
|
182
|
+
if (value == null)
|
|
183
|
+
return { requirements: [], source: "none" };
|
|
184
|
+
if (!Array.isArray(value))
|
|
185
|
+
return { requirements: [], source: "none" };
|
|
186
|
+
const reqs = value.filter(isSecurityRequirement);
|
|
187
|
+
if (reqs.length === 0)
|
|
188
|
+
return { requirements: [], source: "empty" };
|
|
189
|
+
return { requirements: reqs, source: "non-empty" };
|
|
190
|
+
}
|
|
191
|
+
function summarizeAuth(operationSecurity, globalSecurity, knownSchemes) {
|
|
192
|
+
const op = normalizeSecurity(operationSecurity);
|
|
193
|
+
if (op.source === "non-empty") {
|
|
194
|
+
return { alternatives: toAlternatives(op.requirements, knownSchemes) };
|
|
195
|
+
}
|
|
196
|
+
if (op.source === "empty") {
|
|
197
|
+
return { alternatives: [] };
|
|
198
|
+
}
|
|
199
|
+
const global = normalizeSecurity(globalSecurity);
|
|
200
|
+
if (global.source === "non-empty") {
|
|
201
|
+
return { alternatives: toAlternatives(global.requirements, knownSchemes) };
|
|
202
|
+
}
|
|
203
|
+
return { alternatives: [] };
|
|
204
|
+
}
|
|
205
|
+
function toAlternatives(requirements, knownSchemes) {
|
|
206
|
+
const known = new Set(knownSchemes.map((s) => s.key));
|
|
207
|
+
return requirements.map((req) => {
|
|
208
|
+
const out = [];
|
|
209
|
+
for (const [key, scopes] of Object.entries(req)) {
|
|
210
|
+
out.push({
|
|
211
|
+
key,
|
|
212
|
+
scopes: Array.isArray(scopes) ? scopes : []
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
out.sort((a, b) => a.key.localeCompare(b.key));
|
|
216
|
+
out.sort((a, b) => {
|
|
217
|
+
const ak = known.has(a.key) ? 0 : 1;
|
|
218
|
+
const bk = known.has(b.key) ? 0 : 1;
|
|
219
|
+
if (ak !== bk)
|
|
220
|
+
return ak - bk;
|
|
221
|
+
return a.key.localeCompare(b.key);
|
|
222
|
+
});
|
|
223
|
+
return out;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/cli/command-id.ts
|
|
228
|
+
function buildCommandId(parts) {
|
|
229
|
+
const op = kebabCase(parts.operationKey.replace(/\s+/g, "-"));
|
|
230
|
+
return `${parts.specId}:${kebabCase(parts.resource)}:${kebabCase(parts.action)}:${op}`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/cli/schema-shape.ts
|
|
234
|
+
function getSchemaType(schema) {
|
|
235
|
+
if (!schema || typeof schema !== "object")
|
|
236
|
+
return "unknown";
|
|
237
|
+
const t = schema.type;
|
|
238
|
+
if (t === "string")
|
|
239
|
+
return "string";
|
|
240
|
+
if (t === "number")
|
|
241
|
+
return "number";
|
|
242
|
+
if (t === "integer")
|
|
243
|
+
return "integer";
|
|
244
|
+
if (t === "boolean")
|
|
245
|
+
return "boolean";
|
|
246
|
+
if (t === "array")
|
|
247
|
+
return "array";
|
|
248
|
+
if (t === "object")
|
|
249
|
+
return "object";
|
|
250
|
+
return "unknown";
|
|
251
|
+
}
|
|
252
|
+
function getSchemaFormat(schema) {
|
|
253
|
+
if (!schema || typeof schema !== "object")
|
|
254
|
+
return;
|
|
255
|
+
const f = schema.format;
|
|
256
|
+
return typeof f === "string" ? f : undefined;
|
|
257
|
+
}
|
|
258
|
+
function getSchemaEnumStrings(schema) {
|
|
259
|
+
if (!schema || typeof schema !== "object")
|
|
260
|
+
return;
|
|
261
|
+
const e = schema.enum;
|
|
262
|
+
if (!Array.isArray(e))
|
|
263
|
+
return;
|
|
264
|
+
const values = e.filter((v) => typeof v === "string");
|
|
265
|
+
return values.length ? values : undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// src/cli/params.ts
|
|
269
|
+
function deriveParamSpecs(op) {
|
|
270
|
+
const out = [];
|
|
271
|
+
for (const p of op.parameters) {
|
|
272
|
+
const flag = `--${kebabCase(p.name)}`;
|
|
273
|
+
const type = getSchemaType(p.schema);
|
|
274
|
+
const schemaObj = p.schema && typeof p.schema === "object" ? p.schema : undefined;
|
|
275
|
+
const itemsSchema = schemaObj && type === "array" && typeof schemaObj.items === "object" ? schemaObj.items : undefined;
|
|
276
|
+
out.push({
|
|
277
|
+
kind: p.in === "path" ? "positional" : "flag",
|
|
278
|
+
in: p.in,
|
|
279
|
+
name: p.name,
|
|
280
|
+
flag,
|
|
281
|
+
required: p.required,
|
|
282
|
+
description: p.description,
|
|
283
|
+
type,
|
|
284
|
+
format: getSchemaFormat(p.schema),
|
|
285
|
+
enum: getSchemaEnumStrings(p.schema),
|
|
286
|
+
itemType: type === "array" ? getSchemaType(itemsSchema) : undefined,
|
|
287
|
+
itemFormat: type === "array" ? getSchemaFormat(itemsSchema) : undefined,
|
|
288
|
+
itemEnum: type === "array" ? getSchemaEnumStrings(itemsSchema) : undefined,
|
|
289
|
+
schema: schemaObj
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
out.sort((a, b) => {
|
|
293
|
+
if (a.in !== b.in)
|
|
294
|
+
return a.in.localeCompare(b.in);
|
|
295
|
+
return a.name.localeCompare(b.name);
|
|
296
|
+
});
|
|
297
|
+
return out;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// src/cli/positional.ts
|
|
301
|
+
function derivePositionals(action) {
|
|
302
|
+
const byName = new Map;
|
|
303
|
+
for (const name of action.pathArgs) {
|
|
304
|
+
const p = action.params.find((x) => x.in === "path" && x.name === name);
|
|
305
|
+
byName.set(name, {
|
|
306
|
+
name,
|
|
307
|
+
required: true,
|
|
308
|
+
description: p?.description,
|
|
309
|
+
type: p?.type ?? "unknown",
|
|
310
|
+
format: p?.format,
|
|
311
|
+
enum: p?.enum
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return [...byName.values()];
|
|
315
|
+
}
|
|
316
|
+
function deriveFlags(action) {
|
|
317
|
+
return {
|
|
318
|
+
flags: action.params.filter((p) => p.kind === "flag").map((p) => ({
|
|
319
|
+
in: p.in,
|
|
320
|
+
name: p.name,
|
|
321
|
+
flag: p.flag,
|
|
322
|
+
required: p.required,
|
|
323
|
+
description: p.description,
|
|
324
|
+
type: p.type,
|
|
325
|
+
format: p.format,
|
|
326
|
+
enum: p.enum,
|
|
327
|
+
itemType: p.itemType,
|
|
328
|
+
itemFormat: p.itemFormat,
|
|
329
|
+
itemEnum: p.itemEnum
|
|
330
|
+
}))
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/cli/types.ts
|
|
335
|
+
function isJsonSchema(value) {
|
|
336
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/cli/request-body.ts
|
|
340
|
+
function getRequestBody(op) {
|
|
341
|
+
return op.requestBody;
|
|
342
|
+
}
|
|
343
|
+
function deriveRequestBodyInfo(op) {
|
|
344
|
+
const rb = getRequestBody(op);
|
|
345
|
+
if (!rb)
|
|
346
|
+
return;
|
|
347
|
+
const content = [];
|
|
348
|
+
for (const contentType of rb.contentTypes) {
|
|
349
|
+
const schema = rb.schemasByContentType[contentType];
|
|
350
|
+
content.push({
|
|
351
|
+
contentType,
|
|
352
|
+
required: rb.required,
|
|
353
|
+
schemaType: getSchemaType(schema),
|
|
354
|
+
schemaFormat: getSchemaFormat(schema),
|
|
355
|
+
schemaEnum: getSchemaEnumStrings(schema)
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
content.sort((a, b) => a.contentType.localeCompare(b.contentType));
|
|
359
|
+
const hasJson = content.some((c) => c.contentType.includes("json"));
|
|
360
|
+
const hasFormUrlEncoded = content.some((c) => c.contentType === "application/x-www-form-urlencoded");
|
|
361
|
+
const hasMultipart = content.some((c) => c.contentType.startsWith("multipart/"));
|
|
362
|
+
const bodyFlags = ["--data", "--file"];
|
|
363
|
+
const preferredContentType = content.find((c) => c.contentType === "application/json")?.contentType ?? content.find((c) => c.contentType.includes("json"))?.contentType ?? content[0]?.contentType;
|
|
364
|
+
const preferredSchema = preferredContentType ? rb.schemasByContentType[preferredContentType] : undefined;
|
|
365
|
+
return {
|
|
366
|
+
required: rb.required,
|
|
367
|
+
content,
|
|
368
|
+
hasJson,
|
|
369
|
+
hasFormUrlEncoded,
|
|
370
|
+
hasMultipart,
|
|
371
|
+
bodyFlags,
|
|
372
|
+
preferredContentType,
|
|
373
|
+
preferredSchema: isJsonSchema(preferredSchema) ? preferredSchema : undefined
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// src/cli/command-model.ts
|
|
378
|
+
function buildCommandModel(planned, options) {
|
|
379
|
+
const byResource = new Map;
|
|
380
|
+
for (const op of planned) {
|
|
381
|
+
const list = byResource.get(op.resource) ?? [];
|
|
382
|
+
const params = deriveParamSpecs(op);
|
|
383
|
+
const positionals = derivePositionals({ pathArgs: op.pathArgs, params });
|
|
384
|
+
const flags = deriveFlags({ pathArgs: op.pathArgs, params });
|
|
385
|
+
list.push({
|
|
386
|
+
id: buildCommandId({
|
|
387
|
+
specId: options.specId,
|
|
388
|
+
resource: op.resource,
|
|
389
|
+
action: op.action,
|
|
390
|
+
operationKey: op.key
|
|
391
|
+
}),
|
|
392
|
+
key: op.key,
|
|
393
|
+
action: op.action,
|
|
394
|
+
pathArgs: op.pathArgs,
|
|
395
|
+
method: op.method,
|
|
396
|
+
path: op.path,
|
|
397
|
+
operationId: op.operationId,
|
|
398
|
+
tags: op.tags,
|
|
399
|
+
summary: op.summary,
|
|
400
|
+
description: op.description,
|
|
401
|
+
deprecated: op.deprecated,
|
|
402
|
+
style: op.style,
|
|
403
|
+
params,
|
|
404
|
+
positionals,
|
|
405
|
+
flags: flags.flags,
|
|
406
|
+
auth: summarizeAuth(op.security, options.globalSecurity, options.authSchemes ?? []),
|
|
407
|
+
requestBody: deriveRequestBodyInfo(op),
|
|
408
|
+
requestBodySchema: deriveRequestBodyInfo(op)?.preferredSchema
|
|
409
|
+
});
|
|
410
|
+
byResource.set(op.resource, list);
|
|
411
|
+
}
|
|
412
|
+
const resources = [];
|
|
413
|
+
for (const [resource, actions] of byResource.entries()) {
|
|
414
|
+
actions.sort((a, b) => {
|
|
415
|
+
if (a.action !== b.action)
|
|
416
|
+
return a.action.localeCompare(b.action);
|
|
417
|
+
if (a.path !== b.path)
|
|
418
|
+
return a.path.localeCompare(b.path);
|
|
419
|
+
return a.method.localeCompare(b.method);
|
|
420
|
+
});
|
|
421
|
+
resources.push({ resource, actions });
|
|
422
|
+
}
|
|
423
|
+
resources.sort((a, b) => a.resource.localeCompare(b.resource));
|
|
424
|
+
return { resources };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// src/cli/pluralize.ts
|
|
428
|
+
var IRREGULAR = {
|
|
429
|
+
person: "people",
|
|
430
|
+
man: "men",
|
|
431
|
+
woman: "women",
|
|
432
|
+
child: "children",
|
|
433
|
+
tooth: "teeth",
|
|
434
|
+
foot: "feet",
|
|
435
|
+
mouse: "mice",
|
|
436
|
+
goose: "geese"
|
|
437
|
+
};
|
|
438
|
+
var UNCOUNTABLE = new Set([
|
|
439
|
+
"metadata",
|
|
440
|
+
"information",
|
|
441
|
+
"equipment",
|
|
442
|
+
"money",
|
|
443
|
+
"series",
|
|
444
|
+
"species"
|
|
445
|
+
]);
|
|
446
|
+
function pluralize(word) {
|
|
447
|
+
const w = word.trim();
|
|
448
|
+
if (!w)
|
|
449
|
+
return w;
|
|
450
|
+
const lower = w.toLowerCase();
|
|
451
|
+
if (UNCOUNTABLE.has(lower))
|
|
452
|
+
return lower;
|
|
453
|
+
if (IRREGULAR[lower])
|
|
454
|
+
return IRREGULAR[lower];
|
|
455
|
+
if (lower.endsWith("s"))
|
|
456
|
+
return lower;
|
|
457
|
+
if (/[bcdfghjklmnpqrstvwxyz]y$/.test(lower)) {
|
|
458
|
+
return lower.replace(/y$/, "ies");
|
|
459
|
+
}
|
|
460
|
+
if (/(ch|sh|x|z)$/.test(lower)) {
|
|
461
|
+
return `${lower}es`;
|
|
462
|
+
}
|
|
463
|
+
return `${lower}s`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/cli/naming.ts
|
|
467
|
+
var GENERIC_TAGS = new Set(["default", "defaults", "api"]);
|
|
468
|
+
function getPathSegments(path) {
|
|
469
|
+
return path.split("/").map((s) => s.trim()).filter(Boolean);
|
|
470
|
+
}
|
|
471
|
+
function getPathArgs(path) {
|
|
472
|
+
const args = [];
|
|
473
|
+
const re = /\{([^}]+)\}/g;
|
|
474
|
+
while (true) {
|
|
475
|
+
const match = re.exec(path);
|
|
476
|
+
if (!match)
|
|
477
|
+
break;
|
|
478
|
+
args.push(match[1]);
|
|
479
|
+
}
|
|
480
|
+
return args;
|
|
481
|
+
}
|
|
482
|
+
function pickResourceFromTags(tags) {
|
|
483
|
+
if (!tags.length)
|
|
484
|
+
return;
|
|
485
|
+
const first = tags[0]?.trim();
|
|
486
|
+
if (!first)
|
|
487
|
+
return;
|
|
488
|
+
if (GENERIC_TAGS.has(first.toLowerCase()))
|
|
489
|
+
return;
|
|
490
|
+
return first;
|
|
491
|
+
}
|
|
492
|
+
function splitOperationId(operationId) {
|
|
493
|
+
const trimmed = operationId.trim();
|
|
494
|
+
if (!trimmed)
|
|
495
|
+
return {};
|
|
496
|
+
if (trimmed.includes(".")) {
|
|
497
|
+
const [prefix, ...rest] = trimmed.split(".");
|
|
498
|
+
return { prefix, suffix: rest.join(".") };
|
|
499
|
+
}
|
|
500
|
+
if (trimmed.includes("__")) {
|
|
501
|
+
const [prefix, ...rest] = trimmed.split("__");
|
|
502
|
+
return { prefix, suffix: rest.join("__") };
|
|
503
|
+
}
|
|
504
|
+
if (trimmed.includes("_")) {
|
|
505
|
+
const [prefix, ...rest] = trimmed.split("_");
|
|
506
|
+
return { prefix, suffix: rest.join("_") };
|
|
507
|
+
}
|
|
508
|
+
return { suffix: trimmed };
|
|
509
|
+
}
|
|
510
|
+
function inferStyle(op) {
|
|
511
|
+
if (op.path.includes("."))
|
|
512
|
+
return "rpc";
|
|
513
|
+
if (op.operationId?.includes(".") && op.method === "POST")
|
|
514
|
+
return "rpc";
|
|
515
|
+
return "rest";
|
|
516
|
+
}
|
|
517
|
+
function inferResource(op) {
|
|
518
|
+
const tag = pickResourceFromTags(op.tags);
|
|
519
|
+
if (tag)
|
|
520
|
+
return pluralize(kebabCase(tag));
|
|
521
|
+
if (op.operationId) {
|
|
522
|
+
const { prefix } = splitOperationId(op.operationId);
|
|
523
|
+
if (prefix) {
|
|
524
|
+
const fromId = kebabCase(prefix);
|
|
525
|
+
if (fromId === "ping")
|
|
526
|
+
return "ping";
|
|
527
|
+
return pluralize(fromId);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const segments = getPathSegments(op.path);
|
|
531
|
+
let first = segments[0] ?? "api";
|
|
532
|
+
first = first.includes(".") ? first.split(".")[0] : first;
|
|
533
|
+
if (first.toLowerCase() === "ping")
|
|
534
|
+
return "ping";
|
|
535
|
+
const cleaned = first.replace(/^\{.+\}$/, "");
|
|
536
|
+
return pluralize(kebabCase(cleaned || "api"));
|
|
537
|
+
}
|
|
538
|
+
function canonicalizeAction(action) {
|
|
539
|
+
const a = kebabCase(action);
|
|
540
|
+
if (a === "retrieve" || a === "read")
|
|
541
|
+
return "get";
|
|
542
|
+
if (a === "list" || a === "search")
|
|
543
|
+
return "list";
|
|
544
|
+
if (a === "create")
|
|
545
|
+
return "create";
|
|
546
|
+
if (a === "update" || a === "patch")
|
|
547
|
+
return "update";
|
|
548
|
+
if (a === "delete" || a === "remove")
|
|
549
|
+
return "delete";
|
|
550
|
+
return a;
|
|
551
|
+
}
|
|
552
|
+
function inferRestAction(op) {
|
|
553
|
+
if (op.operationId) {
|
|
554
|
+
const { suffix } = splitOperationId(op.operationId);
|
|
555
|
+
if (suffix) {
|
|
556
|
+
const fromId = canonicalizeAction(suffix);
|
|
557
|
+
if (fromId === "get" || fromId === "list" || fromId === "create" || fromId === "update" || fromId === "delete") {
|
|
558
|
+
return fromId;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
const method = op.method.toUpperCase();
|
|
563
|
+
const args = getPathArgs(op.path);
|
|
564
|
+
const hasId = args.length > 0;
|
|
565
|
+
if (method === "GET" && !hasId)
|
|
566
|
+
return "list";
|
|
567
|
+
if (method === "POST" && !hasId)
|
|
568
|
+
return "create";
|
|
569
|
+
if (method === "GET" && hasId)
|
|
570
|
+
return "get";
|
|
571
|
+
if ((method === "PUT" || method === "PATCH") && hasId)
|
|
572
|
+
return "update";
|
|
573
|
+
if (method === "DELETE" && hasId)
|
|
574
|
+
return "delete";
|
|
575
|
+
return kebabCase(method);
|
|
576
|
+
}
|
|
577
|
+
function inferRpcAction(op) {
|
|
578
|
+
if (op.operationId) {
|
|
579
|
+
const { suffix } = splitOperationId(op.operationId);
|
|
580
|
+
if (suffix)
|
|
581
|
+
return canonicalizeAction(suffix);
|
|
582
|
+
}
|
|
583
|
+
const segments = getPathSegments(op.path);
|
|
584
|
+
const last = segments[segments.length - 1] ?? "";
|
|
585
|
+
if (last.includes(".")) {
|
|
586
|
+
const part = last.split(".").pop() ?? last;
|
|
587
|
+
return canonicalizeAction(part);
|
|
588
|
+
}
|
|
589
|
+
return kebabCase(op.method);
|
|
590
|
+
}
|
|
591
|
+
function planOperation(op) {
|
|
592
|
+
const style = inferStyle(op);
|
|
593
|
+
const resource = inferResource(op);
|
|
594
|
+
const action = style === "rpc" ? inferRpcAction(op) : inferRestAction(op);
|
|
595
|
+
return {
|
|
596
|
+
...op,
|
|
597
|
+
key: op.key,
|
|
598
|
+
style,
|
|
599
|
+
resource,
|
|
600
|
+
action,
|
|
601
|
+
canonicalAction: action,
|
|
602
|
+
pathArgs: getPathArgs(op.path).map((a) => kebabCase(a))
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function planOperations(ops) {
|
|
606
|
+
const planned = ops.map(planOperation);
|
|
607
|
+
const counts = new Map;
|
|
608
|
+
for (const op of planned) {
|
|
609
|
+
const key = `${op.resource}:${op.action}`;
|
|
610
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
611
|
+
}
|
|
612
|
+
const seen = new Map;
|
|
613
|
+
return planned.map((op) => {
|
|
614
|
+
const key = `${op.resource}:${op.action}`;
|
|
615
|
+
const total = counts.get(key) ?? 0;
|
|
616
|
+
if (total <= 1)
|
|
617
|
+
return op;
|
|
618
|
+
const idx = (seen.get(key) ?? 0) + 1;
|
|
619
|
+
seen.set(key, idx);
|
|
620
|
+
const suffix = op.operationId ? kebabCase(op.operationId) : kebabCase(`${op.method}-${op.path}`);
|
|
621
|
+
const disambiguatedAction = `${op.action}-${suffix}-${idx}`;
|
|
622
|
+
return {
|
|
623
|
+
...op,
|
|
624
|
+
action: disambiguatedAction,
|
|
625
|
+
aliasOf: `${op.resource} ${op.canonicalAction}`
|
|
626
|
+
};
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// src/cli/operations.ts
|
|
631
|
+
function operationKey(method, path) {
|
|
632
|
+
return `${method.toUpperCase()} ${path}`;
|
|
633
|
+
}
|
|
634
|
+
var HTTP_METHODS = [
|
|
635
|
+
"get",
|
|
636
|
+
"post",
|
|
637
|
+
"put",
|
|
638
|
+
"patch",
|
|
639
|
+
"delete",
|
|
640
|
+
"options",
|
|
641
|
+
"head",
|
|
642
|
+
"trace"
|
|
643
|
+
];
|
|
644
|
+
function normalizeParam(p) {
|
|
645
|
+
if (!p || typeof p !== "object")
|
|
646
|
+
return;
|
|
647
|
+
const loc = p.in;
|
|
648
|
+
const name = p.name;
|
|
649
|
+
if (loc !== "path" && loc !== "query" && loc !== "header" && loc !== "cookie") {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
if (!name)
|
|
653
|
+
return;
|
|
654
|
+
return {
|
|
655
|
+
in: loc,
|
|
656
|
+
name,
|
|
657
|
+
required: Boolean(p.required || loc === "path"),
|
|
658
|
+
description: p.description,
|
|
659
|
+
schema: p.schema
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
function mergeParameters(pathParams, opParams) {
|
|
663
|
+
const merged = new Map;
|
|
664
|
+
for (const p of pathParams ?? []) {
|
|
665
|
+
const normalized = normalizeParam(p);
|
|
666
|
+
if (!normalized)
|
|
667
|
+
continue;
|
|
668
|
+
merged.set(`${normalized.in}:${normalized.name}`, normalized);
|
|
669
|
+
}
|
|
670
|
+
for (const p of opParams ?? []) {
|
|
671
|
+
const normalized = normalizeParam(p);
|
|
672
|
+
if (!normalized)
|
|
673
|
+
continue;
|
|
674
|
+
merged.set(`${normalized.in}:${normalized.name}`, normalized);
|
|
675
|
+
}
|
|
676
|
+
return [...merged.values()];
|
|
677
|
+
}
|
|
678
|
+
function normalizeRequestBody(rb) {
|
|
679
|
+
if (!rb)
|
|
680
|
+
return;
|
|
681
|
+
const content = rb.content ?? {};
|
|
682
|
+
const contentTypes = Object.keys(content);
|
|
683
|
+
const schemasByContentType = {};
|
|
684
|
+
for (const contentType of contentTypes) {
|
|
685
|
+
schemasByContentType[contentType] = content[contentType]?.schema;
|
|
686
|
+
}
|
|
687
|
+
return {
|
|
688
|
+
required: Boolean(rb.required),
|
|
689
|
+
contentTypes,
|
|
690
|
+
schemasByContentType
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
function indexOperations(doc) {
|
|
694
|
+
const out = [];
|
|
695
|
+
const paths = doc.paths ?? {};
|
|
696
|
+
for (const [path, rawPathItem] of Object.entries(paths)) {
|
|
697
|
+
if (!rawPathItem || typeof rawPathItem !== "object")
|
|
698
|
+
continue;
|
|
699
|
+
const pathItem = rawPathItem;
|
|
700
|
+
for (const method of HTTP_METHODS) {
|
|
701
|
+
const op = pathItem[method];
|
|
702
|
+
if (!op)
|
|
703
|
+
continue;
|
|
704
|
+
const parameters = mergeParameters(pathItem.parameters, op.parameters);
|
|
705
|
+
const normalizedMethod = method.toUpperCase();
|
|
706
|
+
out.push({
|
|
707
|
+
key: operationKey(normalizedMethod, path),
|
|
708
|
+
method: normalizedMethod,
|
|
709
|
+
path,
|
|
710
|
+
operationId: op.operationId,
|
|
711
|
+
tags: op.tags ?? [],
|
|
712
|
+
summary: op.summary,
|
|
713
|
+
description: op.description,
|
|
714
|
+
deprecated: op.deprecated,
|
|
715
|
+
security: op.security ?? doc.security,
|
|
716
|
+
parameters,
|
|
717
|
+
requestBody: normalizeRequestBody(op.requestBody)
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
out.sort((a, b) => {
|
|
722
|
+
if (a.path !== b.path)
|
|
723
|
+
return a.path.localeCompare(b.path);
|
|
724
|
+
return a.method.localeCompare(b.method);
|
|
725
|
+
});
|
|
726
|
+
return out;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// src/cli/schema.ts
|
|
730
|
+
function buildSchemaOutput(loaded, operations, planned, servers, authSchemes, commands, commandsIndex, capabilities) {
|
|
731
|
+
return {
|
|
732
|
+
schemaVersion: 1,
|
|
733
|
+
openapi: {
|
|
734
|
+
version: loaded.doc.openapi,
|
|
735
|
+
title: loaded.doc.info?.title,
|
|
736
|
+
infoVersion: loaded.doc.info?.version
|
|
737
|
+
},
|
|
738
|
+
spec: {
|
|
739
|
+
id: loaded.id,
|
|
740
|
+
fingerprint: loaded.fingerprint,
|
|
741
|
+
source: loaded.source
|
|
742
|
+
},
|
|
743
|
+
capabilities,
|
|
744
|
+
servers,
|
|
745
|
+
authSchemes,
|
|
746
|
+
operations,
|
|
747
|
+
planned,
|
|
748
|
+
commands,
|
|
749
|
+
commandsIndex
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// src/cli/server.ts
|
|
754
|
+
function extractVariableNames(url) {
|
|
755
|
+
const names = [];
|
|
756
|
+
const re = /\{([^}]+)\}/g;
|
|
757
|
+
while (true) {
|
|
758
|
+
const match = re.exec(url);
|
|
759
|
+
if (!match)
|
|
760
|
+
break;
|
|
761
|
+
names.push(match[1] ?? "");
|
|
762
|
+
}
|
|
763
|
+
return names.map((n) => n.trim()).filter(Boolean);
|
|
764
|
+
}
|
|
765
|
+
function listServers(doc) {
|
|
766
|
+
const servers = doc.servers ?? [];
|
|
767
|
+
const out = [];
|
|
768
|
+
for (const raw of servers) {
|
|
769
|
+
const s = raw;
|
|
770
|
+
if (!s || typeof s !== "object")
|
|
771
|
+
continue;
|
|
772
|
+
if (typeof s.url !== "string")
|
|
773
|
+
continue;
|
|
774
|
+
const variableNames = extractVariableNames(s.url);
|
|
775
|
+
const variables = [];
|
|
776
|
+
const rawVars = s.variables && typeof s.variables === "object" && !Array.isArray(s.variables) ? s.variables : {};
|
|
777
|
+
for (const name of variableNames) {
|
|
778
|
+
const v = rawVars[name];
|
|
779
|
+
const def = v?.default;
|
|
780
|
+
const desc = v?.description;
|
|
781
|
+
variables.push({
|
|
782
|
+
name,
|
|
783
|
+
default: typeof def === "string" ? def : undefined,
|
|
784
|
+
enum: getSchemaEnumStrings(v),
|
|
785
|
+
description: typeof desc === "string" ? desc : undefined
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
out.push({
|
|
789
|
+
url: s.url,
|
|
790
|
+
description: typeof s.description === "string" ? s.description : undefined,
|
|
791
|
+
variables,
|
|
792
|
+
variableNames
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
return out;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// src/cli/spec-loader.ts
|
|
799
|
+
import SwaggerParser from "@apidevtools/swagger-parser";
|
|
800
|
+
|
|
801
|
+
// src/cli/crypto.ts
|
|
802
|
+
async function sha256Hex(text) {
|
|
803
|
+
const data = new TextEncoder().encode(text);
|
|
804
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
805
|
+
const bytes = new Uint8Array(hash);
|
|
806
|
+
let out = "";
|
|
807
|
+
for (const b of bytes)
|
|
808
|
+
out += b.toString(16).padStart(2, "0");
|
|
809
|
+
return out;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// src/cli/runtime/compat.ts
|
|
813
|
+
import { parse as parseYaml } from "yaml";
|
|
814
|
+
var isBun = typeof globalThis.Bun !== "undefined";
|
|
815
|
+
function parseYamlContent(text) {
|
|
816
|
+
if (isBun) {
|
|
817
|
+
const { YAML } = globalThis.Bun;
|
|
818
|
+
return YAML.parse(text);
|
|
819
|
+
}
|
|
820
|
+
return parseYaml(text);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/cli/spec-id.ts
|
|
824
|
+
function getSpecId(loaded) {
|
|
825
|
+
const title = loaded.doc.info?.title;
|
|
826
|
+
const fromTitle = title ? kebabCase(title) : "";
|
|
827
|
+
if (fromTitle)
|
|
828
|
+
return fromTitle;
|
|
829
|
+
return loaded.fingerprint.slice(0, 12);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// src/cli/stable-json.ts
|
|
833
|
+
function stableStringify(value, options) {
|
|
834
|
+
const visiting = new WeakSet;
|
|
835
|
+
return JSON.stringify(sort(value, visiting), null, options?.space);
|
|
836
|
+
}
|
|
837
|
+
function sort(value, visiting) {
|
|
838
|
+
if (value === null)
|
|
839
|
+
return null;
|
|
840
|
+
if (Array.isArray(value)) {
|
|
841
|
+
if (visiting.has(value))
|
|
842
|
+
return { __specli_circular: true };
|
|
843
|
+
visiting.add(value);
|
|
844
|
+
const out = value.map((v) => sort(v, visiting));
|
|
845
|
+
visiting.delete(value);
|
|
846
|
+
return out;
|
|
847
|
+
}
|
|
848
|
+
if (typeof value === "object") {
|
|
849
|
+
if (visiting.has(value))
|
|
850
|
+
return { __specli_circular: true };
|
|
851
|
+
visiting.add(value);
|
|
852
|
+
const obj = value;
|
|
853
|
+
const out = {};
|
|
854
|
+
for (const key of Object.keys(obj).sort()) {
|
|
855
|
+
out[key] = sort(obj[key], visiting);
|
|
856
|
+
}
|
|
857
|
+
visiting.delete(value);
|
|
858
|
+
return out;
|
|
859
|
+
}
|
|
860
|
+
return value;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// src/cli/spec-loader.ts
|
|
864
|
+
function isProbablyUrl(input) {
|
|
865
|
+
return /^https?:\/\//i.test(input);
|
|
866
|
+
}
|
|
867
|
+
function parseSpecText(text) {
|
|
868
|
+
const trimmed = text.trimStart();
|
|
869
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
870
|
+
return JSON.parse(text);
|
|
871
|
+
}
|
|
872
|
+
return parseYamlContent(text);
|
|
873
|
+
}
|
|
874
|
+
async function loadSpec(options) {
|
|
875
|
+
const { spec, embeddedSpecText } = options;
|
|
876
|
+
let source;
|
|
877
|
+
let inputForParser;
|
|
878
|
+
if (typeof embeddedSpecText === "string") {
|
|
879
|
+
source = "embedded";
|
|
880
|
+
inputForParser = parseSpecText(embeddedSpecText);
|
|
881
|
+
} else if (spec) {
|
|
882
|
+
source = isProbablyUrl(spec) ? "url" : "file";
|
|
883
|
+
inputForParser = spec;
|
|
884
|
+
} else {
|
|
885
|
+
throw new Error("Missing spec. Provide --spec <url|path> or build with an embedded spec.");
|
|
886
|
+
}
|
|
887
|
+
const doc = await SwaggerParser.dereference(inputForParser);
|
|
888
|
+
if (!doc || typeof doc !== "object" || typeof doc.openapi !== "string") {
|
|
889
|
+
throw new Error("Loaded spec is not a valid OpenAPI document");
|
|
890
|
+
}
|
|
891
|
+
const fingerprint = await sha256Hex(stableStringify(doc));
|
|
892
|
+
const id = getSpecId({ doc, fingerprint });
|
|
893
|
+
return { source, id, fingerprint, doc };
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// src/cli/runtime/context.ts
|
|
897
|
+
async function buildRuntimeContext(options) {
|
|
898
|
+
const loaded = await loadSpec({
|
|
899
|
+
spec: options.spec,
|
|
900
|
+
embeddedSpecText: options.embeddedSpecText
|
|
901
|
+
});
|
|
902
|
+
const operations = indexOperations(loaded.doc);
|
|
903
|
+
const servers = listServers(loaded.doc);
|
|
904
|
+
const authSchemes = listAuthSchemes(loaded.doc);
|
|
905
|
+
const planned = planOperations(operations);
|
|
906
|
+
const commands = buildCommandModel(planned, {
|
|
907
|
+
specId: loaded.id,
|
|
908
|
+
globalSecurity: loaded.doc.security,
|
|
909
|
+
authSchemes
|
|
910
|
+
});
|
|
911
|
+
const commandsIndex = buildCommandsIndex(commands);
|
|
912
|
+
const capabilities = deriveCapabilities({
|
|
913
|
+
doc: loaded.doc,
|
|
914
|
+
servers,
|
|
915
|
+
authSchemes,
|
|
916
|
+
operations,
|
|
917
|
+
commands
|
|
918
|
+
});
|
|
919
|
+
const schema = buildSchemaOutput(loaded, operations, planned, servers, authSchemes, commands, commandsIndex, capabilities);
|
|
920
|
+
return {
|
|
921
|
+
loaded,
|
|
922
|
+
operations,
|
|
923
|
+
servers,
|
|
924
|
+
authSchemes,
|
|
925
|
+
planned,
|
|
926
|
+
commands,
|
|
927
|
+
commandsIndex,
|
|
928
|
+
capabilities,
|
|
929
|
+
schema
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
export {
|
|
933
|
+
buildRuntimeContext
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
//# debugId=29AF5DFA74CC64D164756E2164756E21
|