@lovrozagar/honey 0.6.5 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +514 -192
- package/bin/honey.js +25 -0
- package/dist/accepts.d.ts +7 -1
- package/dist/accepts.d.ts.map +1 -1
- package/dist/accepts.js +126 -0
- package/dist/accepts.js.map +1 -0
- package/dist/body-kind.d.ts +26 -0
- package/dist/body-kind.d.ts.map +1 -0
- package/dist/body-kind.js +70 -0
- package/dist/body-kind.js.map +1 -0
- package/dist/body-limit.d.ts +5 -1
- package/dist/body-limit.d.ts.map +1 -1
- package/dist/body-limit.js +126 -0
- package/dist/body-limit.js.map +1 -0
- package/dist/build/index.d.ts +19 -3
- package/dist/build/index.d.ts.map +1 -1
- package/dist/build/index.js +173 -0
- package/dist/build/index.js.map +1 -0
- package/dist/cli-args.d.ts +33 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +102 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli-go/auth.go +26 -0
- package/dist/cli-go/config.go +192 -0
- package/dist/cli-go/errors.go +75 -0
- package/dist/cli-go/multipart.go +79 -0
- package/dist/cli-go/output.go +163 -0
- package/dist/cli-go/root.go +30 -0
- package/dist/cli-go/stream.go +88 -0
- package/dist/cli-go/version.go +5 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +209 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/defaults.js +48 -0
- package/dist/client/defaults.js.map +1 -0
- package/dist/client/error.d.ts +35 -0
- package/dist/client/error.d.ts.map +1 -1
- package/dist/client/error.js +71 -0
- package/dist/client/error.js.map +1 -0
- package/dist/client/http.d.ts +67 -8
- package/dist/client/http.d.ts.map +1 -1
- package/dist/client/http.js +571 -0
- package/dist/client/http.js.map +1 -0
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +95 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/path.d.ts +26 -0
- package/dist/client/path.d.ts.map +1 -0
- package/dist/client/path.js +163 -0
- package/dist/client/path.js.map +1 -0
- package/dist/client/redirect-policy.d.ts +12 -0
- package/dist/client/redirect-policy.d.ts.map +1 -0
- package/dist/client/redirect-policy.js +20 -0
- package/dist/client/redirect-policy.js.map +1 -0
- package/dist/client/sdk.d.ts +36 -0
- package/dist/client/sdk.d.ts.map +1 -1
- package/dist/client/sdk.js +292 -0
- package/dist/client/sdk.js.map +1 -0
- package/dist/client/sse.d.ts +13 -0
- package/dist/client/sse.d.ts.map +1 -1
- package/dist/client/sse.js +129 -0
- package/dist/client/sse.js.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/ws.d.ts +9 -2
- package/dist/client/ws.d.ts.map +1 -1
- package/dist/client/ws.js +112 -0
- package/dist/client/ws.js.map +1 -0
- package/dist/client-go/errors.go +236 -0
- package/dist/client-go/export.go +44 -0
- package/dist/client-go/invalidation.go +371 -0
- package/dist/client-go/nullable.go +62 -0
- package/dist/client-go/realtime.go +354 -0
- package/dist/client-go/result.go +20 -0
- package/dist/client-go/runtime.go +975 -0
- package/dist/client-go/sse.go +159 -0
- package/dist/client-go/transport.go +495 -0
- package/dist/client-go/ws.go +128 -0
- package/dist/client-info.d.ts +36 -0
- package/dist/client-info.d.ts.map +1 -0
- package/dist/client-info.js +53 -0
- package/dist/client-info.js.map +1 -0
- package/dist/client-mcp/README.md +10 -0
- package/dist/client-mcp/server.ts +221 -0
- package/dist/client-mcp/types.ts +19 -0
- package/dist/client-python/_errors.py +115 -0
- package/dist/client-python/_invalidation.py +494 -0
- package/dist/client-python/_realtime.py +179 -0
- package/dist/client-python/_runtime.py +860 -0
- package/dist/client-python/_sse.py +155 -0
- package/dist/client-python/_transport.py +366 -0
- package/dist/client-python/_ws.py +159 -0
- package/dist/client-python/py.typed +0 -0
- package/dist/client-rust/errors.rs +220 -0
- package/dist/client-rust/invalidation.rs +412 -0
- package/dist/client-rust/invalidation_sync.rs +93 -0
- package/dist/client-rust/nullable.rs +17 -0
- package/dist/client-rust/realtime.rs +702 -0
- package/dist/client-rust/result.rs +22 -0
- package/dist/client-rust/runtime.rs +980 -0
- package/dist/client-rust/runtime_sync.rs +484 -0
- package/dist/client-rust/sse.rs +188 -0
- package/dist/client-rust/ws.rs +167 -0
- package/dist/codegen-go-cli.d.ts.map +1 -1
- package/dist/codegen-go-cli.js +964 -0
- package/dist/codegen-go-cli.js.map +1 -0
- package/dist/codegen-go.d.ts +15 -3
- package/dist/codegen-go.d.ts.map +1 -1
- package/dist/codegen-go.js +872 -0
- package/dist/codegen-go.js.map +1 -0
- package/dist/codegen-hash.js +9 -0
- package/dist/codegen-hash.js.map +1 -0
- package/dist/codegen-ir.d.ts +69 -2
- package/dist/codegen-ir.d.ts.map +1 -1
- package/dist/codegen-ir.js +661 -0
- package/dist/codegen-ir.js.map +1 -0
- package/dist/codegen-lang.d.ts +65 -0
- package/dist/codegen-lang.d.ts.map +1 -0
- package/dist/codegen-lang.js +526 -0
- package/dist/codegen-lang.js.map +1 -0
- package/dist/codegen-load.d.ts +31 -0
- package/dist/codegen-load.d.ts.map +1 -0
- package/dist/codegen-load.js +138 -0
- package/dist/codegen-load.js.map +1 -0
- package/dist/codegen-loaders.js +55 -0
- package/dist/codegen-loaders.js.map +1 -0
- package/dist/codegen-mcp.d.ts.map +1 -1
- package/dist/codegen-mcp.js +368 -0
- package/dist/codegen-mcp.js.map +1 -0
- package/dist/codegen-python.d.ts +13 -1
- package/dist/codegen-python.d.ts.map +1 -1
- package/dist/codegen-python.js +828 -0
- package/dist/codegen-python.js.map +1 -0
- package/dist/codegen-route-tree-intern.d.ts +15 -5
- package/dist/codegen-route-tree-intern.d.ts.map +1 -1
- package/dist/codegen-route-tree-intern.js +221 -0
- package/dist/codegen-route-tree-intern.js.map +1 -0
- package/dist/codegen-rust.d.ts.map +1 -1
- package/dist/codegen-rust.js +1293 -0
- package/dist/codegen-rust.js.map +1 -0
- package/dist/codegen-sanitize.d.ts.map +1 -1
- package/dist/codegen-sanitize.js +56 -0
- package/dist/codegen-sanitize.js.map +1 -0
- package/dist/codegen-schema-naming.d.ts +5 -0
- package/dist/codegen-schema-naming.d.ts.map +1 -1
- package/dist/codegen-schema-naming.js +150 -0
- package/dist/codegen-schema-naming.js.map +1 -0
- package/dist/codegen-sdk-model.d.ts +71 -0
- package/dist/codegen-sdk-model.d.ts.map +1 -0
- package/dist/codegen-sdk-model.js +179 -0
- package/dist/codegen-sdk-model.js.map +1 -0
- package/dist/codegen.d.ts +41 -23
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +4610 -0
- package/dist/codegen.js.map +1 -0
- package/dist/context.d.ts +36 -6
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +242 -0
- package/dist/context.js.map +1 -0
- package/dist/cookie-sign.d.ts +16 -2
- package/dist/cookie-sign.d.ts.map +1 -1
- package/dist/cookie-sign.js +104 -0
- package/dist/cookie-sign.js.map +1 -0
- package/dist/cookie.d.ts +12 -0
- package/dist/cookie.d.ts.map +1 -1
- package/dist/cookie.js +138 -0
- package/dist/cookie.js.map +1 -0
- package/dist/cors.d.ts +1 -0
- package/dist/cors.d.ts.map +1 -1
- package/dist/cors.js +87 -0
- package/dist/cors.js.map +1 -0
- package/dist/crypto.d.ts +6 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +38 -0
- package/dist/crypto.js.map +1 -0
- package/dist/csrf.d.ts +19 -0
- package/dist/csrf.d.ts.map +1 -1
- package/dist/csrf.js +79 -0
- package/dist/csrf.js.map +1 -0
- package/dist/curl-logger.d.ts +14 -3
- package/dist/curl-logger.d.ts.map +1 -1
- package/dist/curl-logger.js +199 -0
- package/dist/curl-logger.js.map +1 -0
- package/dist/detect-runtime.js +13 -0
- package/dist/detect-runtime.js.map +1 -0
- package/dist/dict.d.ts +11 -0
- package/dist/dict.d.ts.map +1 -0
- package/dist/dict.js +13 -0
- package/dist/dict.js.map +1 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +44 -0
- package/dist/error.js.map +1 -0
- package/dist/errors.js +39 -0
- package/dist/errors.js.map +1 -0
- package/dist/etag.d.ts +2 -0
- package/dist/etag.d.ts.map +1 -1
- package/dist/etag.js +82 -0
- package/dist/etag.js.map +1 -0
- package/dist/feature-detect.d.ts +25 -0
- package/dist/feature-detect.d.ts.map +1 -0
- package/dist/feature-detect.js +144 -0
- package/dist/feature-detect.js.map +1 -0
- package/dist/feature-load.js +36 -0
- package/dist/feature-load.js.map +1 -0
- package/dist/feature-slots.d.ts +44 -0
- package/dist/feature-slots.d.ts.map +1 -0
- package/dist/feature-slots.js +48 -0
- package/dist/feature-slots.js.map +1 -0
- package/dist/fetch-request.d.ts +10 -0
- package/dist/fetch-request.d.ts.map +1 -0
- package/dist/fetch-request.js +13 -0
- package/dist/fetch-request.js.map +1 -0
- package/dist/gen-process.d.ts +15 -0
- package/dist/gen-process.d.ts.map +1 -0
- package/dist/gen-process.js +43 -0
- package/dist/gen-process.js.map +1 -0
- package/dist/gen-write.d.ts +19 -0
- package/dist/gen-write.d.ts.map +1 -0
- package/dist/gen-write.js +120 -0
- package/dist/gen-write.js.map +1 -0
- package/dist/glob.d.ts +21 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +115 -0
- package/dist/glob.js.map +1 -0
- package/dist/go-type-emitter.d.ts +18 -4
- package/dist/go-type-emitter.d.ts.map +1 -1
- package/dist/go-type-emitter.js +594 -0
- package/dist/go-type-emitter.js.map +1 -0
- package/dist/honey-response.d.ts.map +1 -1
- package/dist/honey-response.js +338 -0
- package/dist/honey-response.js.map +1 -0
- package/dist/i18n-slot.d.ts +8 -0
- package/dist/i18n-slot.d.ts.map +1 -1
- package/dist/i18n-slot.js +14 -0
- package/dist/i18n-slot.js.map +1 -0
- package/dist/i18n.d.ts +3 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +250 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +410 -149
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2324 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +3 -5
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +140 -0
- package/dist/init.js.map +1 -0
- package/dist/input.js +4 -0
- package/dist/input.js.map +1 -0
- package/dist/invalidate-check.d.ts +11 -0
- package/dist/invalidate-check.d.ts.map +1 -1
- package/dist/invalidate-check.js +163 -0
- package/dist/invalidate-check.js.map +1 -0
- package/dist/invoke-user.d.ts +35 -0
- package/dist/invoke-user.d.ts.map +1 -0
- package/dist/invoke-user.js +73 -0
- package/dist/invoke-user.js.map +1 -0
- package/dist/ip-restrict.d.ts +13 -1
- package/dist/ip-restrict.d.ts.map +1 -1
- package/dist/ip-restrict.js +60 -0
- package/dist/ip-restrict.js.map +1 -0
- package/dist/ip.d.ts +39 -0
- package/dist/ip.d.ts.map +1 -0
- package/dist/ip.js +209 -0
- package/dist/ip.js.map +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +113 -0
- package/dist/logger.js.map +1 -0
- package/dist/media-type.d.ts +30 -0
- package/dist/media-type.d.ts.map +1 -0
- package/dist/media-type.js +97 -0
- package/dist/media-type.js.map +1 -0
- package/dist/meta-spec-merge.d.ts +32 -0
- package/dist/meta-spec-merge.d.ts.map +1 -0
- package/dist/meta-spec-merge.js +68 -0
- package/dist/meta-spec-merge.js.map +1 -0
- package/dist/meta-spec.d.ts +16 -3
- package/dist/meta-spec.d.ts.map +1 -1
- package/dist/meta-spec.js +535 -0
- package/dist/meta-spec.js.map +1 -0
- package/dist/middleware.d.ts +23 -6
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +253 -0
- package/dist/middleware.js.map +1 -0
- package/dist/node-request.d.ts +57 -12
- package/dist/node-request.d.ts.map +1 -1
- package/dist/node-request.js +407 -0
- package/dist/node-request.js.map +1 -0
- package/dist/node.d.ts +20 -3
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +396 -0
- package/dist/node.js.map +1 -0
- package/dist/openapi/collect.d.ts +6 -3
- package/dist/openapi/collect.d.ts.map +1 -1
- package/dist/openapi/collect.js +30 -0
- package/dist/openapi/collect.js.map +1 -0
- package/dist/openapi/docs-page.d.ts +42 -0
- package/dist/openapi/docs-page.d.ts.map +1 -0
- package/dist/openapi/docs-page.js +80 -0
- package/dist/openapi/docs-page.js.map +1 -0
- package/dist/openapi/document.d.ts +4 -0
- package/dist/openapi/document.d.ts.map +1 -1
- package/dist/openapi/document.js +785 -0
- package/dist/openapi/document.js.map +1 -0
- package/dist/openapi/json-schema-slot.d.ts +7 -0
- package/dist/openapi/json-schema-slot.d.ts.map +1 -1
- package/dist/openapi/json-schema-slot.js +38 -0
- package/dist/openapi/json-schema-slot.js.map +1 -0
- package/dist/openapi/register.d.ts.map +1 -1
- package/dist/openapi/register.js +20 -0
- package/dist/openapi/register.js.map +1 -0
- package/dist/openapi/scalar.d.ts +3 -1
- package/dist/openapi/scalar.d.ts.map +1 -1
- package/dist/openapi/scalar.js +21 -0
- package/dist/openapi/scalar.js.map +1 -0
- package/dist/openapi/serve.d.ts +9 -0
- package/dist/openapi/serve.d.ts.map +1 -0
- package/dist/openapi/serve.js +36 -0
- package/dist/openapi/serve.js.map +1 -0
- package/dist/openapi/spec-factory.d.ts +30 -1
- package/dist/openapi/spec-factory.d.ts.map +1 -1
- package/dist/openapi/spec-factory.js +52 -0
- package/dist/openapi/spec-factory.js.map +1 -0
- package/dist/openapi/spec.d.ts +14 -5
- package/dist/openapi/spec.d.ts.map +1 -1
- package/dist/openapi/spec.js +50 -0
- package/dist/openapi/spec.js.map +1 -0
- package/dist/openapi/swagger.d.ts +3 -1
- package/dist/openapi/swagger.d.ts.map +1 -1
- package/dist/openapi/swagger.js +22 -0
- package/dist/openapi/swagger.js.map +1 -0
- package/dist/pattern.d.ts +71 -0
- package/dist/pattern.d.ts.map +1 -0
- package/dist/pattern.js +239 -0
- package/dist/pattern.js.map +1 -0
- package/dist/peer.d.ts +17 -0
- package/dist/peer.d.ts.map +1 -0
- package/dist/peer.js +50 -0
- package/dist/peer.js.map +1 -0
- package/dist/plugin.d.ts +67 -16
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +649 -0
- package/dist/plugin.js.map +1 -0
- package/dist/powered-by.d.ts.map +1 -1
- package/dist/powered-by.js +11 -0
- package/dist/powered-by.js.map +1 -0
- package/dist/pretty-json.d.ts.map +1 -1
- package/dist/pretty-json.js +47 -0
- package/dist/pretty-json.js.map +1 -0
- package/dist/producer-stream.d.ts +47 -0
- package/dist/producer-stream.d.ts.map +1 -0
- package/dist/producer-stream.js +267 -0
- package/dist/producer-stream.js.map +1 -0
- package/dist/proxy.d.ts +47 -8
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +274 -0
- package/dist/proxy.js.map +1 -0
- package/dist/python-type-emitter.d.ts +14 -1
- package/dist/python-type-emitter.d.ts.map +1 -1
- package/dist/python-type-emitter.js +157 -0
- package/dist/python-type-emitter.js.map +1 -0
- package/dist/realtime/bus.d.ts +17 -1
- package/dist/realtime/bus.d.ts.map +1 -1
- package/dist/realtime/bus.js +112 -0
- package/dist/realtime/bus.js.map +1 -0
- package/dist/realtime/register.d.ts +3 -0
- package/dist/realtime/register.d.ts.map +1 -0
- package/dist/realtime/register.js +10 -0
- package/dist/realtime/register.js.map +1 -0
- package/dist/realtime/route.d.ts +65 -26
- package/dist/realtime/route.d.ts.map +1 -1
- package/dist/realtime/route.js +72 -0
- package/dist/realtime/route.js.map +1 -0
- package/dist/realtime/server.d.ts +43 -0
- package/dist/realtime/server.d.ts.map +1 -0
- package/dist/realtime/server.js +255 -0
- package/dist/realtime/server.js.map +1 -0
- package/dist/request-id.d.ts +7 -0
- package/dist/request-id.d.ts.map +1 -1
- package/dist/request-id.js +16 -0
- package/dist/request-id.js.map +1 -0
- package/dist/request-limits.d.ts +3 -0
- package/dist/request-limits.d.ts.map +1 -0
- package/dist/request-limits.js +3 -0
- package/dist/request-limits.js.map +1 -0
- package/dist/request-path.d.ts +33 -0
- package/dist/request-path.d.ts.map +1 -0
- package/dist/request-path.js +178 -0
- package/dist/request-path.js.map +1 -0
- package/dist/request-to-curl.d.ts +7 -1
- package/dist/request-to-curl.d.ts.map +1 -1
- package/dist/request-to-curl.js +48 -0
- package/dist/request-to-curl.js.map +1 -0
- package/dist/response.d.ts +25 -2
- package/dist/response.d.ts.map +1 -1
- package/dist/response.js +313 -0
- package/dist/response.js.map +1 -0
- package/dist/rust-type-emitter.d.ts +14 -2
- package/dist/rust-type-emitter.d.ts.map +1 -1
- package/dist/rust-type-emitter.js +463 -0
- package/dist/rust-type-emitter.js.map +1 -0
- package/dist/secure-headers.d.ts.map +1 -1
- package/dist/secure-headers.js +53 -0
- package/dist/secure-headers.js.map +1 -0
- package/dist/serve-register.d.ts +4 -0
- package/dist/serve-register.d.ts.map +1 -1
- package/dist/serve-register.js +9 -0
- package/dist/serve-register.js.map +1 -0
- package/dist/serve-slot.d.ts +3 -0
- package/dist/serve-slot.d.ts.map +1 -1
- package/dist/serve-slot.js +52 -0
- package/dist/serve-slot.js.map +1 -0
- package/dist/serve.d.ts +18 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +157 -0
- package/dist/serve.js.map +1 -0
- package/dist/server-timing.d.ts.map +1 -1
- package/dist/server-timing.js +51 -0
- package/dist/server-timing.js.map +1 -0
- package/dist/static.d.ts +9 -0
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +102 -0
- package/dist/static.js.map +1 -0
- package/dist/telemetry/otel.d.ts +2 -0
- package/dist/telemetry/otel.d.ts.map +1 -1
- package/dist/telemetry/otel.js +135 -0
- package/dist/telemetry/otel.js.map +1 -0
- package/dist/testing.d.ts +12 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +124 -0
- package/dist/testing.js.map +1 -0
- package/dist/timeout.d.ts +6 -0
- package/dist/timeout.d.ts.map +1 -1
- package/dist/timeout.js +36 -0
- package/dist/timeout.js.map +1 -0
- package/dist/tree.d.ts +133 -36
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +437 -0
- package/dist/tree.js.map +1 -0
- package/dist/trust.d.ts +21 -0
- package/dist/trust.d.ts.map +1 -0
- package/dist/trust.js +100 -0
- package/dist/trust.js.map +1 -0
- package/dist/ts-type-emitter.d.ts.map +1 -1
- package/dist/ts-type-emitter.js +150 -0
- package/dist/ts-type-emitter.js.map +1 -0
- package/dist/type-emitter.d.ts +16 -2
- package/dist/type-emitter.d.ts.map +1 -1
- package/dist/type-emitter.js +729 -0
- package/dist/type-emitter.js.map +1 -0
- package/dist/type-extractor.d.ts +8 -4
- package/dist/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor.js +776 -0
- package/dist/type-extractor.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +96 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +14 -2
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +335 -0
- package/dist/validation.js.map +1 -0
- package/dist/with-headers.d.ts +15 -0
- package/dist/with-headers.d.ts.map +1 -0
- package/dist/with-headers.js +45 -0
- package/dist/with-headers.js.map +1 -0
- package/dist/ws/bun.d.ts +11 -5
- package/dist/ws/bun.d.ts.map +1 -1
- package/dist/ws/bun.js +82 -0
- package/dist/ws/bun.js.map +1 -0
- package/dist/ws/cloudflare.d.ts +25 -3
- package/dist/ws/cloudflare.d.ts.map +1 -1
- package/dist/ws/cloudflare.js +117 -0
- package/dist/ws/cloudflare.js.map +1 -0
- package/dist/ws/deno.d.ts +4 -4
- package/dist/ws/deno.d.ts.map +1 -1
- package/dist/ws/deno.js +133 -0
- package/dist/ws/deno.js.map +1 -0
- package/dist/ws/node.d.ts +20 -3
- package/dist/ws/node.d.ts.map +1 -1
- package/dist/ws/node.js +173 -0
- package/dist/ws/node.js.map +1 -0
- package/dist/ws/session.d.ts +11 -0
- package/dist/ws/session.d.ts.map +1 -0
- package/dist/ws/session.js +60 -0
- package/dist/ws/session.js.map +1 -0
- package/dist/ws/shared.d.ts +65 -0
- package/dist/ws/shared.d.ts.map +1 -0
- package/dist/ws/shared.js +103 -0
- package/dist/ws/shared.js.map +1 -0
- package/dist/ws-origin.d.ts +20 -0
- package/dist/ws-origin.d.ts.map +1 -0
- package/dist/ws-origin.js +41 -0
- package/dist/ws-origin.js.map +1 -0
- package/dist/yaml.d.ts +15 -1
- package/dist/yaml.d.ts.map +1 -1
- package/dist/yaml.js +101 -0
- package/dist/yaml.js.map +1 -0
- package/docs/invalidate-spec.md +13 -1
- package/docs/meta-spec.md +36 -12
- package/docs/packaging.md +43 -24
- package/docs/regression-matrix/README.md +32 -0
- package/docs/regression-matrix/review.md +44 -0
- package/docs/regression-matrix/ws1-3.md +97 -0
- package/docs/regression-matrix/ws4-8.md +107 -0
- package/docs/regression-matrix/ws9-12.md +266 -0
- package/docs/sdk.md +102 -52
- package/package.json +183 -52
- package/src/accepts.ts +91 -33
- package/src/body-kind.ts +67 -0
- package/src/body-limit.ts +102 -87
- package/src/build/index.ts +104 -54
- package/src/cli-args.ts +123 -0
- package/src/cli-go/auth.go +12 -1
- package/src/cli-go/config.go +29 -2
- package/src/cli-go/errors.go +28 -10
- package/src/cli-go/multipart.go +51 -27
- package/src/cli-go/output.go +58 -4
- package/src/cli-go/root.go +1 -1
- package/src/cli-go/version.go +2 -2
- package/src/cli.ts +154 -148
- package/src/client/error.ts +56 -1
- package/src/client/http.ts +479 -256
- package/src/client/index.ts +55 -17
- package/src/client/path.ts +169 -0
- package/src/client/redirect-policy.ts +19 -0
- package/src/client/sdk.ts +180 -101
- package/src/client/sse.ts +92 -66
- package/src/client/ws.ts +67 -31
- package/src/client-go/errors.go +41 -12
- package/src/client-go/export.go +44 -0
- package/src/client-go/invalidation.go +52 -54
- package/src/client-go/nullable.go +62 -0
- package/src/client-go/realtime.go +147 -110
- package/src/client-go/runtime.go +646 -88
- package/src/client-go/sse.go +104 -94
- package/src/client-go/transport.go +67 -27
- package/src/client-go/ws.go +49 -25
- package/src/client-info.ts +79 -0
- package/src/client-mcp/server.ts +133 -3
- package/src/client-python/_invalidation.py +207 -291
- package/src/client-python/_realtime.py +63 -40
- package/src/client-python/_runtime.py +501 -119
- package/src/client-python/_sse.py +130 -74
- package/src/client-python/_transport.py +37 -9
- package/src/client-python/_ws.py +50 -22
- package/src/client-rust/errors.rs +21 -1
- package/src/client-rust/invalidation.rs +202 -231
- package/src/client-rust/invalidation_sync.rs +16 -229
- package/src/client-rust/nullable.rs +17 -0
- package/src/client-rust/realtime.rs +217 -217
- package/src/client-rust/runtime.rs +602 -177
- package/src/client-rust/runtime_sync.rs +169 -145
- package/src/client-rust/sse.rs +142 -121
- package/src/client-rust/ws.rs +61 -37
- package/src/codegen-go-cli.ts +607 -728
- package/src/codegen-go.ts +710 -770
- package/src/codegen-ir.ts +381 -75
- package/src/codegen-lang.ts +517 -0
- package/src/codegen-load.ts +150 -0
- package/src/codegen-mcp.ts +207 -108
- package/src/codegen-python.ts +684 -816
- package/src/codegen-route-tree-intern.ts +51 -25
- package/src/codegen-rust.ts +946 -1240
- package/src/codegen-sanitize.ts +0 -4
- package/src/codegen-schema-naming.ts +19 -6
- package/src/codegen-sdk-model.ts +228 -0
- package/src/codegen.ts +1488 -1140
- package/src/context.ts +130 -11
- package/src/cookie-sign.ts +85 -16
- package/src/cookie.ts +66 -11
- package/src/cors.ts +55 -54
- package/src/crypto.ts +27 -5
- package/src/csrf.ts +57 -25
- package/src/curl-logger.ts +155 -114
- package/src/dict.ts +12 -0
- package/src/error.ts +4 -2
- package/src/etag.ts +58 -34
- package/src/feature-detect.ts +147 -0
- package/src/feature-slots.ts +70 -0
- package/src/fetch-request.ts +13 -0
- package/src/gen-process.ts +42 -0
- package/src/gen-write.ts +127 -0
- package/src/glob.ts +109 -0
- package/src/go-type-emitter.ts +406 -155
- package/src/honey-response.ts +36 -9
- package/src/i18n-slot.ts +10 -0
- package/src/i18n.ts +98 -23
- package/src/index.ts +1893 -1761
- package/src/init.ts +40 -28
- package/src/invalidate-check.ts +42 -4
- package/src/invoke-user.ts +95 -0
- package/src/ip-restrict.ts +47 -161
- package/src/ip.ts +194 -0
- package/src/logger.ts +65 -36
- package/src/media-type.ts +104 -0
- package/src/meta-spec-merge.ts +80 -0
- package/src/meta-spec.ts +0 -0
- package/src/middleware.ts +112 -12
- package/src/node-request.ts +242 -52
- package/src/node.ts +318 -75
- package/src/openapi/collect.ts +19 -44
- package/src/openapi/docs-page.ts +89 -0
- package/src/openapi/document.ts +503 -224
- package/src/openapi/json-schema-slot.ts +40 -0
- package/src/openapi/register.ts +7 -2
- package/src/openapi/scalar.ts +11 -10
- package/src/openapi/serve.ts +39 -0
- package/src/openapi/spec-factory.ts +50 -1
- package/src/openapi/spec.ts +53 -30
- package/src/openapi/swagger.ts +12 -11
- package/src/pattern.ts +245 -0
- package/src/peer.ts +53 -0
- package/src/plugin.ts +270 -185
- package/src/powered-by.ts +2 -2
- package/src/pretty-json.ts +25 -9
- package/src/producer-stream.ts +296 -0
- package/src/proxy.ts +271 -55
- package/src/python-type-emitter.ts +68 -56
- package/src/realtime/bus.ts +56 -9
- package/src/realtime/register.ts +13 -0
- package/src/realtime/route.ts +129 -80
- package/src/realtime/server.ts +298 -0
- package/src/request-id.ts +13 -3
- package/src/request-limits.ts +2 -0
- package/src/request-path.ts +165 -0
- package/src/request-to-curl.ts +28 -7
- package/src/response.ts +143 -103
- package/src/rust-type-emitter.ts +265 -170
- package/src/secure-headers.ts +8 -4
- package/src/serve-register.ts +4 -0
- package/src/serve-slot.ts +38 -0
- package/src/serve.ts +113 -25
- package/src/server-timing.ts +11 -7
- package/src/static.ts +85 -32
- package/src/telemetry/otel.ts +29 -7
- package/src/testing.ts +76 -14
- package/src/timeout.ts +15 -7
- package/src/tree.ts +405 -333
- package/src/trust.ts +117 -0
- package/src/ts-type-emitter.ts +18 -12
- package/src/type-emitter.ts +227 -89
- package/src/type-extractor.ts +562 -424
- package/src/types.ts +8 -0
- package/src/validation.ts +221 -96
- package/src/with-headers.ts +45 -0
- package/src/ws/bun.ts +53 -15
- package/src/ws/cloudflare.ts +81 -19
- package/src/ws/deno.ts +124 -36
- package/src/ws/node.ts +173 -79
- package/src/ws/session.ts +73 -0
- package/src/ws/shared.ts +140 -0
- package/src/ws-origin.ts +53 -0
- package/src/yaml.ts +45 -22
- package/dist/client-mcp/server.d.ts +0 -10
- package/dist/client-mcp/server.d.ts.map +0 -1
- package/dist/client-mcp/types.d.ts +0 -16
- package/dist/client-mcp/types.d.ts.map +0 -1
- package/dist/cloudflare/realtime.d.ts +0 -30
- package/dist/cloudflare/realtime.d.ts.map +0 -1
- package/dist/realtime/buffer.d.ts +0 -18
- package/dist/realtime/buffer.d.ts.map +0 -1
- package/dist/realtime/longpoll.d.ts +0 -23
- package/dist/realtime/longpoll.d.ts.map +0 -1
- package/dist/realtime/protocol.d.ts +0 -50
- package/dist/realtime/protocol.d.ts.map +0 -1
- package/dist/realtime/runtime.d.ts +0 -74
- package/dist/realtime/runtime.d.ts.map +0 -1
- package/src/cloudflare/realtime.ts +0 -234
- package/src/realtime/buffer.ts +0 -101
- package/src/realtime/longpoll.ts +0 -148
- package/src/realtime/protocol.ts +0 -128
- package/src/realtime/runtime.ts +0 -427
package/src/codegen-go-cli.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/* Go CLI emitter — generates a complete cobra-based Go CLI module from an OpenAPI 3.1 spec.
|
|
2
2
|
*
|
|
3
|
-
*
|
|
3
|
+
* - Input is the SDK request model (codegen-sdk-model.ts), shared with the Go SDK emitter.
|
|
4
4
|
* - Static runtime files (config.go, auth.go, output.go, errors.go, stream.go, multipart.go,
|
|
5
|
-
* version.go, root.go) are read from ./cli-go/
|
|
6
|
-
* into the output file map under "cli-go/<name>".
|
|
5
|
+
* version.go, root.go) are read from ./cli-go/ and copied verbatim under "internal/cli/".
|
|
7
6
|
* - Per-resource cobra command files are emitted as cmd/<resource>.go.
|
|
8
|
-
* -
|
|
7
|
+
* - URLs, query encoding, SSE parsing and status errors go through the SDK's exported
|
|
8
|
+
* Honey* helpers, so the CLI sends exactly what the SDK sends.
|
|
9
9
|
*
|
|
10
10
|
* Two SDK wiring modes:
|
|
11
11
|
* - sdkModulePath set → go.mod emits `require` + `replace`; no internal/sdk/ files.
|
|
@@ -15,10 +15,13 @@
|
|
|
15
15
|
|
|
16
16
|
import { readFileSync } from "node:fs"
|
|
17
17
|
import { fileURLToPath } from "node:url"
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
18
|
+
import type { OpenApiSpecInput } from "./codegen.ts"
|
|
19
|
+
import { serviceMapOf } from "./codegen.ts"
|
|
20
|
+
import { GO_KEYWORDS, GO_PREDECLARED, NameScope, cmpCodeUnit, goExported, goLocal, goString } from "./codegen-lang.ts"
|
|
21
|
+
import type { IRInfo, IRParam, IRSchema } from "./codegen-ir.ts"
|
|
22
|
+
import { buildSdkModel } from "./codegen-sdk-model.ts"
|
|
23
|
+
import type { SdkModel, SdkOp } from "./codegen-sdk-model.ts"
|
|
24
|
+
import { generateGoSDK } from "./codegen-go.ts"
|
|
22
25
|
|
|
23
26
|
const DEFAULT_MODULE_PATH = "example.com/cli"
|
|
24
27
|
|
|
@@ -26,14 +29,6 @@ function resolveModulePath(options: GoCLIOptions): string {
|
|
|
26
29
|
return options.modulePath ?? DEFAULT_MODULE_PATH
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
function enumStrings(schema: Record<string, unknown>): string[] {
|
|
30
|
-
return Array.isArray(schema.enum) ? (schema.enum as unknown[]).map((v) => String(v)) : []
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function isLastEventIdName(name: string): boolean {
|
|
34
|
-
return name === "last-event-id" || name === "last_event_id" || name === "lastEventId"
|
|
35
|
-
}
|
|
36
|
-
|
|
37
32
|
/* ── types ── */
|
|
38
33
|
|
|
39
34
|
export type GoCLIOptions = {
|
|
@@ -52,66 +47,47 @@ export type GeneratedGoCLI = {
|
|
|
52
47
|
skippedOperations: string[]
|
|
53
48
|
}
|
|
54
49
|
|
|
55
|
-
type
|
|
56
|
-
components?: { schemas?: Record<string, Record<string, unknown>> }
|
|
57
|
-
info: { description?: string; title: string; version: string }
|
|
58
|
-
openapi: string
|
|
59
|
-
paths: Record<string, Record<string, Record<string, unknown>>>
|
|
60
|
-
}
|
|
50
|
+
type FlagKind = "string" | "int" | "float" | "bool" | "strings"
|
|
61
51
|
|
|
62
|
-
type
|
|
52
|
+
type Flag = {
|
|
53
|
+
/** Wire name of the param / property. */
|
|
63
54
|
name: string
|
|
64
55
|
flagName: string
|
|
65
56
|
varName: string
|
|
57
|
+
kind: FlagKind
|
|
66
58
|
required: boolean
|
|
67
|
-
schema: Record<string, unknown>
|
|
68
|
-
defaultValue: unknown
|
|
69
|
-
enumValues: string[]
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
type BodyScalarInfo = {
|
|
73
|
-
propName: string
|
|
74
|
-
flagName: string
|
|
75
|
-
varName: string
|
|
76
|
-
schemaType: string
|
|
77
59
|
enumValues: string[]
|
|
60
|
+
defaultValue: unknown
|
|
61
|
+
where: "path" | "query" | "header" | "body" | "file"
|
|
78
62
|
}
|
|
79
63
|
|
|
80
64
|
type BodyShape =
|
|
81
65
|
| { kind: "none" }
|
|
82
|
-
| { kind: "json-flat"; scalars:
|
|
83
|
-
| { kind: "json-complex"; required: boolean }
|
|
84
|
-
| {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
scalarFields: BodyScalarInfo[]
|
|
88
|
-
useGenericFileFlag: boolean
|
|
89
|
-
}
|
|
66
|
+
| { kind: "json-flat"; scalars: Flag[]; required: boolean; dataVar: string }
|
|
67
|
+
| { kind: "json-complex"; required: boolean; dataVar: string }
|
|
68
|
+
| { kind: "form"; scalars: Flag[]; required: boolean }
|
|
69
|
+
| { kind: "multipart"; scalars: Flag[]; files: Flag[] }
|
|
70
|
+
| { kind: "raw"; contentType: string; required: boolean; dataVar: string }
|
|
90
71
|
|
|
91
72
|
type CommandInfo = {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
path: string
|
|
99
|
-
summary: string
|
|
100
|
-
description: string
|
|
101
|
-
pathParams: ParamInfo[]
|
|
102
|
-
queryParams: ParamInfo[]
|
|
73
|
+
op: SdkOp
|
|
74
|
+
cmdVarName: string
|
|
75
|
+
cmdUse: string
|
|
76
|
+
pathFlags: Flag[]
|
|
77
|
+
queryFlags: Flag[]
|
|
78
|
+
headerFlags: Flag[]
|
|
103
79
|
body: BodyShape
|
|
104
|
-
|
|
105
|
-
hasLastEventId: boolean
|
|
106
|
-
has204: boolean
|
|
107
|
-
responseType: "json" | "none"
|
|
80
|
+
lastEventIdVar?: string
|
|
108
81
|
}
|
|
109
82
|
|
|
110
83
|
type ResourceGroup = {
|
|
111
|
-
resource: string
|
|
112
|
-
cmdUse: string
|
|
113
|
-
|
|
114
|
-
|
|
84
|
+
resource: string
|
|
85
|
+
cmdUse: string
|
|
86
|
+
/** prefix for per-command var names */
|
|
87
|
+
cmdVarBase: string
|
|
88
|
+
/** the resource's own cobra.Command var */
|
|
89
|
+
groupVar: string
|
|
90
|
+
fileName: string
|
|
115
91
|
commands: CommandInfo[]
|
|
116
92
|
}
|
|
117
93
|
|
|
@@ -123,13 +99,13 @@ export function toKebab(s: string): string {
|
|
|
123
99
|
return s
|
|
124
100
|
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
|
|
125
101
|
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
126
|
-
.replace(/[
|
|
127
|
-
.replace(
|
|
102
|
+
.replace(/[^A-Za-z0-9]+/g, "-")
|
|
103
|
+
.replace(/^-+|-+$/g, "")
|
|
128
104
|
.toLowerCase()
|
|
129
105
|
}
|
|
130
106
|
|
|
131
107
|
export function toFlagName(paramName: string): string {
|
|
132
|
-
return toKebab(paramName)
|
|
108
|
+
return toKebab(paramName) || "value"
|
|
133
109
|
}
|
|
134
110
|
|
|
135
111
|
/** Upper snake case for env var: toSnakeUpper("project-id") → "PROJECT_ID". */
|
|
@@ -141,21 +117,6 @@ export function envVarFromFlag(flagName: string, envPrefix: string): string {
|
|
|
141
117
|
return `${envPrefix}_${toSnakeUpper(flagName)}`
|
|
142
118
|
}
|
|
143
119
|
|
|
144
|
-
/** Go var name from a param name: snake_case or kebab-case → camelCase with preserved acronyms.
|
|
145
|
-
* e.g. "project_id" → "projectID", "user-name" → "userName", "type" → "type_". */
|
|
146
|
-
function goCamelVar(name: string): string {
|
|
147
|
-
const pascal = goPascal(name)
|
|
148
|
-
if (!pascal) return pascal
|
|
149
|
-
/* preserve acronym tail: "Id" → "ID", "Url" → "URL" */
|
|
150
|
-
const fixed = pascal
|
|
151
|
-
.replace(/\bId\b/g, "ID")
|
|
152
|
-
.replace(/\bUrl\b/g, "URL")
|
|
153
|
-
.replace(/Id$/, "ID")
|
|
154
|
-
.replace(/Url$/, "URL")
|
|
155
|
-
const camel = fixed.charAt(0).toLowerCase() + fixed.slice(1)
|
|
156
|
-
return safeResourceName(camel)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
120
|
/* ── runtime template loader ── */
|
|
160
121
|
|
|
161
122
|
let runtimeCache: Map<string, string> | null = null
|
|
@@ -179,260 +140,228 @@ export function loadGoCliRuntimeTemplates(): Map<string, string> {
|
|
|
179
140
|
return cache
|
|
180
141
|
}
|
|
181
142
|
|
|
182
|
-
/* ──
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
143
|
+
/* ── naming ── */
|
|
144
|
+
|
|
145
|
+
/** Persistent flags on the root command, plus cobra's own. A command flag must not shadow them. */
|
|
146
|
+
const GLOBAL_FLAGS = ["api-key", "base-url", "output", "verbose", "config", "timeout", "help", "version"]
|
|
147
|
+
|
|
148
|
+
/** Identifiers every cmd/*.go file shares: imports and the package-level names root.go declares. */
|
|
149
|
+
const CMD_PACKAGE_NAMES = [
|
|
150
|
+
...GO_KEYWORDS,
|
|
151
|
+
...GO_PREDECLARED,
|
|
152
|
+
"rootCmd",
|
|
153
|
+
"Execute",
|
|
154
|
+
"configFromCtx",
|
|
155
|
+
"configKey",
|
|
156
|
+
"runtimeConfig",
|
|
157
|
+
"EnvPrefix",
|
|
158
|
+
"ConfigName",
|
|
159
|
+
"DefaultBaseURL",
|
|
160
|
+
"AuthHeaderName",
|
|
161
|
+
"AuthHeaderPrefix",
|
|
162
|
+
"newHTTPClient",
|
|
163
|
+
"doJSON",
|
|
164
|
+
"init",
|
|
165
|
+
]
|
|
166
|
+
|
|
167
|
+
/** File stems Go treats specially, or that the generator writes itself. */
|
|
168
|
+
const GOOS_GOARCH =
|
|
169
|
+
/_(aix|android|darwin|dragonfly|freebsd|hurd|illumos|ios|js|linux|nacl|netbsd|openbsd|plan9|solaris|wasip1|windows|zos|386|amd64|arm|arm64|loong64|mips|mipsle|mips64|mips64le|ppc64|ppc64le|riscv64|s390x|wasm|test)$/
|
|
170
|
+
|
|
171
|
+
function resourceFileName(resource: string, taken: NameScope): string {
|
|
172
|
+
let stem = resource.replace(/[^A-Za-z0-9_-]/g, "_").replace(/^[-_.]+/, "")
|
|
173
|
+
if (stem === "" || /^(root|main|doc)$/i.test(stem) || GOOS_GOARCH.test(stem.toLowerCase())) stem = `res_${stem}`
|
|
174
|
+
return `${taken.claim(stem)}.go`
|
|
208
175
|
}
|
|
209
176
|
|
|
210
|
-
function
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const name = String(p.name)
|
|
216
|
-
const schema = (p.schema as Record<string, unknown>) ?? { type: "string" }
|
|
217
|
-
out.push({
|
|
218
|
-
defaultValue: schema.default,
|
|
219
|
-
enumValues: enumStrings(schema),
|
|
220
|
-
flagName: toFlagName(name),
|
|
221
|
-
name,
|
|
222
|
-
required: p.required === true,
|
|
223
|
-
schema,
|
|
224
|
-
varName: goCamelVar(name),
|
|
225
|
-
})
|
|
177
|
+
function flagKind(schema: IRSchema, resolve: (s: IRSchema) => IRSchema): FlagKind {
|
|
178
|
+
let s = resolve(schema)
|
|
179
|
+
if (s.kind === "nullable") s = resolve(s.inner)
|
|
180
|
+
if (s.kind === "array") {
|
|
181
|
+
return "strings"
|
|
226
182
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return t === "string" || t === "integer" || t === "number" || t === "boolean"
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function detectBodyShape(op: Record<string, unknown>): BodyShape {
|
|
235
|
-
const requestBody = op.requestBody as Record<string, unknown> | undefined
|
|
236
|
-
if (!requestBody) return { kind: "none" }
|
|
237
|
-
const required = requestBody.required === true
|
|
238
|
-
const content = requestBody.content as Record<string, Record<string, unknown>> | undefined
|
|
239
|
-
if (!content) return { kind: "none" }
|
|
240
|
-
|
|
241
|
-
/* multipart wins over json */
|
|
242
|
-
const multipart = content["multipart/form-data"]
|
|
243
|
-
if (multipart) {
|
|
244
|
-
const schema = (multipart.schema as Record<string, unknown>) ?? {}
|
|
245
|
-
const props = (schema.properties as Record<string, Record<string, unknown>>) ?? {}
|
|
246
|
-
const reqList = new Set(Array.isArray(schema.required) ? (schema.required as string[]) : [])
|
|
247
|
-
const binaryFields: { name: string; required: boolean }[] = []
|
|
248
|
-
const scalarFields: BodyScalarInfo[] = []
|
|
249
|
-
for (const [propName, propSchema] of Object.entries(props)) {
|
|
250
|
-
const type = propSchema.type
|
|
251
|
-
const format = propSchema.format
|
|
252
|
-
if (type === "string" && format === "binary") {
|
|
253
|
-
binaryFields.push({ name: propName, required: reqList.has(propName) })
|
|
254
|
-
} else if (isScalarType(type)) {
|
|
255
|
-
scalarFields.push({
|
|
256
|
-
enumValues: enumStrings(propSchema),
|
|
257
|
-
flagName: toFlagName(propName),
|
|
258
|
-
propName,
|
|
259
|
-
schemaType: String(type),
|
|
260
|
-
varName: goCamelVar(propName),
|
|
261
|
-
})
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
return {
|
|
265
|
-
binaryFields,
|
|
266
|
-
kind: "multipart",
|
|
267
|
-
scalarFields,
|
|
268
|
-
useGenericFileFlag: binaryFields.length === 1,
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const jsonContent = content["application/json"]
|
|
273
|
-
if (!jsonContent) return { kind: "none" }
|
|
274
|
-
const schema = (jsonContent.schema as Record<string, unknown>) ?? {}
|
|
275
|
-
|
|
276
|
-
/* oneOf/anyOf/allOf, array-root, or non-object → complex */
|
|
277
|
-
if (schema.oneOf || schema.anyOf || schema.allOf) {
|
|
278
|
-
return { kind: "json-complex", required }
|
|
279
|
-
}
|
|
280
|
-
if (schema.type !== "object") {
|
|
281
|
-
return { kind: "json-complex", required }
|
|
183
|
+
if (s.kind === "scalar") {
|
|
184
|
+
if (s.type === "integer") return "int"
|
|
185
|
+
if (s.type === "number") return "float"
|
|
186
|
+
if (s.type === "boolean") return "bool"
|
|
282
187
|
}
|
|
188
|
+
if (s.kind === "const" && typeof s.value === "boolean") return "bool"
|
|
189
|
+
return "string"
|
|
190
|
+
}
|
|
283
191
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
allScalar = false
|
|
290
|
-
break
|
|
291
|
-
}
|
|
292
|
-
scalars.push({
|
|
293
|
-
enumValues: enumStrings(propSchema),
|
|
294
|
-
flagName: toFlagName(propName),
|
|
295
|
-
propName,
|
|
296
|
-
schemaType: String(propSchema.type),
|
|
297
|
-
varName: goCamelVar(propName),
|
|
298
|
-
})
|
|
299
|
-
}
|
|
300
|
-
if (!allScalar) return { kind: "json-complex", required }
|
|
301
|
-
return { kind: "json-flat", required, scalars }
|
|
192
|
+
function enumOf(schema: IRSchema, resolve: (s: IRSchema) => IRSchema): string[] {
|
|
193
|
+
let s = resolve(schema)
|
|
194
|
+
if (s.kind === "nullable") s = resolve(s.inner)
|
|
195
|
+
if (s.kind === "scalar" && s.enum) return s.enum.map(String)
|
|
196
|
+
return []
|
|
302
197
|
}
|
|
303
198
|
|
|
304
|
-
function
|
|
305
|
-
|
|
306
|
-
if (
|
|
307
|
-
return
|
|
199
|
+
function isScalarSchema(schema: IRSchema, resolve: (s: IRSchema) => IRSchema): boolean {
|
|
200
|
+
let s = resolve(schema)
|
|
201
|
+
if (s.kind === "nullable") s = resolve(s.inner)
|
|
202
|
+
return s.kind === "scalar" || s.kind === "const"
|
|
308
203
|
}
|
|
309
204
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
205
|
+
/** Go literal for a flag default, or the zero value. */
|
|
206
|
+
function flagDefault(f: Flag): string {
|
|
207
|
+
const d = f.defaultValue
|
|
208
|
+
switch (f.kind) {
|
|
209
|
+
case "int":
|
|
210
|
+
return typeof d === "number" && Number.isInteger(d) ? String(d) : "0"
|
|
211
|
+
case "float":
|
|
212
|
+
return typeof d === "number" && Number.isFinite(d) ? String(d) : "0"
|
|
213
|
+
case "bool":
|
|
214
|
+
return d === true ? "true" : "false"
|
|
215
|
+
case "strings":
|
|
216
|
+
return Array.isArray(d) ? `[]string{${d.map((x) => goString(String(x))).join(", ")}}` : "nil"
|
|
217
|
+
default:
|
|
218
|
+
return typeof d === "string" ? goString(d) : `""`
|
|
318
219
|
}
|
|
319
|
-
return false
|
|
320
220
|
}
|
|
321
221
|
|
|
322
222
|
/* ── command collection ── */
|
|
323
223
|
|
|
324
|
-
function collectCLIMethods(
|
|
325
|
-
groups: Map<string, ResourceGroup>
|
|
326
|
-
skipped: string[]
|
|
327
|
-
serviceMap: Record<string, Record<string, unknown>>
|
|
328
|
-
} {
|
|
329
|
-
toIR(spec as Parameters<typeof toIR>[0])
|
|
330
|
-
const { serviceMap } = collectSDKMethods(spec as Parameters<typeof collectSDKMethods>[0])
|
|
331
|
-
const resolved = resolveRefs(spec as Parameters<typeof resolveRefs>[0]) as unknown as OpenApiSpec
|
|
332
|
-
|
|
224
|
+
function collectCLIMethods(model: SdkModel, pkg: NameScope): { groups: Map<string, ResourceGroup>; skipped: string[] } {
|
|
333
225
|
const groups = new Map<string, ResourceGroup>()
|
|
334
226
|
const skipped: string[] = []
|
|
335
227
|
const seenCmdPaths = new Map<string, string>()
|
|
228
|
+
const fileNames = new NameScope(["root", "main"], (n) => n.toLowerCase())
|
|
229
|
+
const { resolve } = model
|
|
336
230
|
|
|
337
|
-
for (const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
231
|
+
for (const op of model.ops) {
|
|
232
|
+
if (op.stream === "ws" || op.stream === "realtime") {
|
|
233
|
+
skipped.push(op.id)
|
|
234
|
+
continue
|
|
235
|
+
}
|
|
342
236
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
237
|
+
const segments = op.segments
|
|
238
|
+
const resource = segments[0] ?? op.id
|
|
239
|
+
/* Top-level ops (single segment) become `<resource> call` — cobra rejects leading dashes. */
|
|
240
|
+
const actionSegments = segments.length === 1 ? ["call"] : segments.slice(1).map((seg) => toKebab(seg))
|
|
241
|
+
const cmdUse = actionSegments.join("-")
|
|
242
|
+
|
|
243
|
+
/* collision check: (resource, cmdUse) must be unique */
|
|
244
|
+
const key = `${resource}|${cmdUse}`
|
|
245
|
+
const prior = seenCmdPaths.get(key)
|
|
246
|
+
if (prior !== undefined) {
|
|
247
|
+
throw new Error(
|
|
248
|
+
`go-cli codegen: command path collision for ${resource} ${cmdUse} — operations: ${prior}, ${op.id}`,
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
seenCmdPaths.set(key, op.id)
|
|
252
|
+
|
|
253
|
+
let group = groups.get(resource)
|
|
254
|
+
if (!group) {
|
|
255
|
+
const cmdVarBase = goLocal(resource)
|
|
256
|
+
group = {
|
|
257
|
+
cmdUse: toKebab(resource) || "resource",
|
|
258
|
+
cmdVarBase,
|
|
259
|
+
groupVar: pkg.claim(`${cmdVarBase}Cmd`),
|
|
260
|
+
commands: [],
|
|
261
|
+
fileName: resourceFileName(resource, fileNames),
|
|
262
|
+
resource,
|
|
346
263
|
}
|
|
264
|
+
groups.set(resource, group)
|
|
265
|
+
}
|
|
347
266
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
267
|
+
const cmdVarName = pkg.claim(`${group.cmdVarBase}${goExported(cmdUse)}Cmd`)
|
|
268
|
+
const flagScope = new NameScope(GLOBAL_FLAGS)
|
|
269
|
+
const varFor = (flag: string) => pkg.claim(`${cmdVarName}${goExported(flag)}Var`)
|
|
270
|
+
const claimFlag = (name: string, prefix: string): string => {
|
|
271
|
+
const base = toFlagName(name)
|
|
272
|
+
return flagScope.has(base) ? flagScope.claim(`${prefix}-${base}`) : flagScope.claim(base)
|
|
273
|
+
}
|
|
274
|
+
const mkFlag = (p: IRParam, where: Flag["where"], prefix: string): Flag => {
|
|
275
|
+
const flagName = claimFlag(p.name, prefix)
|
|
276
|
+
return {
|
|
277
|
+
defaultValue: where === "query" || where === "header" ? p.default : undefined,
|
|
278
|
+
enumValues: enumOf(p.schema, resolve),
|
|
279
|
+
flagName,
|
|
280
|
+
kind: flagKind(p.schema, resolve),
|
|
281
|
+
name: p.name,
|
|
282
|
+
required: where === "path" || p.required === true,
|
|
283
|
+
varName: varFor(flagName),
|
|
284
|
+
where,
|
|
363
285
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* path flags claim first: a path param keeps its plain flag name */
|
|
289
|
+
const pathFlags = op.pathParams.map((p) => ({ ...mkFlag(p, "path", "path"), kind: "string" as const }))
|
|
290
|
+
const queryFlags: Flag[] = []
|
|
291
|
+
let lastEventIdVar: string | undefined
|
|
292
|
+
for (const q of op.query) {
|
|
293
|
+
if (op.stream === "sse" && /^last[-_]?event[-_]?id$/i.test(q.name)) continue
|
|
294
|
+
queryFlags.push(mkFlag(q, "query", "query"))
|
|
295
|
+
}
|
|
296
|
+
const headerFlags = op.headers.map((h) => mkFlag(h, "header", "header"))
|
|
297
|
+
if (op.stream === "sse") {
|
|
298
|
+
const flagName = flagScope.claim("last-event-id")
|
|
299
|
+
lastEventIdVar = varFor(flagName)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
let body: BodyShape = { kind: "none" }
|
|
303
|
+
const b = op.body
|
|
304
|
+
if (b) {
|
|
305
|
+
const scalarFlags = (schema: IRSchema): Flag[] | null => {
|
|
306
|
+
const s = resolve(schema)
|
|
307
|
+
if (s.kind !== "object") return null
|
|
308
|
+
const out: Flag[] = []
|
|
309
|
+
for (const f of s.fields) {
|
|
310
|
+
if (!isScalarSchema(f.schema, resolve)) return null
|
|
311
|
+
out.push(mkFlag({ name: f.name, required: false, schema: f.schema }, "body", "body"))
|
|
374
312
|
}
|
|
375
|
-
|
|
313
|
+
return out
|
|
376
314
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
315
|
+
if (b.kind === "json") {
|
|
316
|
+
const dataFlag = flagScope.claim("data")
|
|
317
|
+
const dataVar = varFor(dataFlag)
|
|
318
|
+
const scalars = scalarFlags(b.schema)
|
|
319
|
+
body = scalars
|
|
320
|
+
? { dataVar, kind: "json-flat", required: b.required, scalars }
|
|
321
|
+
: { dataVar, kind: "json-complex", required: b.required }
|
|
322
|
+
} else if (b.kind === "form") {
|
|
323
|
+
body = { kind: "form", required: b.required, scalars: scalarFlags(b.schema) ?? [] }
|
|
324
|
+
} else if (b.kind === "multipart") {
|
|
325
|
+
const scalars: Flag[] = []
|
|
326
|
+
const files: Flag[] = []
|
|
327
|
+
const fileParts = b.parts.filter((p) => p.type === "file")
|
|
328
|
+
for (const part of b.parts) {
|
|
329
|
+
if (part.type === "file") {
|
|
330
|
+
const flagName = fileParts.length === 1 ? flagScope.claim("file") : claimFlag(part.name, "file")
|
|
331
|
+
files.push({
|
|
332
|
+
defaultValue: undefined,
|
|
333
|
+
enumValues: [],
|
|
334
|
+
flagName,
|
|
335
|
+
kind: "string",
|
|
336
|
+
name: part.name,
|
|
337
|
+
required: fileParts.length === 1 && b.required,
|
|
338
|
+
varName: varFor(flagName),
|
|
339
|
+
where: "file",
|
|
340
|
+
})
|
|
341
|
+
} else if (part.schema && isScalarSchema(part.schema, resolve)) {
|
|
342
|
+
scalars.push(mkFlag({ name: part.name, required: false, schema: part.schema }, "body", "body"))
|
|
390
343
|
}
|
|
391
|
-
return s
|
|
392
|
-
})
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/* query rename: cobra reserves --help, --version */
|
|
396
|
-
for (const q of queryParams) {
|
|
397
|
-
if (q.flagName === "help" || q.flagName === "version") {
|
|
398
|
-
q.flagName = `query-${q.flagName}`
|
|
399
344
|
}
|
|
345
|
+
body = { files, kind: "multipart", scalars }
|
|
346
|
+
} else {
|
|
347
|
+
const dataFlag = flagScope.claim("data")
|
|
348
|
+
body = { contentType: b.contentType, dataVar: varFor(dataFlag), kind: "raw", required: b.required }
|
|
400
349
|
}
|
|
401
|
-
|
|
402
|
-
const actionPascal = goPascal(action)
|
|
403
|
-
const cmdVarName = `${group.cmdVarBase}${actionPascal}Cmd`
|
|
404
|
-
const has204 = has204Response(op)
|
|
405
|
-
const hasJson = hasJsonResponse(op)
|
|
406
|
-
let responseType: "json" | "none" = "none"
|
|
407
|
-
if (!sse && hasJson) responseType = "json"
|
|
408
|
-
|
|
409
|
-
group.commands.push({
|
|
410
|
-
action,
|
|
411
|
-
body,
|
|
412
|
-
cmdUse,
|
|
413
|
-
cmdVarName,
|
|
414
|
-
description: String(op.description ?? ""),
|
|
415
|
-
has204,
|
|
416
|
-
hasLastEventId,
|
|
417
|
-
isSSE: sse,
|
|
418
|
-
method: method.toUpperCase(),
|
|
419
|
-
opId: operationId,
|
|
420
|
-
path: rawPath,
|
|
421
|
-
pathParams,
|
|
422
|
-
queryParams,
|
|
423
|
-
resource,
|
|
424
|
-
responseType,
|
|
425
|
-
summary: String(op.summary ?? ""),
|
|
426
|
-
})
|
|
427
350
|
}
|
|
351
|
+
|
|
352
|
+
group.commands.push({ body, cmdUse, cmdVarName, headerFlags, lastEventIdVar, op, pathFlags, queryFlags })
|
|
428
353
|
}
|
|
429
354
|
|
|
430
|
-
return { groups,
|
|
355
|
+
return { groups, skipped }
|
|
431
356
|
}
|
|
432
357
|
|
|
433
358
|
/* ── emit: cmd/root.go ── */
|
|
434
359
|
|
|
435
|
-
function emitRootFile(
|
|
360
|
+
function emitRootFile(
|
|
361
|
+
groups: ResourceGroup[],
|
|
362
|
+
options: GoCLIOptions,
|
|
363
|
+
auth: { headerName: string; prefix: string },
|
|
364
|
+
): string {
|
|
436
365
|
const envPrefix = options.envPrefix ?? toSnakeUpper(options.binaryName)
|
|
437
366
|
const configName = options.configName ?? options.binaryName
|
|
438
367
|
const defaultBase = options.defaultBaseURL ?? ""
|
|
@@ -444,24 +373,27 @@ function emitRootFile(groups: ResourceGroup[], options: GoCLIOptions): string {
|
|
|
444
373
|
l.push(`import (`)
|
|
445
374
|
l.push(`\t"context"`)
|
|
446
375
|
l.push(`\t"fmt"`)
|
|
447
|
-
l.push(`\t"
|
|
376
|
+
l.push(`\t"net/http"`)
|
|
377
|
+
l.push(`\t"time"`)
|
|
448
378
|
l.push(``)
|
|
449
379
|
l.push(`\t"github.com/spf13/cobra"`)
|
|
450
380
|
l.push(``)
|
|
451
|
-
l.push(`\tcli
|
|
381
|
+
l.push(`\tcli ${goString(cliImportPath(options))}`)
|
|
452
382
|
l.push(`)`)
|
|
453
383
|
l.push(``)
|
|
454
|
-
l.push(`var _ = fmt.Sprintf`)
|
|
455
|
-
l.push(`var _ = os.Exit`)
|
|
456
|
-
l.push(``)
|
|
457
384
|
l.push(`// EnvPrefix is the prefix used for all <PREFIX>_<FLAG> environment variables.`)
|
|
458
|
-
l.push(`const EnvPrefix = ${
|
|
385
|
+
l.push(`const EnvPrefix = ${goString(envPrefix)}`)
|
|
459
386
|
l.push(``)
|
|
460
387
|
l.push(`// ConfigName is the directory name under $XDG_CONFIG_HOME / ~/.config for the config file.`)
|
|
461
|
-
l.push(`const ConfigName = ${
|
|
388
|
+
l.push(`const ConfigName = ${goString(configName)}`)
|
|
462
389
|
l.push(``)
|
|
463
390
|
l.push(`// DefaultBaseURL is used when neither --base-url, env var, nor the config file provide one.`)
|
|
464
|
-
l.push(`const DefaultBaseURL = ${
|
|
391
|
+
l.push(`const DefaultBaseURL = ${goString(defaultBase)}`)
|
|
392
|
+
l.push(``)
|
|
393
|
+
l.push(`// AuthHeaderName and AuthHeaderPrefix come from the API's security scheme.`)
|
|
394
|
+
l.push(`const AuthHeaderName = ${goString(auth.headerName)}`)
|
|
395
|
+
l.push(``)
|
|
396
|
+
l.push(`const AuthHeaderPrefix = ${goString(auth.prefix)}`)
|
|
465
397
|
l.push(``)
|
|
466
398
|
l.push(`type configKey struct{}`)
|
|
467
399
|
l.push(``)
|
|
@@ -470,13 +402,27 @@ function emitRootFile(groups: ResourceGroup[], options: GoCLIOptions): string {
|
|
|
470
402
|
l.push(`\tCfg cli.Config`)
|
|
471
403
|
l.push(`}`)
|
|
472
404
|
l.push(``)
|
|
473
|
-
l.push(`//
|
|
405
|
+
l.push(`// started is set once flags, args and required flags are valid; any error`)
|
|
406
|
+
l.push(`// before that is a usage error (exit 4), whatever cobra calls it.`)
|
|
407
|
+
l.push(`var started bool`)
|
|
408
|
+
l.push(``)
|
|
409
|
+
l.push(`// rootCmd is the top-level cobra command for the CLI.`)
|
|
474
410
|
l.push(`var rootCmd = &cobra.Command{`)
|
|
475
|
-
l.push(`\tUse: ${
|
|
476
|
-
l.push(`\tShort: ${
|
|
411
|
+
l.push(`\tUse: ${goString(options.binaryName)},`)
|
|
412
|
+
l.push(`\tShort: ${goString(options.description ?? `${options.binaryName} CLI`)},`)
|
|
477
413
|
l.push(`\tVersion: cli.Version,`)
|
|
478
414
|
l.push(`\tSilenceUsage: true,`)
|
|
415
|
+
l.push(`\tSilenceErrors: true,`)
|
|
479
416
|
l.push(`\tPersistentPreRunE: func(cmd *cobra.Command, args []string) error {`)
|
|
417
|
+
l.push(`\t\t/* cobra checks required flags only after this hook, so a missing one would`)
|
|
418
|
+
l.push(`\t\t surface as a generic error (exit 3): check them here, as usage errors */`)
|
|
419
|
+
l.push(`\t\tif err := cmd.ValidateRequiredFlags(); err != nil {`)
|
|
420
|
+
l.push(`\t\t\treturn cli.Usage(err)`)
|
|
421
|
+
l.push(`\t\t}`)
|
|
422
|
+
l.push(`\t\tif err := cmd.ValidateFlagGroups(); err != nil {`)
|
|
423
|
+
l.push(`\t\t\treturn cli.Usage(err)`)
|
|
424
|
+
l.push(`\t\t}`)
|
|
425
|
+
l.push(`\t\tstarted = true`)
|
|
480
426
|
l.push(`\t\tcfg, err := cli.LoadConfig(cmd.Flags(), EnvPrefix, ConfigName)`)
|
|
481
427
|
l.push(`\t\tif err != nil {`)
|
|
482
428
|
l.push(`\t\t\treturn err`)
|
|
@@ -484,6 +430,9 @@ function emitRootFile(groups: ResourceGroup[], options: GoCLIOptions): string {
|
|
|
484
430
|
l.push(`\t\tif cfg.BaseURL == "" {`)
|
|
485
431
|
l.push(`\t\t\tcfg.BaseURL = DefaultBaseURL`)
|
|
486
432
|
l.push(`\t\t}`)
|
|
433
|
+
l.push(`\t\tif _, err := time.ParseDuration(cfg.Timeout); err != nil {`)
|
|
434
|
+
l.push(`\t\t\treturn cli.Usage(err)`)
|
|
435
|
+
l.push(`\t\t}`)
|
|
487
436
|
l.push(`\t\tctx := context.WithValue(cmd.Context(), configKey{}, runtimeConfig{Cfg: cfg})`)
|
|
488
437
|
l.push(`\t\tcmd.SetContext(ctx)`)
|
|
489
438
|
l.push(`\t\treturn nil`)
|
|
@@ -492,7 +441,20 @@ function emitRootFile(groups: ResourceGroup[], options: GoCLIOptions): string {
|
|
|
492
441
|
l.push(``)
|
|
493
442
|
l.push(`// Execute runs the root command.`)
|
|
494
443
|
l.push(`func Execute() error {`)
|
|
495
|
-
l.push(`\
|
|
444
|
+
l.push(`\terr := rootCmd.Execute()`)
|
|
445
|
+
l.push(`\tif err != nil && !started {`)
|
|
446
|
+
l.push(`\t\treturn cli.Usage(err)`)
|
|
447
|
+
l.push(`\t}`)
|
|
448
|
+
l.push(`\treturn err`)
|
|
449
|
+
l.push(`}`)
|
|
450
|
+
l.push(``)
|
|
451
|
+
l.push(`// groupRun shows a bare group's help and rejects an unknown subcommand as a`)
|
|
452
|
+
l.push(`// usage error (cobra would print help and exit 0).`)
|
|
453
|
+
l.push(`func groupRun(cmd *cobra.Command, args []string) error {`)
|
|
454
|
+
l.push(`\tif len(args) > 0 {`)
|
|
455
|
+
l.push(`\t\treturn cli.Usage(fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath()))`)
|
|
456
|
+
l.push(`\t}`)
|
|
457
|
+
l.push(`\treturn cmd.Help()`)
|
|
496
458
|
l.push(`}`)
|
|
497
459
|
l.push(``)
|
|
498
460
|
l.push(`func configFromCtx(ctx context.Context) cli.Config {`)
|
|
@@ -502,17 +464,35 @@ function emitRootFile(groups: ResourceGroup[], options: GoCLIOptions): string {
|
|
|
502
464
|
l.push(`\treturn cli.Config{}`)
|
|
503
465
|
l.push(`}`)
|
|
504
466
|
l.push(``)
|
|
467
|
+
l.push(`// newHTTPClient returns a client without an overall timeout: regular calls`)
|
|
468
|
+
l.push(`// bound themselves with --timeout, streams and uploads do not.`)
|
|
469
|
+
l.push(`func newHTTPClient() *http.Client {`)
|
|
470
|
+
l.push(`\treturn &http.Client{`)
|
|
471
|
+
l.push(`\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {`)
|
|
472
|
+
l.push(`\t\t\t/* never forward the API key to another host */`)
|
|
473
|
+
l.push(`\t\t\tif len(via) >= 10 || req.URL.Host != via[0].URL.Host || req.URL.Scheme != via[0].URL.Scheme {`)
|
|
474
|
+
l.push(`\t\t\t\treturn http.ErrUseLastResponse`)
|
|
475
|
+
l.push(`\t\t\t}`)
|
|
476
|
+
l.push(`\t\t\treturn nil`)
|
|
477
|
+
l.push(`\t\t},`)
|
|
478
|
+
l.push(`\t}`)
|
|
479
|
+
l.push(`}`)
|
|
480
|
+
l.push(``)
|
|
505
481
|
l.push(`func init() {`)
|
|
506
|
-
l.push(
|
|
482
|
+
l.push(
|
|
483
|
+
`\trootCmd.PersistentFlags().String("api-key", "", "API key (overrides env + config file; visible to other local users in the process list, prefer the env var)")`,
|
|
484
|
+
)
|
|
507
485
|
l.push(`\trootCmd.PersistentFlags().String("base-url", "", "Override API base URL")`)
|
|
508
486
|
l.push(`\trootCmd.PersistentFlags().String("output", "json", "Output mode: json|ndjson|yaml|table")`)
|
|
509
487
|
l.push(`\trootCmd.PersistentFlags().Bool("verbose", false, "Print request metadata to stderr")`)
|
|
510
488
|
l.push(`\trootCmd.PersistentFlags().String("config", "", "Path to config file")`)
|
|
511
|
-
l.push(
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
489
|
+
l.push(
|
|
490
|
+
`\trootCmd.PersistentFlags().String("timeout", "30s", "Request timeout (Go duration); streams are not bounded")`,
|
|
491
|
+
)
|
|
492
|
+
l.push(`\trootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {`)
|
|
493
|
+
l.push(`\t\treturn cli.Usage(err)`)
|
|
494
|
+
l.push(`\t})`)
|
|
495
|
+
for (const g of groups) l.push(`\trootCmd.AddCommand(${g.groupVar})`)
|
|
516
496
|
l.push(`}`)
|
|
517
497
|
l.push(``)
|
|
518
498
|
return l.join("\n")
|
|
@@ -530,114 +510,101 @@ function sdkImportPath(options: GoCLIOptions): string {
|
|
|
530
510
|
/* ── emit: cmd/<resource>.go ── */
|
|
531
511
|
|
|
532
512
|
function emitResourceFile(group: ResourceGroup, options: GoCLIOptions): string {
|
|
513
|
+
const body: string[] = []
|
|
514
|
+
|
|
515
|
+
const resShort = group.commands[0]?.op.summary || `${group.cmdUse} commands`
|
|
516
|
+
body.push(`var ${group.groupVar} = &cobra.Command{`)
|
|
517
|
+
body.push(`\tUse: ${goString(group.cmdUse)},`)
|
|
518
|
+
body.push(`\tShort: ${goString(firstLine(resShort))},`)
|
|
519
|
+
body.push(`\tArgs: cobra.ArbitraryArgs,`)
|
|
520
|
+
body.push(`\tRunE: groupRun,`)
|
|
521
|
+
body.push(`}`)
|
|
522
|
+
body.push(``)
|
|
523
|
+
|
|
524
|
+
for (const cmd of group.commands) emitCommand(body, cmd)
|
|
525
|
+
|
|
526
|
+
/* init() wires subcommands + registers their flags */
|
|
527
|
+
body.push(`func init() {`)
|
|
528
|
+
for (const cmd of group.commands) body.push(`\t${group.groupVar}.AddCommand(${cmd.cmdVarName})`)
|
|
529
|
+
for (const cmd of group.commands) emitFlagRegistrations(body, cmd)
|
|
530
|
+
body.push(`}`)
|
|
531
|
+
body.push(``)
|
|
532
|
+
|
|
533
|
+
const src = body.join("\n")
|
|
534
|
+
const imports: string[] = []
|
|
535
|
+
const uses = (re: RegExp) => re.test(src)
|
|
536
|
+
if (uses(/\bcontext\./)) imports.push("context")
|
|
537
|
+
if (uses(/\bbytes\./)) imports.push("bytes")
|
|
538
|
+
if (uses(/\bjson\./)) imports.push("encoding/json")
|
|
539
|
+
if (uses(/\berrors\./)) imports.push("errors")
|
|
540
|
+
if (uses(/\bfmt\./)) imports.push("fmt")
|
|
541
|
+
if (uses(/\bio\./)) imports.push("io")
|
|
542
|
+
if (uses(/\bhttp\./)) imports.push("net/http")
|
|
543
|
+
if (uses(/\burl\./)) imports.push("net/url")
|
|
544
|
+
if (uses(/\bos\./)) imports.push("os")
|
|
545
|
+
if (uses(/\bslices\./)) imports.push("slices")
|
|
546
|
+
if (uses(/\bstrconv\./)) imports.push("strconv")
|
|
547
|
+
if (uses(/\bstrings\./)) imports.push("strings")
|
|
548
|
+
if (uses(/\btime\./)) imports.push("time")
|
|
549
|
+
|
|
533
550
|
const l: string[] = []
|
|
534
|
-
const hasSSE = group.commands.some((c) => c.isSSE)
|
|
535
551
|
l.push(`// Code generated by honey. DO NOT EDIT.`)
|
|
536
552
|
l.push(`package cmd`)
|
|
537
553
|
l.push(``)
|
|
538
554
|
l.push(`import (`)
|
|
539
|
-
l.push(`\t"
|
|
540
|
-
l.push(`\t"context"`)
|
|
541
|
-
l.push(`\t"encoding/json"`)
|
|
542
|
-
l.push(`\t"fmt"`)
|
|
543
|
-
l.push(`\t"io"`)
|
|
544
|
-
if (hasSSE) l.push(`\t"iter"`)
|
|
545
|
-
l.push(`\t"net/http"`)
|
|
546
|
-
l.push(`\t"net/url"`)
|
|
547
|
-
l.push(`\t"os"`)
|
|
548
|
-
l.push(`\t"slices"`)
|
|
549
|
-
l.push(`\t"strings"`)
|
|
550
|
-
l.push(`\t"time"`)
|
|
555
|
+
for (const p of imports.sort(cmpCodeUnit)) l.push(`\t"${p}"`)
|
|
551
556
|
l.push(``)
|
|
552
557
|
l.push(`\t"github.com/spf13/cobra"`)
|
|
553
558
|
l.push(``)
|
|
554
|
-
l.push(`\tcli
|
|
555
|
-
l.push(`\tsdk
|
|
559
|
+
l.push(`\tcli ${goString(cliImportPath(options))}`)
|
|
560
|
+
if (uses(/\bsdk\./)) l.push(`\tsdk ${goString(sdkImportPath(options))}`)
|
|
556
561
|
l.push(`)`)
|
|
557
562
|
l.push(``)
|
|
558
|
-
l.push(
|
|
559
|
-
l.
|
|
560
|
-
|
|
561
|
-
l.push(`var _ = fmt.Sprintf`)
|
|
562
|
-
l.push(`var _ = io.Discard`)
|
|
563
|
-
l.push(`var _ = http.MethodGet`)
|
|
564
|
-
l.push(`var _ = url.PathEscape`)
|
|
565
|
-
l.push(`var _ = os.Stdout`)
|
|
566
|
-
l.push(`var _ = slices.Contains[[]string]`)
|
|
567
|
-
l.push(`var _ = strings.NewReader`)
|
|
568
|
-
l.push(`var _ = time.Second`)
|
|
569
|
-
l.push(`var _ = sdk.SSEEvent{}`)
|
|
570
|
-
l.push(`var _ = cli.Version`)
|
|
571
|
-
if (hasSSE) l.push(`var _ iter.Seq2[cli.SSEEvent, error]`)
|
|
572
|
-
l.push(``)
|
|
563
|
+
l.push(src)
|
|
564
|
+
return l.join("\n")
|
|
565
|
+
}
|
|
573
566
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
l.push(`\tUse: ${JSON.stringify(group.cmdUse)},`)
|
|
578
|
-
l.push(`\tShort: ${JSON.stringify(resShort)},`)
|
|
579
|
-
l.push(`}`)
|
|
580
|
-
l.push(``)
|
|
567
|
+
function firstLine(s: string): string {
|
|
568
|
+
return s.split(/\r\n|\r|\n/)[0] ?? ""
|
|
569
|
+
}
|
|
581
570
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
571
|
+
function allFlags(cmd: CommandInfo): Flag[] {
|
|
572
|
+
const out = [...cmd.pathFlags, ...cmd.queryFlags, ...cmd.headerFlags]
|
|
573
|
+
if (cmd.body.kind === "json-flat" || cmd.body.kind === "form") out.push(...cmd.body.scalars)
|
|
574
|
+
if (cmd.body.kind === "multipart") out.push(...cmd.body.scalars, ...cmd.body.files)
|
|
575
|
+
return out
|
|
576
|
+
}
|
|
586
577
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
578
|
+
function goVarType(kind: FlagKind): string {
|
|
579
|
+
switch (kind) {
|
|
580
|
+
case "int":
|
|
581
|
+
return "int64"
|
|
582
|
+
case "float":
|
|
583
|
+
return "float64"
|
|
584
|
+
case "bool":
|
|
585
|
+
return "bool"
|
|
586
|
+
case "strings":
|
|
587
|
+
return "[]string"
|
|
588
|
+
default:
|
|
589
|
+
return "string"
|
|
594
590
|
}
|
|
595
|
-
l.push(`}`)
|
|
596
|
-
l.push(``)
|
|
597
|
-
|
|
598
|
-
return l.join("\n")
|
|
599
591
|
}
|
|
600
592
|
|
|
601
593
|
/** Emits var declarations for every flag-backing Go variable + the cobra.Command literal. */
|
|
602
|
-
function emitCommand(l: string[],
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
l.push(`var ${cmd.
|
|
606
|
-
}
|
|
607
|
-
for (const q of cmd.queryParams) {
|
|
608
|
-
l.push(`var ${cmd.cmdVarName}${goPascal(q.name)}Var ${goFlagVarType(q)}`)
|
|
609
|
-
}
|
|
610
|
-
if (cmd.body.kind === "json-flat" || cmd.body.kind === "json-complex") {
|
|
611
|
-
l.push(`var ${cmd.cmdVarName}DataVar string`)
|
|
612
|
-
}
|
|
613
|
-
if (cmd.body.kind === "json-flat") {
|
|
614
|
-
for (const s of cmd.body.scalars) {
|
|
615
|
-
l.push(`var ${cmd.cmdVarName}${goPascal(s.propName)}Var ${goScalarVarType(s.schemaType)}`)
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
if (cmd.body.kind === "multipart") {
|
|
619
|
-
for (const s of cmd.body.scalarFields) {
|
|
620
|
-
l.push(`var ${cmd.cmdVarName}${goPascal(s.propName)}Var ${goScalarVarType(s.schemaType)}`)
|
|
621
|
-
}
|
|
622
|
-
if (cmd.body.useGenericFileFlag) {
|
|
623
|
-
l.push(`var ${cmd.cmdVarName}FileVar string`)
|
|
624
|
-
} else {
|
|
625
|
-
for (const f of cmd.body.binaryFields) {
|
|
626
|
-
l.push(`var ${cmd.cmdVarName}${goPascal(f.name)}Var string`)
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
if (cmd.hasLastEventId) {
|
|
631
|
-
l.push(`var ${cmd.cmdVarName}LastEventIDVar string`)
|
|
594
|
+
function emitCommand(l: string[], cmd: CommandInfo): void {
|
|
595
|
+
for (const f of allFlags(cmd)) l.push(`var ${f.varName} ${goVarType(f.kind)}`)
|
|
596
|
+
if (cmd.body.kind === "json-flat" || cmd.body.kind === "json-complex" || cmd.body.kind === "raw") {
|
|
597
|
+
l.push(`var ${cmd.body.dataVar} string`)
|
|
632
598
|
}
|
|
599
|
+
if (cmd.lastEventIdVar) l.push(`var ${cmd.lastEventIdVar} string`)
|
|
633
600
|
l.push(``)
|
|
634
601
|
|
|
635
|
-
const shortDesc = cmd.summary || cmd.
|
|
602
|
+
const shortDesc = firstLine(cmd.op.summary) || cmd.cmdUse
|
|
636
603
|
l.push(`var ${cmd.cmdVarName} = &cobra.Command{`)
|
|
637
|
-
l.push(`\tUse: ${
|
|
638
|
-
l.push(`\tShort: ${
|
|
604
|
+
l.push(`\tUse: ${goString(cmd.cmdUse)},`)
|
|
605
|
+
l.push(`\tShort: ${goString(shortDesc)},`)
|
|
606
|
+
if (cmd.op.description) l.push(`\tLong: ${goString(cmd.op.description)},`)
|
|
639
607
|
|
|
640
|
-
/* PreRunE: validate required body + enums */
|
|
641
608
|
const preRunELines = emitPreRunE(cmd)
|
|
642
609
|
if (preRunELines.length > 0) {
|
|
643
610
|
l.push(`\tPreRunE: func(cmd *cobra.Command, args []string) error {`)
|
|
@@ -647,285 +614,251 @@ function emitCommand(l: string[], group: ResourceGroup, cmd: CommandInfo, option
|
|
|
647
614
|
}
|
|
648
615
|
|
|
649
616
|
l.push(`\tRunE: func(cmd *cobra.Command, args []string) error {`)
|
|
650
|
-
emitRunE(l, cmd
|
|
617
|
+
emitRunE(l, cmd)
|
|
651
618
|
l.push(`\t},`)
|
|
652
619
|
l.push(`}`)
|
|
653
620
|
l.push(``)
|
|
654
621
|
}
|
|
655
622
|
|
|
656
|
-
function goFlagVarType(q: ParamInfo): string {
|
|
657
|
-
const t = q.schema.type
|
|
658
|
-
if (t === "integer") return "int64"
|
|
659
|
-
if (t === "number") return "float64"
|
|
660
|
-
if (t === "boolean") return "bool"
|
|
661
|
-
if (t === "array") return "[]string"
|
|
662
|
-
return "string"
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
function goScalarVarType(t: string): string {
|
|
666
|
-
if (t === "integer") return "int64"
|
|
667
|
-
if (t === "number") return "float64"
|
|
668
|
-
if (t === "boolean") return "bool"
|
|
669
|
-
return "string"
|
|
670
|
-
}
|
|
671
|
-
|
|
672
623
|
function emitFlagRegistrations(l: string[], cmd: CommandInfo): void {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
if (cmd.body.kind === "json-flat") {
|
|
692
|
-
for (const s of cmd.body.scalars) {
|
|
693
|
-
emitBodyScalarFlag(l, cmd, s)
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
if (cmd.body.kind === "multipart") {
|
|
697
|
-
for (const s of cmd.body.scalarFields) {
|
|
698
|
-
emitBodyScalarFlag(l, cmd, s)
|
|
624
|
+
const c = cmd.cmdVarName
|
|
625
|
+
for (const f of allFlags(cmd)) {
|
|
626
|
+
let help: string
|
|
627
|
+
switch (f.where) {
|
|
628
|
+
case "path":
|
|
629
|
+
help = `Path parameter ${f.name}`
|
|
630
|
+
break
|
|
631
|
+
case "query":
|
|
632
|
+
help = `Query param ${f.name}`
|
|
633
|
+
break
|
|
634
|
+
case "header":
|
|
635
|
+
help = `Header ${f.name}`
|
|
636
|
+
break
|
|
637
|
+
case "file":
|
|
638
|
+
help = `Path to the ${f.name} file to upload`
|
|
639
|
+
break
|
|
640
|
+
default:
|
|
641
|
+
help = `Body field ${f.name}`
|
|
699
642
|
}
|
|
700
|
-
if (
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
l.push(
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
643
|
+
if (f.enumValues.length > 0) help += ` (one of: ${f.enumValues.join(", ")})`
|
|
644
|
+
const h = goString(help)
|
|
645
|
+
const n = goString(f.flagName)
|
|
646
|
+
const def = flagDefault(f)
|
|
647
|
+
switch (f.kind) {
|
|
648
|
+
case "int":
|
|
649
|
+
l.push(`\t${c}.Flags().Int64Var(&${f.varName}, ${n}, ${def}, ${h})`)
|
|
650
|
+
break
|
|
651
|
+
case "float":
|
|
652
|
+
l.push(`\t${c}.Flags().Float64Var(&${f.varName}, ${n}, ${def}, ${h})`)
|
|
653
|
+
break
|
|
654
|
+
case "bool":
|
|
655
|
+
l.push(`\t${c}.Flags().BoolVar(&${f.varName}, ${n}, ${def}, ${h})`)
|
|
656
|
+
break
|
|
657
|
+
case "strings":
|
|
658
|
+
l.push(`\t${c}.Flags().StringSliceVar(&${f.varName}, ${n}, ${def}, ${h})`)
|
|
659
|
+
break
|
|
660
|
+
default:
|
|
661
|
+
l.push(`\t${c}.Flags().StringVar(&${f.varName}, ${n}, ${def}, ${h})`)
|
|
716
662
|
}
|
|
663
|
+
if (f.required) l.push(`\t${c}.MarkFlagRequired(${n})`)
|
|
717
664
|
}
|
|
718
|
-
if (cmd.
|
|
719
|
-
l.push(
|
|
720
|
-
`\t${cmd.cmdVarName}.Flags().StringVar(&${cmd.cmdVarName}LastEventIDVar, "last-event-id", "", "SSE Last-Event-ID resume token")`,
|
|
721
|
-
)
|
|
665
|
+
if (cmd.body.kind === "json-flat" || cmd.body.kind === "json-complex" || cmd.body.kind === "raw") {
|
|
666
|
+
l.push(`\t${c}.Flags().StringVar(&${cmd.body.dataVar}, "data", "", "Request body: literal, @file, or - for stdin")`)
|
|
722
667
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
function emitQueryFlag(l: string[], cmd: CommandInfo, q: ParamInfo): void {
|
|
726
|
-
const v = `${cmd.cmdVarName}${goPascal(q.name)}Var`
|
|
727
|
-
const t = q.schema.type
|
|
728
|
-
const help =
|
|
729
|
-
q.enumValues.length > 0 ? `Query param ${q.name} (one of: ${q.enumValues.join(", ")})` : `Query param ${q.name}`
|
|
730
|
-
const helpStr = JSON.stringify(help)
|
|
731
|
-
|
|
732
|
-
if (t === "integer") {
|
|
733
|
-
const def = typeof q.defaultValue === "number" ? q.defaultValue : 0
|
|
734
|
-
l.push(`\t${cmd.cmdVarName}.Flags().Int64Var(&${v}, ${JSON.stringify(q.flagName)}, ${def}, ${helpStr})`)
|
|
735
|
-
} else if (t === "number") {
|
|
736
|
-
const def = typeof q.defaultValue === "number" ? q.defaultValue : 0
|
|
737
|
-
l.push(`\t${cmd.cmdVarName}.Flags().Float64Var(&${v}, ${JSON.stringify(q.flagName)}, ${def}, ${helpStr})`)
|
|
738
|
-
} else if (t === "boolean") {
|
|
739
|
-
const def = q.defaultValue === true
|
|
740
|
-
l.push(
|
|
741
|
-
`\t${cmd.cmdVarName}.Flags().BoolVar(&${v}, ${JSON.stringify(q.flagName)}, ${def ? "true" : "false"}, ${helpStr})`,
|
|
742
|
-
)
|
|
743
|
-
} else if (t === "array") {
|
|
744
|
-
const defArr = Array.isArray(q.defaultValue) ? q.defaultValue : []
|
|
745
|
-
const defStr = `[]string{${defArr.map((x) => JSON.stringify(String(x))).join(", ")}}`
|
|
746
|
-
l.push(`\t${cmd.cmdVarName}.Flags().StringSliceVar(&${v}, ${JSON.stringify(q.flagName)}, ${defStr}, ${helpStr})`)
|
|
747
|
-
} else {
|
|
748
|
-
const def = typeof q.defaultValue === "string" ? q.defaultValue : ""
|
|
749
|
-
l.push(
|
|
750
|
-
`\t${cmd.cmdVarName}.Flags().StringVar(&${v}, ${JSON.stringify(q.flagName)}, ${JSON.stringify(def)}, ${helpStr})`,
|
|
751
|
-
)
|
|
752
|
-
}
|
|
753
|
-
if (q.required) {
|
|
754
|
-
l.push(`\t${cmd.cmdVarName}.MarkFlagRequired(${JSON.stringify(q.flagName)})`)
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
function emitBodyScalarFlag(l: string[], cmd: CommandInfo, s: BodyScalarInfo): void {
|
|
759
|
-
const v = `${cmd.cmdVarName}${goPascal(s.propName)}Var`
|
|
760
|
-
const help =
|
|
761
|
-
s.enumValues.length > 0
|
|
762
|
-
? `Body field ${s.propName} (one of: ${s.enumValues.join(", ")})`
|
|
763
|
-
: `Body field ${s.propName}`
|
|
764
|
-
const helpStr = JSON.stringify(help)
|
|
765
|
-
if (s.schemaType === "integer") {
|
|
766
|
-
l.push(`\t${cmd.cmdVarName}.Flags().Int64Var(&${v}, ${JSON.stringify(s.flagName)}, 0, ${helpStr})`)
|
|
767
|
-
} else if (s.schemaType === "number") {
|
|
768
|
-
l.push(`\t${cmd.cmdVarName}.Flags().Float64Var(&${v}, ${JSON.stringify(s.flagName)}, 0, ${helpStr})`)
|
|
769
|
-
} else if (s.schemaType === "boolean") {
|
|
770
|
-
l.push(`\t${cmd.cmdVarName}.Flags().BoolVar(&${v}, ${JSON.stringify(s.flagName)}, false, ${helpStr})`)
|
|
771
|
-
} else {
|
|
772
|
-
l.push(`\t${cmd.cmdVarName}.Flags().StringVar(&${v}, ${JSON.stringify(s.flagName)}, "", ${helpStr})`)
|
|
668
|
+
if (cmd.lastEventIdVar) {
|
|
669
|
+
l.push(`\t${c}.Flags().StringVar(&${cmd.lastEventIdVar}, "last-event-id", "", "SSE Last-Event-ID resume token")`)
|
|
773
670
|
}
|
|
774
671
|
}
|
|
775
672
|
|
|
776
673
|
function emitPreRunE(cmd: CommandInfo): string[] {
|
|
777
674
|
const lines: string[] = []
|
|
778
|
-
/* required body on POST/PUT/PATCH */
|
|
779
675
|
if (
|
|
780
|
-
(cmd.
|
|
781
|
-
(cmd.body.kind === "json-flat" || cmd.body.kind === "json-complex") &&
|
|
676
|
+
(cmd.body.kind === "json-flat" || cmd.body.kind === "json-complex" || cmd.body.kind === "raw") &&
|
|
782
677
|
cmd.body.required
|
|
783
678
|
) {
|
|
784
|
-
const dataVar = `${cmd.cmdVarName}DataVar`
|
|
785
679
|
const anyFlag =
|
|
786
680
|
cmd.body.kind === "json-flat" && cmd.body.scalars.length > 0
|
|
787
|
-
? cmd.body.scalars.map((s) => `cmd.Flags().Changed(${
|
|
681
|
+
? cmd.body.scalars.map((s) => `cmd.Flags().Changed(${goString(s.flagName)})`).join(" || ")
|
|
788
682
|
: "false"
|
|
789
|
-
lines.push(`if ${dataVar} == "" && !(${anyFlag}) {`)
|
|
790
|
-
lines.push(`\treturn
|
|
683
|
+
lines.push(`if ${cmd.body.dataVar} == "" && !(${anyFlag}) {`)
|
|
684
|
+
lines.push(`\treturn cli.Usage(errors.New("body required: pass --data or per-field flags"))`)
|
|
791
685
|
lines.push(`}`)
|
|
792
686
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
lines.push(`\treturn
|
|
687
|
+
for (const f of allFlags(cmd)) {
|
|
688
|
+
if (f.enumValues.length === 0 || f.kind !== "string") continue
|
|
689
|
+
const values = f.enumValues.map(goString).join(", ")
|
|
690
|
+
const msg = goString(`--${f.flagName} must be one of: ${f.enumValues.join(", ")}`)
|
|
691
|
+
lines.push(
|
|
692
|
+
`if cmd.Flags().Changed(${goString(f.flagName)}) && !slices.Contains([]string{${values}}, ${f.varName}) {`,
|
|
693
|
+
)
|
|
694
|
+
lines.push(`\treturn cli.Usage(errors.New(${msg}))`)
|
|
801
695
|
lines.push(`}`)
|
|
802
696
|
}
|
|
803
|
-
if (cmd.body.kind === "json-flat") {
|
|
804
|
-
for (const s of cmd.body.scalars) {
|
|
805
|
-
if (s.enumValues.length === 0) continue
|
|
806
|
-
if (s.schemaType !== "string") continue
|
|
807
|
-
const v = `${cmd.cmdVarName}${goPascal(s.propName)}Var`
|
|
808
|
-
const values = s.enumValues.map((x) => JSON.stringify(x)).join(", ")
|
|
809
|
-
lines.push(
|
|
810
|
-
`if cmd.Flags().Changed(${JSON.stringify(s.flagName)}) && !slices.Contains([]string{${values}}, ${v}) {`,
|
|
811
|
-
)
|
|
812
|
-
lines.push(`\treturn fmt.Errorf("--${s.flagName} must be one of: ${s.enumValues.join(", ")}")`)
|
|
813
|
-
lines.push(`}`)
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
697
|
return lines
|
|
817
698
|
}
|
|
818
699
|
|
|
819
|
-
|
|
700
|
+
/** Go expression rendering a flag variable as a wire string. */
|
|
701
|
+
function scalarString(f: Flag, expr = f.varName): string {
|
|
702
|
+
switch (f.kind) {
|
|
703
|
+
case "int":
|
|
704
|
+
return `strconv.FormatInt(${expr}, 10)`
|
|
705
|
+
case "float":
|
|
706
|
+
return `strconv.FormatFloat(${expr}, 'g', -1, 64)`
|
|
707
|
+
case "bool":
|
|
708
|
+
return `strconv.FormatBool(${expr})`
|
|
709
|
+
default:
|
|
710
|
+
return expr
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function emitRunE(l: string[], cmd: CommandInfo): void {
|
|
820
715
|
const ind = "\t\t"
|
|
716
|
+
const { op } = cmd
|
|
821
717
|
l.push(`${ind}ctx := cmd.Context()`)
|
|
822
718
|
l.push(`${ind}cfg := configFromCtx(ctx)`)
|
|
823
719
|
l.push(`${ind}apiKey, err := cli.ResolveAPIKey(cfg)`)
|
|
824
|
-
l.push(`${ind}if err != nil {
|
|
825
|
-
l.push(`${ind}
|
|
826
|
-
l.push(`${ind}
|
|
827
|
-
l.push(`${ind}if
|
|
828
|
-
l.push(`${ind}
|
|
829
|
-
l.push(`${ind}if cfg.Timeout != "" {`)
|
|
830
|
-
l.push(`${ind}\tif d, perr := time.ParseDuration(cfg.Timeout); perr == nil { timeout = d }`)
|
|
720
|
+
l.push(`${ind}if err != nil {`)
|
|
721
|
+
l.push(`${ind}\treturn err`)
|
|
722
|
+
l.push(`${ind}}`)
|
|
723
|
+
l.push(`${ind}if cfg.BaseURL == "" {`)
|
|
724
|
+
l.push(`${ind}\treturn cli.Usage(errors.New("base URL required: set --base-url, env, or config"))`)
|
|
831
725
|
l.push(`${ind}}`)
|
|
832
|
-
l.push(`${ind}httpClient := &http.Client{Timeout: timeout}`)
|
|
833
726
|
|
|
834
|
-
/* path
|
|
835
|
-
|
|
836
|
-
const
|
|
837
|
-
l.push(`${ind}${
|
|
727
|
+
/* path */
|
|
728
|
+
if (cmd.pathFlags.length > 0) {
|
|
729
|
+
const params = cmd.pathFlags.map((f) => `${goString(f.name)}: ${f.varName}`).join(", ")
|
|
730
|
+
l.push(`${ind}path, err := sdk.HoneyExpandPath(${goString(op.path)}, map[string]string{${params}})`)
|
|
731
|
+
l.push(`${ind}if err != nil {`)
|
|
732
|
+
l.push(`${ind}\treturn cli.Usage(err)`)
|
|
733
|
+
l.push(`${ind}}`)
|
|
734
|
+
} else {
|
|
735
|
+
l.push(`${ind}path := ${goString(op.path)}`)
|
|
838
736
|
}
|
|
839
737
|
|
|
840
|
-
/*
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
738
|
+
/* query: a required flag is always sent, an optional one only when set */
|
|
739
|
+
l.push(`${ind}var query [][2]string`)
|
|
740
|
+
for (const f of cmd.queryFlags) {
|
|
741
|
+
const guard = f.required ? "" : `if cmd.Flags().Changed(${goString(f.flagName)}) `
|
|
742
|
+
if (f.kind === "strings") {
|
|
743
|
+
l.push(`${ind}${guard}{`)
|
|
744
|
+
l.push(`${ind}\tfor _, v := range ${f.varName} {`)
|
|
745
|
+
l.push(`${ind}\t\tquery = append(query, [2]string{${goString(f.name)}, v})`)
|
|
746
|
+
l.push(`${ind}\t}`)
|
|
747
|
+
l.push(`${ind}}`)
|
|
748
|
+
} else {
|
|
749
|
+
l.push(`${ind}${guard}{`)
|
|
750
|
+
l.push(`${ind}\tquery = append(query, [2]string{${goString(f.name)}, ${scalarString(f)}})`)
|
|
751
|
+
l.push(`${ind}}`)
|
|
752
|
+
}
|
|
847
753
|
}
|
|
848
|
-
l.push(`${ind}
|
|
754
|
+
l.push(`${ind}reqURL, err := sdk.HoneyBuildURL(cfg.BaseURL, path, query)`)
|
|
755
|
+
l.push(`${ind}if err != nil {`)
|
|
756
|
+
l.push(`${ind}\treturn cli.Usage(err)`)
|
|
757
|
+
l.push(`${ind}}`)
|
|
849
758
|
|
|
850
|
-
/* body
|
|
851
|
-
let
|
|
852
|
-
let
|
|
853
|
-
|
|
854
|
-
|
|
759
|
+
/* body */
|
|
760
|
+
let bodyExpr = "nil"
|
|
761
|
+
let contentTypeExpr = ""
|
|
762
|
+
const b = cmd.body
|
|
763
|
+
if (b.kind === "json-flat") {
|
|
855
764
|
l.push(`${ind}merged := map[string]any{}`)
|
|
856
|
-
l.push(`${ind}if ${
|
|
857
|
-
l.push(`${ind}\tdataBytes, derr := cli.ReadDataFlag(${
|
|
858
|
-
l.push(`${ind}\tif derr != nil {
|
|
765
|
+
l.push(`${ind}if ${b.dataVar} != "" {`)
|
|
766
|
+
l.push(`${ind}\tdataBytes, derr := cli.ReadDataFlag(${b.dataVar})`)
|
|
767
|
+
l.push(`${ind}\tif derr != nil {`)
|
|
768
|
+
l.push(`${ind}\t\treturn cli.Usage(derr)`)
|
|
769
|
+
l.push(`${ind}\t}`)
|
|
859
770
|
l.push(`${ind}\tif len(dataBytes) > 0 {`)
|
|
860
|
-
l.push(
|
|
861
|
-
|
|
862
|
-
)
|
|
771
|
+
l.push(`${ind}\t\tif jerr := json.Unmarshal(dataBytes, &merged); jerr != nil {`)
|
|
772
|
+
l.push(`${ind}\t\t\treturn cli.Usage(fmt.Errorf("parse --data: %w", jerr))`)
|
|
773
|
+
l.push(`${ind}\t\t}`)
|
|
863
774
|
l.push(`${ind}\t}`)
|
|
864
775
|
l.push(`${ind}}`)
|
|
865
|
-
for (const s of
|
|
866
|
-
|
|
867
|
-
l.push(
|
|
868
|
-
|
|
869
|
-
)
|
|
776
|
+
for (const s of b.scalars) {
|
|
777
|
+
l.push(`${ind}if cmd.Flags().Changed(${goString(s.flagName)}) {`)
|
|
778
|
+
l.push(`${ind}\tmerged[${goString(s.name)}] = ${s.varName}`)
|
|
779
|
+
l.push(`${ind}}`)
|
|
870
780
|
}
|
|
871
|
-
l.push(`${ind}bodyBytes,
|
|
872
|
-
l.push(`${ind}if
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
781
|
+
l.push(`${ind}bodyBytes, err := json.Marshal(merged)`)
|
|
782
|
+
l.push(`${ind}if err != nil {`)
|
|
783
|
+
l.push(`${ind}\treturn err`)
|
|
784
|
+
l.push(`${ind}}`)
|
|
785
|
+
bodyExpr = "bytes.NewReader(bodyBytes)"
|
|
786
|
+
contentTypeExpr = goString(op.body?.contentType ?? "application/json")
|
|
787
|
+
} else if (b.kind === "json-complex" || b.kind === "raw") {
|
|
788
|
+
l.push(`${ind}bodyBytes, err := cli.ReadDataFlag(${b.dataVar})`)
|
|
789
|
+
l.push(`${ind}if err != nil {`)
|
|
790
|
+
l.push(`${ind}\treturn cli.Usage(err)`)
|
|
791
|
+
l.push(`${ind}}`)
|
|
792
|
+
bodyExpr = "bytes.NewReader(bodyBytes)"
|
|
793
|
+
contentTypeExpr = goString(op.body?.contentType ?? "application/json")
|
|
794
|
+
} else if (b.kind === "form") {
|
|
795
|
+
l.push(`${ind}form := url.Values{}`)
|
|
796
|
+
for (const s of b.scalars) {
|
|
797
|
+
l.push(`${ind}if cmd.Flags().Changed(${goString(s.flagName)}) {`)
|
|
798
|
+
l.push(`${ind}\tform.Set(${goString(s.name)}, ${scalarString(s)})`)
|
|
799
|
+
l.push(`${ind}}`)
|
|
800
|
+
}
|
|
801
|
+
bodyExpr = "strings.NewReader(form.Encode())"
|
|
802
|
+
contentTypeExpr = `"application/x-www-form-urlencoded"`
|
|
803
|
+
} else if (b.kind === "multipart") {
|
|
881
804
|
l.push(`${ind}fields := map[string]string{}`)
|
|
882
|
-
for (const s of
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
`${ind}if cmd.Flags().Changed(${JSON.stringify(s.flagName)}) { fields[${JSON.stringify(s.propName)}] = ${v} }`,
|
|
887
|
-
)
|
|
888
|
-
} else {
|
|
889
|
-
l.push(
|
|
890
|
-
`${ind}if cmd.Flags().Changed(${JSON.stringify(s.flagName)}) { fields[${JSON.stringify(s.propName)}] = fmt.Sprint(${v}) }`,
|
|
891
|
-
)
|
|
892
|
-
}
|
|
805
|
+
for (const s of b.scalars) {
|
|
806
|
+
l.push(`${ind}if cmd.Flags().Changed(${goString(s.flagName)}) {`)
|
|
807
|
+
l.push(`${ind}\tfields[${goString(s.name)}] = ${scalarString(s)}`)
|
|
808
|
+
l.push(`${ind}}`)
|
|
893
809
|
}
|
|
894
810
|
l.push(`${ind}files := map[string]string{}`)
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
l.push(
|
|
898
|
-
|
|
899
|
-
)
|
|
900
|
-
} else {
|
|
901
|
-
for (const f of cmd.body.binaryFields) {
|
|
902
|
-
const v = `${cmd.cmdVarName}${goPascal(f.name)}Var`
|
|
903
|
-
l.push(`${ind}if ${v} != "" { files[${JSON.stringify(f.name)}] = ${v} }`)
|
|
904
|
-
}
|
|
811
|
+
for (const f of b.files) {
|
|
812
|
+
l.push(`${ind}if ${f.varName} != "" {`)
|
|
813
|
+
l.push(`${ind}\tfiles[${goString(f.name)}] = ${f.varName}`)
|
|
814
|
+
l.push(`${ind}}`)
|
|
905
815
|
}
|
|
906
|
-
l.push(`${ind}mpReader, mpCT,
|
|
907
|
-
l.push(`${ind}if
|
|
908
|
-
l.push(`${ind}
|
|
909
|
-
|
|
910
|
-
|
|
816
|
+
l.push(`${ind}mpReader, mpCT, err := cli.BuildMultipart(fields, files)`)
|
|
817
|
+
l.push(`${ind}if err != nil {`)
|
|
818
|
+
l.push(`${ind}\treturn err`)
|
|
819
|
+
l.push(`${ind}}`)
|
|
820
|
+
bodyExpr = "mpReader"
|
|
821
|
+
contentTypeExpr = "mpCT"
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/* regular calls get --timeout; streams and uploads only stop on ^C */
|
|
825
|
+
const bounded = op.stream === null && b.kind !== "multipart"
|
|
826
|
+
if (bounded) {
|
|
827
|
+
l.push(`${ind}timeout, err := time.ParseDuration(cfg.Timeout)`)
|
|
828
|
+
l.push(`${ind}if err != nil {`)
|
|
829
|
+
l.push(`${ind}\treturn cli.Usage(err)`)
|
|
830
|
+
l.push(`${ind}}`)
|
|
831
|
+
l.push(`${ind}ctx, cancel := context.WithTimeout(ctx, timeout)`)
|
|
832
|
+
l.push(`${ind}defer cancel()`)
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
l.push(`${ind}req, err := http.NewRequestWithContext(ctx, ${goString(op.method)}, reqURL, ${bodyExpr})`)
|
|
836
|
+
l.push(`${ind}if err != nil {`)
|
|
837
|
+
l.push(`${ind}\treturn err`)
|
|
838
|
+
l.push(`${ind}}`)
|
|
839
|
+
if (contentTypeExpr) l.push(`${ind}req.Header.Set("Content-Type", ${contentTypeExpr})`)
|
|
840
|
+
for (const f of cmd.headerFlags) {
|
|
841
|
+
const guard = f.required ? "" : `if cmd.Flags().Changed(${goString(f.flagName)}) `
|
|
842
|
+
const value = f.kind === "strings" ? `strings.Join(${f.varName}, ",")` : scalarString(f)
|
|
843
|
+
l.push(`${ind}${guard}{`)
|
|
844
|
+
l.push(`${ind}\treq.Header.Set(${goString(f.name)}, ${value})`)
|
|
845
|
+
l.push(`${ind}}`)
|
|
911
846
|
}
|
|
847
|
+
l.push(`${ind}cli.ApplyAuth(req.Header, AuthHeaderName, AuthHeaderPrefix, apiKey)`)
|
|
912
848
|
|
|
913
|
-
|
|
914
|
-
if (cmd.isSSE) {
|
|
915
|
-
l.push(`${ind}req, rerr := http.NewRequestWithContext(ctx, "GET", reqURL, nil)`)
|
|
916
|
-
l.push(`${ind}if rerr != nil { return rerr }`)
|
|
849
|
+
if (op.stream === "sse") {
|
|
917
850
|
l.push(`${ind}req.Header.Set("Accept", "text/event-stream")`)
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
l.push(
|
|
921
|
-
|
|
922
|
-
)
|
|
851
|
+
if (cmd.lastEventIdVar) {
|
|
852
|
+
l.push(`${ind}if ${cmd.lastEventIdVar} != "" {`)
|
|
853
|
+
l.push(`${ind}\treq.Header.Set("Last-Event-ID", ${cmd.lastEventIdVar})`)
|
|
854
|
+
l.push(`${ind}}`)
|
|
923
855
|
}
|
|
924
|
-
l.push(`${ind}resp,
|
|
925
|
-
l.push(`${ind}if
|
|
926
|
-
l.push(`${ind}
|
|
927
|
-
l.push(`${ind}
|
|
856
|
+
l.push(`${ind}resp, err := newHTTPClient().Do(req)`)
|
|
857
|
+
l.push(`${ind}if err != nil {`)
|
|
858
|
+
l.push(`${ind}\treturn err`)
|
|
859
|
+
l.push(`${ind}}`)
|
|
928
860
|
/* wrap SDK's Seq2[sdk.SSEEvent] → Seq2[cli.SSEEvent] so the runtime stays SDK-agnostic. */
|
|
861
|
+
l.push(`${ind}it := sdk.HoneyParseSSEStream(ctx, resp)`)
|
|
929
862
|
l.push(`${ind}wrapped := func(yield func(cli.SSEEvent, error) bool) {`)
|
|
930
863
|
l.push(`${ind}\tfor ev, iterErr := range it {`)
|
|
931
864
|
l.push(`${ind}\t\tif !yield(cli.SSEEvent{Data: ev.Data, Event: ev.Event, ID: ev.ID, Retry: ev.Retry}, iterErr) {`)
|
|
@@ -937,75 +870,33 @@ function emitRunE(l: string[], cmd: CommandInfo, _options: GoCLIOptions): void {
|
|
|
937
870
|
return
|
|
938
871
|
}
|
|
939
872
|
|
|
940
|
-
|
|
941
|
-
l.push(`${ind}
|
|
942
|
-
l.push(`${ind}
|
|
943
|
-
|
|
944
|
-
l.push(`${ind}req.Header.Set("Content-Type", "application/json")`)
|
|
945
|
-
} else if (contentType === "__multipart__") {
|
|
946
|
-
l.push(`${ind}req.Header.Set("Content-Type", mpCT) // multipart/form-data; boundary=...`)
|
|
947
|
-
}
|
|
948
|
-
l.push(`${ind}if apiKey != "" { req.Header.Set("Authorization", "Bearer "+apiKey) }`)
|
|
949
|
-
l.push(`${ind}resp, herr := httpClient.Do(req)`)
|
|
950
|
-
l.push(`${ind}if herr != nil { return herr }`)
|
|
873
|
+
l.push(`${ind}resp, err := newHTTPClient().Do(req)`)
|
|
874
|
+
l.push(`${ind}if err != nil {`)
|
|
875
|
+
l.push(`${ind}\treturn err`)
|
|
876
|
+
l.push(`${ind}}`)
|
|
951
877
|
l.push(`${ind}defer resp.Body.Close()`)
|
|
952
|
-
l.push(`${ind}body,
|
|
953
|
-
l.push(`${ind}if
|
|
954
|
-
l.push(`${ind}
|
|
955
|
-
l.push(
|
|
956
|
-
`${ind}\treturn &sdk.StatusError{StatusCode: resp.StatusCode, Body: body, Response: resp, Message: string(body)}`,
|
|
957
|
-
)
|
|
878
|
+
l.push(`${ind}body, err := io.ReadAll(resp.Body)`)
|
|
879
|
+
l.push(`${ind}if err != nil {`)
|
|
880
|
+
l.push(`${ind}\treturn err`)
|
|
958
881
|
l.push(`${ind}}`)
|
|
959
|
-
if (
|
|
960
|
-
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
l.push(`${ind}
|
|
965
|
-
l.push(`${ind}
|
|
882
|
+
l.push(`${ind}if err := sdk.HoneyRaiseForStatus(resp, body); err != nil {`)
|
|
883
|
+
l.push(`${ind}\treturn err`)
|
|
884
|
+
l.push(`${ind}}`)
|
|
885
|
+
const kind = op.success.kind
|
|
886
|
+
if (kind === "json") {
|
|
887
|
+
l.push(`${ind}out, err := cli.DecodeJSON(body)`)
|
|
888
|
+
l.push(`${ind}if err != nil {`)
|
|
889
|
+
l.push(`${ind}\treturn err`)
|
|
966
890
|
l.push(`${ind}}`)
|
|
967
891
|
l.push(`${ind}return cli.Emit(out, cfg.Output, os.Stdout)`)
|
|
892
|
+
} else if (kind === "text" || kind === "binary") {
|
|
893
|
+
l.push(`${ind}_, err = os.Stdout.Write(body)`)
|
|
894
|
+
l.push(`${ind}return err`)
|
|
968
895
|
} else {
|
|
969
896
|
l.push(`${ind}return nil`)
|
|
970
897
|
}
|
|
971
898
|
}
|
|
972
899
|
|
|
973
|
-
function buildPathExpr(cmd: CommandInfo): string {
|
|
974
|
-
if (cmd.pathParams.length === 0) return JSON.stringify(cmd.path)
|
|
975
|
-
let expr = JSON.stringify(cmd.path)
|
|
976
|
-
for (const p of cmd.pathParams) {
|
|
977
|
-
expr = expr.replace(`{${p.name}}`, `" + url.PathEscape(${p.varName}) + "`)
|
|
978
|
-
}
|
|
979
|
-
expr = expr.replace(/"" \+ /g, "").replace(/ \+ ""/g, "")
|
|
980
|
-
return expr
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
function querySetStmt(q: ParamInfo, v: string): string {
|
|
984
|
-
const name = JSON.stringify(q.name)
|
|
985
|
-
switch (q.schema.type) {
|
|
986
|
-
case "integer":
|
|
987
|
-
return `q.Set(${name}, fmt.Sprintf("%d", ${v}))`
|
|
988
|
-
case "number":
|
|
989
|
-
return `q.Set(${name}, fmt.Sprintf("%g", ${v}))`
|
|
990
|
-
case "boolean":
|
|
991
|
-
return `q.Set(${name}, fmt.Sprintf("%t", ${v}))`
|
|
992
|
-
case "array":
|
|
993
|
-
return `for _, v := range ${v} { q.Add(${name}, v) }`
|
|
994
|
-
default:
|
|
995
|
-
return `q.Set(${name}, ${v})`
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
function emitQueryAssembly(l: string[], ind: string, cmd: CommandInfo, q: ParamInfo): void {
|
|
1000
|
-
const v = `${cmd.cmdVarName}${goPascal(q.name)}Var`
|
|
1001
|
-
const stmt = querySetStmt(q, v)
|
|
1002
|
-
if (q.required) {
|
|
1003
|
-
l.push(`${ind}${stmt}`)
|
|
1004
|
-
return
|
|
1005
|
-
}
|
|
1006
|
-
l.push(`${ind}if cmd.Flags().Changed(${JSON.stringify(q.flagName)}) { ${stmt} }`)
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
900
|
/* ── emit: main.go ── */
|
|
1010
901
|
|
|
1011
902
|
function emitMainFile(options: GoCLIOptions): string {
|
|
@@ -1018,13 +909,13 @@ function emitMainFile(options: GoCLIOptions): string {
|
|
|
1018
909
|
l.push(`\t"fmt"`)
|
|
1019
910
|
l.push(`\t"os"`)
|
|
1020
911
|
l.push(``)
|
|
1021
|
-
l.push(`\tcli
|
|
1022
|
-
l.push(`\t
|
|
912
|
+
l.push(`\tcli ${goString(`${module}/internal/cli`)}`)
|
|
913
|
+
l.push(`\t${goString(`${module}/cmd`)}`)
|
|
1023
914
|
l.push(`)`)
|
|
1024
915
|
l.push(``)
|
|
1025
916
|
l.push(`func main() {`)
|
|
1026
917
|
l.push(`\tif err := cmd.Execute(); err != nil {`)
|
|
1027
|
-
l.push(`\t\tfmt.Fprintln(os.Stderr, err)`)
|
|
918
|
+
l.push(`\t\tfmt.Fprintln(os.Stderr, cli.Sanitize(err.Error()))`)
|
|
1028
919
|
l.push(`\t\tos.Exit(cli.ExitFor(err))`)
|
|
1029
920
|
l.push(`\t}`)
|
|
1030
921
|
l.push(`}`)
|
|
@@ -1039,7 +930,7 @@ function emitGoMod(options: GoCLIOptions, hasEmbeddedSDK: boolean): string {
|
|
|
1039
930
|
const l: string[] = []
|
|
1040
931
|
l.push(`module ${mod}`)
|
|
1041
932
|
l.push(``)
|
|
1042
|
-
l.push(`go 1.
|
|
933
|
+
l.push(`go 1.24`)
|
|
1043
934
|
l.push(``)
|
|
1044
935
|
l.push(`require (`)
|
|
1045
936
|
l.push(`\tgithub.com/spf13/cobra v1.8.1`)
|
|
@@ -1062,10 +953,10 @@ function emitGoMod(options: GoCLIOptions, hasEmbeddedSDK: boolean): string {
|
|
|
1062
953
|
|
|
1063
954
|
/* ── emit: README.md ── */
|
|
1064
955
|
|
|
1065
|
-
function emitReadme(
|
|
956
|
+
function emitReadme(info: IRInfo, options: GoCLIOptions): string {
|
|
1066
957
|
const envPrefix = options.envPrefix ?? toSnakeUpper(options.binaryName)
|
|
1067
958
|
const configName = options.configName ?? options.binaryName
|
|
1068
|
-
const title =
|
|
959
|
+
const title = info.title ?? options.binaryName
|
|
1069
960
|
return [
|
|
1070
961
|
`# ${options.binaryName}`,
|
|
1071
962
|
``,
|
|
@@ -1074,22 +965,22 @@ function emitReadme(spec: OpenApiSpec, options: GoCLIOptions): string {
|
|
|
1074
965
|
`## Install`,
|
|
1075
966
|
``,
|
|
1076
967
|
"```sh",
|
|
1077
|
-
`go build -o ${options.binaryName}
|
|
968
|
+
`go build -ldflags "-X ${resolveModulePath(options)}/internal/cli.Version=1.0.0" -o ${options.binaryName} .`,
|
|
1078
969
|
"```",
|
|
1079
970
|
``,
|
|
1080
971
|
`## Auth`,
|
|
1081
972
|
``,
|
|
1082
973
|
`Resolution order (high → low):`,
|
|
1083
974
|
``,
|
|
1084
|
-
`1. \`--api-key <value
|
|
975
|
+
`1. \`--api-key <value>\` (visible in the process list; prefer the env var)`,
|
|
1085
976
|
`2. \`${envPrefix}_API_KEY\` env var`,
|
|
1086
|
-
`3. \`~/.config/${configName}/config.toml\` → \`api_key = "..."
|
|
977
|
+
`3. \`~/.config/${configName}/config.toml\` → \`api_key = "..."\` (keep it \`chmod 600\`)`,
|
|
1087
978
|
`4. \`$XDG_CONFIG_HOME/${configName}/config.toml\``,
|
|
1088
979
|
``,
|
|
1089
980
|
`## Global flags`,
|
|
1090
981
|
``,
|
|
1091
982
|
`- \`--api-key\`, \`--base-url\`, \`--output\` (json|ndjson|yaml|table)`,
|
|
1092
|
-
`- \`--verbose\`, \`--config\`, \`--timeout
|
|
983
|
+
`- \`--verbose\`, \`--config\`, \`--timeout\` (regular calls only; streams and uploads are not bounded)`,
|
|
1093
984
|
``,
|
|
1094
985
|
`## Exit codes`,
|
|
1095
986
|
``,
|
|
@@ -1097,8 +988,8 @@ function emitReadme(spec: OpenApiSpec, options: GoCLIOptions): string {
|
|
|
1097
988
|
`- 1: 4xx client error`,
|
|
1098
989
|
`- 2: 5xx server error`,
|
|
1099
990
|
`- 3: network failure / timeout`,
|
|
1100
|
-
`- 4: bad flags / missing api-key`,
|
|
1101
|
-
`- 5: unexpected 3xx`,
|
|
991
|
+
`- 4: bad flags / bad config / missing api-key`,
|
|
992
|
+
`- 5: unexpected 3xx (redirects to another host are not followed)`,
|
|
1102
993
|
``,
|
|
1103
994
|
].join("\n")
|
|
1104
995
|
}
|
|
@@ -1116,12 +1007,10 @@ function copyEmbeddedSDK(spec: Record<string, unknown>, options: GoCLIOptions, o
|
|
|
1116
1007
|
}
|
|
1117
1008
|
|
|
1118
1009
|
/* ── ParseSSEStreamExported shim ──
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1121
|
-
* When embedding, add a small sse_export.go into internal/sdk/.
|
|
1122
|
-
* When using external SDK, we still need the same symbol — emit an adapter file. */
|
|
1010
|
+
* Older generated CLIs call ParseSSEStreamExported; kept so they keep compiling against
|
|
1011
|
+
* an embedded SDK. New code uses the Honey* helpers in export.go. */
|
|
1123
1012
|
|
|
1124
|
-
function emitSSEExportShim(
|
|
1013
|
+
function emitSSEExportShim(files: Record<string, string>, hasEmbedded: boolean): void {
|
|
1125
1014
|
const shim = [
|
|
1126
1015
|
`// Code generated by honey. DO NOT EDIT.`,
|
|
1127
1016
|
`package sdk`,
|
|
@@ -1138,18 +1027,17 @@ function emitSSEExportShim(options: GoCLIOptions, files: Record<string, string>,
|
|
|
1138
1027
|
`}`,
|
|
1139
1028
|
``,
|
|
1140
1029
|
].join("\n")
|
|
1141
|
-
if (hasEmbedded)
|
|
1142
|
-
files["internal/sdk/sse_export.go"] = shim
|
|
1143
|
-
}
|
|
1144
|
-
/* external-SDK mode: consumer's SDK must already export or we rely on ParseSSEStreamExported existing.
|
|
1145
|
-
We include the shim in embed mode only; external mode assumes SDK regenerated w/ same honey. */
|
|
1030
|
+
if (hasEmbedded) files["internal/sdk/sse_export.go"] = shim
|
|
1146
1031
|
}
|
|
1147
1032
|
|
|
1148
1033
|
/* ── public entrypoint ── */
|
|
1149
1034
|
|
|
1150
1035
|
export function generateGoCLI(spec: Record<string, unknown>, options: GoCLIOptions): GeneratedGoCLI {
|
|
1151
|
-
const
|
|
1152
|
-
const
|
|
1036
|
+
const input = spec as unknown as OpenApiSpecInput
|
|
1037
|
+
const model = buildSdkModel(input)
|
|
1038
|
+
const serviceMap = serviceMapOf(model.ir)
|
|
1039
|
+
const pkg = new NameScope(CMD_PACKAGE_NAMES)
|
|
1040
|
+
const { groups, skipped } = collectCLIMethods(model, pkg)
|
|
1153
1041
|
|
|
1154
1042
|
const hasEmbeddedSDK = !options.sdkModulePath
|
|
1155
1043
|
const files: Record<string, string> = {}
|
|
@@ -1159,29 +1047,20 @@ export function generateGoCLI(spec: Record<string, unknown>, options: GoCLIOptio
|
|
|
1159
1047
|
files[`internal/cli/${name}`] = content
|
|
1160
1048
|
}
|
|
1161
1049
|
|
|
1162
|
-
/* main.go */
|
|
1163
1050
|
files["main.go"] = emitMainFile(options)
|
|
1164
1051
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
files["cmd/root.go"] = emitRootFile(sortedGroups, options)
|
|
1052
|
+
const sortedGroups = [...groups.values()].sort((a, b) => cmpCodeUnit(a.resource, b.resource))
|
|
1053
|
+
files["cmd/root.go"] = emitRootFile(sortedGroups, options, model.ir.auth)
|
|
1168
1054
|
|
|
1169
|
-
/* cmd/<resource>.go */
|
|
1170
1055
|
for (const g of sortedGroups) {
|
|
1171
1056
|
files[`cmd/${g.fileName}`] = emitResourceFile(g, options)
|
|
1172
1057
|
}
|
|
1173
1058
|
|
|
1174
|
-
/* go.mod */
|
|
1175
1059
|
files["go.mod"] = emitGoMod(options, hasEmbeddedSDK)
|
|
1060
|
+
files["README.md"] = emitReadme(model.ir.info, options)
|
|
1176
1061
|
|
|
1177
|
-
|
|
1178
|
-
files
|
|
1179
|
-
|
|
1180
|
-
/* SDK embed if sdkModulePath not set */
|
|
1181
|
-
if (hasEmbeddedSDK) {
|
|
1182
|
-
copyEmbeddedSDK(spec, options, files)
|
|
1183
|
-
}
|
|
1184
|
-
emitSSEExportShim(options, files, hasEmbeddedSDK)
|
|
1062
|
+
if (hasEmbeddedSDK) copyEmbeddedSDK(spec, options, files)
|
|
1063
|
+
emitSSEExportShim(files, hasEmbeddedSDK)
|
|
1185
1064
|
|
|
1186
|
-
return { files, serviceMap, skippedOperations: skipped }
|
|
1065
|
+
return { files, serviceMap: serviceMap as Record<string, Record<string, unknown>>, skippedOperations: skipped }
|
|
1187
1066
|
}
|