@pikku/cli 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/cli.schema.json +1 -1
  2. package/console-app/assets/index-DiYPTQU_.js +676 -0
  3. package/console-app/index.html +6 -1
  4. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +30 -3
  5. package/dist/.pikku/agent/pikku-agent-types.gen.js +13 -0
  6. package/dist/.pikku/agent/pikku-agent-wirings-meta.gen.js +2 -2
  7. package/dist/.pikku/agent/pikku-agent-wirings.gen.d.ts +1 -1
  8. package/dist/.pikku/agent/pikku-agent-wirings.gen.js +1 -1
  9. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  10. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  11. package/dist/.pikku/channel/pikku-channels-meta.gen.js +2 -2
  12. package/dist/.pikku/channel/pikku-channels.gen.d.ts +1 -1
  13. package/dist/.pikku/channel/pikku-channels.gen.js +1 -1
  14. package/dist/.pikku/cli/pikku-cli-channel.js +57 -3
  15. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  16. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  17. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  18. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  19. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +2 -2
  20. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +228 -10
  21. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  22. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  23. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  24. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  25. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  26. package/dist/.pikku/function/pikku-function-types.gen.d.ts +14 -11
  27. package/dist/.pikku/function/pikku-function-types.gen.js +25 -13
  28. package/dist/.pikku/function/pikku-functions-meta.gen.js +2 -2
  29. package/dist/.pikku/function/pikku-functions-meta.gen.json +359 -105
  30. package/dist/.pikku/function/pikku-functions.gen.js +3 -3
  31. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  32. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  33. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +2 -2
  34. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
  35. package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
  36. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  37. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  38. package/dist/.pikku/mcp/pikku-mcp-wirings-meta.gen.js +2 -2
  39. package/dist/.pikku/mcp/pikku-mcp-wirings.gen.d.ts +1 -1
  40. package/dist/.pikku/mcp/pikku-mcp-wirings.gen.js +1 -1
  41. package/dist/.pikku/pikku-bootstrap.gen.js +2 -2
  42. package/dist/.pikku/pikku-services.gen.d.ts +2 -1
  43. package/dist/.pikku/pikku-services.gen.js +1 -0
  44. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  45. package/dist/.pikku/pikku-types.gen.js +1 -1
  46. package/dist/.pikku/pikku-websocket.gen.d.ts +1 -1
  47. package/dist/.pikku/pikku-websocket.gen.js +1 -1
  48. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  49. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  50. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +2 -2
  51. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  52. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  53. package/dist/.pikku/rpc/pikku-remote-rpc-workers.gen.js +1 -1
  54. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +2 -2
  55. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +15 -6
  56. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  57. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  58. package/dist/.pikku/scheduler/pikku-schedulers-wirings-meta.gen.js +2 -2
  59. package/dist/.pikku/scheduler/pikku-schedulers-wirings.gen.d.ts +1 -1
  60. package/dist/.pikku/scheduler/pikku-schedulers-wirings.gen.js +1 -1
  61. package/dist/.pikku/schemas/register.gen.js +15 -5
  62. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  63. package/dist/.pikku/schemas/schemas/PikkuNewAddonInput.schema.json +1 -0
  64. package/dist/.pikku/schemas/schemas/PikkuNewFunctionInput.schema.json +1 -0
  65. package/dist/.pikku/schemas/schemas/PikkuNewMiddlewareInput.schema.json +1 -0
  66. package/dist/.pikku/schemas/schemas/PikkuNewPermissionInput.schema.json +1 -0
  67. package/dist/.pikku/schemas/schemas/PikkuNewWiringInput.schema.json +1 -0
  68. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  69. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  70. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  71. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  72. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  73. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  74. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  75. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  76. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  77. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  78. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  79. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  80. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +2 -2
  81. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.d.ts +1 -1
  82. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  83. package/dist/src/cli.wiring.js +190 -9
  84. package/dist/src/functions/commands/all.js +6 -6
  85. package/dist/src/functions/commands/info.d.ts +9 -0
  86. package/dist/src/functions/commands/info.js +283 -0
  87. package/dist/src/functions/commands/new-addon.d.ts +34 -0
  88. package/dist/src/functions/commands/new-addon.js +636 -0
  89. package/dist/src/functions/commands/new-function.d.ts +10 -0
  90. package/dist/src/functions/commands/new-function.js +79 -0
  91. package/dist/src/functions/commands/new-middleware.d.ts +10 -0
  92. package/dist/src/functions/commands/new-middleware.js +48 -0
  93. package/dist/src/functions/commands/new-permission.d.ts +10 -0
  94. package/dist/src/functions/commands/new-permission.js +45 -0
  95. package/dist/src/functions/commands/new-wiring.d.ts +10 -0
  96. package/dist/src/functions/commands/new-wiring.js +102 -0
  97. package/dist/src/functions/commands/pikku-command-bootstrap.js +11 -40
  98. package/dist/src/functions/commands/versions-check.js +85 -3
  99. package/dist/src/functions/commands/versions-update.js +1 -1
  100. package/dist/src/functions/runtimes/nextjs/serialize-nextjs-backend-wrapper.js +0 -4
  101. package/dist/src/functions/wirings/ai-agent/pikku-command-ai-agent-types.js +3 -2
  102. package/dist/src/functions/wirings/ai-agent/pikku-command-ai-agent.js +5 -5
  103. package/dist/src/functions/wirings/ai-agent/serialize-agent-map.d.ts +1 -1
  104. package/dist/src/functions/wirings/ai-agent/serialize-ai-agent-types.d.ts +1 -1
  105. package/dist/src/functions/wirings/ai-agent/serialize-ai-agent-types.js +48 -3
  106. package/dist/src/functions/wirings/ai-agent/serialize-public-agent.js +30 -52
  107. package/dist/src/functions/wirings/channels/pikku-channels.js +1 -1
  108. package/dist/src/functions/wirings/channels/pikku-command-channel-types.js +2 -2
  109. package/dist/src/functions/wirings/channels/pikku-command-channels.js +1 -1
  110. package/dist/src/functions/wirings/channels/serialize-typed-channel-map.d.ts +4 -4
  111. package/dist/src/functions/wirings/cli/pikku-command-cli.js +1 -1
  112. package/dist/src/functions/wirings/cli/serialize-channel-cli-client.d.ts +2 -2
  113. package/dist/src/functions/wirings/cli/serialize-channel-cli.d.ts +1 -1
  114. package/dist/src/functions/wirings/cli/serialize-local-cli-bootstrap.d.ts +1 -1
  115. package/dist/src/functions/wirings/console/pikku-command-console-functions.js +2 -1
  116. package/dist/src/functions/wirings/console/pikku-command-nodes-meta.js +1 -1
  117. package/dist/src/functions/wirings/console/serialize-console-functions.d.ts +1 -1
  118. package/dist/src/functions/wirings/console/serialize-console-functions.js +18 -157
  119. package/dist/src/functions/wirings/functions/pikku-command-addon-types.d.ts +1 -0
  120. package/dist/src/functions/wirings/functions/pikku-command-addon-types.js +33 -0
  121. package/dist/src/functions/wirings/functions/pikku-command-function-types-split.js +1 -1
  122. package/dist/src/functions/wirings/functions/pikku-command-function-types.js +4 -4
  123. package/dist/src/functions/wirings/functions/pikku-command-functions.js +8 -14
  124. package/dist/src/functions/wirings/functions/schemas.js +1 -1
  125. package/dist/src/functions/wirings/functions/serialize-addon-types.d.ts +1 -0
  126. package/dist/src/functions/wirings/functions/{serialize-external-types.js → serialize-addon-types.js} +16 -15
  127. package/dist/src/functions/wirings/functions/serialize-function-imports.d.ts +3 -3
  128. package/dist/src/functions/wirings/functions/serialize-function-imports.js +3 -3
  129. package/dist/src/functions/wirings/functions/serialize-function-types.js +28 -14
  130. package/dist/src/functions/wirings/functions/serialize-pikku-types-hub.d.ts +1 -1
  131. package/dist/src/functions/wirings/functions/serialize-pikku-types-hub.js +2 -2
  132. package/dist/src/functions/wirings/http/pikku-command-http-routes.js +1 -1
  133. package/dist/src/functions/wirings/http/pikku-http-routes.js +1 -1
  134. package/dist/src/functions/wirings/http/serialize-typed-http-map.d.ts +3 -3
  135. package/dist/src/functions/wirings/mcp/pikku-command-mcp.js +1 -1
  136. package/dist/src/functions/wirings/package/pikku-command-package.js +5 -5
  137. package/dist/src/functions/wirings/package/serialize-package.js +2 -2
  138. package/dist/src/functions/wirings/permissions/pikku-command-permissions.js +0 -5
  139. package/dist/src/functions/wirings/queue/serialize-queue-map.d.ts +2 -2
  140. package/dist/src/functions/wirings/queue/serialize-queue-meta.d.ts +1 -1
  141. package/dist/src/functions/wirings/rpc/pikku-command-rpc-map.js +6 -6
  142. package/dist/src/functions/wirings/rpc/pikku-command-rpc.js +2 -4
  143. package/dist/src/functions/wirings/rpc/serialize-public-rpc.js +25 -16
  144. package/dist/src/functions/wirings/rpc/serialize-rpc-wrapper.js +43 -7
  145. package/dist/src/functions/wirings/rpc/serialize-typed-rpc-map.d.ts +8 -3
  146. package/dist/src/functions/wirings/rpc/serialize-typed-rpc-map.js +41 -31
  147. package/dist/src/functions/wirings/scheduler/serialize-scheduler-meta.d.ts +1 -1
  148. package/dist/src/functions/wirings/secrets/serialize-secrets-types.d.ts +2 -2
  149. package/dist/src/functions/wirings/triggers/serialize-trigger-meta.d.ts +1 -1
  150. package/dist/src/functions/wirings/triggers/serialize-trigger-meta.js +2 -2
  151. package/dist/src/functions/wirings/variables/serialize-variables-types.d.ts +2 -2
  152. package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +2 -2
  153. package/dist/src/functions/wirings/workflow/serialize-workflow-map.d.ts +2 -2
  154. package/dist/src/functions/wirings/workflow/serialize-workflow-meta.js +2 -2
  155. package/dist/src/services/cli-logger-forwarder.service.d.ts +4 -3
  156. package/dist/src/services/cli-logger.service.d.ts +3 -2
  157. package/dist/src/services.d.ts +4 -3
  158. package/dist/src/services.js +2 -3
  159. package/dist/src/utils/check-required-types.d.ts +1 -1
  160. package/dist/src/utils/contract-versions.d.ts +1 -1
  161. package/dist/src/utils/file-writer.d.ts +6 -1
  162. package/dist/src/utils/file-writer.js +14 -1
  163. package/dist/src/utils/generate-bootstrap-file.d.ts +2 -2
  164. package/dist/src/utils/openapi/codegen.d.ts +19 -0
  165. package/dist/src/utils/openapi/codegen.js +288 -0
  166. package/dist/src/utils/openapi/naming.d.ts +30 -0
  167. package/dist/src/utils/openapi/naming.js +167 -0
  168. package/dist/src/utils/openapi/parse-openapi.d.ts +36 -0
  169. package/dist/src/utils/openapi/parse-openapi.js +196 -0
  170. package/dist/src/utils/openapi/zod-codegen.d.ts +53 -0
  171. package/dist/src/utils/openapi/zod-codegen.js +251 -0
  172. package/dist/src/utils/pikku-cli-config.d.ts +2 -2
  173. package/dist/src/utils/pikku-cli-config.js +8 -14
  174. package/dist/src/utils/pikku-files-and-methods.d.ts +1 -1
  175. package/dist/src/utils/pikku-files-and-methods.js +1 -1
  176. package/dist/src/utils/serialize-import-map.d.ts +2 -2
  177. package/dist/src/utils/serialize-import-map.js +1 -1
  178. package/dist/src/utils/serialize-meta-ts.js +1 -1
  179. package/dist/src/utils/serialize-schemas.d.ts +2 -2
  180. package/dist/tsconfig.tsbuildinfo +1 -1
  181. package/package.json +3 -3
  182. package/console-app/assets/index-C19L3UJu.js +0 -637
  183. package/dist/src/functions/wirings/functions/pikku-command-external-types.d.ts +0 -1
  184. package/dist/src/functions/wirings/functions/pikku-command-external-types.js +0 -33
  185. package/dist/src/functions/wirings/functions/serialize-external-types.d.ts +0 -1
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Derives function/method names from HTTP method + path.
3
+ * Uses operationId when available, otherwise generates from path segments.
4
+ */
5
+ const IRREGULAR_PLURALS = {
6
+ addresses: 'address',
7
+ statuses: 'status',
8
+ indices: 'index',
9
+ analyses: 'analysis',
10
+ quizzes: 'quiz',
11
+ matrices: 'matrix',
12
+ vertices: 'vertex',
13
+ aliases: 'alias',
14
+ buses: 'bus',
15
+ };
16
+ export function singularize(word) {
17
+ const lower = word.toLowerCase();
18
+ if (IRREGULAR_PLURALS[lower]) {
19
+ const singular = IRREGULAR_PLURALS[lower];
20
+ return word[0] === word[0].toUpperCase()
21
+ ? singular.charAt(0).toUpperCase() + singular.slice(1)
22
+ : singular;
23
+ }
24
+ if (lower.endsWith('ies') && lower.length > 4) {
25
+ return word.slice(0, -3) + 'y';
26
+ }
27
+ // -shes, -ches, -xes, -zes, -sses → drop "es"
28
+ if (lower.endsWith('shes') || lower.endsWith('ches') || lower.endsWith('xes') || lower.endsWith('zes') || lower.endsWith('sses')) {
29
+ return word.slice(0, -2);
30
+ }
31
+ // General: drop trailing "s" (covers courses→course, products→product, etc.)
32
+ if (lower.endsWith('s') && !lower.endsWith('ss') && !lower.endsWith('us') && lower.length > 2) {
33
+ return word.slice(0, -1);
34
+ }
35
+ return word;
36
+ }
37
+ export function toCamelCase(str) {
38
+ return str
39
+ .replace(/[^a-zA-Z0-9]+(.)/g, (_, c) => c.toUpperCase())
40
+ .replace(/^[A-Z]/, (c) => c.toLowerCase());
41
+ }
42
+ export function toPascalCase(str) {
43
+ const camel = toCamelCase(str);
44
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
45
+ }
46
+ /**
47
+ * Strip common API prefix from paths (e.g. /api/v2/).
48
+ * Uses majority voting — a prefix shared by >75% of paths is stripped,
49
+ * so a few outlier paths (e.g. /oauth/...) don't break detection.
50
+ */
51
+ export function detectCommonPrefix(paths) {
52
+ if (paths.length === 0)
53
+ return '';
54
+ const segments = paths.map((p) => p.replace(/^\//, '').split('/'));
55
+ const threshold = Math.ceil(paths.length * 0.75);
56
+ // Find the longest prefix shared by at least `threshold` paths
57
+ let prefixLen = 0;
58
+ for (let i = 0; i < 10; i++) {
59
+ // Count how many paths have the same segment at position i as the first path
60
+ const candidateSeg = segments[0][i];
61
+ if (!candidateSeg || candidateSeg.startsWith('{'))
62
+ break;
63
+ const count = segments.filter((s) => s.length > i + 1 && s[i] === candidateSeg).length;
64
+ if (count >= threshold) {
65
+ prefixLen = i + 1;
66
+ }
67
+ else {
68
+ break;
69
+ }
70
+ }
71
+ if (prefixLen === 0)
72
+ return '';
73
+ return '/' + segments[0].slice(0, prefixLen).join('/') + '/';
74
+ }
75
+ /** Convert operationId to camelCase function name */
76
+ function fromOperationId(operationId) {
77
+ // operationId might be snake_case, camelCase, PascalCase, or kebab-case
78
+ return toCamelCase(operationId
79
+ .replace(/[^a-zA-Z0-9_-]/g, '_')
80
+ .replace(/_+/g, '_')
81
+ .replace(/^_|_$/g, ''));
82
+ }
83
+ const METHOD_PREFIXES = {
84
+ get: 'get',
85
+ post: 'create',
86
+ put: 'update',
87
+ patch: 'update',
88
+ delete: 'delete',
89
+ };
90
+ /**
91
+ * Generate function names for a list of operations.
92
+ * Handles collision detection and auto-deduplication.
93
+ */
94
+ export function generateOperationNames(operations, commonPrefix) {
95
+ const results = [];
96
+ const usedNames = new Map();
97
+ for (const op of operations) {
98
+ let name;
99
+ if (op.operationId) {
100
+ name = fromOperationId(op.operationId);
101
+ }
102
+ else {
103
+ name = deriveNameFromPath(op.method, op.path, commonPrefix);
104
+ }
105
+ // Handle collisions
106
+ const existing = usedNames.get(name) ?? 0;
107
+ if (existing > 0) {
108
+ usedNames.set(name, existing + 1);
109
+ name = `${name}${existing + 1}`;
110
+ }
111
+ usedNames.set(name, (usedNames.get(name) ?? 0) || 1);
112
+ results.push({
113
+ method: op.method,
114
+ path: op.path,
115
+ functionName: name,
116
+ methodName: name,
117
+ });
118
+ }
119
+ return results;
120
+ }
121
+ function deriveNameFromPath(method, path, commonPrefix) {
122
+ // Strip common prefix
123
+ let cleanPath = path;
124
+ if (commonPrefix && cleanPath.startsWith(commonPrefix)) {
125
+ cleanPath = '/' + cleanPath.slice(commonPrefix.length);
126
+ }
127
+ const segments = cleanPath
128
+ .replace(/^\//, '')
129
+ .split('/')
130
+ .filter(Boolean);
131
+ const methodLower = method.toLowerCase();
132
+ const prefix = METHOD_PREFIXES[methodLower] || methodLower;
133
+ // Separate param and non-param segments
134
+ const nonParams = [];
135
+ let hasTrailingParam = false;
136
+ for (let i = 0; i < segments.length; i++) {
137
+ if (segments[i].startsWith('{')) {
138
+ if (i === segments.length - 1) {
139
+ hasTrailingParam = true;
140
+ }
141
+ }
142
+ else {
143
+ nonParams.push(segments[i]);
144
+ }
145
+ }
146
+ if (nonParams.length === 0) {
147
+ return prefix;
148
+ }
149
+ // For GET with trailing param: singularize the last non-param segment
150
+ // For GET without trailing param: keep plural (list)
151
+ // For POST without trailing param: use prefix (create)
152
+ const parts = nonParams.map((seg, i) => {
153
+ const isLast = i === nonParams.length - 1;
154
+ let word = seg;
155
+ if (isLast && hasTrailingParam) {
156
+ word = singularize(word);
157
+ }
158
+ // For GET on collection (no trailing param), use "list" prefix instead of "get"
159
+ return toPascalCase(word);
160
+ });
161
+ // For GET on collection, use "list" instead of "get"
162
+ let finalPrefix = prefix;
163
+ if (methodLower === 'get' && !hasTrailingParam) {
164
+ finalPrefix = 'list';
165
+ }
166
+ return finalPrefix + parts.join('');
167
+ }
@@ -0,0 +1,36 @@
1
+ import type { OpenAPISchema } from './zod-codegen.js';
2
+ export interface ParsedSpec {
3
+ info: {
4
+ title: string;
5
+ version: string;
6
+ description?: string;
7
+ };
8
+ baseUrl: string;
9
+ authType: 'bearer' | 'oauth2' | 'apiKey' | 'none';
10
+ operations: ParsedOperation[];
11
+ componentSchemas: Record<string, OpenAPISchema>;
12
+ }
13
+ export interface ParsedOperation {
14
+ operationId?: string;
15
+ method: string;
16
+ path: string;
17
+ summary?: string;
18
+ description?: string;
19
+ tags: string[];
20
+ pathParams: ParsedParam[];
21
+ queryParams: ParsedParam[];
22
+ requestBody?: OpenAPISchema;
23
+ responseSchema?: OpenAPISchema;
24
+ responseDescription?: string;
25
+ }
26
+ export interface ParsedParam {
27
+ name: string;
28
+ required: boolean;
29
+ schema: OpenAPISchema;
30
+ description?: string;
31
+ }
32
+ /**
33
+ * Read and parse an OpenAPI spec from a file path.
34
+ * Supports both YAML (.yaml, .yml) and JSON (.json) files.
35
+ */
36
+ export declare function parseOpenAPISpec(filePath: string): Promise<ParsedSpec>;
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Parses OpenAPI YAML/JSON specs, resolves $ref pointers, and produces a normalized IR.
3
+ */
4
+ import { readFile } from 'fs/promises';
5
+ import { parse as parseYAML } from 'yaml';
6
+ /**
7
+ * Read and parse an OpenAPI spec from a file path.
8
+ * Supports both YAML (.yaml, .yml) and JSON (.json) files.
9
+ */
10
+ export async function parseOpenAPISpec(filePath) {
11
+ const content = await readFile(filePath, 'utf-8');
12
+ let doc;
13
+ if (filePath.endsWith('.json')) {
14
+ doc = JSON.parse(content);
15
+ }
16
+ else {
17
+ doc = parseYAML(content);
18
+ }
19
+ // Resolve all $ref pointers in-place
20
+ resolveRefs(doc, doc);
21
+ const info = {
22
+ title: doc.info?.title ?? 'Unknown API',
23
+ version: doc.info?.version ?? '1.0.0',
24
+ description: doc.info?.description,
25
+ };
26
+ const baseUrl = extractBaseUrl(doc);
27
+ const authType = detectAuthType(doc);
28
+ // Extract component schemas
29
+ const componentSchemas = {};
30
+ if (doc.components?.schemas) {
31
+ for (const [name, schema] of Object.entries(doc.components.schemas)) {
32
+ componentSchemas[name] = schema;
33
+ }
34
+ }
35
+ // Extract operations
36
+ const operations = [];
37
+ if (doc.paths) {
38
+ for (const [path, pathItem] of Object.entries(doc.paths)) {
39
+ // Shared parameters at the path level
40
+ const sharedParams = pathItem.parameters ?? [];
41
+ for (const method of ['get', 'post', 'put', 'patch', 'delete']) {
42
+ const op = pathItem[method];
43
+ if (!op)
44
+ continue;
45
+ const allParams = [...sharedParams, ...(op.parameters ?? [])];
46
+ operations.push({
47
+ operationId: op.operationId,
48
+ method,
49
+ path,
50
+ summary: op.summary,
51
+ description: op.description,
52
+ tags: op.tags ?? [],
53
+ pathParams: extractParams(allParams, 'path'),
54
+ queryParams: extractParams(allParams, 'query'),
55
+ requestBody: extractRequestBody(op),
56
+ responseSchema: extractResponseSchema(op),
57
+ responseDescription: extractResponseDescription(op),
58
+ });
59
+ }
60
+ }
61
+ }
62
+ return { info, baseUrl, authType, operations, componentSchemas };
63
+ }
64
+ /** Recursively resolve $ref pointers in-place */
65
+ function resolveRefs(node, root) {
66
+ if (node === null || typeof node !== 'object')
67
+ return node;
68
+ if (Array.isArray(node)) {
69
+ for (let i = 0; i < node.length; i++) {
70
+ node[i] = resolveRefs(node[i], root);
71
+ }
72
+ return node;
73
+ }
74
+ if (typeof node.$ref === 'string') {
75
+ const resolved = resolveRefPath(node.$ref, root);
76
+ if (resolved && typeof resolved === 'object') {
77
+ // Merge any sibling properties (like description overrides)
78
+ const { $ref, ...siblings } = node;
79
+ const result = { ...resolved, ...siblings };
80
+ // Don't recurse infinitely — mark as resolved
81
+ return result;
82
+ }
83
+ return node;
84
+ }
85
+ for (const key of Object.keys(node)) {
86
+ node[key] = resolveRefs(node[key], root);
87
+ }
88
+ return node;
89
+ }
90
+ function resolveRefPath(ref, root) {
91
+ if (!ref.startsWith('#/'))
92
+ return undefined;
93
+ const parts = ref.slice(2).split('/');
94
+ let current = root;
95
+ for (const part of parts) {
96
+ const decoded = part.replace(/~1/g, '/').replace(/~0/g, '~');
97
+ if (current == null || typeof current !== 'object')
98
+ return undefined;
99
+ current = current[decoded];
100
+ }
101
+ return current;
102
+ }
103
+ function extractBaseUrl(doc) {
104
+ // OpenAPI 3.x
105
+ if (doc.servers && doc.servers.length > 0) {
106
+ return doc.servers[0].url ?? '';
107
+ }
108
+ // Swagger 2.x
109
+ if (doc.host) {
110
+ const scheme = doc.schemes?.[0] ?? 'https';
111
+ const basePath = doc.basePath ?? '';
112
+ return `${scheme}://${doc.host}${basePath}`;
113
+ }
114
+ return '';
115
+ }
116
+ function detectAuthType(doc) {
117
+ const securitySchemes = doc.components?.securitySchemes ?? doc.securityDefinitions ?? {};
118
+ for (const scheme of Object.values(securitySchemes)) {
119
+ if (scheme.type === 'oauth2')
120
+ return 'oauth2';
121
+ if (scheme.type === 'http' && scheme.scheme === 'bearer')
122
+ return 'bearer';
123
+ if (scheme.type === 'apiKey')
124
+ return 'apiKey';
125
+ }
126
+ return 'none';
127
+ }
128
+ function extractParams(params, location) {
129
+ return params
130
+ .filter((p) => p.in === location)
131
+ .map((p) => ({
132
+ name: p.name,
133
+ required: p.required ?? location === 'path',
134
+ schema: (p.schema ?? { type: 'string' }),
135
+ description: p.description,
136
+ }));
137
+ }
138
+ function extractRequestBody(op) {
139
+ const body = op.requestBody;
140
+ if (!body)
141
+ return undefined;
142
+ const content = body.content;
143
+ if (!content)
144
+ return undefined;
145
+ // Prefer JSON
146
+ const jsonContent = content['application/json'];
147
+ if (jsonContent?.schema)
148
+ return jsonContent.schema;
149
+ // Fallback to first content type
150
+ const firstKey = Object.keys(content)[0];
151
+ if (firstKey && content[firstKey]?.schema) {
152
+ return content[firstKey].schema;
153
+ }
154
+ return undefined;
155
+ }
156
+ function extractResponseDescription(op) {
157
+ const responses = op.responses;
158
+ if (!responses)
159
+ return undefined;
160
+ for (const code of ['200', '201', '202']) {
161
+ const resp = responses[code];
162
+ if (resp?.description)
163
+ return resp.description;
164
+ }
165
+ return undefined;
166
+ }
167
+ function extractResponseSchema(op) {
168
+ const responses = op.responses;
169
+ if (!responses)
170
+ return undefined;
171
+ // Look for 2xx responses in order of preference
172
+ for (const code of ['200', '201', '202', '204']) {
173
+ const resp = responses[code];
174
+ if (!resp)
175
+ continue;
176
+ const content = resp.content;
177
+ if (!content)
178
+ continue;
179
+ const jsonContent = content['application/json'];
180
+ if (jsonContent?.schema)
181
+ return jsonContent.schema;
182
+ const firstKey = Object.keys(content)[0];
183
+ if (firstKey && content[firstKey]?.schema) {
184
+ return content[firstKey].schema;
185
+ }
186
+ }
187
+ // Fallback: any 2xx
188
+ for (const [code, resp] of Object.entries(responses)) {
189
+ if (code.startsWith('2') && resp.content) {
190
+ const jsonContent = resp.content['application/json'];
191
+ if (jsonContent?.schema)
192
+ return jsonContent.schema;
193
+ }
194
+ }
195
+ return undefined;
196
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Converts OpenAPI schemas into Zod code strings.
3
+ *
4
+ * Chaining order: base → refinements → .nullable() → .optional() → .default() → .describe()
5
+ */
6
+ export interface OpenAPISchema {
7
+ type?: string;
8
+ format?: string;
9
+ description?: string;
10
+ enum?: unknown[];
11
+ items?: OpenAPISchema;
12
+ properties?: Record<string, OpenAPISchema>;
13
+ required?: string[];
14
+ nullable?: boolean;
15
+ default?: unknown;
16
+ minimum?: number;
17
+ maximum?: number;
18
+ exclusiveMinimum?: number;
19
+ exclusiveMaximum?: number;
20
+ minLength?: number;
21
+ maxLength?: number;
22
+ pattern?: string;
23
+ minItems?: number;
24
+ maxItems?: number;
25
+ oneOf?: OpenAPISchema[];
26
+ anyOf?: OpenAPISchema[];
27
+ allOf?: OpenAPISchema[];
28
+ additionalProperties?: boolean | OpenAPISchema;
29
+ $ref?: string;
30
+ readOnly?: boolean;
31
+ writeOnly?: boolean;
32
+ }
33
+ export interface ZodCodegenContext {
34
+ /** Map from component schema name to its Zod variable name */
35
+ schemaRefs: Map<string, string>;
36
+ /** Track which refs are actually used */
37
+ usedRefs: Set<string>;
38
+ /** Indent level for readability */
39
+ indent: number;
40
+ }
41
+ export declare function createContext(schemaRefs?: Map<string, string>): ZodCodegenContext;
42
+ /**
43
+ * Convert a single OpenAPI schema to a Zod expression string.
44
+ * Does NOT include .optional() — that's handled at the property level based on `required`.
45
+ */
46
+ export declare function schemaToZod(schema: OpenAPISchema, ctx: ZodCodegenContext, opts?: {
47
+ optional?: boolean;
48
+ }): string;
49
+ /**
50
+ * Generate the Zod variable name for a component schema.
51
+ * e.g. "PaginatedResponse" → "PaginatedResponseSchema"
52
+ */
53
+ export declare function schemaVarName(name: string): string;
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Converts OpenAPI schemas into Zod code strings.
3
+ *
4
+ * Chaining order: base → refinements → .nullable() → .optional() → .default() → .describe()
5
+ */
6
+ export function createContext(schemaRefs) {
7
+ return {
8
+ schemaRefs: schemaRefs ?? new Map(),
9
+ usedRefs: new Set(),
10
+ indent: 0,
11
+ };
12
+ }
13
+ /**
14
+ * Convert a single OpenAPI schema to a Zod expression string.
15
+ * Does NOT include .optional() — that's handled at the property level based on `required`.
16
+ */
17
+ export function schemaToZod(schema, ctx, opts = {}) {
18
+ let code = schemaToZodBase(schema, ctx);
19
+ // Refinements
20
+ code = applyRefinements(code, schema);
21
+ // Nullable
22
+ if (schema.nullable) {
23
+ code += '.nullable()';
24
+ }
25
+ // Optional
26
+ if (opts.optional) {
27
+ code += '.optional()';
28
+ }
29
+ // Default
30
+ if (schema.default !== undefined) {
31
+ code += `.default(${JSON.stringify(schema.default)})`;
32
+ }
33
+ // Description
34
+ if (schema.description) {
35
+ code += `.describe(${JSON.stringify(schema.description)})`;
36
+ }
37
+ return code;
38
+ }
39
+ function schemaToZodBase(schema, ctx) {
40
+ // Handle $ref
41
+ if (schema.$ref) {
42
+ const refName = schema.$ref.split('/').pop();
43
+ const zodName = ctx.schemaRefs.get(refName);
44
+ if (zodName) {
45
+ ctx.usedRefs.add(refName);
46
+ return zodName;
47
+ }
48
+ // Unknown ref — fallback to z.unknown()
49
+ return 'z.unknown()';
50
+ }
51
+ // Handle allOf — merge into single object
52
+ if (schema.allOf && schema.allOf.length > 0) {
53
+ return handleAllOf(schema.allOf, ctx);
54
+ }
55
+ // Handle oneOf/anyOf — union
56
+ if (schema.oneOf && schema.oneOf.length > 0) {
57
+ return handleUnion(schema.oneOf, ctx);
58
+ }
59
+ if (schema.anyOf && schema.anyOf.length > 0) {
60
+ return handleUnion(schema.anyOf, ctx);
61
+ }
62
+ // Handle enum
63
+ if (schema.enum && schema.enum.length > 0) {
64
+ return handleEnum(schema.enum);
65
+ }
66
+ // Handle by type
67
+ switch (schema.type) {
68
+ case 'string':
69
+ return handleString(schema);
70
+ case 'integer':
71
+ return 'z.number().int()';
72
+ case 'number':
73
+ return 'z.number()';
74
+ case 'boolean':
75
+ return 'z.boolean()';
76
+ case 'array':
77
+ return handleArray(schema, ctx);
78
+ case 'object':
79
+ return handleObject(schema, ctx);
80
+ default:
81
+ // No type specified but has properties — treat as object
82
+ if (schema.properties) {
83
+ return handleObject(schema, ctx);
84
+ }
85
+ return 'z.unknown()';
86
+ }
87
+ }
88
+ function handleString(schema) {
89
+ switch (schema.format) {
90
+ case 'uuid':
91
+ return 'z.string().uuid()';
92
+ case 'date-time':
93
+ return 'z.string().datetime()';
94
+ case 'date':
95
+ return 'z.string().date()';
96
+ case 'email':
97
+ return 'z.string().email()';
98
+ case 'uri':
99
+ case 'url':
100
+ return 'z.string().url()';
101
+ default:
102
+ return 'z.string()';
103
+ }
104
+ }
105
+ function handleEnum(values) {
106
+ // If all values are strings, use z.enum
107
+ if (values.every((v) => typeof v === 'string')) {
108
+ const enumValues = values.map((v) => JSON.stringify(v)).join(', ');
109
+ return `z.enum([${enumValues}])`;
110
+ }
111
+ // Mixed types — use z.union of z.literal
112
+ const literals = values.map((v) => `z.literal(${JSON.stringify(v)})`).join(', ');
113
+ return `z.union([${literals}])`;
114
+ }
115
+ function handleArray(schema, ctx) {
116
+ const itemsZod = schema.items
117
+ ? schemaToZodBase(schema.items, ctx)
118
+ : 'z.unknown()';
119
+ return `z.array(${itemsZod})`;
120
+ }
121
+ function indent(ctx) {
122
+ return ' '.repeat(ctx.indent);
123
+ }
124
+ function handleObject(schema, ctx) {
125
+ if (!schema.properties || Object.keys(schema.properties).length === 0) {
126
+ // Object with no defined properties
127
+ if (schema.additionalProperties) {
128
+ const valueSchema = typeof schema.additionalProperties === 'object'
129
+ ? schemaToZod(schema.additionalProperties, ctx)
130
+ : 'z.unknown()';
131
+ return `z.record(z.string(), ${valueSchema})`;
132
+ }
133
+ return 'z.record(z.string(), z.unknown())';
134
+ }
135
+ const inner = { ...ctx, indent: ctx.indent + 1 };
136
+ const pad = indent(inner);
137
+ const closePad = indent(ctx);
138
+ const requiredSet = new Set(schema.required ?? []);
139
+ const entries = [];
140
+ for (const [key, propSchema] of Object.entries(schema.properties)) {
141
+ const isOptional = !requiredSet.has(key);
142
+ const propZod = schemaToZod(propSchema, inner, { optional: isOptional });
143
+ entries.push(`${pad}${safeKey(key)}: ${propZod},`);
144
+ }
145
+ return `z.object({\n${entries.join('\n')}\n${closePad}})`;
146
+ }
147
+ function handleAllOf(schemas, ctx) {
148
+ // Collect all properties from all schemas
149
+ const mergedProps = {};
150
+ const mergedRequired = [];
151
+ for (const sub of schemas) {
152
+ if (sub.$ref) {
153
+ const refName = sub.$ref.split('/').pop();
154
+ const zodName = ctx.schemaRefs.get(refName);
155
+ if (zodName) {
156
+ ctx.usedRefs.add(refName);
157
+ // If it's a pure ref in an allOf, we'd need to merge/extend
158
+ // For simplicity, if we have refs mixed with objects, use .merge()
159
+ }
160
+ }
161
+ if (sub.properties) {
162
+ Object.assign(mergedProps, sub.properties);
163
+ }
164
+ if (sub.required) {
165
+ mergedRequired.push(...sub.required);
166
+ }
167
+ }
168
+ // If we have only refs, use .merge()
169
+ const refSchemas = schemas.filter((s) => s.$ref);
170
+ const objSchemas = schemas.filter((s) => !s.$ref && (s.properties || s.type === 'object'));
171
+ if (refSchemas.length > 0 && objSchemas.length === 0) {
172
+ const parts = refSchemas.map((s) => {
173
+ const refName = s.$ref.split('/').pop();
174
+ ctx.usedRefs.add(refName);
175
+ return ctx.schemaRefs.get(refName) || 'z.object({})';
176
+ });
177
+ if (parts.length === 1)
178
+ return parts[0];
179
+ return parts.reduce((acc, part) => `${acc}.merge(${part})`);
180
+ }
181
+ // Otherwise, merge into a single object
182
+ if (Object.keys(mergedProps).length > 0) {
183
+ return handleObject({
184
+ type: 'object',
185
+ properties: mergedProps,
186
+ required: [...new Set(mergedRequired)],
187
+ }, ctx);
188
+ }
189
+ // Fallback
190
+ return 'z.unknown()';
191
+ }
192
+ function handleUnion(schemas, ctx) {
193
+ const members = schemas.map((s) => schemaToZodBase(s, ctx));
194
+ if (members.length === 1)
195
+ return members[0];
196
+ return `z.union([${members.join(', ')}])`;
197
+ }
198
+ function applyRefinements(code, schema) {
199
+ let result = code;
200
+ // String refinements
201
+ if (schema.type === 'string' || (!schema.type && !schema.format)) {
202
+ if (schema.minLength !== undefined) {
203
+ result += `.min(${schema.minLength})`;
204
+ }
205
+ if (schema.maxLength !== undefined) {
206
+ result += `.max(${schema.maxLength})`;
207
+ }
208
+ if (schema.pattern) {
209
+ result += `.regex(new RegExp(${JSON.stringify(schema.pattern)}))`;
210
+ }
211
+ }
212
+ // Number refinements
213
+ if (schema.type === 'number' || schema.type === 'integer') {
214
+ if (schema.minimum !== undefined) {
215
+ result += `.min(${schema.minimum})`;
216
+ }
217
+ if (schema.maximum !== undefined) {
218
+ result += `.max(${schema.maximum})`;
219
+ }
220
+ if (schema.exclusiveMinimum !== undefined) {
221
+ result += `.gt(${schema.exclusiveMinimum})`;
222
+ }
223
+ if (schema.exclusiveMaximum !== undefined) {
224
+ result += `.lt(${schema.exclusiveMaximum})`;
225
+ }
226
+ }
227
+ // Array refinements
228
+ if (schema.type === 'array') {
229
+ if (schema.minItems !== undefined) {
230
+ result += `.min(${schema.minItems})`;
231
+ }
232
+ if (schema.maxItems !== undefined) {
233
+ result += `.max(${schema.maxItems})`;
234
+ }
235
+ }
236
+ return result;
237
+ }
238
+ /** Ensure property keys are safe identifiers, or quote them */
239
+ function safeKey(key) {
240
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) {
241
+ return key;
242
+ }
243
+ return JSON.stringify(key);
244
+ }
245
+ /**
246
+ * Generate the Zod variable name for a component schema.
247
+ * e.g. "PaginatedResponse" → "PaginatedResponseSchema"
248
+ */
249
+ export function schemaVarName(name) {
250
+ return name.endsWith('Schema') ? name : `${name}Schema`;
251
+ }