@redocly/cli 2.47.0 → 2.49.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.
Files changed (133) hide show
  1. package/lib/chunks/{QRD3XQNU.js → 2U54VUOE.js} +1 -1
  2. package/lib/chunks/3JJEI7CJ.js +456 -0
  3. package/lib/chunks/4I2OVMBJ.js +11 -0
  4. package/lib/chunks/{REBHK2JR.js → 4JDAOKRJ.js} +1 -1
  5. package/lib/chunks/5JIGUBC3.js +7 -0
  6. package/lib/chunks/{QVV2EKMT.js → 5SAFIITS.js} +1 -1
  7. package/lib/chunks/6KCIFTIG.js +57 -0
  8. package/lib/chunks/{5NR4DJW4.js → 6VSHPQXO.js} +1 -1
  9. package/lib/chunks/{W2NJCZOS.js → 74WJ7S7J.js} +1 -1
  10. package/lib/chunks/{QL4V2A4A.js → 7R5PBTLG.js} +1 -1
  11. package/lib/chunks/BT3ELZVX.js +42 -0
  12. package/lib/chunks/C5TR4MMN.js +19 -0
  13. package/lib/chunks/CCR5RYCX.js +20 -0
  14. package/lib/chunks/DCN3GSUA.js +7 -0
  15. package/lib/chunks/{TEQA5OM7.js → E5I67ZCZ.js} +1 -1
  16. package/lib/chunks/FUMCRNTK.js +78 -0
  17. package/lib/chunks/GNMJA2V2.js +16 -0
  18. package/lib/chunks/GRJG3TOI.js +14 -0
  19. package/lib/chunks/HA22GUKQ.js +7 -0
  20. package/lib/chunks/HJ36YGCI.js +184 -0
  21. package/lib/chunks/HJYFWIVE.js +9 -0
  22. package/lib/chunks/HL5FOKC4.js +16 -0
  23. package/lib/chunks/{SL6AH3N2.js → JBNPGEIJ.js} +1 -1
  24. package/lib/chunks/KHEMMNFU.js +26 -0
  25. package/lib/chunks/{SN2EC2ZR.js → KK2STV73.js} +1 -1
  26. package/lib/chunks/KK77ANZH.js +7 -0
  27. package/lib/chunks/L3TZNNJJ.js +13 -0
  28. package/lib/chunks/M6KLAAY4.js +7 -0
  29. package/lib/chunks/MHITBEGC.js +90 -0
  30. package/lib/chunks/NB54XRRU.js +10 -0
  31. package/lib/chunks/NSIQWNYL.js +7 -0
  32. package/lib/chunks/{YQW463Z7.js → NTPLYWAB.js} +1 -1
  33. package/lib/chunks/{5PXRDPHV.js → NUNOXB7Z.js} +1 -1
  34. package/lib/chunks/NWMTC4AN.js +7 -0
  35. package/lib/chunks/NYLAOZPO.js +22 -0
  36. package/lib/chunks/O4R6BSAS.js +20 -0
  37. package/lib/chunks/OKITK6EF.js +30 -0
  38. package/lib/chunks/OUCZOBEX.js +22 -0
  39. package/lib/chunks/PFYPTG4G.js +7 -0
  40. package/lib/chunks/QZH7EL26.js +7 -0
  41. package/lib/chunks/R4XFBOJU.js +9 -0
  42. package/lib/chunks/REFX2EKS.js +13 -0
  43. package/lib/chunks/{S4MX7JPH.js → RXOEH2LN.js} +1 -1
  44. package/lib/chunks/SGBB4RNM.js +11 -0
  45. package/lib/chunks/SQVG3CEM.js +8 -0
  46. package/lib/chunks/{RIFZSMUA.js → T5EPIBMA.js} +1 -1
  47. package/lib/chunks/TLQZAJQU.js +18 -0
  48. package/lib/chunks/TSB6PODJ.js +13 -0
  49. package/lib/chunks/TSRK76GA.js +19 -0
  50. package/lib/chunks/{34J2ZJDK.js → U2H2LXZE.js} +1 -1
  51. package/lib/chunks/UKSQ7B5H.js +2268 -0
  52. package/lib/chunks/UMQN5HTL.js +42 -0
  53. package/lib/chunks/V2LJ4UGM.js +7 -0
  54. package/lib/chunks/VBPP2WUF.js +7 -0
  55. package/lib/chunks/WPGKH5NI.js +177 -0
  56. package/lib/chunks/WXVC3KOI.js +67 -0
  57. package/lib/chunks/{KEUH3PST.js → YI34NB7D.js} +1 -1
  58. package/lib/chunks/YOJGIGE3.js +7 -0
  59. package/lib/chunks/{TO2KJ36N.js → YP5GHFYE.js} +1 -1
  60. package/lib/chunks/YXXYE4G2.js +7 -0
  61. package/lib/chunks/ZBL4IVEW.js +11 -0
  62. package/lib/chunks/ZMCRMR2V.js +5400 -0
  63. package/lib/chunks/ZOUQBK53.js +14 -0
  64. package/lib/chunks/ZXKOHMC6.js +177 -0
  65. package/lib/eject-assets/AGENTS.md +137 -0
  66. package/lib/eject-assets/generators/cli/docs.ts +224 -0
  67. package/lib/eject-assets/generators/cli/engine-source.ts +21 -0
  68. package/lib/eject-assets/generators/cli/index.ts +90 -0
  69. package/lib/eject-assets/generators/cli/render.ts +348 -0
  70. package/lib/eject-assets/generators/go/client.ts +57 -0
  71. package/lib/eject-assets/generators/go/descriptor.ts +41 -0
  72. package/lib/eject-assets/generators/go/index.ts +336 -0
  73. package/lib/eject-assets/generators/go/models.ts +179 -0
  74. package/lib/eject-assets/generators/go/naming.ts +49 -0
  75. package/lib/eject-assets/generators/go/operations.ts +296 -0
  76. package/lib/eject-assets/generators/go/pagination.ts +194 -0
  77. package/lib/eject-assets/generators/go/types.ts +60 -0
  78. package/lib/eject-assets/generators/mock/faker.ts +214 -0
  79. package/lib/eject-assets/generators/mock/index.ts +35 -0
  80. package/lib/eject-assets/generators/mock/render.ts +282 -0
  81. package/lib/eject-assets/generators/mock/sample.ts +316 -0
  82. package/lib/eject-assets/generators/mock/values.ts +63 -0
  83. package/lib/eject-assets/generators/php/client.ts +63 -0
  84. package/lib/eject-assets/generators/php/descriptor.ts +58 -0
  85. package/lib/eject-assets/generators/php/index.ts +237 -0
  86. package/lib/eject-assets/generators/php/models.ts +275 -0
  87. package/lib/eject-assets/generators/php/naming.ts +51 -0
  88. package/lib/eject-assets/generators/php/operations.ts +232 -0
  89. package/lib/eject-assets/generators/php/pagination.ts +133 -0
  90. package/lib/eject-assets/generators/php/types.ts +148 -0
  91. package/lib/eject-assets/generators/python/client.ts +123 -0
  92. package/lib/eject-assets/generators/python/descriptor.ts +52 -0
  93. package/lib/eject-assets/generators/python/index.ts +251 -0
  94. package/lib/eject-assets/generators/python/models.ts +242 -0
  95. package/lib/eject-assets/generators/python/naming.ts +46 -0
  96. package/lib/eject-assets/generators/python/operations.ts +147 -0
  97. package/lib/eject-assets/generators/python/pagination.ts +128 -0
  98. package/lib/eject-assets/generators/python/types.ts +54 -0
  99. package/lib/eject-assets/generators/swr/index.ts +37 -0
  100. package/lib/eject-assets/generators/swr/render.ts +78 -0
  101. package/lib/eject-assets/generators/tanstack-query/index.ts +48 -0
  102. package/lib/eject-assets/generators/tanstack-query/render.ts +346 -0
  103. package/lib/eject-assets/generators/transformers/index.ts +46 -0
  104. package/lib/eject-assets/generators/transformers/render.ts +506 -0
  105. package/lib/eject-assets/generators/typescript/banner.ts +35 -0
  106. package/lib/eject-assets/generators/typescript/client.ts +254 -0
  107. package/lib/eject-assets/generators/typescript/descriptor.ts +137 -0
  108. package/lib/eject-assets/generators/typescript/index.ts +101 -0
  109. package/lib/eject-assets/generators/typescript/inline-runtime.ts +135 -0
  110. package/lib/eject-assets/generators/typescript/operation-signature.ts +62 -0
  111. package/lib/eject-assets/generators/typescript/operation-types.ts +17 -0
  112. package/lib/eject-assets/generators/typescript/operations.ts +524 -0
  113. package/lib/eject-assets/generators/typescript/response-headers.ts +74 -0
  114. package/lib/eject-assets/generators/typescript/type-guards.ts +159 -0
  115. package/lib/eject-assets/generators/typescript/types.ts +172 -0
  116. package/lib/eject-assets/generators/zod/index.ts +32 -0
  117. package/lib/eject-assets/generators/zod/schemas.ts +456 -0
  118. package/lib/eject-assets/skills/cli-generator/SKILL.md +121 -0
  119. package/lib/eject-assets/skills/client-generators/SKILL.md +142 -0
  120. package/lib/eject-assets/skills/go-generator/SKILL.md +96 -0
  121. package/lib/eject-assets/skills/mock-generator/SKILL.md +50 -0
  122. package/lib/eject-assets/skills/php-generator/SKILL.md +112 -0
  123. package/lib/eject-assets/skills/python-generator/SKILL.md +110 -0
  124. package/lib/eject-assets/skills/swr-generator/SKILL.md +50 -0
  125. package/lib/eject-assets/skills/tanstack-query-generator/SKILL.md +55 -0
  126. package/lib/eject-assets/skills/transformers-generator/SKILL.md +47 -0
  127. package/lib/eject-assets/skills/typescript-generator/SKILL.md +95 -0
  128. package/lib/eject-assets/skills/zod-generator/SKILL.md +54 -0
  129. package/lib/index.js +271 -193
  130. package/package.json +1 -1
  131. package/lib/chunks/B6HS7CVY.js +0 -184
  132. package/lib/chunks/PIJLRHNN.js +0 -8
  133. package/lib/chunks/Y5HLCNL5.js +0 -2542
@@ -0,0 +1,90 @@
1
+ // Ejected from @redocly/client-generator@0.4.1 — the built-in "cli" generator.
2
+ // This file is yours: edit freely; the generated client stays machine-owned and is
3
+ // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
+ // `redocly eject-generator cli --update`.
5
+ import {
6
+ type CodeSample,
7
+ type Generator,
8
+ groupSlug,
9
+ type OperationModel,
10
+ type SampleContext,
11
+ } from '@redocly/client-generator';
12
+ import { join } from 'node:path';
13
+
14
+ import { renderCliDocs } from './docs.ts';
15
+ import { cliRuntimeSource } from './engine-source.ts';
16
+ import { cliAuthSchemes, commandData, renderCliModule } from './render.ts';
17
+
18
+ /**
19
+ * The cli generator: a bin-ready `<stem>.cli.ts` — a zero-dependency, typed
20
+ * command-line interface over the sibling client (typed flags, `--json`
21
+ * bodies, env auth, `--page-all`, SSE/blob output, a documented exit-code
22
+ * contract). Requires `typescript` (throw mode); wires zod validation when co-selected.
23
+ */
24
+ export const cliGenerator: Generator = ({ model, output, banner, emit, selected, pagination }) => {
25
+ const content = renderCliModule(model, {
26
+ stem: output.stem,
27
+ importExt: emit.importExt ?? 'js',
28
+ zodSelected: selected?.includes('zod') ?? false,
29
+ pagination,
30
+ argsStyle: emit.argsStyle ?? 'grouped',
31
+ runtime: emit.runtime ?? 'inline',
32
+ });
33
+ const entry = { path: join(output.dir, `${output.stem}.cli.ts`), content };
34
+ if (emit.runtime !== 'module') return [entry];
35
+ const header = banner.map((line) => `// ${line}`).join('\n');
36
+ return [
37
+ entry,
38
+ {
39
+ path: join(output.dir, 'runtime', 'cli.ts'),
40
+ content: `${header}\n\n${cliRuntimeSource().trim()}\n`,
41
+ },
42
+ ];
43
+ };
44
+
45
+ /**
46
+ * The CLI's own reference page, written when `client.docs` is on: the usage line, the
47
+ * global flags, the credential variables, the exit codes, and one section per command.
48
+ * It renders from `commandData` — the same table `runCli` dispatches on — so the page
49
+ * cannot describe a tool other than the one beside it.
50
+ */
51
+ export const cliDocs: Generator = ({ model, output, emit, pagination }) => {
52
+ const content = renderCliDocs(commandData(model, { pagination }), {
53
+ title: `${model.title} command-line reference`,
54
+ frontmatter: emit.docsFrontmatter === true,
55
+ name: output.stem,
56
+ schemes: cliAuthSchemes(model),
57
+ });
58
+ return [{ path: join(output.dir, `${output.stem}.cli.md`), content }];
59
+ };
60
+
61
+ /** One shell invocation per operation — feeds `x-codeSamples` for docs. */
62
+ export function cliSample(op: OperationModel, ctx: SampleContext): CodeSample | undefined {
63
+ const command = commandData(ctx.model, { pagination: ctx.pagination }).find(
64
+ (candidate) => candidate.name === op.name
65
+ );
66
+ if (command === undefined) return undefined;
67
+ const words = [
68
+ 'client',
69
+ ...(command.group ? [groupSlug(command.group)] : []),
70
+ command.name,
71
+ ...command.positionals.map((positional) => `<${positional.name}>`),
72
+ ...command.flags.filter((flag) => flag.required).map((flag) => `--${flag.name} <${flag.type}>`),
73
+ ...(command.body ? ["--json '<json>'"] : []),
74
+ ];
75
+ return {
76
+ lang: 'shell',
77
+ label: 'CLI',
78
+ source: `npx tsx client.cli.ts ${words.slice(1).join(' ')}\n`,
79
+ };
80
+ }
81
+
82
+ export default {
83
+ name: 'cli',
84
+ run: cliGenerator,
85
+ sample: cliSample,
86
+ docs: cliDocs,
87
+ requires: ["typescript","zod"],
88
+ errorModes: ["throw"],
89
+ requiresGenerator: '^0.4.1',
90
+ };
@@ -0,0 +1,348 @@
1
+ // Ejected from @redocly/client-generator@0.4.1 — the built-in "cli" generator.
2
+ // This file is yours: edit freely; the generated client stays machine-owned and is
3
+ // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
+ // `redocly eject-generator cli --update`.
5
+ // The cli emitter: derives pure `CliCommand[]` data from the IR and renders
6
+ // `<stem>.cli.ts` — a shebang entry that embeds (inline) or imports (package)
7
+ // the `runCli` engine and dispatches through the sibling generated client.
8
+
9
+ import {
10
+ type ApiModel,
11
+ casing,
12
+ type CliAuthScheme,
13
+ type CliCommand,
14
+ type CliFlag,
15
+ constantCase,
16
+ groupSlug,
17
+ type ModelPagination,
18
+ type OperationModel,
19
+ type ParamModel,
20
+ type SchemaModel,
21
+ } from '@redocly/client-generator';
22
+ import { flatInputShape } from '@redocly/client-generator/contracts/typescript';
23
+ import { logger } from '@redocly/openapi-core';
24
+
25
+ import { embedCliRuntime } from './engine-source.ts';
26
+
27
+ // The generated-by banner every emitted module carries (same lines as the pipeline's
28
+ // `input.banner`, rendered in `//` syntax).
29
+ const HEADER = `// Generated by @redocly/client-generator — do not edit by hand.
30
+ // Source: OpenAPI description. Re-run \`redocly generate-client\` to update.`;
31
+
32
+ function kebab(name: string): string {
33
+ return casing.snake(name).replace(/_/g, '-');
34
+ }
35
+
36
+ function flagFor(param: ParamModel): CliFlag {
37
+ const schema = param.schema;
38
+ const type: CliFlag['type'] =
39
+ schema.kind === 'array'
40
+ ? 'array'
41
+ : schema.kind === 'scalar' && (schema.scalar === 'integer' || schema.scalar === 'number')
42
+ ? 'number'
43
+ : schema.kind === 'scalar' && schema.scalar === 'boolean'
44
+ ? 'boolean'
45
+ : 'string';
46
+ return {
47
+ name: kebab(param.name),
48
+ param: param.name,
49
+ type,
50
+ required: param.required,
51
+ ...(schema.kind === 'enum' ? { enum: schema.values.map(String) } : {}),
52
+ ...(param.description !== undefined ? { description: param.description } : {}),
53
+ };
54
+ }
55
+
56
+ /** Mirrors `computeResponse`: a blob operation has binary success content and no JSON alternative. */
57
+ function isBlobOp(op: OperationModel): boolean {
58
+ const responses = op.successResponses;
59
+ if (responses.some((response) => response.contentType.toLowerCase().includes('json'))) {
60
+ return false;
61
+ }
62
+ return responses.some(
63
+ (response) =>
64
+ response.contentType.startsWith('image/') ||
65
+ response.contentType === 'application/octet-stream'
66
+ );
67
+ }
68
+
69
+ function jsonSuccessSchema(op: OperationModel): SchemaModel | undefined {
70
+ return op.successResponses.find((response) => response.contentType.toLowerCase().includes('json'))
71
+ ?.schema;
72
+ }
73
+
74
+ /**
75
+ * Whether a flat-style call spells this operation's body as its own properties — the same
76
+ * decision the client's types make, so the dispatcher never has to guess from a value.
77
+ */
78
+ function mergedBodyFlag(
79
+ op: OperationModel,
80
+ model: ApiModel,
81
+ argsStyle: 'grouped' | 'flat' | undefined
82
+ ): { merged?: true } {
83
+ if (argsStyle !== 'flat') return {};
84
+ const shape = flatInputShape(op, model.schemas);
85
+ return 'mergeBody' in shape && shape.mergeBody ? { merged: true } : {};
86
+ }
87
+
88
+ /**
89
+ * The operations a flat-style run still addresses by layer: their merged names would
90
+ * collide, so the client's own input type keeps the namespaced shape and the dispatcher
91
+ * has to build that shape too.
92
+ */
93
+ function groupedInputFlag(
94
+ op: OperationModel,
95
+ model: ApiModel,
96
+ argsStyle: 'grouped' | 'flat' | undefined
97
+ ): { argsStyle?: 'grouped' } {
98
+ if (argsStyle !== 'flat') return {};
99
+ return 'collisions' in flatInputShape(op, model.schemas) ? { argsStyle: 'grouped' } : {};
100
+ }
101
+
102
+ /** Every operation as pure command data — the table `runCli` interprets. */
103
+ export function commandData(
104
+ model: ApiModel,
105
+ emit: { pagination?: ModelPagination; argsStyle?: 'grouped' | 'flat' }
106
+ ): CliCommand[] {
107
+ const commands: CliCommand[] = [];
108
+ for (const service of model.services) {
109
+ for (const op of service.operations) {
110
+ const jsonBody = op.requestBody?.contentType.toLowerCase().includes('json')
111
+ ? op.requestBody
112
+ : undefined;
113
+ const responseSchema = jsonSuccessSchema(op);
114
+ commands.push({
115
+ ...(op.tags.length > 0 ? { group: op.tags[0] } : {}),
116
+ name: op.name,
117
+ ...(op.summary !== undefined ? { summary: op.summary } : {}),
118
+ method: op.method.toUpperCase(),
119
+ path: op.path,
120
+ positionals: op.pathParams.map((param) => ({
121
+ name: param.name,
122
+ type: flagFor(param).type,
123
+ ...(param.description !== undefined ? { description: param.description } : {}),
124
+ })),
125
+ flags: op.queryParams.map(flagFor),
126
+ ...(jsonBody
127
+ ? { body: { required: jsonBody.required, ...mergedBodyFlag(op, model, emit.argsStyle) } }
128
+ : {}),
129
+ ...(jsonBody === undefined && op.requestBody !== undefined
130
+ ? { unsupportedBody: op.requestBody.contentType }
131
+ : {}),
132
+ ...(emit.pagination?.has(op.name) === true ? { paginated: true } : {}),
133
+ ...groupedInputFlag(op, model, emit.argsStyle),
134
+ ...(op.sse !== undefined ? { sse: true } : {}),
135
+ ...(isBlobOp(op) ? { blob: true } : {}),
136
+ ...(jsonBody !== undefined || responseSchema !== undefined
137
+ ? {
138
+ schemas: {
139
+ ...(jsonBody ? { request: jsonBody.schema } : {}),
140
+ ...(responseSchema !== undefined ? { response: responseSchema } : {}),
141
+ },
142
+ }
143
+ : {}),
144
+ });
145
+ }
146
+ }
147
+ return commands;
148
+ }
149
+
150
+ /**
151
+ * The self-execution guard both generated entries share. Realpath on both sides: some
152
+ * runners resolve symlinks in `import.meta.url` but not in `argv[1]` (macOS temp dirs,
153
+ * installed bin symlinks); the catch covers an entry that is not a file (REPL, node -e).
154
+ */
155
+ const ENTRY_GUARD = `function isProcessEntry(): boolean {
156
+ if (process.argv[1] === undefined) return false;
157
+ try {
158
+ return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1]);
159
+ } catch {
160
+ return false;
161
+ }
162
+ }
163
+ if (isProcessEntry()) {
164
+ process.exit(await run());
165
+ }`;
166
+
167
+ /** JSON as a TS expression: U+2028/U+2029 are line terminators in code contexts. */
168
+ function codeJson(value: unknown, indent?: number): string {
169
+ return JSON.stringify(value, null, indent)
170
+ .replace(/\u2028/g, '\\u2028')
171
+ .replace(/\u2029/g, '\\u2029');
172
+ }
173
+
174
+ export type CliModuleOptions = {
175
+ stem: string;
176
+ importExt: string;
177
+ zodSelected: boolean;
178
+ pagination?: ModelPagination;
179
+ /** The sibling client's call shape, which the dispatcher builds its inputs for. */
180
+ argsStyle?: 'grouped' | 'flat';
181
+ /** `'module'` imports the engine from `./runtime/cli` instead of embedding it. */
182
+ runtime?: 'inline' | 'module';
183
+ };
184
+
185
+ /**
186
+ * The auth schemes as the CLI sees them: every apiKey placement is one `apiKey` kind,
187
+ * since the credential is read from the same env variable either way. Exported so the
188
+ * docs generator names the same variables the runtime reads.
189
+ */
190
+ export function cliAuthSchemes(model: ApiModel): CliAuthScheme[] {
191
+ return model.securitySchemes.map((scheme) => ({
192
+ key: scheme.key,
193
+ kind: scheme.kind === 'bearer' || scheme.kind === 'basic' ? scheme.kind : 'apiKey',
194
+ }));
195
+ }
196
+
197
+ /** How an operation named after a tag is reached — the two halves of `parseInvocation`. */
198
+ function shadowedAddress(command: CliCommand): string {
199
+ return command.group === undefined
200
+ ? `${command.name} (keeps the bare word, so the "${command.name}" group has no help page)`
201
+ : `${command.name} (run it as "${groupSlug(command.group)} ${command.name}")`;
202
+ }
203
+
204
+ /**
205
+ * A leading group name is read as the group, so an operation whose name is also a tag name
206
+ * resolves unusually: a tagged one loses the bare form and runs as `<its group> <name>`,
207
+ * and an untagged one keeps the bare form and hides that group's help. Nothing becomes
208
+ * unreachable either way, but only the description's author can rename a side of the
209
+ * collision, so say it once at generation time.
210
+ */
211
+ function warnShadowedCommands(commands: CliCommand[]): void {
212
+ const slugs = new Set(commands.filter((c) => c.group).map((c) => groupSlug(c.group as string)));
213
+ const shadowed = commands.filter((command) => slugs.has(command.name));
214
+ if (shadowed.length === 0) return;
215
+ logger.warn(
216
+ `generate-client: cli reads a leading group name as the group, so ${shadowed.length} operation(s) named after a tag resolve unusually — rename the operation or the tag: ${shadowed
217
+ .map(shadowedAddress)
218
+ .join(', ')}.\n`
219
+ );
220
+ }
221
+
222
+ /** The whole `<stem>.cli.ts` file. */
223
+ export function renderCliModule(model: ApiModel, options: CliModuleOptions): string {
224
+ const commands = commandData(model, {
225
+ pagination: options.pagination,
226
+ argsStyle: options.argsStyle,
227
+ });
228
+ warnShadowedCommands(commands);
229
+ const schemes = cliAuthSchemes(model);
230
+ const clientModule = `./${options.stem}.${options.importExt}`;
231
+ const clientImports = ['client', 'configure', ...(options.zodSelected ? ['use'] : [])];
232
+
233
+ const parts = [
234
+ '#!/usr/bin/env node',
235
+ HEADER,
236
+ 'import { readFileSync, realpathSync, writeFileSync } from "node:fs";\nimport { fileURLToPath } from "node:url";',
237
+ [
238
+ `import { ${clientImports.join(', ')} } from "${clientModule}";`,
239
+ ...(options.zodSelected
240
+ ? [`import { zodValidation } from "./${options.stem}.zod.${options.importExt}";`]
241
+ : []),
242
+ ].join('\n'),
243
+ options.runtime === 'module'
244
+ ? `import { invokedName, runCli, type CliCommand, type CliWiring } from "./runtime/cli.${options.importExt}";`
245
+ : '// ─── Embedded cli engine (@redocly/client-generator) ───\n' + embedCliRuntime(),
246
+ `export const COMMANDS: CliCommand[] = ${codeJson(commands, 2)};`,
247
+ ...(options.zodSelected
248
+ ? [
249
+ // A dry run never sends the request, so its "response" is the stub the dry-run
250
+ // fetch returns — validating that reports drift that does not exist. Request
251
+ // validation still runs, which is what makes `--dry-run` a useful preflight.
252
+ `use(zodValidation(process.argv.includes("--dry-run") ? { response: false } : {}));`,
253
+ ]
254
+ : []),
255
+ `export const wiring: CliWiring = {
256
+ name: invokedName(process.argv[1], ${codeJson(options.stem)}),
257
+ envPrefix: ${codeJson(constantCase(options.stem))},
258
+ client,
259
+ ${options.argsStyle === 'flat' ? ' argsStyle: "flat",\n' : ''} configure,
260
+ schemes: ${codeJson(schemes)},
261
+ env: process.env,
262
+ stdin: () => readFileSync(0, "utf-8"),
263
+ readFile: (path: string) => readFileSync(path, "utf-8"),
264
+ writeFile: (path: string, data: Uint8Array) => writeFileSync(path, data),
265
+ stdout: (line: string) => console.log(line),
266
+ stderr: (line: string) => console.error(line),
267
+ };
268
+
269
+ /** Run this CLI programmatically; defaults to the process argv. */
270
+ export const run = (argv: string[] = process.argv.slice(2)): Promise<number> =>
271
+ runCli(COMMANDS, wiring, argv);
272
+
273
+ // Re-exported so a composed entry can run these commands without its own runtime copy.
274
+ export { runCli };
275
+
276
+ // Self-execute only as the process entry, so importing this module is side-effect-safe:
277
+ // composed binaries and login-style wrappers import COMMANDS/wiring/run instead of
278
+ // editing this generated file.
279
+ ${ENTRY_GUARD}`,
280
+ ];
281
+ return parts.join('\n\n') + '\n';
282
+ }
283
+
284
+ export type ComposedCliSource = {
285
+ /** The api alias from `apis:` — it becomes the namespace the shell types. */
286
+ alias: string;
287
+ /** Relative specifier of that api's generated cli module, extension included. */
288
+ modulePath: string;
289
+ };
290
+
291
+ /**
292
+ * The composed entry `client.cliOutput` produces: one binary over every api that selected
293
+ * `cli`, each behind its alias as the namespace, with `<BINNAME>_<ALIAS>` credential
294
+ * prefixes. It imports `runCli` from the first source's module — generated code, so the
295
+ * inline runtime's zero-dependency promise holds — and exports `SOURCES` so an adopter
296
+ * layers custom commands (a `login`) around it without editing a generated file.
297
+ */
298
+ export function renderComposedCliEntry(sources: ComposedCliSource[], stem: string): string {
299
+ const prefix = constantCase(stem);
300
+ // An identifier can't start with a digit, and two aliases can sanitize identically —
301
+ // the underscore and the index keep every import binding legal and unique.
302
+ const idents = new Map<string, string>();
303
+ sources.forEach(({ alias }, index) => {
304
+ const sanitized = alias.replace(/[^A-Za-z0-9]/g, '_');
305
+ const legal = /^[A-Za-z_]/.test(sanitized) ? sanitized : `_${sanitized}`;
306
+ idents.set(alias, [...idents.values()].includes(legal) ? `${legal}_${index}` : legal);
307
+ });
308
+ const identFor = (alias: string): string => idents.get(alias)!;
309
+ const imports = sources.map(({ alias, modulePath }, index) => {
310
+ const ident = identFor(alias);
311
+ const runtime = index === 0 ? ', runCli' : '';
312
+ return `import { COMMANDS as ${ident}Commands, wiring as ${ident}Wiring${runtime} } from ${JSON.stringify(modulePath)};`;
313
+ });
314
+ const entries = sources.map(({ alias }) => {
315
+ const ident = identFor(alias);
316
+ const namespace = kebab(alias);
317
+ const aliasPrefix = `${prefix}_${constantCase(alias)}`;
318
+ return ` {
319
+ namespace: ${JSON.stringify(namespace)},
320
+ commands: ${ident}Commands,
321
+ wiring: { ...${ident}Wiring, envPrefix: ${JSON.stringify(aliasPrefix)} },
322
+ },`;
323
+ });
324
+ return (
325
+ [
326
+ '#!/usr/bin/env node',
327
+ HEADER,
328
+ [
329
+ 'import { realpathSync } from "node:fs";',
330
+ 'import { fileURLToPath } from "node:url";',
331
+ ...imports,
332
+ ].join('\n'),
333
+ `/** The composed sources — import SOURCES to layer custom commands around this binary. */
334
+ export const SOURCES = [
335
+ ${entries.join('\n')}
336
+ ];
337
+
338
+ /** Run the composed CLI programmatically; defaults to the process argv. */
339
+ export const run = (argv: string[] = process.argv.slice(2)): Promise<number> =>
340
+ runCli(SOURCES, argv);
341
+
342
+ // Re-exported so a wrapper (a custom \`login\` command) can run these sources itself.
343
+ export { runCli };
344
+
345
+ ${ENTRY_GUARD}`,
346
+ ].join('\n\n') + '\n'
347
+ );
348
+ }
@@ -0,0 +1,57 @@
1
+ // Ejected from @redocly/client-generator@0.4.1 — the built-in "go" generator.
2
+ // This file is yours: edit freely; the generated client stays machine-owned and is
3
+ // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
+ // `redocly eject-generator go --update`.
5
+ // The `client` stage: one `<Name>URL` function per declared server.
6
+
7
+ import {
8
+ type ApiModel,
9
+ identifierFor,
10
+ type ServerModel,
11
+ serverUrlParts,
12
+ } from '@redocly/client-generator';
13
+ import { exported, type GoPrinter } from '@redocly/client-generator/printers/go';
14
+
15
+ import { GO, naming } from './naming.ts';
16
+
17
+ /** The server URL as a Go expression: literals concatenated with declared-variable args. */
18
+ function serverUrlExpression(server: ServerModel): string {
19
+ const parts = serverUrlParts(server).map((part) =>
20
+ part.kind === 'literal'
21
+ ? naming.string(part.value)
22
+ : identifierFor(part.name, { style: 'camel', reserved: GO })
23
+ );
24
+ return parts.join(' + ');
25
+ }
26
+
27
+ /** One `<Name>URL` function per declared server; server variables become parameters. */
28
+ export function writeGoServers(printer: GoPrinter, model: ApiModel): void {
29
+ const servers = model.servers ?? [];
30
+ if (servers.length === 0) return;
31
+ const usedNames = new Set<string>();
32
+ servers.forEach((server, index) => {
33
+ let name = `${exported(server.description ?? `server${index + 1}`)}URL`;
34
+ if (usedNames.has(name)) name = `${name}${index + 1}`;
35
+ usedNames.add(name);
36
+ const params = server.variables.map(
37
+ (variable) => `${identifierFor(variable.name, { style: 'camel', reserved: GO })} string`
38
+ );
39
+ const defaults = server.variables
40
+ .map(
41
+ (variable) =>
42
+ `${identifierFor(variable.name, { style: 'camel', reserved: GO })} default: ${naming.string(variable.default)}`
43
+ )
44
+ .join(', ');
45
+ printer.line(
46
+ `// ${name} returns the ${naming.string(server.description ?? server.url)} base URL${defaults === '' ? '.' : ` (${defaults}).`}`
47
+ );
48
+ printer.block(
49
+ `func ${name}(${params.join(', ')}) string {`,
50
+ () => {
51
+ printer.line(`return ${serverUrlExpression(server)}`);
52
+ },
53
+ '}'
54
+ );
55
+ printer.blank();
56
+ });
57
+ }
@@ -0,0 +1,41 @@
1
+ // Ejected from @redocly/client-generator@0.4.1 — the built-in "go" generator.
2
+ // This file is yours: edit freely; the generated client stays machine-owned and is
3
+ // rebuilt by `redocly generate-client`. Newer generator versions merge in with
4
+ // `redocly eject-generator go --update`.
5
+ // The `descriptor` stage: the operations-table composite literals — security
6
+ // OR-alternatives and the pagination spec.
7
+
8
+ import {
9
+ type ApiModel,
10
+ type NeutralPaginationRule,
11
+ type OperationModel,
12
+ securityRequirements,
13
+ } from '@redocly/client-generator';
14
+
15
+ import { naming } from './naming.ts';
16
+
17
+ /** Go composite literal for one operation's security OR-alternatives. */
18
+ export function goSecurityLiteral(op: OperationModel, model: ApiModel): string | undefined {
19
+ const alternatives = securityRequirements(op, model).map((alternative) =>
20
+ alternative.map((spec) =>
21
+ spec.kind === 'apiKey'
22
+ ? `{Scheme: ${naming.string(spec.scheme)}, Kind: "apiKey", Name: ${naming.string(spec.name)}, In: ${naming.string(spec.in)}}`
23
+ : `{Scheme: ${naming.string(spec.scheme)}, Kind: ${naming.string(spec.kind)}}`
24
+ )
25
+ );
26
+ if (alternatives.length === 0) return undefined;
27
+ return `[][]SecuritySpec{${alternatives.map((specs) => `{${specs.join(', ')}}`).join(', ')}}`;
28
+ }
29
+
30
+ /** The neutral rule as a `&PaginationSpec{…}` composite literal for the operations table. */
31
+ export function goPaginationLiteral(rule: NeutralPaginationRule): string {
32
+ const fields = [
33
+ `Style: ${naming.string(rule.style)}`,
34
+ ...(rule.param !== undefined ? [`Param: ${naming.string(rule.param)}`] : []),
35
+ ...(rule.nextCursor !== undefined ? [`NextCursor: ${naming.string(rule.nextCursor)}`] : []),
36
+ ...(rule.hasMore !== undefined ? [`HasMore: ${naming.string(rule.hasMore)}`] : []),
37
+ ...(rule.limitParam !== undefined ? [`LimitParam: ${naming.string(rule.limitParam)}`] : []),
38
+ ...(rule.items !== undefined ? [`Items: ${naming.string(rule.items)}`] : []),
39
+ ];
40
+ return `&PaginationSpec{${fields.join(', ')}}`;
41
+ }