micro-contracts 0.16.4 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/dist/micro-contracts.bundle.mjs +4682 -0
  2. package/dist/micro-contracts.bundle.mjs.map +7 -0
  3. package/package.json +20 -23
  4. package/dist/agents/context-builder.d.ts +0 -5
  5. package/dist/agents/context-builder.d.ts.map +0 -1
  6. package/dist/agents/context-builder.js +0 -239
  7. package/dist/agents/context-builder.js.map +0 -1
  8. package/dist/agents/formatter.d.ts +0 -9
  9. package/dist/agents/formatter.d.ts.map +0 -1
  10. package/dist/agents/formatter.js +0 -124
  11. package/dist/agents/formatter.js.map +0 -1
  12. package/dist/agents/index.d.ts +0 -5
  13. package/dist/agents/index.d.ts.map +0 -1
  14. package/dist/agents/index.js +0 -3
  15. package/dist/agents/index.js.map +0 -1
  16. package/dist/agents/orchestrator.d.ts +0 -5
  17. package/dist/agents/orchestrator.d.ts.map +0 -1
  18. package/dist/agents/orchestrator.js +0 -105
  19. package/dist/agents/orchestrator.js.map +0 -1
  20. package/dist/agents/types.d.ts +0 -21
  21. package/dist/agents/types.d.ts.map +0 -1
  22. package/dist/agents/types.js +0 -2
  23. package/dist/agents/types.js.map +0 -1
  24. package/dist/cli/templates.d.ts +0 -22
  25. package/dist/cli/templates.d.ts.map +0 -1
  26. package/dist/cli/templates.js +0 -445
  27. package/dist/cli/templates.js.map +0 -1
  28. package/dist/cli.d.ts +0 -3
  29. package/dist/cli.d.ts.map +0 -1
  30. package/dist/cli.js +0 -1142
  31. package/dist/cli.js.map +0 -1
  32. package/dist/commands/audit-guardrails.d.ts +0 -13
  33. package/dist/commands/audit-guardrails.d.ts.map +0 -1
  34. package/dist/commands/audit-guardrails.js +0 -32
  35. package/dist/commands/audit-guardrails.js.map +0 -1
  36. package/dist/commands/audit-openapi.d.ts +0 -13
  37. package/dist/commands/audit-openapi.d.ts.map +0 -1
  38. package/dist/commands/audit-openapi.js +0 -32
  39. package/dist/commands/audit-openapi.js.map +0 -1
  40. package/dist/commands/propose-overlays.d.ts +0 -13
  41. package/dist/commands/propose-overlays.d.ts.map +0 -1
  42. package/dist/commands/propose-overlays.js +0 -32
  43. package/dist/commands/propose-overlays.js.map +0 -1
  44. package/dist/commands/review-published.d.ts +0 -13
  45. package/dist/commands/review-published.d.ts.map +0 -1
  46. package/dist/commands/review-published.js +0 -32
  47. package/dist/commands/review-published.js.map +0 -1
  48. package/dist/generated/commands.d.ts +0 -2
  49. package/dist/generated/commands.d.ts.map +0 -1
  50. package/dist/generated/commands.js +0 -2
  51. package/dist/generated/commands.js.map +0 -1
  52. package/dist/generated/dsl/agents.d.ts +0 -34
  53. package/dist/generated/dsl/agents.d.ts.map +0 -1
  54. package/dist/generated/dsl/agents.js +0 -98
  55. package/dist/generated/dsl/agents.js.map +0 -1
  56. package/dist/generated/dsl/handoffs.d.ts +0 -457
  57. package/dist/generated/dsl/handoffs.d.ts.map +0 -1
  58. package/dist/generated/dsl/handoffs.js +0 -205
  59. package/dist/generated/dsl/handoffs.js.map +0 -1
  60. package/dist/generated/dsl/index.d.ts +0 -13
  61. package/dist/generated/dsl/index.d.ts.map +0 -1
  62. package/dist/generated/dsl/index.js +0 -10
  63. package/dist/generated/dsl/index.js.map +0 -1
  64. package/dist/generated/dsl/tasks.d.ts +0 -26
  65. package/dist/generated/dsl/tasks.d.ts.map +0 -1
  66. package/dist/generated/dsl/tasks.js +0 -133
  67. package/dist/generated/dsl/tasks.js.map +0 -1
  68. package/dist/generated/dsl/workflows.d.ts +0 -34
  69. package/dist/generated/dsl/workflows.d.ts.map +0 -1
  70. package/dist/generated/dsl/workflows.js +0 -84
  71. package/dist/generated/dsl/workflows.js.map +0 -1
  72. package/dist/generated/index.d.ts +0 -7
  73. package/dist/generated/index.d.ts.map +0 -1
  74. package/dist/generated/index.js +0 -7
  75. package/dist/generated/index.js.map +0 -1
  76. package/dist/generated/policy-runtime.d.ts +0 -114
  77. package/dist/generated/policy-runtime.d.ts.map +0 -1
  78. package/dist/generated/policy-runtime.js +0 -195
  79. package/dist/generated/policy-runtime.js.map +0 -1
  80. package/dist/generated/policy.d.ts +0 -619
  81. package/dist/generated/policy.d.ts.map +0 -1
  82. package/dist/generated/policy.js +0 -802
  83. package/dist/generated/policy.js.map +0 -1
  84. package/dist/generated/program.d.ts +0 -112
  85. package/dist/generated/program.d.ts.map +0 -1
  86. package/dist/generated/program.js +0 -266
  87. package/dist/generated/program.js.map +0 -1
  88. package/dist/generated/schemas.d.ts +0 -778
  89. package/dist/generated/schemas.d.ts.map +0 -1
  90. package/dist/generated/schemas.js +0 -1005
  91. package/dist/generated/schemas.js.map +0 -1
  92. package/dist/generated/types.d.ts +0 -395
  93. package/dist/generated/types.d.ts.map +0 -1
  94. package/dist/generated/types.js +0 -3
  95. package/dist/generated/types.js.map +0 -1
  96. package/dist/generator/dependencyGenerator.d.ts +0 -43
  97. package/dist/generator/dependencyGenerator.d.ts.map +0 -1
  98. package/dist/generator/dependencyGenerator.js +0 -159
  99. package/dist/generator/dependencyGenerator.js.map +0 -1
  100. package/dist/generator/index.d.ts +0 -39
  101. package/dist/generator/index.d.ts.map +0 -1
  102. package/dist/generator/index.js +0 -720
  103. package/dist/generator/index.js.map +0 -1
  104. package/dist/generator/inputHash.d.ts +0 -23
  105. package/dist/generator/inputHash.d.ts.map +0 -1
  106. package/dist/generator/inputHash.js +0 -145
  107. package/dist/generator/inputHash.js.map +0 -1
  108. package/dist/generator/linter.d.ts +0 -26
  109. package/dist/generator/linter.d.ts.map +0 -1
  110. package/dist/generator/linter.js +0 -352
  111. package/dist/generator/linter.js.map +0 -1
  112. package/dist/generator/overlayProcessor.d.ts +0 -90
  113. package/dist/generator/overlayProcessor.d.ts.map +0 -1
  114. package/dist/generator/overlayProcessor.js +0 -541
  115. package/dist/generator/overlayProcessor.js.map +0 -1
  116. package/dist/generator/schemaGenerator.d.ts +0 -10
  117. package/dist/generator/schemaGenerator.d.ts.map +0 -1
  118. package/dist/generator/schemaGenerator.js +0 -299
  119. package/dist/generator/schemaGenerator.js.map +0 -1
  120. package/dist/generator/serviceGenerator.d.ts +0 -41
  121. package/dist/generator/serviceGenerator.d.ts.map +0 -1
  122. package/dist/generator/serviceGenerator.js +0 -296
  123. package/dist/generator/serviceGenerator.js.map +0 -1
  124. package/dist/generator/templateProcessor.d.ts +0 -245
  125. package/dist/generator/templateProcessor.d.ts.map +0 -1
  126. package/dist/generator/templateProcessor.js +0 -821
  127. package/dist/generator/templateProcessor.js.map +0 -1
  128. package/dist/generator/typeGenerator.d.ts +0 -9
  129. package/dist/generator/typeGenerator.d.ts.map +0 -1
  130. package/dist/generator/typeGenerator.js +0 -424
  131. package/dist/generator/typeGenerator.js.map +0 -1
  132. package/dist/generator/typeGenerator.test.d.ts +0 -2
  133. package/dist/generator/typeGenerator.test.d.ts.map +0 -1
  134. package/dist/generator/typeGenerator.test.js +0 -122
  135. package/dist/generator/typeGenerator.test.js.map +0 -1
  136. package/dist/guardrails/allowlist.d.ts +0 -45
  137. package/dist/guardrails/allowlist.d.ts.map +0 -1
  138. package/dist/guardrails/allowlist.js +0 -261
  139. package/dist/guardrails/allowlist.js.map +0 -1
  140. package/dist/guardrails/config.d.ts +0 -40
  141. package/dist/guardrails/config.d.ts.map +0 -1
  142. package/dist/guardrails/config.js +0 -174
  143. package/dist/guardrails/config.js.map +0 -1
  144. package/dist/guardrails/docs.d.ts +0 -24
  145. package/dist/guardrails/docs.d.ts.map +0 -1
  146. package/dist/guardrails/docs.js +0 -138
  147. package/dist/guardrails/docs.js.map +0 -1
  148. package/dist/guardrails/drift.d.ts +0 -23
  149. package/dist/guardrails/drift.d.ts.map +0 -1
  150. package/dist/guardrails/drift.js +0 -127
  151. package/dist/guardrails/drift.js.map +0 -1
  152. package/dist/guardrails/index.d.ts +0 -19
  153. package/dist/guardrails/index.d.ts.map +0 -1
  154. package/dist/guardrails/index.js +0 -23
  155. package/dist/guardrails/index.js.map +0 -1
  156. package/dist/guardrails/lint.d.ts +0 -20
  157. package/dist/guardrails/lint.d.ts.map +0 -1
  158. package/dist/guardrails/lint.js +0 -274
  159. package/dist/guardrails/lint.js.map +0 -1
  160. package/dist/guardrails/manifest.d.ts +0 -63
  161. package/dist/guardrails/manifest.d.ts.map +0 -1
  162. package/dist/guardrails/manifest.js +0 -299
  163. package/dist/guardrails/manifest.js.map +0 -1
  164. package/dist/guardrails/runner.d.ts +0 -43
  165. package/dist/guardrails/runner.d.ts.map +0 -1
  166. package/dist/guardrails/runner.js +0 -365
  167. package/dist/guardrails/runner.js.map +0 -1
  168. package/dist/guardrails/typecheck.d.ts +0 -15
  169. package/dist/guardrails/typecheck.d.ts.map +0 -1
  170. package/dist/guardrails/typecheck.js +0 -104
  171. package/dist/guardrails/typecheck.js.map +0 -1
  172. package/dist/guardrails/types.d.ts +0 -202
  173. package/dist/guardrails/types.d.ts.map +0 -1
  174. package/dist/guardrails/types.js +0 -8
  175. package/dist/guardrails/types.js.map +0 -1
  176. package/dist/index.d.ts.map +0 -1
  177. package/dist/index.js.map +0 -1
  178. package/dist/types.d.ts +0 -583
  179. package/dist/types.d.ts.map +0 -1
  180. package/dist/types.js +0 -305
  181. package/dist/types.js.map +0 -1
@@ -1,720 +0,0 @@
1
- /**
2
- * micro-contracts Generator
3
- */
4
- import fs from 'fs';
5
- import path from 'path';
6
- import yaml from 'js-yaml';
7
- import { isMultiModuleConfig, resolveModuleConfig, } from '../types.js';
8
- import { generateTypes } from './typeGenerator.js';
9
- import { generateSchemas } from './schemaGenerator.js';
10
- import { generateServiceInterfaces } from './serviceGenerator.js';
11
- import { lintSpec, formatLintResults } from './linter.js';
12
- import { processOverlays, generateExtensionInterfaces, formatOverlayLog, rebaseRefs, } from './overlayProcessor.js';
13
- import { buildTemplateContext, generateWithTemplate, loadTemplate, resolveTemplatePath, } from './templateProcessor.js';
14
- import { validateDependsOn, } from './dependencyGenerator.js';
15
- import { extractDependencies } from '../types.js';
16
- export { generateTypes } from './typeGenerator.js';
17
- export { generateSchemas } from './schemaGenerator.js';
18
- export { generateServiceInterfaces } from './serviceGenerator.js';
19
- export { lintSpec, formatLintResults } from './linter.js';
20
- export { processOverlays, generateExtensionInterfaces } from './overlayProcessor.js';
21
- export { buildTemplateContext, generateWithTemplate } from './templateProcessor.js';
22
- export { collectInputFiles, computeInputHash } from './inputHash.js';
23
- /**
24
- * Write file only if content has changed (ignoring timestamp in header).
25
- * This prevents unnecessary git diffs when only the timestamp changes.
26
- * Returns true if file was written, false if content was unchanged.
27
- */
28
- function writeFileIfChanged(filePath, newContent) {
29
- // Resolve to absolute path for consistency
30
- const absolutePath = path.resolve(filePath);
31
- if (fs.existsSync(absolutePath)) {
32
- const existingContent = fs.readFileSync(absolutePath, 'utf-8');
33
- if (existingContent === newContent) {
34
- return false; // No change, skip writing
35
- }
36
- }
37
- fs.writeFileSync(absolutePath, newContent);
38
- return true;
39
- }
40
- /**
41
- * Write file and log result. Uses writeFileIfChanged to avoid unnecessary updates.
42
- */
43
- function writeAndLog(filePath, content, indent = ' ') {
44
- const written = writeFileIfChanged(filePath, content);
45
- if (written) {
46
- console.log(`${indent}Written: ${filePath}`);
47
- }
48
- else {
49
- console.log(`${indent}Unchanged: ${filePath}`);
50
- }
51
- }
52
- /**
53
- * Load OpenAPI spec from file
54
- */
55
- export function loadOpenAPISpec(filePath) {
56
- const content = fs.readFileSync(filePath, 'utf-8');
57
- if (filePath.endsWith('.yaml') || filePath.endsWith('.yml')) {
58
- return yaml.load(content);
59
- }
60
- else if (filePath.endsWith('.json')) {
61
- return JSON.parse(content);
62
- }
63
- else {
64
- throw new Error(`Unsupported file format: ${filePath}`);
65
- }
66
- }
67
- /**
68
- * Load config from file (supports both legacy and multi-module formats)
69
- */
70
- export function loadConfig(configPath) {
71
- const content = fs.readFileSync(configPath, 'utf-8');
72
- return yaml.load(content);
73
- }
74
- /**
75
- * Parse module filter from options
76
- */
77
- function parseModuleFilter(modules) {
78
- if (!modules)
79
- return null;
80
- if (Array.isArray(modules))
81
- return modules;
82
- return modules.split(',').map(m => m.trim()).filter(Boolean);
83
- }
84
- /**
85
- * Generate all files from config
86
- */
87
- export async function generate(config, options = {}) {
88
- // Handle multi-module config
89
- if (isMultiModuleConfig(config)) {
90
- await generateMultiModule(config, options);
91
- return;
92
- }
93
- // Legacy single-module config is no longer supported
94
- throw new Error('Legacy single-module configuration format is no longer supported. ' +
95
- 'Please migrate to the multi-module format with a "modules:" section. ' +
96
- 'See README.md for configuration examples.');
97
- }
98
- /**
99
- * Generate for multi-module config
100
- */
101
- async function generateMultiModule(config, options) {
102
- const moduleFilter = parseModuleFilter(options.modules);
103
- const moduleNames = Object.keys(config.modules);
104
- // Filter modules if specified
105
- const targetModules = moduleFilter
106
- ? moduleNames.filter(m => moduleFilter.includes(m))
107
- : moduleNames;
108
- if (targetModules.length === 0) {
109
- if (moduleFilter) {
110
- console.error(`No matching modules found. Available: ${moduleNames.join(', ')}`);
111
- process.exit(1);
112
- }
113
- console.log('No modules defined in config.');
114
- return;
115
- }
116
- console.log(`Generating for modules: ${targetModules.join(', ')}`);
117
- // Generate each module
118
- for (const moduleName of targetModules) {
119
- const moduleConfig = config.modules[moduleName];
120
- const resolved = resolveModuleConfig(moduleName, moduleConfig, config.defaults);
121
- console.log(`\n${'='.repeat(60)}`);
122
- console.log(`Module: ${moduleName}`);
123
- console.log(`${'='.repeat(60)}`);
124
- await generateModule(resolved, options);
125
- }
126
- console.log('\nGeneration complete!');
127
- }
128
- /**
129
- * Generate for a single resolved module
130
- */
131
- async function generateModule(config, options) {
132
- // Load OpenAPI spec
133
- const openapiPath = path.resolve(config.openapi);
134
- console.log(`Loading OpenAPI spec from: ${openapiPath}`);
135
- if (!fs.existsSync(openapiPath)) {
136
- throw new Error(`OpenAPI spec not found: ${openapiPath}`);
137
- }
138
- let spec = loadOpenAPISpec(openapiPath);
139
- console.log(` Title: ${spec.info.title}`);
140
- console.log(` Version: ${spec.info.version}`);
141
- // Run linting first (unless skipped)
142
- if (!options.skipLint) {
143
- console.log('\nLinting OpenAPI spec...');
144
- const lintResult = lintSpec(spec, { screen: config.screen });
145
- console.log(formatLintResults(lintResult));
146
- if (!lintResult.valid) {
147
- throw new Error('Lint failed. Fix errors before generating.');
148
- }
149
- }
150
- // Apply overlays if configured
151
- let overlayResult = null;
152
- if (config.overlays.length > 0) {
153
- console.log('\nApplying overlays...');
154
- overlayResult = processOverlays(spec, {
155
- collision: config.overlayCollision,
156
- files: config.overlays,
157
- }, process.cwd(), openapiPath // Pass spec path for $ref rebasing
158
- );
159
- spec = overlayResult.spec;
160
- console.log(formatOverlayLog(overlayResult));
161
- // Note: Transformed spec is written to packages/contract/*/docs/openapi.generated.yaml
162
- }
163
- const generateAll = !options.contractsOnly && !options.serverOnly &&
164
- !options.frontendOnly && !options.docsOnly;
165
- // Validate and generate dependencies
166
- const dependencies = extractDependencies(spec);
167
- if (config.dependsOn) {
168
- const validation = validateDependsOn(dependencies.allDeps.map(d => d.raw), config.dependsOn, config.name);
169
- if (!validation.valid) {
170
- for (const err of validation.errors) {
171
- console.error(`ERROR: ${err}`);
172
- }
173
- throw new Error('Dependency validation failed');
174
- }
175
- }
176
- // Generate contract package
177
- if (generateAll || options.contractsOnly) {
178
- await generateContractPackage(spec, config, false, overlayResult);
179
- // Generate public contract if there are public endpoints
180
- if (hasPublicEndpoints(spec)) {
181
- await generateContractPackage(spec, config, true, overlayResult);
182
- }
183
- // Generate deps/ re-exports if module has dependencies
184
- if (dependencies.allDeps.length > 0) {
185
- await generateDepsReExports(config, dependencies);
186
- }
187
- }
188
- // Generate using new outputs system if configured
189
- if (config.outputs.length > 0) {
190
- await generateFromOutputs(spec, config, overlayResult, options);
191
- }
192
- else {
193
- // Fallback to legacy server/frontend generation
194
- // Generate server routes
195
- if ((generateAll || options.serverOnly) && config.server) {
196
- await generateServerRoutes(spec, config, overlayResult);
197
- }
198
- // Generate frontend clients
199
- if ((generateAll || options.frontendOnly) && config.frontend) {
200
- await generateFrontendClient(spec, config, overlayResult);
201
- }
202
- }
203
- // Documentation (Redoc HTML) generation removed.
204
- // Use @redocly/cli directly if needed:
205
- // npx @redocly/cli build-docs openapi.generated.yaml -o api-reference.html
206
- }
207
- /**
208
- * Check if spec has any public endpoints
209
- */
210
- function hasPublicEndpoints(spec) {
211
- for (const pathItem of Object.values(spec.paths)) {
212
- for (const method of ['get', 'post', 'put', 'patch', 'delete']) {
213
- const operation = pathItem[method];
214
- if (operation && operation['x-micro-contracts-published'] === true) {
215
- return true;
216
- }
217
- }
218
- }
219
- return false;
220
- }
221
- /**
222
- * Generate contract package
223
- */
224
- async function generateContractPackage(spec, config, publicOnly, overlayResult = null) {
225
- const outputDir = publicOnly ? config.contractPublicOutput : config.contractOutput;
226
- const label = publicOnly ? 'public contract' : 'contract';
227
- console.log(`\nGenerating ${label} package...`);
228
- // For public contract, use filtered spec
229
- const targetSpec = publicOnly ? filterPublicSpec(spec) : spec;
230
- // Create directories
231
- const dirs = [
232
- outputDir,
233
- path.join(outputDir, 'services'),
234
- path.join(outputDir, 'schemas'),
235
- path.join(outputDir, 'errors'),
236
- path.join(outputDir, 'docs'),
237
- ];
238
- for (const dir of dirs) {
239
- fs.mkdirSync(dir, { recursive: true });
240
- }
241
- // Note: We no longer delete files before generating to enable change detection.
242
- // Orphaned files from removed services/schemas should be manually cleaned up.
243
- // Generate service interfaces
244
- console.log(` Generating service interfaces...`);
245
- const serviceInterfaces = generateServiceInterfaces(targetSpec, {
246
- publicOnly,
247
- serviceTemplate: config.serviceTemplate,
248
- });
249
- for (const [name, content] of serviceInterfaces) {
250
- const fileName = name === 'index' ? 'index.ts' : `${name}ServiceApi.ts`;
251
- const filePath = path.join(outputDir, 'services', fileName);
252
- writeAndLog(filePath, content);
253
- }
254
- // Generate types (use filtered spec for public)
255
- console.log(` Generating schema types...`);
256
- const typesContent = generateTypes(targetSpec);
257
- const typesPath = path.join(outputDir, 'schemas', 'types.ts');
258
- writeAndLog(typesPath, typesContent);
259
- // Generate validators (JSON Schemas) - use filtered spec for public
260
- console.log(` Generating validators...`);
261
- const validatorsContent = generateSchemas(targetSpec);
262
- const validatorsPath = path.join(outputDir, 'schemas', 'validators.ts');
263
- writeAndLog(validatorsPath, validatorsContent);
264
- // Generate schemas index
265
- const schemasIndex = `/**
266
- * Schema exports
267
- * Auto-generated - DO NOT EDIT
268
- */
269
-
270
- export * from './types.js';
271
- export { allSchemas } from './validators.js';
272
- `;
273
- writeAndLog(path.join(outputDir, 'schemas', 'index.ts'), schemasIndex);
274
- // Generate errors
275
- const hasEndpoints = Object.keys(targetSpec.paths).length > 0;
276
- console.log(` Generating error types...`);
277
- const errorsContent = hasEndpoints ? generateErrors() : generateEmptyErrors();
278
- writeAndLog(path.join(outputDir, 'errors', 'index.ts'), errorsContent);
279
- // Generate overlay interfaces if overlays were applied
280
- if (overlayResult && overlayResult.extensionInfo.size > 0 && !publicOnly) {
281
- console.log(` Generating overlay interfaces...`);
282
- const overlaysDir = path.join(outputDir, 'overlays');
283
- fs.mkdirSync(overlaysDir, { recursive: true });
284
- const overlayContent = generateExtensionInterfaces(overlayResult.extensionInfo);
285
- const overlayPath = path.join(overlaysDir, 'index.ts');
286
- writeAndLog(overlayPath, overlayContent);
287
- }
288
- // Generate package index
289
- const hasOverlays = overlayResult && overlayResult.extensionInfo.size > 0 && !publicOnly;
290
- const indexContent = `/**
291
- * ${publicOnly ? 'Public ' : ''}Contract Package
292
- * Auto-generated - DO NOT EDIT
293
- */
294
-
295
- export * from './services/index.js';
296
- export * from './schemas/index.js';
297
- export * from './errors/index.js';
298
- ${hasOverlays ? "export * from './overlays/index.js';" : ''}
299
- `;
300
- writeAndLog(path.join(outputDir, 'index.ts'), indexContent);
301
- // Copy OpenAPI spec to docs with source info header
302
- // Rebase $ref paths from source directory to output directory
303
- const sourceDir = path.dirname(config.openapi);
304
- const docsDir = path.join(outputDir, 'docs');
305
- const rebasedSpec = rebaseRefs(targetSpec, sourceDir, docsDir);
306
- const specHeader = `# Auto-generated OpenAPI specification
307
- # DO NOT EDIT MANUALLY
308
- #
309
- # Source: ${config.openapi}
310
- # Regenerate: micro-contracts generate
311
- ${publicOnly ? '# Filtered for public endpoints only\n' : ''}
312
- `;
313
- const yamlContent = specHeader + yaml.dump(rebasedSpec, { lineWidth: -1 });
314
- writeAndLog(path.join(docsDir, 'openapi.generated.yaml'), yamlContent);
315
- }
316
- /**
317
- * Generate using flexible outputs configuration
318
- */
319
- async function generateFromOutputs(spec, config, overlayResult, options) {
320
- const generateAll = !options.contractsOnly && !options.serverOnly &&
321
- !options.frontendOnly && !options.docsOnly;
322
- console.log(`\nGenerating from outputs configuration...`);
323
- const hasPublic = hasPublicEndpoints(spec);
324
- for (const output of config.outputs) {
325
- // Skip disabled outputs
326
- if (!output.enabled)
327
- continue;
328
- // Check conditions
329
- if (output.condition === 'hasPublicEndpoints' && !hasPublic) {
330
- console.log(` Skipping ${output.id} (no public endpoints)`);
331
- continue;
332
- }
333
- const hasOverlays = overlayResult && overlayResult.extensionInfo.size > 0;
334
- if (output.condition === 'hasOverlays' && !hasOverlays) {
335
- console.log(` Skipping ${output.id} (no overlays)`);
336
- continue;
337
- }
338
- // Filter by generation type
339
- const isServerOutput = output.id.includes('server');
340
- const isFrontendOutput = output.id.includes('frontend') || output.id.includes('client');
341
- if (options.serverOnly && !isServerOutput)
342
- continue;
343
- if (options.frontendOnly && !isFrontendOutput)
344
- continue;
345
- if (!generateAll && !options.serverOnly && !options.frontendOnly)
346
- continue;
347
- // Check if file exists and overwrite is disabled
348
- if (!output.overwrite && fs.existsSync(output.output)) {
349
- console.log(` Skipping ${output.id} (file exists, overwrite=false)`);
350
- continue;
351
- }
352
- console.log(` Generating ${output.id}...`);
353
- try {
354
- // Build template context with output-specific config
355
- // Expand {module} placeholders in config values
356
- const expandPlaceholder = (val, fallback) => (val?.replace(/{module}/g, config.name) ?? fallback);
357
- const templateContext = buildTemplateContext(spec, config.name, {
358
- servicesPath: expandPlaceholder(output.config?.servicesPath, `fastify.services.${config.name}`),
359
- contractPackage: expandPlaceholder(output.config?.contractPackage, `@project/contract/${config.name}`),
360
- extensionInfo: overlayResult?.extensionInfo,
361
- appliedOverlays: overlayResult?.appliedOverlays,
362
- screen: config.screen,
363
- });
364
- // Add output-specific config to context
365
- const extendedContext = {
366
- ...templateContext,
367
- outputConfig: output.config || {},
368
- };
369
- // Resolve and load template
370
- const specDir = path.dirname(config.openapi).replace(/\/openapi$/, '').replace(`/${config.name}`, '');
371
- const templatePath = resolveTemplatePath({
372
- specDir,
373
- moduleName: config.name,
374
- templateName: path.basename(output.template),
375
- }) || output.template;
376
- if (!fs.existsSync(templatePath)) {
377
- console.warn(` Warning: Template not found: ${output.template}`);
378
- continue;
379
- }
380
- const template = loadTemplate(templatePath);
381
- const content = template(extendedContext);
382
- // Ensure output directory exists
383
- const outputDir = path.dirname(output.output);
384
- if (!fs.existsSync(outputDir)) {
385
- fs.mkdirSync(outputDir, { recursive: true });
386
- }
387
- // Write output file (only if content changed)
388
- writeAndLog(output.output, content);
389
- }
390
- catch (error) {
391
- console.error(` Error generating ${output.id}:`, error instanceof Error ? error.message : error);
392
- }
393
- }
394
- }
395
- /**
396
- * Generate deps/ re-exports for cross-module dependencies
397
- */
398
- async function generateDepsReExports(config, dependencies) {
399
- if (dependencies.allDeps.length === 0) {
400
- console.log(`\n No dependencies declared, skipping deps/ generation`);
401
- return;
402
- }
403
- console.log(`\nGenerating deps/ re-exports...`);
404
- // Build contract-published paths map
405
- const contractPublicPaths = new Map();
406
- // Group deps by module
407
- const moduleNames = new Set();
408
- for (const dep of dependencies.allDeps) {
409
- moduleNames.add(dep.module);
410
- }
411
- for (const moduleName of moduleNames) {
412
- // Assume contract-published follows same pattern as config
413
- contractPublicPaths.set(moduleName, `@project/contract-published/${moduleName}`);
414
- }
415
- // Generate deps files directly from already-extracted dependencies
416
- const generatedFiles = generateSimpleDepsFiles(config.name, dependencies, contractPublicPaths);
417
- for (const file of generatedFiles) {
418
- const dir = path.dirname(file.path);
419
- if (!fs.existsSync(dir)) {
420
- fs.mkdirSync(dir, { recursive: true });
421
- }
422
- writeAndLog(file.path, file.content, ' ');
423
- }
424
- }
425
- /**
426
- * Generate simple deps files without complex type resolution
427
- */
428
- function generateSimpleDepsFiles(moduleName, dependencies, _contractPublicPaths) {
429
- const files = [];
430
- // Group deps by target module
431
- const depsByModule = new Map();
432
- for (const dep of dependencies.allDeps) {
433
- if (!depsByModule.has(dep.module)) {
434
- depsByModule.set(dep.module, []);
435
- }
436
- depsByModule.get(dep.module).push({
437
- service: dep.service,
438
- method: dep.method,
439
- raw: dep.raw,
440
- });
441
- }
442
- // Generate file for each target module
443
- for (const [targetModule, deps] of depsByModule) {
444
- // Use relative path from packages/contract/{module}/deps/ to packages/contract-published/{target}/
445
- // From packages/contract/{module}/deps/ to packages/contract-published/{target}/
446
- // ../../../contract-published/{target}/
447
- const relativePathPrefix = `../../../contract-published/${targetModule}`;
448
- const content = `/**
449
- * Auto-generated from x-micro-contracts-depend-on - DO NOT EDIT
450
- * Source module: ${moduleName}
451
- * Target module: ${targetModule}
452
- * Dependencies: ${deps.map(d => d.raw).join(', ')}
453
- */
454
-
455
- // Re-exported types from ${targetModule} (contract-published)
456
- export type * from '${relativePathPrefix}/schemas/types.js';
457
- export type * from '${relativePathPrefix}/services/index.js';
458
- `;
459
- files.push({
460
- path: `packages/contract/${moduleName}/deps/${targetModule}.ts`,
461
- content,
462
- });
463
- }
464
- // Generate index file
465
- if (files.length > 0) {
466
- const indexContent = `/**
467
- * Auto-generated deps index - DO NOT EDIT
468
- */
469
-
470
- ${Array.from(depsByModule.keys()).map(m => `export * from './${m}.js';`).join('\n')}
471
- `;
472
- files.push({
473
- path: `packages/contract/${moduleName}/deps/index.ts`,
474
- content: indexContent,
475
- });
476
- }
477
- return files;
478
- }
479
- /**
480
- * Generate server routes
481
- */
482
- async function generateServerRoutes(spec, config, overlayResult = null) {
483
- if (!config.server)
484
- return;
485
- const outputDir = path.resolve(config.server.output);
486
- const routesFile = config.server.routes;
487
- console.log(`\nGenerating server routes...`);
488
- fs.mkdirSync(outputDir, { recursive: true });
489
- // Template is required for server routes generation
490
- if (!config.server.template) {
491
- throw new Error('Server template is required. Please specify server.template in your config.');
492
- }
493
- const templateContext = buildTemplateContext(spec, config.name, {
494
- servicesPath: config.server.servicesPath,
495
- contractPackage: `@project/contract/${config.name}`,
496
- extensionInfo: overlayResult?.extensionInfo,
497
- appliedOverlays: overlayResult?.appliedOverlays,
498
- screen: config.screen,
499
- });
500
- const routesContent = generateWithTemplate(config.server.template, 'server', templateContext);
501
- const routesPath = path.join(outputDir, routesFile);
502
- writeAndLog(routesPath, routesContent, ' ');
503
- }
504
- /**
505
- * Generate frontend client
506
- */
507
- async function generateFrontendClient(spec, config, overlayResult = null) {
508
- if (!config.frontend)
509
- return;
510
- const outputDir = path.resolve(config.frontend.output);
511
- const clientFile = config.frontend.client;
512
- console.log(`\nGenerating frontend client...`);
513
- fs.mkdirSync(outputDir, { recursive: true });
514
- // Template is required for frontend client generation
515
- if (!config.frontend.template) {
516
- throw new Error('Frontend template is required. Please specify frontend.template in your config.');
517
- }
518
- const templateContext = buildTemplateContext(spec, config.name, {
519
- contractPackage: `@project/contract/${config.name}`,
520
- extensionInfo: overlayResult?.extensionInfo,
521
- appliedOverlays: overlayResult?.appliedOverlays,
522
- screen: config.screen,
523
- });
524
- const clientContent = generateWithTemplate(config.frontend.template, 'frontend', templateContext);
525
- const clientPath = path.join(outputDir, clientFile);
526
- writeAndLog(clientPath, clientContent, ' ');
527
- // Generate service re-exports
528
- const serviceContent = generateServiceReExports(config.name);
529
- const servicePath = path.join(outputDir, config.frontend.service);
530
- writeAndLog(servicePath, serviceContent, ' ');
531
- }
532
- /**
533
- * Generate service re-exports file
534
- */
535
- function generateServiceReExports(moduleName) {
536
- const lines = [];
537
- lines.push('/**');
538
- lines.push(' * Service re-exports');
539
- lines.push(' * Auto-generated - DO NOT EDIT');
540
- lines.push('');
541
- // Re-export API clients from api.generated
542
- lines.push('// API clients');
543
- lines.push("export * from './api.generated';");
544
- lines.push('');
545
- // Re-export types from contract package
546
- lines.push('// Contract types');
547
- lines.push(`export * from '@project/contract/${moduleName}/schemas';`);
548
- lines.push(`export * from '@project/contract/${moduleName}/services';`);
549
- lines.push(`export * from '@project/contract/${moduleName}/errors';`);
550
- lines.push('');
551
- return lines.join('\n');
552
- }
553
- /**
554
- * Generate error types
555
- */
556
- function generateErrors() {
557
- return `/**
558
- * Error types
559
- * Auto-generated - DO NOT EDIT
560
- */
561
-
562
- // Re-export ProblemDetails from schemas (RFC 9457)
563
- export type { ProblemDetails, ValidationError } from '../schemas/types.js';
564
- import type { ProblemDetails } from '../schemas/types.js';
565
-
566
- /**
567
- * API Error wrapper
568
- */
569
- export class ApiError extends Error {
570
- constructor(
571
- public readonly status: number,
572
- public readonly problem: ProblemDetails,
573
- public readonly requestId?: string,
574
- ) {
575
- super(problem.title);
576
- this.name = 'ApiError';
577
- }
578
-
579
- get isValidationError(): boolean {
580
- return this.status === 400;
581
- }
582
-
583
- get isNotFound(): boolean {
584
- return this.status === 404;
585
- }
586
-
587
- get isServerError(): boolean {
588
- return this.status >= 500;
589
- }
590
- }
591
- `;
592
- }
593
- /**
594
- * Generate empty error types (when no endpoints exist)
595
- */
596
- function generateEmptyErrors() {
597
- return `/**
598
- * Error types
599
- * Auto-generated - DO NOT EDIT
600
- *
601
- * No endpoints defined - error types not needed.
602
- */
603
- `;
604
- }
605
- /**
606
- * Filter OpenAPI spec for public endpoints only
607
- */
608
- function filterPublicSpec(spec) {
609
- const filtered = {
610
- ...spec,
611
- paths: {},
612
- tags: [],
613
- components: {
614
- schemas: {},
615
- responses: spec.components?.responses,
616
- parameters: spec.components?.parameters,
617
- requestBodies: spec.components?.requestBodies,
618
- },
619
- };
620
- // Collect all referenced schemas and tags from public endpoints
621
- const usedSchemas = new Set();
622
- const usedTags = new Set();
623
- // Filter paths to only include x-micro-contracts-published: true
624
- for (const [pathKey, pathItem] of Object.entries(spec.paths)) {
625
- const filteredPathItem = {};
626
- let hasPublicOperation = false;
627
- for (const method of ['get', 'post', 'put', 'patch', 'delete']) {
628
- const operation = pathItem[method];
629
- if (operation && operation['x-micro-contracts-published'] === true) {
630
- filteredPathItem[method] = operation;
631
- hasPublicOperation = true;
632
- // Collect schema references from this operation
633
- collectSchemaRefs(operation, usedSchemas);
634
- // Collect tags from this operation
635
- if (operation.tags) {
636
- for (const tag of operation.tags) {
637
- usedTags.add(tag);
638
- }
639
- }
640
- }
641
- }
642
- if (hasPublicOperation) {
643
- filtered.paths[pathKey] = filteredPathItem;
644
- }
645
- }
646
- // Recursively resolve schema references
647
- const allUsedSchemas = resolveSchemaRefsRecursively(spec, usedSchemas);
648
- // Filter schemas to only include used ones
649
- if (spec.components?.schemas) {
650
- for (const [schemaName, schema] of Object.entries(spec.components.schemas)) {
651
- if (allUsedSchemas.has(schemaName)) {
652
- filtered.components.schemas[schemaName] = schema;
653
- }
654
- }
655
- }
656
- // Filter tags to only include used ones
657
- if (spec.tags) {
658
- filtered.tags = spec.tags.filter(tag => usedTags.has(tag.name));
659
- }
660
- // Clean up empty components
661
- if (Object.keys(filtered.components?.schemas || {}).length === 0) {
662
- delete filtered.components?.schemas;
663
- }
664
- // Clean up empty tags
665
- if (filtered.tags?.length === 0) {
666
- delete filtered.tags;
667
- }
668
- return filtered;
669
- }
670
- /**
671
- * Collect $ref references from an operation
672
- */
673
- function collectSchemaRefs(obj, refs) {
674
- if (!obj || typeof obj !== 'object')
675
- return;
676
- if (Array.isArray(obj)) {
677
- for (const item of obj) {
678
- collectSchemaRefs(item, refs);
679
- }
680
- return;
681
- }
682
- const record = obj;
683
- // Check for $ref
684
- if (typeof record['$ref'] === 'string') {
685
- const ref = record['$ref'];
686
- const match = ref.match(/#\/components\/schemas\/(.+)/);
687
- if (match) {
688
- refs.add(match[1]);
689
- }
690
- }
691
- // Recurse into nested objects
692
- for (const value of Object.values(record)) {
693
- collectSchemaRefs(value, refs);
694
- }
695
- }
696
- /**
697
- * Recursively resolve schema references (schemas can reference other schemas)
698
- */
699
- function resolveSchemaRefsRecursively(spec, initialRefs) {
700
- const allRefs = new Set(initialRefs);
701
- const toProcess = [...initialRefs];
702
- while (toProcess.length > 0) {
703
- const schemaName = toProcess.pop();
704
- const schema = spec.components?.schemas?.[schemaName];
705
- if (!schema)
706
- continue;
707
- // Collect refs from this schema
708
- const nestedRefs = new Set();
709
- collectSchemaRefs(schema, nestedRefs);
710
- // Add new refs to process
711
- for (const ref of nestedRefs) {
712
- if (!allRefs.has(ref)) {
713
- allRefs.add(ref);
714
- toProcess.push(ref);
715
- }
716
- }
717
- }
718
- return allRefs;
719
- }
720
- //# sourceMappingURL=index.js.map