@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,14 @@
1
+ import { createRequire as __createRequire } from 'node:module';
2
+ import { fileURLToPath as __fileURLToPath } from 'node:url';
3
+ import { dirname as __pathDirname } from 'node:path';
4
+ const require = __createRequire(import.meta.url);
5
+ var __filename = __fileURLToPath(import.meta.url);
6
+ var __dirname = __pathDirname(__filename);
7
+ import{a as S,b as f}from"./REFX2EKS.js";import{a as N}from"./UKSQ7B5H.js";import{b as E,c as U,d as j,e as K,f as J,g as z,h as Q}from"./4I2OVMBJ.js";import{a as M}from"./PFYPTG4G.js";import{b as O,c as H,d as w,e as B,g as F,h as G,i as T,j as W,k as _,n as A}from"./HJYFWIVE.js";var c=new S,y=H.go;function V(e){if(e===void 0)return"client";if(!/^[a-z_][a-z0-9_]*$/.test(e)||y.has(e))throw new M(`goPackage "${e}" is not a valid Go package name: use lowercase letters, digits, and underscores, don't start with a digit, and avoid Go keywords.`);return e}function R(e){let t=new Set,n=[];for(let r of e.services)for(let o of r.operations){let a=f(o.name),s=2;for(;t.has(a);)a=`${f(o.name)}${s++}`;t.add(a),n.push({op:o,ident:a})}return n}function oe(e){return J(e).map(n=>n.kind==="literal"?c.string(n.value):w(n.name,{style:"camel",reserved:y})).join(" + ")}function X(e,t){let n=t.servers??[];if(n.length===0)return;let r=new Set;n.forEach((o,a)=>{let s=`${f(o.description??`server${a+1}`)}URL`;r.has(s)&&(s=`${s}${a+1}`),r.add(s);let i=o.variables.map(l=>`${w(l.name,{style:"camel",reserved:y})} string`),u=o.variables.map(l=>`${w(l.name,{style:"camel",reserved:y})} default: ${c.string(l.default)}`).join(", ");e.line(`// ${s} returns the ${c.string(o.description??o.url)} base URL${u===""?".":` (${u}).`}`),e.block(`func ${s}(${i.join(", ")}) string {`,()=>{e.line(`return ${oe(o)}`)},"}"),e.blank()})}function Y(e,t){let n=z(e,t).map(r=>r.map(o=>o.kind==="apiKey"?`{Scheme: ${c.string(o.scheme)}, Kind: "apiKey", Name: ${c.string(o.name)}, In: ${c.string(o.in)}}`:`{Scheme: ${c.string(o.scheme)}, Kind: ${c.string(o.kind)}}`));if(n.length!==0)return`[][]SecuritySpec{${n.map(r=>`{${r.join(", ")}}`).join(", ")}}`}function Z(e){return`&PaginationSpec{${[`Style: ${c.string(e.style)}`,...e.param!==void 0?[`Param: ${c.string(e.param)}`]:[],...e.nextCursor!==void 0?[`NextCursor: ${c.string(e.nextCursor)}`]:[],...e.hasMore!==void 0?[`HasMore: ${c.string(e.hasMore)}`]:[],...e.limitParam!==void 0?[`LimitParam: ${c.string(e.limitParam)}`]:[],...e.items!==void 0?[`Items: ${c.string(e.items)}`]:[]].join(", ")}}`}function g(e,t="string"){if(T(e)){let n=g(W(e),t);return n.startsWith("*")||n==="any"?n:`*${n}`}switch(e.kind){case"scalar":if(t==="Date"&&e.scalar==="string"){if(e.metadata?.format==="date-time")return"time.Time";if(e.metadata?.format==="date")return"Date"}return{string:"string",integer:"int64",number:"float64",boolean:"bool"}[e.scalar];case"array":return`[]${g(e.items,t)}`;case"record":return`map[string]${g(e.value,t)}`;case"ref":return f(e.name);case"literal":return typeof e.value=="string"?"string":typeof e.value=="boolean"?"bool":"float64";case"enum":return{string:"string",integer:"int64",number:"float64",boolean:"bool"}[e.scalar];case"omit":return f(e.base);case"union":case"null":case"object":case"intersection":case"unknown":return"any"}}function ae(e,t,n,r,o){e.doc(f(t),o),e.block(`type ${f(t)} struct {`,()=>{for(let a of n){let s=f(a.name),i=g(a.schema,r),u=`\`json:"${a.name}"\``;a.required||(!i.startsWith("*")&&!i.startsWith("[]")&&!i.startsWith("map[")&&i!=="any"&&(i=`*${i}`),u=`\`json:"${a.name},omitempty"\``),e.line(`${s} ${i} ${u}`)}},"}"),e.blank()}function D(e,t="string"){let n=new S;n.line("package client"),n.blank(),e.schemas.some(({schema:a})=>G(a,e)!==void 0)&&(n.line('import "encoding/json"'),n.blank());let o=se(e,t);return t==="Date"&&o.includes("time.Time")&&(n.line('import "time"'),n.blank()),n.line(o),n.toString()}function se(e,t){let n=new S;for(let{name:r,schema:o}of e.schemas){let a=_(o);if(a!==void 0){let i=a.scalar==="string"?"string":"int64";n.doc(f(r),o.description),n.line(`type ${f(r)} ${i}`),n.blank(),n.block("const (",()=>{let u=new Set;a.values.forEach(l=>{let d=O.pascal(String(l))||"Value",m="";for(let k=2;u.has(d+m);k++)m=String(k);u.add(d+m);let p=f(r)+d+m;n.line(`${p} ${f(r)} = ${c.literal(l)}`)})},")"),n.blank();continue}if(o.kind==="object"||o.kind==="intersection"){let i=F(o,e);if(i!==void 0){ae(n,r,i.properties,t,i.description??o.description);continue}}let s=G(o,e);if(s!==void 0){let i=f(r),u=s.cases.map(l=>`${l.value} -> ${f(l.schemaName)}`).join(", ");n.line(`// ${i} is a discriminated union ("${s.property}"): ${u}.`),n.line(`type ${i} = any`),n.blank(),n.line(`// Unmarshal${i} decodes into the member selected by "${s.property}".`),n.block(`func Unmarshal${i}(data []byte) (${i}, error) {`,()=>{n.block("var probe struct {",()=>{n.line(`Discriminant string \`json:"${s.property}"\``)},"}"),n.block("if err := json.Unmarshal(data, &probe); err != nil {",()=>{n.line("return nil, err")},"}"),n.line("switch probe.Discriminant {");for(let l of s.cases)n.block(`case ${c.string(l.value)}:`,()=>{n.line(`var value ${f(l.schemaName)}`),n.line("err := json.Unmarshal(data, &value)"),n.line("return value, err")});n.line("}"),n.line("var fallback any"),n.line("err := json.Unmarshal(data, &fallback)"),n.line("return fallback, err")},"}"),n.blank();continue}n.doc(f(r),o.description),n.line(`type ${f(r)} = ${g(o,t)}`),n.blank()}return n.toString()}function q(e,t){if(t==="string")return e;let n=e.startsWith("*")?`(${e})`:e;return t==="time.Time"?`${n}.Format(time.RFC3339)`:t==="Date"?`${n}.Format("2006-01-02")`:t==="int64"?`strconv.FormatInt(${e}, 10)`:t==="float64"?`strconv.FormatFloat(${e}, 'f', -1, 64)`:t==="bool"?`strconv.FormatBool(${e})`:`fmt.Sprint(${e})`}var ie=["c","ctx","body","params","out","op"];function I(e,t){let n=B(e.pathParams.map(r=>r.name),{style:"camel",reserved:y,taken:ie});return e.pathParams.map((r,o)=>({param:r,go:n[o],type:g(r.schema,t)}))}function le(e,t){let n=new Set;return(e.successResponseHeaders??[]).map(r=>{let o=f(r.name),a=o,s=2;for(;n.has(a);)a=`${o}${s++}`;n.add(a);let i=A(r.schema,t),u={integer:{goType:"*int64",helper:"headerInt64"},number:{goType:"*float64",helper:"headerFloat64"},boolean:{goType:"*bool",helper:"headerBool"},string:{goType:"*string",helper:"headerString"}}[i];return{field:a,name:r.name,...u}})}function L(e,t,n,r,o,a=!1){let s=I(t,r),i=t.queryParams.length>0,u=U(t),l=u===void 0?void 0:g(u,r),d=a?le(t,o):[];a&&(e.line(`// ${n}Headers carries the declared response headers of ${n}WithHeaders (nil when absent or unparsable).`),e.block(`type ${n}Headers struct {`,()=>{for(let $ of d)e.line(`${$.field} ${$.goType}`)},"}"),e.blank());let m=["ctx context.Context",...s.map(({go:$,type:v})=>`${$} ${v}`),...t.requestBody?[`body ${g(t.requestBody.schema,r)}`]:[],...i?[`params *${n}Params`]:[]],p=j(t),k=a?l===void 0?`(${n}Headers, error)`:`(${l}, ${n}Headers, error)`:p!==void 0?"func(yield func(ServerSentEvent, error) bool)":l===void 0?"error":`(${l}, error)`,b=$=>a?l===void 0?`return headers, ${$}`:`return out, headers, ${$}`:l===void 0?`return ${$}`:`return out, ${$}`,P=a?`${n}WithHeaders`:n;e.doc(P,a?`Like ${n}, also returning the declared response headers.`:t.summary),e.block(`func (c *Client) ${P}(${m.join(", ")}) ${k} {`,()=>{p===void 0&&l!==void 0&&e.line(`var out ${l}`),a&&e.line(`var headers ${n}Headers`),e.line(`op := operations[${c.string(t.specName??t.name)}]`),e.line("authHeaders, query := resolveAuth(op.Security, c.config.Auth)"),i&&e.block("if params != nil {",()=>{for(let h of t.queryParams){let x=f(h.name);e.block(`if params.${x} != nil {`,()=>{if(h.schema.kind==="array"){let C=g(h.schema.items,r);e.block(`for _, item := range *params.${x} {`,()=>{e.line(`query.Add(${c.string(h.name)}, ${q("item",C)})`)},"}")}else e.line(`query.Set(${c.string(h.name)}, ${q(`*params.${x}`,g(h.schema,r))})`)},"}")}},"}");let $=s.map(({param:h,go:x,type:C})=>`${c.string(h.name)}: ${q(x,C)}`).join(", ");if(e.line(`requestURL := buildURL(c.config.ServerURL, op.Path, map[string]string{${$}})`),p!==void 0){e.block("open := func(extraHeaders map[string]string) (*http.Response, error) {",()=>{e.line("merged := map[string]string{}"),e.block("for key, value := range authHeaders {",()=>{e.line("merged[key] = value")},"}"),e.block("for key, value := range extraHeaders {",()=>{e.line("merged[key] = value")},"}"),e.line("return send(ctx, &c.config, requestSpec{OperationID: op.ID, Method: op.Method, URL: requestURL, Headers: merged, Query: query})")},"}"),e.line(`return iterSSE(open, ${p.schema!==void 0&&p.schema.kind!=="unknown"})`);return}let v=["OperationID: op.ID","Method: op.Method","URL: requestURL","Headers: authHeaders","Query: query"];if(t.requestBody&&K(t)?(e.line("contentType, reader, err := toMultipart(body)"),e.block("if err != nil {",()=>{e.line(b("err"))},"}"),v.push("Body: reader"),v.push("ContentType: contentType")):t.requestBody&&(e.line("payload, err := json.Marshal(body)"),e.block("if err != nil {",()=>{e.line(b("err"))},"}"),v.push("Body: bytes.NewReader(payload)"),v.push(`ContentType: ${c.string(t.requestBody.contentType)}`)),e.line(`resp, err := send(ctx, &c.config, requestSpec{${v.join(", ")}})`),e.block("if err != nil {",()=>{e.line(b("err"))},"}"),e.block("if resp.StatusCode >= 400 {",()=>{e.line(b("apiErrorFrom(resp, requestURL)"))},"}"),a){e.block(`if err := decodeJSON(resp, ${l===void 0?"nil":"&out"}); err != nil {`,()=>{e.line(b("err"))},"}");for(let h of d)e.line(`headers.${h.field} = ${h.helper}(resp.Header, ${c.string(h.name)})`);e.line(l===void 0?"return headers, nil":"return out, headers, nil")}else l===void 0?e.line("return decodeJSON(resp, nil)"):(e.block("if err := decodeJSON(resp, &out); err != nil {",()=>{e.line("return out, err")},"}"),e.line("return out, nil"))},"}"),e.blank()}function ee(e,t,n,r,o,a){let s=I(t,r),i=t.queryParams.length>0,u=["ctx context.Context",...s.map(({go:d,type:m})=>`${d} ${m}`),...i?[`params *${n}Params`]:[]].join(", "),l=()=>{e.line(`op := operations[${c.string(t.specName??t.name)}]`),e.line("base := url.Values{}"),i&&e.block("if params != nil {",()=>{for(let d of t.queryParams){let m=f(d.name);e.block(`if params.${m} != nil {`,()=>{e.line(`base.Set(${c.string(d.name)}, ${q(`*params.${m}`,g(d.schema,r))})`)},"}")}},"}"),e.block("call := func(pageParams url.Values) (any, *http.Response, error) {",()=>{e.line("authHeaders, query := resolveAuth(op.Security, c.config.Auth)"),e.block("for key, values := range pageParams {",()=>{e.block("for _, value := range values {",()=>{e.line("query.Set(key, value)")},"}")},"}");let d=s.map(({param:m,go:p,type:k})=>`${c.string(m.name)}: ${q(p,k)}`).join(", ");e.line(`requestURL := buildURL(c.config.ServerURL, op.Path, map[string]string{${d}})`),e.line("resp, err := send(ctx, &c.config, requestSpec{OperationID: op.ID, Method: op.Method, URL: requestURL, Headers: authHeaders, Query: query})"),e.block("if err != nil {",()=>{e.line("return nil, nil, err")},"}"),e.block("if resp.StatusCode >= 400 {",()=>{e.line("return nil, resp, apiErrorFrom(resp, requestURL)")},"}"),e.line("var raw any"),e.block("if err := decodeJSON(resp, &raw); err != nil {",()=>{e.line("return nil, resp, err")},"}"),e.line("return raw, resp, nil")},"}"),e.line("pages := iterPages(call, *op.Pagination, base)")};e.line(`// ${n}Pages iterates ${n} response pages; use with \`for page, err := range\`.`),e.block(`func (c *Client) ${n}Pages(${u}) func(yield func(${o}, error) bool) {`,()=>{l(),e.block(`return func(yield func(${o}, error) bool) {`,()=>{e.block("pages(func(raw any, err error) bool {",()=>{e.line(`var page ${o}`),e.block("if err == nil {",()=>{e.line("err = reencode(raw, &page)")},"}"),e.line("return yield(page, err)")},"})")},"}")},"}"),e.blank(),e.line(`// ${n}Items iterates the items of every ${n} page.`),e.block(`func (c *Client) ${n}Items(${u}) func(yield func(${a}, error) bool) {`,()=>{l(),e.block(`return func(yield func(${a}, error) bool) {`,()=>{e.block("pages(func(raw any, err error) bool {",()=>{e.block("if err != nil {",()=>{e.line(`var zero ${a}`),e.line("return yield(zero, err)")},"}"),e.line("pageItems, _ := resolvePointer(raw, op.Pagination.Items).([]any)"),e.block("for _, item := range pageItems {",()=>{e.line(`var typed ${a}`),e.block("if err := reencode(item, &typed); err != nil {",()=>{e.line("return yield(typed, err)")},"}"),e.block("if !yield(typed, nil) {",()=>{e.line("return false")},"}")},"}"),e.line("return true")},"})")},"}")},"}"),e.blank()}function ne(e){let t=e.split(`
8
+ `),n=[],r=!1;for(let o of t)if(!o.startsWith("package ")){if(o.startsWith("import (")){r=!0;continue}if(r){o.startsWith(")")&&(r=!1);continue}o.startsWith("import ")||n.push(o)}return n.join(`
9
+ `).trim()}var te=["bytes","context","encoding/base64","encoding/json","errors","fmt","io","math/rand","mime/multipart","net/http","net/url","strconv","strings","time"];function re(e,t,n,r,o,a){e.line(ne(D(t,r))),e.blank(),X(e,t),a&&(e.line("// \u2500\u2500\u2500 Embedded runtime (@redocly/client-generator go runtime) \u2500\u2500\u2500"),e.line(ne(N)),e.blank()),e.block("type operationMeta struct {",()=>{e.line("ID string"),e.line("Method string"),e.line("Path string"),e.line("Security [][]SecuritySpec"),e.line("Pagination *PaginationSpec")},"}"),e.blank(),e.block("var operations = map[string]operationMeta{",()=>{for(let{op:s,ident:i}of R(t)){let u=s.specName??s.name,l=Y(s,t),d=o.get(i),m=[`ID: ${c.string(u)}`,`Method: ${c.string(s.method.toUpperCase())}`,`Path: ${c.string(s.path)}`,...l!==void 0?[`Security: ${l}`]:[],...d!==void 0?[`Pagination: ${Z(d)}`]:[]];e.line(`${c.string(u)}: {${m.join(", ")}},`)}},"}"),e.blank();for(let{op:s,ident:i}of R(t))s.queryParams.length!==0&&(e.block(`type ${i}Params struct {`,()=>{for(let u of s.queryParams){let l=g(u.schema,r);e.line(`${f(u.name)} ${l.startsWith("*")?l:`*${l}`}`)}},"}"),e.blank());e.doc("Client",`Client for ${t.title} (${t.version}).`),e.block("type Client struct {",()=>{e.line("config Config")},"}"),e.blank(),e.block("func New(config Config) *Client {",()=>{e.block('if config.ServerURL == "" {',()=>{e.line(`config.ServerURL = ${c.string(n.serverUrl??t.serverUrl??"")}`)},"}"),e.line("return &Client{config: config}")},"}"),e.blank();for(let{op:s,ident:i}of R(t)){L(e,s,i,r),j(s)===void 0&&(s.successResponseHeaders?.length??0)>0&&L(e,s,i,r,t,!0);let u=o.get(i);if(u===void 0)continue;let l=U(s),d=l===void 0?"any":g(l,r),m=Q(l,u.items,t);ee(e,s,i,r,d,m===void 0?"any":g(m,r))}}var ze=({model:e,output:t,banner:n,emit:r,pagination:o})=>{let a=new S,s=r.dateType??"string",i=V(r.goPackage),u=new Map;for(let{op:b,ident:P}of R(e)){let $=o?.get(b.name)?.spec;$!==void 0&&u.set(P,$)}a.line(`// Code generated by @redocly/client-generator (go) from "${e.title}" ${e.version}. DO NOT EDIT.`),a.line("// Regenerate with `redocly generate-client`. Standard library only \u2014 zero dependencies."),a.line(`package ${i}`),a.blank();let l=r.runtime!=="module",d=l?te:(()=>{let b=new S;re(b,e,r,s,u,!1);let P=b.toString();return te.filter($=>new RegExp(`\\b${$.split("/").pop()}\\.`).test(P))})();a.block("import (",()=>{for(let b of d)a.line(c.string(b))},")"),a.blank(),re(a,e,r,s,u,l);let m={path:t.path.replace(/\.[^.\\/]+$/,".go"),content:a.toString()};if(l)return[m];let p=n.map(b=>`// ${b}`).join(`
10
+ `),k=N.replace(/^package .*$/m,`package ${i}`);return[m,{path:m.path.replace(/[^\\/]+$/,"runtime.go"),content:`${p}
11
+ ${k.trimEnd()}
12
+ `}]};function ce(e,t){let n=t.emit.dateType??"string",r=t.emit.goPackage??"client",o=R(t.model).find(u=>u.op.name===e.name)?.ident??f(e.name),a=["ctx",...e.pathParams.map(u=>`"<${w(u.name,{style:"camel",reserved:y})}>"`),...e.requestBody?[`${g(e.requestBody.schema,n)}{ /* \u2026 */ }`]:[],...e.queryParams.length>0?["nil"]:[]],s=`client.${o}(${a.join(", ")})`,i=j(e)!==void 0?`stream := ${s}`:U(e)===void 0?`err := ${s}`:`result, err := ${s}`;return{lang:"go",label:"Go SDK",source:`client := ${r}.New(${r}.Config{})
13
+ ${i}
14
+ `}}var Qe=({model:e,output:t,emit:n,pagination:r})=>[{path:t.path.replace(/\.[^.\\/]+$/,".go.md"),content:E(e,{title:`${e.title} Go SDK reference`,frontmatter:n.docsFrontmatter===!0,language:{name:"go",label:"Go",fence:"go",requires:"The SDK needs the standard library only."},sample:o=>ce(o,{model:e,emit:n,outputPath:t.path}),paginated:new Set(r?.keys()??[])})}];export{g as a,D as b,ze as c,ce as d,Qe as e};
@@ -0,0 +1,177 @@
1
+ import { createRequire as __createRequire } from 'node:module';
2
+ import { fileURLToPath as __fileURLToPath } from 'node:url';
3
+ import { dirname as __pathDirname } from 'node:path';
4
+ const require = __createRequire(import.meta.url);
5
+ var __filename = __fileURLToPath(import.meta.url);
6
+ var __dirname = __pathDirname(__filename);
7
+ import"./4I2OVMBJ.js";import{a as h}from"./ZMCRMR2V.js";import"./PFYPTG4G.js";import"./HJ36YGCI.js";import"./NEWT5A6S.js";import{e as g,h as p,k as y}from"./NB54XRRU.js";import"./HJYFWIVE.js";import"./RFWIIJFG.js";import{join as M}from"node:path";var a=" ";function f(e){return`${y(e)}Schema`}var x=new Map;function u(e,r=x,t=""){return k(w(e,r,t),e)}function w(e,r,t){switch(e.kind){case"scalar":return S(e.scalar,e.metadata);case"object":return b(e.properties,r,t);case"array":return`z.array(${u(e.items,r,t)})`;case"record":return`z.record(z.string(), ${u(e.value,r,t)})`;case"ref":return`z.lazy(() => ${f(e.name)})`;case"literal":return`z.literal(${p(e.value)})`;case"enum":return z(e.values);case"union":return E(e.members,r,t);case"intersection":return e.members.map(n=>u(n,r,t)).reduce((n,o)=>`${n}.and(${o})`);case"null":return"z.null()";case"unknown":return"z.unknown()";case"omit":return q(e.base,e.keys,r,t)}}function S(e,r){switch(e){case"string":return r?.format==="binary"?"z.instanceof(Blob)":"z.string()";case"integer":return"z.number().int()";case"number":return"z.number()";case"boolean":return"z.boolean()"}}function c(e){return g(e)===e?e:JSON.stringify(e)}function b(e,r,t){if(e.length===0)return"z.object({})";let n=t+a;return`z.object({
8
+ ${e.map((s,i)=>{let d=u(s.schema,r,n),l=s.required?d:`${d}.optional()`,v=i===e.length-1?"":",";return`${n}${c(s.name)}: ${l}${v}`}).join(`
9
+ `)}
10
+ ${t}})`}function z(e){return e.every(r=>typeof r=="string")?`z.enum([${e.map(r=>JSON.stringify(r)).join(", ")}])`:`z.union([${e.map(r=>`z.literal(${p(r)})`).join(", ")}])`}function E(e,r,t){let n=e.map(o=>u(o,r,t));return n.length===1?n[0]:`z.union([${n.join(", ")}])`}function q(e,r,t,n){let o=t.get(e);if(o&&o.kind!=="object")return u(m(o,r,t,new Set([e])),t,n);let s=r.map(i=>`${c(i)}: true`).join(", ");return`${f(e)}.omit({ ${s} })`}function m(e,r,t,n){switch(e.kind){case"object":return{...e,properties:e.properties.filter(o=>!r.includes(o.name))};case"union":case"intersection":return{...e,members:e.members.map(o=>m(o,r,t,n))};case"ref":{if(n.has(e.name))return e;let o=t.get(e.name);if(!o)return e;if(o.kind==="object"){let s=r.filter(i=>o.properties.some(d=>d.name===i));return s.length>0?{kind:"omit",base:e.name,keys:s}:e}return m(o,r,t,new Set([...n,e.name]))}default:return e}}function k(e,r){let t=r.metadata;if(!t)return e;let n=e;return r.kind==="scalar"&&r.scalar==="string"&&(t.minLength!==void 0&&(n=`${n}.min(${t.minLength})`),t.maxLength!==void 0&&(n=`${n}.max(${t.maxLength})`),t.pattern!==void 0&&(n=`${n}.regex(new RegExp(${JSON.stringify(t.pattern)}))`)),r.kind==="scalar"&&(r.scalar==="number"||r.scalar==="integer")&&(t.minimum!==void 0&&(n=`${n}.min(${t.minimum})`),t.maximum!==void 0&&(n=`${n}.max(${t.maximum})`),t.exclusiveMinimum!==void 0&&(n=`${n}.gt(${t.exclusiveMinimum})`),t.exclusiveMaximum!==void 0&&(n=`${n}.lt(${t.exclusiveMaximum})`)),r.kind==="array"&&(t.minItems!==void 0&&(n=`${n}.min(${t.minItems})`),t.maxItems!==void 0&&(n=`${n}.max(${t.maxItems})`)),n}function j(e,r){let t=[];for(let n of h(e.services)){if(n.sse!==void 0)continue;let o=n.requestBody,s=o&&o.contentType.toLowerCase().includes("json")?u(o.schema,r,a):void 0,i=n.successResponses.find(l=>l.contentType.toLowerCase().includes("json")),d=i?u(i.schema,r,a):void 0;!s&&!d||t.push({name:n.specName??n.name,request:s,response:d})}return t}function Z(e){let r=e.flatMap(n=>[`${a}${c(n.name)}: {`,...n.request?[`${a}${a}request: z.ZodType;`]:[],...n.response?[`${a}${a}response: z.ZodType;`]:[],`${a}};`]),t=e.map((n,o)=>{let s=[...n.request?[`request: ${n.request}`]:[],...n.response?[`response: ${n.response}`]:[]].join(", "),i=o===e.length-1?"":",";return`${a}${c(n.name)}: { ${s} }${i}`});return["/**"," * Request/response validators by operationId \u2014 powers `zodValidation`, or import one directly."," */","export const operationSchemas: {",...r,"} = {",...t,"};"].join(`
11
+ `)}var I=`/** \`request\`/\`response\` validators for one operation (an absent side is not validated). */
12
+ export type OperationSchemaSet = { request?: z.ZodType; response?: z.ZodType };
13
+
14
+ const schemaIndex: Partial<Record<string, OperationSchemaSet>> = operationSchemas;
15
+
16
+ /** One flattened validation problem: the full nested path and a short preview of the value. */
17
+ export type ZodViolation = { path: string; message: string; received: string };
18
+
19
+ /** A request or response payload failed validation. Requests throw it; response handling is configurable. */
20
+ export class ZodValidationError extends Error {
21
+ // Declared and assigned in the body, NOT as constructor parameter properties: those
22
+ // need a transform, so they break \`node --experimental-strip-types\` for anything
23
+ // importing this module (the generated CLI runs that way).
24
+ readonly operationId: string;
25
+ readonly direction: "request" | "response";
26
+ readonly issues: z.ZodError["issues"];
27
+ readonly violations: ZodViolation[];
28
+
29
+ constructor(
30
+ operationId: string,
31
+ direction: "request" | "response",
32
+ issues: z.ZodError["issues"],
33
+ violations: ZodViolation[]
34
+ ) {
35
+ const detail = violations
36
+ .slice(0, 5)
37
+ .map((violation) => \`\${violation.path || "(root)"}: \${violation.message} (received \${violation.received})\`)
38
+ .join("; ");
39
+ const more = violations.length > 5 ? \`; \u2026and \${violations.length - 5} more\` : "";
40
+ super(\`\${direction === "request" ? "Request" : "Response"} validation failed for operation "\${operationId}": \${detail}\${more}\`);
41
+ this.operationId = operationId;
42
+ this.direction = direction;
43
+ this.issues = issues;
44
+ this.violations = violations;
45
+ this.name = "ZodValidationError";
46
+ }
47
+ }
48
+
49
+ // Flatten zod issues into leaf violations. Union branches (zod 3 \`unionErrors\`, zod 4
50
+ // nested \`errors\`) are recursed into, so the message names the actual failing fields
51
+ // instead of just the union root ("Invalid input").
52
+ function flattenIssues(
53
+ issues: z.ZodError["issues"],
54
+ value: unknown,
55
+ base: PropertyKey[] = []
56
+ ): ZodViolation[] {
57
+ const violations: ZodViolation[] = [];
58
+ for (const issue of issues) {
59
+ const path = [...base, ...issue.path];
60
+ const nested = nestedIssueLists(issue);
61
+ if (nested.length > 0) {
62
+ for (const sub of nested) violations.push(...flattenIssues(sub, value, path));
63
+ } else {
64
+ violations.push({
65
+ path: path.map(String).join("."),
66
+ message: issue.message,
67
+ received: preview(valueAt(value, path)),
68
+ });
69
+ }
70
+ }
71
+ return violations;
72
+ }
73
+
74
+ /** The nested issue lists of a union-ish issue, across zod 3 (\`unionErrors\`) and zod 4 (\`errors\`). */
75
+ function nestedIssueLists(issue: unknown): Array<z.ZodError["issues"]> {
76
+ const candidate = issue as {
77
+ unionErrors?: Array<{ issues: z.ZodError["issues"] }>;
78
+ errors?: Array<z.ZodError["issues"]>;
79
+ };
80
+ if (Array.isArray(candidate.unionErrors)) return candidate.unionErrors.map((error) => error.issues);
81
+ if (Array.isArray(candidate.errors)) return candidate.errors;
82
+ return [];
83
+ }
84
+
85
+ function valueAt(value: unknown, path: PropertyKey[]): unknown {
86
+ let current = value;
87
+ for (const key of path) {
88
+ if (current === null || typeof current !== "object") return undefined;
89
+ current = (current as Record<PropertyKey, unknown>)[key];
90
+ }
91
+ return current;
92
+ }
93
+
94
+ /** A short single-line preview of the offending value. NOTE: validation output can surface
95
+ * payload data \u2014 route \`onViolation\` to a scrubbed logger when responses may carry secrets. */
96
+ function preview(value: unknown): string {
97
+ let text: string;
98
+ try {
99
+ text = JSON.stringify(value) ?? String(value);
100
+ } catch {
101
+ text = String(value);
102
+ }
103
+ return text.length > 80 ? \`\${text.slice(0, 77)}\u2026\` : text;
104
+ }
105
+
106
+ export type ZodValidationOptions = {
107
+ /** Validate request bodies before any network call; a failure THROWS (it is the caller's own bug). Default: true. */
108
+ request?: boolean;
109
+ /** Replace the outgoing body with the parsed result, dropping keys the schema does not declare
110
+ * (for strict-DTO servers that 400 on excess properties). Runs request validation. Default: false. */
111
+ stripRequestBodies?: boolean;
112
+ /** Response drift handling: \`"warn"\` (default) reports via \`onViolation\` and lets the call
113
+ * succeed \u2014 a server drifting from its description should not crash the consumer;
114
+ * \`"throw"\` fails the call (even on result-mode clients); \`false\` skips response validation. */
115
+ response?: "warn" | "throw" | false;
116
+ /** Sink for \`"warn"\` mode. Default: \`console.warn\` with the error message. */
117
+ onViolation?: (error: ZodValidationError) => void;
118
+ };
119
+
120
+ /**
121
+ * Schema-validation middleware for the generated client: \`use(zodValidation())\`.
122
+ * Request bodies are validated before any network call and throw on failure; successful
123
+ * JSON responses are validated against the operation's response schema and WARN by
124
+ * default (see \`ZodValidationOptions.response\`). Operations without a schema pass
125
+ * through untouched. Payloads are never mutated unless \`stripRequestBodies\` is set.
126
+ */
127
+ export function zodValidation(options: ZodValidationOptions = {}) {
128
+ const { request = true, stripRequestBodies = false, response = "warn", onViolation } = options;
129
+ const report = onViolation ?? ((error: ZodValidationError) => console.warn(error.message));
130
+ return {
131
+ onRequest(context: { body?: unknown; operation: { id: string } }): void {
132
+ if ((!request && !stripRequestBodies) || context.body === undefined) return;
133
+ const schema = schemaIndex[context.operation.id]?.request;
134
+ if (!schema) return;
135
+ const result = schema.safeParse(context.body);
136
+ if (!result.success) {
137
+ throw new ZodValidationError(
138
+ context.operation.id,
139
+ "request",
140
+ result.error.issues,
141
+ flattenIssues(result.error.issues, context.body)
142
+ );
143
+ }
144
+ // zod object schemas drop undeclared keys during parsing, so the parsed value
145
+ // IS the declared shape (intersections keep their own zod semantics).
146
+ if (stripRequestBodies) context.body = result.data;
147
+ },
148
+ async onResponse(incoming: Response, context: { operation: { id: string } }): Promise<void> {
149
+ if (response === false || !incoming.ok) return;
150
+ const schema = schemaIndex[context.operation.id]?.response;
151
+ if (!schema) return;
152
+ const contentType = (incoming.headers.get("content-type") ?? "").toLowerCase();
153
+ if (!contentType.includes("json")) return;
154
+ const payload: unknown = await incoming.clone().json();
155
+ const result = schema.safeParse(payload);
156
+ if (!result.success) {
157
+ const error = new ZodValidationError(
158
+ context.operation.id,
159
+ "response",
160
+ result.error.issues,
161
+ flattenIssues(result.error.issues, payload)
162
+ );
163
+ if (response === "throw") throw error;
164
+ report(error);
165
+ }
166
+ },
167
+ };
168
+ }`;function $(e){let r=new Map(e.schemas.map(o=>[o.name,o.schema])),t=j(e,r);if(e.schemas.length===0&&t.length===0)return"";let n=['import { z } from "zod";',...e.schemas.map(o=>`export const ${f(o.name)} = ${u(o.schema,r)};`)];return t.length===0?n.join(`
169
+
170
+ `):(n.push(Z(t)),`${n.join(`
171
+
172
+ `)}
173
+ ${I}
174
+ `)}var T=({model:e,output:r,banner:t})=>{let n=$(e);if(n==="")return[];let o=t.map(s=>`// ${s}`).join(`
175
+ `);return[{path:M(r.dir,`${r.stem}.zod.ts`),content:`${o}
176
+
177
+ ${n}`}]};export{T as zodGenerator};
@@ -0,0 +1,137 @@
1
+ # Writing custom client generators
2
+
3
+ A generator is a plain module: `(input) => GeneratedFile[]`. It receives the
4
+ language-agnostic API model and returns files — in ANY output language. It runs
5
+ in the same pass as the built-ins; select it by path in `redocly.yaml`:
6
+
7
+ ```yaml
8
+ client:
9
+ generators: [typescript, ./generators/my-generator.mjs]
10
+ ```
11
+
12
+ ## The contract
13
+
14
+ ```js
15
+ /** @type {import('@redocly/client-generator').CustomGenerator} */
16
+ export default {
17
+ name: 'my-generator',
18
+ run({ model, output, outputMode, emit }) {
19
+ return [{ path: output.path.replace(/\.ts$/, '.mine.txt'), content: '…' }];
20
+ },
21
+ // Optional: one idiomatic call snippet per operation for docs (x-codeSamples),
22
+ // collected into an overlay file when `client.codeSamples: true` is set.
23
+ sample(operation, { model, emit }) {
24
+ return { lang: 'python', source: '…' };
25
+ },
26
+ // Optional: the reference page for what `run` emits, written when `client.docs` (or
27
+ // --docs) is on. Same `{ path, content }` shape as `run`; `renderReferencePage` gives
28
+ // the standard layout and takes `sample` for its snippets. A generator documents itself.
29
+ docs({ model, output, emit }) {
30
+ return [{ path: output.path.replace(/\.ts$/, '.mine.md'), content: '…' }];
31
+ },
32
+ };
33
+ ```
34
+
35
+ ## Declaring options
36
+
37
+ A generator that needs configuration declares it as a schema; `run` then receives
38
+ `options` already validated, with defaults applied:
39
+
40
+ ```js
41
+ export default {
42
+ name: 'permissions-matrix',
43
+ options: {
44
+ type: 'object',
45
+ properties: { groupBy: { enum: ['tag', 'path'], default: 'tag' } },
46
+ additionalProperties: false,
47
+ },
48
+ run({ model, output, options }) {
49
+ return [
50
+ { path: output.path.replace(/\.ts$/, '.permissions.md'), content: render(options.groupBy) },
51
+ ];
52
+ },
53
+ };
54
+ ```
55
+
56
+ Users set them per generator name:
57
+
58
+ ```yaml
59
+ client:
60
+ generators: [typescript, ./generators/permissions-matrix.mjs]
61
+ options:
62
+ permissions-matrix:
63
+ groupBy: path
64
+ ```
65
+
66
+ The supported subset is a top-level `type: 'object'` with `properties`, `required`, and
67
+ `additionalProperties`; each property is a scalar (`string`/`number`/`boolean`), an
68
+ `enum`, or an array of scalars, and may carry a `default` and a `description`. Don't
69
+ validate options inside `run` — an unknown key, a wrong type, a value outside an `enum`,
70
+ or a missing `required` key already fails generation before `run` is called.
71
+
72
+ Rules: output is deterministic (same description → same bytes); never add
73
+ dependencies to the generated client; **never hand-edit generated output** —
74
+ edit this generator and regenerate. Emitted file paths must stay inside the
75
+ `--output` directory (subdirectories are fine) — escapes are rejected.
76
+ Optionally declare `requiresGenerator` — the `@redocly/client-generator` version
77
+ range you wrote this against (`'^1.2.0'`, `'~1.2.0'`, `'>=1.2.0'`, or an exact
78
+ version). A CLI outside the range then fails with the fix path instead of feeding
79
+ your generator an unexpected model shape. Ejected generators carry it
80
+ automatically; hand-written ones without it are taken as current.
81
+
82
+ ## The model (IR)
83
+
84
+ `model.services[].operations[]` — each operation carries `name`, `specName`,
85
+ `method`, `path`, `tags`, `pathParams`/`queryParams`/`headerParams`/`cookieParams`,
86
+ `requestBody`, `successResponses`/`errorResponses` (each with a `schema`), and
87
+ `security`. `model.schemas` holds the named schemas. Every schema is a
88
+ discriminated union on `kind`: `scalar`, `array`, `object`, `record`, `ref`,
89
+ `literal`, `enum`, `union` (optionally with a discriminator), `intersection`
90
+ (allOf), `null`, `unknown`, `omit`.
91
+
92
+ ## Helpers (import from '@redocly/client-generator')
93
+
94
+ | Helper | Use |
95
+ | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
96
+ | `flattenAllOf(schema, model)` | The merged property view of allOf compositions — languages without intersection types render this. |
97
+ | `deref(schema, model)` | Follow a `ref` chain to the schema it names (cycle-guarded). |
98
+ | `jsonSuccessSchema(op)` / `sseResponse(op)` | The primary JSON success schema; the `text/event-stream` response when the operation streams. |
99
+ | `isMultipartBody(op)` | Whether the request body is multipart. |
100
+ | `serverUrlParts(server)` | A server-URL template as literal/variable parts, ready for any concatenation syntax. |
101
+ | `securityRequirements(op, model)` | The operation's security as OR-alternatives of AND-sets, denormalized against the declared schemes. |
102
+ | `paginationItemSchema(pageSchema, itemsPointer, model)` | The raw element schema behind a pagination rule's `items` pointer — a `ref` element keeps its name. |
103
+ | `discriminatorCases(schema, model)` | `{ property, cases }` dispatch table for discriminated unions. |
104
+ | `isNullable(schema)` / `unwrapNullable(schema)` | Detect and strip `null` union members (`Optional[T]`, pointers, `Option<T>`). |
105
+ | `enumValues(schema)` | Values plus SCREAMING_SNAKE member-name suggestions. |
106
+ | `headerCoerceType(schema, model)` | Response-header coerce hint (`integer`/`number`/`boolean`/`string`) through refs, nullables, and allOf wrappers. |
107
+ | `casing` / `identifierFor(name, { style, reserved })` | camel/pascal/snake/screaming; keyword-safe identifiers (`RESERVED_WORDS.python/go/typescript` shipped). |
108
+ | `uniqueIdentifiers(names, { style, reserved, taken })` | The same, made unique among themselves and among names you already took — for a signature that takes one argument per parameter. |
109
+ | `Printer` | Indentation-aware text builder — no manual whitespace bookkeeping. |
110
+ | `docText(description)` | Description as trimmed lines for any comment syntax. |
111
+ | `schemaAtPointer(schema, pointer, model)` | Resolve an RFC 6901 JSON pointer over a schema (through refs and allOf) — e.g. a pagination `items` pointer to its element type. |
112
+ | `paginationRuleFor(op, config)` | The pagination rule that applies to an operation (per-op config > extension > fitting convention), normalized. |
113
+ | `renderReferencePage(model, options)` | The Markdown reference page a generator's `docs` hook returns — your `sample` hook supplies its call snippets. |
114
+ | `NotSupportedError` | Throw it to reject an option the generator can't honor — the CLI prints the message as a user error, not a crash. |
115
+ | `AUTHORING_HELPER_NAMES` | The list of the above (introspection). |
116
+
117
+ Worked example: the built-in `python` generator
118
+ (`packages/client-generator/src/generators/python/index.ts` in the Redocly CLI repo) is
119
+ authored with exactly this toolkit and nothing else — models via `flattenAllOf`/
120
+ `enumValues`/`discriminatorCases`, all code through `Printer`, every name through
121
+ `identifierFor(..., RESERVED_WORDS.python)`.
122
+
123
+ A generator that emits TypeScript may additionally use the source-text renderers from
124
+ `@redocly/client-generator/generate` — `tsType` (schema → type), `tsJsdoc`, `codeLiteral`,
125
+ `operationSignature`, `pascalCase`, `safeIdent`. There is no AST toolkit and no
126
+ `typescript` dependency: every generator prints text through `Printer`.
127
+
128
+ ## The loop
129
+
130
+ 1. Edit the generator.
131
+ 2. Run `redocly generate-client`.
132
+ 3. Inspect `git diff` of the generated output.
133
+ 4. Repeat. Generated files are never hand-edited.
134
+
135
+ If you had to work around a **missing helper** or a wrong default, tell the user
136
+ and suggest filing an issue at https://github.com/Redocly/redocly-cli/issues —
137
+ include the generator snippet and the helper you expected to exist.
@@ -0,0 +1,224 @@
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-docs emitter: renders the Markdown reference for the generated CLI from the
6
+ // SAME command table `runCli` dispatches on, and the same `groupSlug`/`constantCase` the
7
+ // runtime addresses groups and reads credentials with. A second model would drift from
8
+ // the tool the first time either side changed.
9
+
10
+ import {
11
+ type CliCommand,
12
+ type CliFlag,
13
+ constantCase,
14
+ groupSlug,
15
+ Printer,
16
+ } from '@redocly/client-generator';
17
+
18
+ export type CliDocsOptions = {
19
+ /** Page heading. */
20
+ title: string;
21
+ /** Emit YAML front matter carrying the title, for docs sites that expect it. */
22
+ frontmatter: boolean;
23
+ /** The generated file's stem: what the page calls the command, and what its credential
24
+ * variables derive from. A reader who installs it under another bin name renames only
25
+ * the command — the variables are fixed at generation. */
26
+ name: string;
27
+ /** Auth schemes the description declares, in the order the CLI resolves them. */
28
+ schemes: Array<{ key: string; kind: 'bearer' | 'basic' | 'apiKey' }>;
29
+ };
30
+
31
+ /** Table-cell-safe text: one line, and pipes/backslashes escaped so they don't alter columns/escaping. */
32
+ function cell(text: string | undefined): string {
33
+ return (text ?? '').replace(/\s+/g, ' ').trim().replace(/\\/g, '\\\\').replace(/\|/g, '\\|');
34
+ }
35
+
36
+ /** How a command is typed at the prompt: `<group-slug> <name>`, or just `<name>`. */
37
+ function address(command: CliCommand): string {
38
+ return [command.group === undefined ? undefined : groupSlug(command.group), command.name]
39
+ .filter(Boolean)
40
+ .join(' ');
41
+ }
42
+
43
+ function usageLine(name: string, command: CliCommand): string {
44
+ const words = [
45
+ name,
46
+ address(command),
47
+ ...command.positionals.map((positional) => `<${positional.name}>`),
48
+ ...command.flags.filter((flag) => flag.required).map((flag) => `--${flag.name} <${flag.type}>`),
49
+ ...(command.body ? [command.body.required ? "--json '<json>'" : "[--json '<json>']"] : []),
50
+ ];
51
+ return words.filter((word) => word !== '').join(' ');
52
+ }
53
+
54
+ function writeFlagTable(printer: Printer, flags: CliFlag[]): void {
55
+ printer.line('| Flag | Type | Required | Description |');
56
+ printer.line('| ---- | ---- | -------- | ----------- |');
57
+ for (const flag of flags) {
58
+ const description = [
59
+ cell(flag.description),
60
+ flag.enum === undefined
61
+ ? ''
62
+ : `One of ${flag.enum.map((value) => `\`${value}\``).join(', ')}.`,
63
+ flag.type === 'array' ? 'Repeat the flag for multiple values.' : '',
64
+ ]
65
+ .filter((part) => part !== '')
66
+ .join(' ');
67
+ printer.line(
68
+ `| \`--${flag.name}\` | ${flag.type} | ${flag.required ? 'yes' : 'no'} | ${description} |`
69
+ );
70
+ }
71
+ printer.blank();
72
+ }
73
+
74
+ function writeCommand(printer: Printer, command: CliCommand, options: CliDocsOptions): void {
75
+ printer.line(`### \`${address(command)}\``);
76
+ printer.blank();
77
+ if (command.summary !== undefined) {
78
+ printer.line(cell(command.summary));
79
+ printer.blank();
80
+ }
81
+ printer.line(`\`${command.method} ${command.path}\``);
82
+ printer.blank();
83
+ printer.line('```sh');
84
+ printer.line(usageLine(options.name, command));
85
+ printer.line('```');
86
+ printer.blank();
87
+ if (command.positionals.length > 0) {
88
+ printer.line('| Argument | Description |');
89
+ printer.line('| -------- | ----------- |');
90
+ for (const positional of command.positionals) {
91
+ printer.line(`| \`<${positional.name}>\` | ${cell(positional.description)} |`);
92
+ }
93
+ printer.blank();
94
+ }
95
+ if (command.flags.length > 0) writeFlagTable(printer, command.flags);
96
+ const notes = [
97
+ command.body === undefined
98
+ ? ''
99
+ : `Takes a JSON body${command.body.required ? ' (required)' : ''}: \`--json '<json>'\`, \`--json @file.json\`, or \`--json @-\` for stdin.`,
100
+ command.unsupportedBody === undefined
101
+ ? ''
102
+ : `Takes a \`${command.unsupportedBody}\` body, which the CLI cannot build — call this operation through the generated client instead.`,
103
+ command.paginated === true
104
+ ? 'Paginated: `--page-all` follows every page, printing one JSON page per line.'
105
+ : '',
106
+ command.sse === true ? 'Streams server-sent events as one JSON object per line.' : '',
107
+ command.blob === true ? 'Returns binary content, so `--output <path>` is required.' : '',
108
+ ].filter((note) => note !== '');
109
+ for (const note of notes) printer.line(note);
110
+ if (notes.length > 0) printer.blank();
111
+ }
112
+
113
+ /** The whole page: heading, global flags, credentials, exit codes, then every command. */
114
+ export function renderCliDocs(commands: CliCommand[], options: CliDocsOptions): string {
115
+ const printer = new Printer();
116
+ if (options.frontmatter) {
117
+ printer.line('---');
118
+ printer.line(`title: ${options.title}`);
119
+ printer.line('---');
120
+ printer.blank();
121
+ }
122
+ printer.line(`# ${options.title}`);
123
+ printer.blank();
124
+ printer.line(
125
+ `Generated command-line reference for \`${options.name}\`, produced from the API description by \`redocly generate-client\`.`
126
+ );
127
+ printer.line('Re-run generation to update it — this file is not hand-edited.');
128
+ printer.blank();
129
+
130
+ printer.line('## Usage');
131
+ printer.blank();
132
+ printer.line('```sh');
133
+ printer.line(`${options.name} <command> [flags]`);
134
+ printer.line(`${options.name} --help`);
135
+ printer.line(`${options.name} schema <command> # request/response schemas`);
136
+ printer.line('```');
137
+ printer.blank();
138
+ printer.line(
139
+ 'Install the file under any `bin` name: the command takes that name, and the credential variables below do not change.'
140
+ );
141
+ printer.blank();
142
+
143
+ printer.line('## Global flags');
144
+ printer.blank();
145
+ printer.line('| Flag | Description |');
146
+ printer.line('| ---- | ----------- |');
147
+ // `--token` mirrors the CLI itself: without a bearer scheme the tool rejects the flag,
148
+ // so the reference must not list it.
149
+ const hasBearer = options.schemes.some((scheme) => scheme.kind === 'bearer');
150
+ for (const [flag, description] of [
151
+ ['--server-url <url>', 'Override the server URL included in the client.'],
152
+ ['--format <json\\|ndjson>', 'Output format.'],
153
+ ['--dry-run', 'Print the prepared request, credentials redacted, without sending it.'],
154
+ ['--page-all', 'Follow pagination, printing one JSON page per line.'],
155
+ ['--output <path>', 'Write the response body to a file. Required for binary responses.'],
156
+ ...(hasBearer ? [['--token <token>', 'Bearer token, overriding the environment.']] : []),
157
+ ['--json <json\\|@file\\|@->', 'Request body, inline or from a file or stdin.'],
158
+ ] as const) {
159
+ printer.line(`| \`${flag}\` | ${description} |`);
160
+ }
161
+ printer.blank();
162
+
163
+ const prefix = constantCase(options.name);
164
+ printer.line('## Credentials');
165
+ printer.blank();
166
+ if (options.schemes.length === 0) {
167
+ printer.line('The description declares no security schemes, so no credentials are read.');
168
+ } else {
169
+ printer.line('Credentials come from the environment:');
170
+ printer.blank();
171
+ printer.line('| Scheme | Variable |');
172
+ printer.line('| ------ | -------- |');
173
+ for (const scheme of options.schemes) {
174
+ const variable =
175
+ scheme.kind === 'bearer'
176
+ ? `\`${prefix}_TOKEN\` (or \`--token\`)`
177
+ : scheme.kind === 'basic'
178
+ ? `\`${prefix}_USERNAME\` and \`${prefix}_PASSWORD\``
179
+ : `\`${prefix}_API_KEY_${constantCase(scheme.key)}\``;
180
+ printer.line(`| ${scheme.kind} (\`${scheme.key}\`) | ${variable} |`);
181
+ }
182
+ }
183
+ printer.blank();
184
+
185
+ printer.line('## Exit codes');
186
+ printer.blank();
187
+ printer.line('| Code | Meaning |');
188
+ printer.line('| ---- | ------- |');
189
+ for (const [code, meaning] of [
190
+ [0, 'success'],
191
+ [1, 'API error (status other than 401 or 403)'],
192
+ [2, 'auth error (401 or 403)'],
193
+ [3, 'validation error'],
194
+ [4, 'usage error (unknown command or flag, bad `--json`)'],
195
+ ] as const) {
196
+ printer.line(`| ${code} | ${meaning} |`);
197
+ }
198
+ printer.blank();
199
+ printer.line('Errors print one JSON object to stderr, so stdout stays clean for piping.');
200
+ printer.blank();
201
+
202
+ // One section per tag, in the order the description declares them, then the untagged
203
+ // commands — the same order `--help` lists them in.
204
+ const groups = [...new Set(commands.map((command) => command.group))];
205
+ for (const group of groups) {
206
+ const inGroup = commands.filter((command) => command.group === group);
207
+ if (group === undefined) {
208
+ printer.line('## Commands');
209
+ printer.blank();
210
+ } else {
211
+ printer.line(`## ${group}`);
212
+ printer.blank();
213
+ printer.line(`Addressed as \`${options.name} ${groupSlug(group)} <command>\`.`);
214
+ printer.blank();
215
+ }
216
+ for (const command of inGroup) writeCommand(printer, command, options);
217
+ }
218
+ return (
219
+ printer
220
+ .toString()
221
+ .replace(/\n{3,}/g, '\n\n')
222
+ .trimEnd() + '\n'
223
+ );
224
+ }
@@ -0,0 +1,21 @@
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 engine's embeddable source, snapshotted at prepare time (see
6
+ // scripts/generate-runtime-sources.mjs).
7
+
8
+ import {
9
+ RUNTIME_SOURCES,
10
+ RUNTIME_SOURCES_STRIPPED,
11
+ } from '@redocly/client-generator/runtime-sources';
12
+
13
+ /** The cli engine (`runCli` + types) stripped for embedding into `<stem>.cli.ts`. */
14
+ export function embedCliRuntime(): string {
15
+ return RUNTIME_SOURCES_STRIPPED['cli.ts'];
16
+ }
17
+
18
+ /** The cli engine RAW, for `runtime: 'module'` (written as `runtime/cli.ts`). */
19
+ export function cliRuntimeSource(): string {
20
+ return RUNTIME_SOURCES['cli.ts'];
21
+ }