micro-contracts 0.16.4 → 0.17.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 (181) hide show
  1. package/dist/micro-contracts.bundle.mjs +4683 -0
  2. package/dist/micro-contracts.bundle.mjs.map +7 -0
  3. package/package.json +22 -25
  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
package/dist/cli.js DELETED
@@ -1,1142 +0,0 @@
1
- #!/usr/bin/env node
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { createRequire } from 'module';
5
- import yaml from 'yaml';
6
- import { createProgram } from './generated/program.js';
7
- import { generate, loadConfig, loadOpenAPISpec, lintSpec, formatLintResults, computeInputHash } from './generator/index.js';
8
- import { isMultiModuleConfig } from './types.js';
9
- import { getStarterTemplates, getScreenStarterTemplates } from './cli/templates.js';
10
- import { runAllChecks, formatCheckResults, formatSingleCheckResult, formatCheckStart, formatCheckSummary, getAvailableChecks, createGuardrailsConfig, generateManifest, writeManifest, canSkipGeneration, GATE_DESCRIPTIONS, loadGuardrailsConfigWithPath, } from './guardrails/index.js';
11
- import { commandAuditOpenapi } from './commands/audit-openapi.js';
12
- import { commandReviewPublished } from './commands/review-published.js';
13
- import { commandProposeOverlays } from './commands/propose-overlays.js';
14
- import { commandAuditGuardrails } from './commands/audit-guardrails.js';
15
- const require = createRequire(import.meta.url);
16
- const pkg = require('../package.json');
17
- function findConfigFile() {
18
- const candidates = [
19
- 'micro-contracts.config.yaml',
20
- 'micro-contracts.config.yml',
21
- 'api-framework.config.yaml',
22
- 'api-framework.config.yml',
23
- ];
24
- for (const candidate of candidates) {
25
- const configPath = path.resolve(candidate);
26
- if (fs.existsSync(configPath))
27
- return configPath;
28
- }
29
- return null;
30
- }
31
- const handlers = {
32
- // ── generate ──────────────────────────────────────────
33
- generate: async (opts) => {
34
- try {
35
- let config;
36
- const configPath = opts.config
37
- ? path.resolve(opts.config)
38
- : findConfigFile();
39
- if (!configPath) {
40
- console.error('Error: No config file found.');
41
- console.error('Create micro-contracts.config.yaml or use --config <path>');
42
- process.exit(1);
43
- }
44
- if (!fs.existsSync(configPath)) {
45
- console.error(`Config file not found: ${configPath}`);
46
- process.exit(1);
47
- }
48
- console.log(`Using config: ${configPath}`);
49
- config = loadConfig(configPath);
50
- const useCache = opts.cache !== false && !opts.force;
51
- let inputHash;
52
- if (isMultiModuleConfig(config)) {
53
- inputHash = computeInputHash(config, configPath, pkg.version);
54
- if (useCache) {
55
- const manifestDir = opts.manifestDir || 'packages/';
56
- const skipCheck = await canSkipGeneration(manifestDir, inputHash);
57
- if (skipCheck.skip) {
58
- console.log(`No input changes detected, skipping generation (${skipCheck.reason})`);
59
- return;
60
- }
61
- }
62
- }
63
- await generate(config, {
64
- contractsOnly: opts.contractsOnly,
65
- serverOnly: opts.serverOnly,
66
- frontendOnly: opts.frontendOnly,
67
- docsOnly: opts.docsOnly,
68
- skipLint: opts.skipLint,
69
- modules: opts.module,
70
- });
71
- if (opts.manifest !== false) {
72
- const { config: guardrailsConfig } = loadGuardrailsConfigWithPath();
73
- if (guardrailsConfig?.generated && guardrailsConfig.generated.length > 0) {
74
- const manifestDir = opts.manifestDir || 'packages/';
75
- if (fs.existsSync(manifestDir)) {
76
- const manifestInputHash = (opts.cache !== false) ? inputHash : undefined;
77
- const { manifest, changed } = await generateManifest(manifestDir, {
78
- generatorVersion: pkg.version,
79
- inputHash: manifestInputHash,
80
- });
81
- const fileCount = Object.keys(manifest.files).length;
82
- if (changed) {
83
- const manifestPath = writeManifest(manifest, manifestDir);
84
- console.log(`\nManifest updated: ${manifestPath} (${fileCount} files)`);
85
- }
86
- else {
87
- console.log(`\nManifest unchanged (${fileCount} files)`);
88
- }
89
- }
90
- }
91
- }
92
- }
93
- catch (error) {
94
- console.error('Generation failed:', error instanceof Error ? error.message : error);
95
- process.exit(1);
96
- }
97
- },
98
- // ── lint ───────────────────────────────────────────────
99
- lint: async (input) => {
100
- try {
101
- const specPath = path.resolve(input);
102
- if (!fs.existsSync(specPath)) {
103
- console.error(`OpenAPI spec not found: ${specPath}`);
104
- process.exit(1);
105
- }
106
- console.log(`Linting: ${specPath}\n`);
107
- const spec = loadOpenAPISpec(specPath);
108
- const result = lintSpec(spec, { strict: false });
109
- console.log(formatLintResults(result));
110
- if (!result.valid) {
111
- process.exit(1);
112
- }
113
- }
114
- catch (error) {
115
- console.error('Lint failed:', error instanceof Error ? error.message : error);
116
- process.exit(1);
117
- }
118
- },
119
- // ── init ───────────────────────────────────────────────
120
- init: async (name, opts) => {
121
- console.log(`Initializing module "${name}"${opts.screens ? ' (screen spec)' : ''}...\n`);
122
- const specDirs = [
123
- 'spec',
124
- 'spec/default/templates',
125
- 'spec/_shared/openapi',
126
- 'spec/_shared/overlays',
127
- `spec/${name}/openapi`,
128
- ];
129
- for (const dir of specDirs) {
130
- if (!fs.existsSync(dir)) {
131
- fs.mkdirSync(dir, { recursive: true });
132
- console.log(`Created: ${dir}/`);
133
- }
134
- }
135
- if (!opts.skipTemplates) {
136
- const starterTemplates = getStarterTemplates();
137
- for (const [filename, content] of Object.entries(starterTemplates)) {
138
- const templatePath = path.join('spec/default/templates', filename);
139
- if (!fs.existsSync(templatePath)) {
140
- fs.writeFileSync(templatePath, content);
141
- console.log(`Created: ${templatePath}`);
142
- }
143
- }
144
- if (opts.screens) {
145
- const screenTemplates = getScreenStarterTemplates();
146
- for (const [filename, content] of Object.entries(screenTemplates)) {
147
- const templatePath = path.join('spec/default/templates', filename);
148
- if (!fs.existsSync(templatePath)) {
149
- fs.writeFileSync(templatePath, content);
150
- console.log(`Created: ${templatePath}`);
151
- }
152
- }
153
- }
154
- }
155
- const problemDetailsPath = 'spec/_shared/openapi/problem-details.yaml';
156
- if (!fs.existsSync(problemDetailsPath)) {
157
- fs.writeFileSync(problemDetailsPath, generateProblemDetailsSchema());
158
- console.log(`Created: ${problemDetailsPath}`);
159
- }
160
- const spectralPath = 'spec/spectral.yaml';
161
- if (!fs.existsSync(spectralPath)) {
162
- fs.writeFileSync(spectralPath, generateSpectralRules());
163
- console.log(`Created: ${spectralPath}`);
164
- }
165
- if (opts.screens) {
166
- const screenSpecDir = `spec/${name}/openapi`;
167
- fs.mkdirSync(screenSpecDir, { recursive: true });
168
- const screenSpecPath = path.join(screenSpecDir, `${name}.yaml`);
169
- if (!fs.existsSync(screenSpecPath)) {
170
- fs.writeFileSync(screenSpecPath, generateScreenSpecTemplate(name));
171
- console.log(`Created: ${screenSpecPath}`);
172
- }
173
- }
174
- const baseDir = path.resolve(opts.dir ?? 'src', name);
175
- const dirs = [
176
- baseDir,
177
- path.join(baseDir, 'services'),
178
- path.join(baseDir, 'models'),
179
- ];
180
- for (const dir of dirs) {
181
- if (!fs.existsSync(dir)) {
182
- fs.mkdirSync(dir, { recursive: true });
183
- console.log(`Created: ${dir}/`);
184
- }
185
- }
186
- const files = [
187
- [path.join(baseDir, 'db.ts'), generateDbTemplate()],
188
- [path.join(baseDir, 'container.ts'), generateContainerTemplate(name)],
189
- [path.join(baseDir, 'services', 'index.ts'), '// Export service classes\n'],
190
- [path.join(baseDir, 'models', 'index.ts'), '// Export models\n'],
191
- ];
192
- for (const [filePath, content] of files) {
193
- if (!fs.existsSync(filePath)) {
194
- fs.writeFileSync(filePath, content);
195
- console.log(`Created: ${filePath}`);
196
- }
197
- }
198
- const configPath = path.resolve('micro-contracts.config.yaml');
199
- if (!fs.existsSync(configPath)) {
200
- const configContent = opts.screens
201
- ? generateScreenConfigTemplate(name)
202
- : generateConfigTemplate(name);
203
- fs.writeFileSync(configPath, configContent);
204
- console.log(`Created: ${configPath}`);
205
- }
206
- if (opts.openapi) {
207
- const openapiPath = path.resolve(opts.openapi);
208
- if (!fs.existsSync(openapiPath)) {
209
- console.error(`OpenAPI file not found: ${openapiPath}`);
210
- process.exit(1);
211
- }
212
- const outputPath = opts.output
213
- ? path.resolve(opts.output)
214
- : path.resolve(`spec/${name}/openapi/${name}.yaml`);
215
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
216
- console.log(`\nProcessing OpenAPI: ${openapiPath}`);
217
- const processed = processOpenAPIWithExtensions(openapiPath);
218
- fs.writeFileSync(outputPath, processed.yaml);
219
- console.log(`Created: ${outputPath}`);
220
- console.log(` - Added x-micro-contracts-service to ${processed.stats.servicesAdded} operations`);
221
- console.log(` - Added x-micro-contracts-method to ${processed.stats.methodsAdded} operations`);
222
- if (processed.stats.services.length > 0) {
223
- console.log(` - Detected services: ${processed.stats.services.join(', ')}`);
224
- }
225
- }
226
- console.log(`\nModule "${name}" initialized!`);
227
- if (opts.screens) {
228
- console.log(`\nNext steps:`);
229
- console.log(` 1. Edit spec/${name}/openapi/${name}.yaml with your screen definitions`);
230
- console.log(` 2. Define ViewModels in components/schemas`);
231
- console.log(` 3. Add navigation links and x-events`);
232
- console.log(` 4. Run: npx micro-contracts generate`);
233
- }
234
- else if (!opts.openapi) {
235
- console.log(`\nNext steps:`);
236
- console.log(` 1. Create spec/${name}/openapi/${name}.yaml with your API spec`);
237
- console.log(` 2. Add x-micro-contracts-service and x-micro-contracts-method to operations`);
238
- console.log(` 3. Run: npx micro-contracts generate`);
239
- console.log(`\nTip: Use --openapi to auto-add extensions:`);
240
- console.log(` npx micro-contracts init ${name} --openapi path/to/spec.yaml`);
241
- }
242
- else {
243
- console.log(`\nNext steps:`);
244
- console.log(` 1. Review the generated extensions in spec/${name}/openapi/${name}.yaml`);
245
- console.log(` 2. Run: npx micro-contracts generate`);
246
- }
247
- },
248
- // ── check ─────────────────────────────────────────────
249
- check: async (opts) => {
250
- try {
251
- if (opts.listGates) {
252
- console.log('\nAvailable gates:\n');
253
- for (const [gate, description] of Object.entries(GATE_DESCRIPTIONS)) {
254
- console.log(` Gate ${gate}: ${description}`);
255
- }
256
- console.log('\nUsage: micro-contracts check --gate 1,2,3');
257
- console.log('');
258
- return;
259
- }
260
- if (opts.list) {
261
- console.log('\nAvailable checks:\n');
262
- for (const check of getAvailableChecks({ guardrailsPath: opts.guardrails })) {
263
- const gateStr = check.gate !== undefined ? `[G${check.gate}]` : ' ';
264
- console.log(` ${gateStr} ${check.name.padEnd(20)} - ${check.description}`);
265
- }
266
- console.log('');
267
- return;
268
- }
269
- let gates;
270
- if (opts.gate) {
271
- gates = opts.gate.split(',').map((s) => {
272
- const num = parseInt(s.trim(), 10);
273
- if (num < 1 || num > 5 || isNaN(num)) {
274
- throw new Error(`Invalid gate number: ${s}. Must be 1-5.`);
275
- }
276
- return num;
277
- });
278
- }
279
- console.log('');
280
- console.log('🔍 AI Guardrail Check Results');
281
- console.log('═'.repeat(50));
282
- console.log('');
283
- const isStreaming = process.stdout.isTTY !== false;
284
- const checkOptions = {
285
- only: opts.only?.split(',').map((s) => s.trim()),
286
- skip: opts.skip?.split(',').map((s) => s.trim()),
287
- gates,
288
- verbose: opts.verbose,
289
- fix: opts.fix,
290
- guardrailsPath: opts.guardrails,
291
- generatedDir: opts.generatedDir,
292
- changedFilesPath: opts.changedFiles,
293
- onCheckStart: isStreaming ? (check) => {
294
- if (process.stdout.isTTY) {
295
- process.stdout.write(formatCheckStart(check) + '\r');
296
- }
297
- } : undefined,
298
- onCheckComplete: isStreaming ? (result, check) => {
299
- if (process.stdout.isTTY) {
300
- process.stdout.clearLine?.(0);
301
- process.stdout.cursorTo?.(0);
302
- }
303
- console.log(formatSingleCheckResult(result, check, opts.verbose));
304
- } : undefined,
305
- };
306
- const summary = await runAllChecks(checkOptions);
307
- if (isStreaming) {
308
- console.log(formatCheckSummary(summary, summary.checks));
309
- }
310
- else {
311
- console.log(formatCheckResults(summary, opts.verbose, summary.checks));
312
- }
313
- if (summary.failed > 0) {
314
- process.exit(1);
315
- }
316
- }
317
- catch (error) {
318
- console.error('Check failed:', error instanceof Error ? error.message : error);
319
- process.exit(1);
320
- }
321
- },
322
- // ── pipeline ──────────────────────────────────────────
323
- pipeline: async (opts) => {
324
- try {
325
- const startTime = Date.now();
326
- const verbose = opts.verbose;
327
- let hasFailure = false;
328
- let generatePassed = false;
329
- let generateDuration = 0;
330
- let generateSkipped = false;
331
- let generateError = null;
332
- const allResults = [];
333
- console.log('');
334
- console.log('🚀 Running AI Guardrails Pipeline');
335
- console.log('═'.repeat(50));
336
- console.log('');
337
- const skipChecks = opts.skip?.split(',').map((s) => s.trim()) || [];
338
- const isStreaming = process.stdout.isTTY !== false;
339
- const baseCheckOptions = {
340
- verbose,
341
- skip: skipChecks,
342
- guardrailsPath: opts.guardrails,
343
- generatedDir: opts.generatedDir,
344
- onCheckStart: isStreaming ? (check) => {
345
- if (process.stdout.isTTY) {
346
- process.stdout.write(formatCheckStart(check) + '\r');
347
- }
348
- } : undefined,
349
- onCheckComplete: isStreaming ? (result, check) => {
350
- if (process.stdout.isTTY) {
351
- process.stdout.clearLine?.(0);
352
- process.stdout.cursorTo?.(0);
353
- }
354
- console.log(formatSingleCheckResult(result, check, verbose));
355
- } : undefined,
356
- };
357
- // Step 1: Gate 1,2
358
- if (verbose) {
359
- console.log('┌─────────────────────────────────────────────────┐');
360
- console.log('│ Step 1: Pre-generation checks (Gate 1, 2) │');
361
- console.log('└─────────────────────────────────────────────────┘');
362
- console.log('');
363
- }
364
- const gate12Summary = await runAllChecks({ ...baseCheckOptions, gates: [1, 2] });
365
- allResults.push(...gate12Summary.results);
366
- if (verbose)
367
- console.log(formatCheckSummary(gate12Summary, gate12Summary.checks));
368
- if (gate12Summary.failed > 0) {
369
- hasFailure = true;
370
- if (!opts.continueOnError) {
371
- console.log('');
372
- console.log('❌ Gate 1,2 failed. Stopping pipeline.');
373
- console.log(' Use --continue-on-error to continue despite failures.');
374
- process.exit(1);
375
- }
376
- if (verbose)
377
- console.log('⚠️ Gate 1,2 had failures. Continuing due to --continue-on-error.\n');
378
- }
379
- // Step 2: Generate
380
- let generationCacheSkipped = false;
381
- if (verbose) {
382
- console.log('┌─────────────────────────────────────────────────┐');
383
- console.log('│ Step 2: Generate contracts │');
384
- console.log('└─────────────────────────────────────────────────┘');
385
- console.log('');
386
- }
387
- const generateStartTime = Date.now();
388
- if (isStreaming && process.stdout.isTTY) {
389
- process.stdout.write(' ⋯ Generate running...\r');
390
- }
391
- try {
392
- const configPath = opts.config ? path.resolve(opts.config) : findConfigFile();
393
- if (!configPath) {
394
- generateSkipped = true;
395
- generateDuration = Date.now() - generateStartTime;
396
- if (isStreaming && process.stdout.isTTY) {
397
- process.stdout.clearLine?.(0);
398
- process.stdout.cursorTo?.(0);
399
- }
400
- console.log(' ○ Generate SKIP (no config file)');
401
- }
402
- else {
403
- if (verbose)
404
- console.log(` Using config: ${configPath}`);
405
- const config = loadConfig(configPath);
406
- const useCache = opts.cache !== false && !opts.force;
407
- let inputHash;
408
- if (isMultiModuleConfig(config)) {
409
- inputHash = computeInputHash(config, configPath, pkg.version);
410
- if (useCache) {
411
- const manifestDir = opts.generatedDir || 'packages/';
412
- const skipCheck = await canSkipGeneration(manifestDir, inputHash);
413
- if (skipCheck.skip) {
414
- generationCacheSkipped = true;
415
- generateSkipped = true;
416
- generateDuration = Date.now() - generateStartTime;
417
- if (isStreaming && process.stdout.isTTY) {
418
- process.stdout.clearLine?.(0);
419
- process.stdout.cursorTo?.(0);
420
- }
421
- console.log(` ○ Generate SKIP (${skipCheck.reason})`);
422
- }
423
- }
424
- }
425
- if (!generationCacheSkipped) {
426
- const originalLog = console.log;
427
- if (!verbose)
428
- console.log = () => { };
429
- try {
430
- await generate(config, {
431
- skipLint: opts.skipLint,
432
- contractsOnly: opts.contractsOnly,
433
- serverOnly: opts.serverOnly,
434
- frontendOnly: opts.frontendOnly,
435
- docsOnly: opts.docsOnly,
436
- });
437
- if (opts.manifest !== false) {
438
- const { config: guardrailsConfig } = loadGuardrailsConfigWithPath(opts.guardrails);
439
- if (guardrailsConfig?.generated && guardrailsConfig.generated.length > 0) {
440
- const manifestDir = opts.generatedDir || 'packages/';
441
- if (fs.existsSync(manifestDir)) {
442
- const manifestInputHash = (opts.cache !== false) ? inputHash : undefined;
443
- const { manifest, changed } = await generateManifest(manifestDir, {
444
- generatorVersion: pkg.version,
445
- inputHash: manifestInputHash,
446
- });
447
- if (verbose) {
448
- const fileCount = Object.keys(manifest.files).length;
449
- if (changed) {
450
- const mPath = writeManifest(manifest, manifestDir);
451
- originalLog(` Manifest updated: ${mPath} (${fileCount} files)`);
452
- }
453
- else {
454
- originalLog(` Manifest unchanged (${fileCount} files)`);
455
- }
456
- }
457
- else if (changed) {
458
- writeManifest(manifest, manifestDir);
459
- }
460
- }
461
- }
462
- }
463
- generatePassed = true;
464
- }
465
- finally {
466
- if (!verbose)
467
- console.log = originalLog;
468
- }
469
- generateDuration = Date.now() - generateStartTime;
470
- if (isStreaming && process.stdout.isTTY) {
471
- process.stdout.clearLine?.(0);
472
- process.stdout.cursorTo?.(0);
473
- }
474
- console.log(` ✓ Generate PASS (${generateDuration}ms)`);
475
- if (verbose)
476
- console.log('');
477
- }
478
- }
479
- }
480
- catch (error) {
481
- generateDuration = Date.now() - generateStartTime;
482
- hasFailure = true;
483
- generateError = error instanceof Error ? error.message : String(error);
484
- if (isStreaming && process.stdout.isTTY) {
485
- process.stdout.clearLine?.(0);
486
- process.stdout.cursorTo?.(0);
487
- }
488
- console.log(` ✗ Generate FAIL (${generateDuration}ms)`);
489
- console.log(` ${generateError}`);
490
- if (!opts.continueOnError) {
491
- console.log('');
492
- console.log('❌ Generation failed. Stopping pipeline.');
493
- process.exit(1);
494
- }
495
- if (verbose)
496
- console.log('⚠️ Continuing due to --continue-on-error.');
497
- }
498
- // Step 3: Gate 3,4,5
499
- if (verbose) {
500
- console.log('');
501
- console.log('┌─────────────────────────────────────────────────┐');
502
- console.log('│ Step 3: Post-generation checks (Gate 3, 4, 5) │');
503
- console.log('└─────────────────────────────────────────────────┘');
504
- console.log('');
505
- }
506
- const gate3Skips = generationCacheSkipped
507
- ? [...skipChecks, 'drift', 'manifest']
508
- : skipChecks;
509
- const gate345Summary = await runAllChecks({
510
- ...baseCheckOptions,
511
- skip: gate3Skips,
512
- gates: [3, 4, 5],
513
- });
514
- allResults.push(...gate345Summary.results);
515
- if (verbose)
516
- console.log(formatCheckSummary(gate345Summary, gate345Summary.checks));
517
- if (gate345Summary.failed > 0)
518
- hasFailure = true;
519
- // Final Summary
520
- const totalDuration = Date.now() - startTime;
521
- const totalPassed = gate12Summary.passed + gate345Summary.passed + (generatePassed ? 1 : 0);
522
- const totalFailed = gate12Summary.failed + gate345Summary.failed + (generateError ? 1 : 0);
523
- const totalSkipped = gate12Summary.skipped + gate345Summary.skipped + (generateSkipped ? 1 : 0);
524
- console.log('');
525
- console.log('━'.repeat(50));
526
- console.log('📊 Pipeline Summary');
527
- console.log('━'.repeat(50));
528
- console.log('');
529
- console.log(` Total Passed: ${totalPassed}`);
530
- console.log(` Total Failed: ${totalFailed}`);
531
- if (totalSkipped > 0)
532
- console.log(` Total Skipped: ${totalSkipped}`);
533
- console.log(` Duration: ${totalDuration}ms`);
534
- console.log('');
535
- if (hasFailure) {
536
- console.log('❌ Pipeline completed with failures.');
537
- const failedResults = allResults.filter(r => r.status === 'fail' && r.details && r.details.length > 0);
538
- if (failedResults.length > 0) {
539
- console.log('');
540
- console.log('📋 Failed Check Details:');
541
- for (const result of failedResults) {
542
- console.log(` ▶ ${result.name}`);
543
- for (const detail of result.details) {
544
- console.log(` ${detail}`);
545
- }
546
- }
547
- }
548
- process.exit(1);
549
- }
550
- else {
551
- console.log('✅ Pipeline completed successfully!');
552
- }
553
- console.log('');
554
- }
555
- catch (error) {
556
- console.error('Pipeline failed:', error instanceof Error ? error.message : error);
557
- process.exit(1);
558
- }
559
- },
560
- // ── deps ──────────────────────────────────────────────
561
- deps: async (opts) => {
562
- try {
563
- const configPath = opts.config
564
- ? path.resolve(opts.config)
565
- : findConfigFile();
566
- if (!configPath) {
567
- console.error('Error: No config file found.');
568
- process.exit(1);
569
- }
570
- const config = loadConfig(configPath);
571
- if (!config.modules) {
572
- console.error('Error: Config must have modules defined.');
573
- process.exit(1);
574
- }
575
- const moduleDeps = new Map();
576
- for (const [moduleName, moduleConfig] of Object.entries(config.modules)) {
577
- if (opts.module && moduleName !== opts.module)
578
- continue;
579
- const openapiPath = path.resolve(path.dirname(configPath), moduleConfig.openapi);
580
- if (!fs.existsSync(openapiPath)) {
581
- console.warn(`Warning: OpenAPI spec not found: ${openapiPath}`);
582
- continue;
583
- }
584
- const spec = loadOpenAPISpec(openapiPath);
585
- const openApiDeps = spec.info['x-micro-contracts-depend-on'] || [];
586
- const configDeps = moduleConfig.dependsOn || [];
587
- moduleDeps.set(moduleName, { deps: openApiDeps, openApiDeps, configDeps });
588
- }
589
- if (opts.graph) {
590
- outputDependencyGraph(moduleDeps);
591
- }
592
- else if (opts.impact) {
593
- outputImpactAnalysis(moduleDeps, opts.impact);
594
- }
595
- else if (opts.whoDependsOn) {
596
- outputWhoDependsOn(moduleDeps, opts.whoDependsOn);
597
- }
598
- else if (opts.validate) {
599
- validateDependencies(moduleDeps);
600
- }
601
- else {
602
- outputAllDependencies(moduleDeps);
603
- }
604
- }
605
- catch (error) {
606
- console.error('Deps analysis failed:', error instanceof Error ? error.message : error);
607
- process.exit(1);
608
- }
609
- },
610
- // ── guardrails-init ───────────────────────────────────
611
- guardrailsInit: async (opts) => {
612
- try {
613
- const outputPath = opts.output ?? 'guardrails.yaml';
614
- if (fs.existsSync(outputPath)) {
615
- console.error(`File already exists: ${outputPath}`);
616
- console.error('Use --output to specify a different path.');
617
- process.exit(1);
618
- }
619
- createGuardrailsConfig(outputPath);
620
- console.log(`Created: ${outputPath}`);
621
- console.log('\nNext steps:');
622
- console.log(' 1. Review and customize the guardrails configuration');
623
- console.log(' 2. Run: micro-contracts check');
624
- }
625
- catch (error) {
626
- console.error('Failed to create guardrails config:', error instanceof Error ? error.message : error);
627
- process.exit(1);
628
- }
629
- },
630
- // ── manifest ──────────────────────────────────────────
631
- manifest: async (opts) => {
632
- try {
633
- const baseDir = opts.dir ?? 'packages/';
634
- if (!fs.existsSync(baseDir)) {
635
- console.error(`Directory not found: ${baseDir}`);
636
- process.exit(1);
637
- }
638
- if (opts.verify) {
639
- const { verifyManifest, formatManifestResult } = await import('./guardrails/index.js');
640
- const result = await verifyManifest(baseDir);
641
- console.log(formatManifestResult(result));
642
- if (!result.valid) {
643
- process.exit(1);
644
- }
645
- }
646
- else {
647
- console.log(`Generating manifest for: ${baseDir}`);
648
- const { manifest, changed } = await generateManifest(baseDir, {
649
- generatorVersion: pkg.version,
650
- });
651
- const fileCount = Object.keys(manifest.files).length;
652
- if (changed) {
653
- const manifestPath = writeManifest(manifest, baseDir);
654
- console.log(`Manifest updated: ${manifestPath} (${fileCount} files)`);
655
- }
656
- else {
657
- console.log(`Manifest unchanged (${fileCount} files)`);
658
- }
659
- }
660
- }
661
- catch (error) {
662
- console.error('Manifest operation failed:', error instanceof Error ? error.message : error);
663
- process.exit(1);
664
- }
665
- },
666
- // ── audit-openapi (LLM) ──────────────────────────────
667
- auditOpenapi: async (opts) => {
668
- return commandAuditOpenapi({
669
- config: opts.config,
670
- module: opts.module,
671
- adapter: opts.adapter,
672
- model: opts.model,
673
- showPrompt: opts.showPrompt,
674
- failOn: opts.failOn,
675
- output: opts.output,
676
- reportFormat: opts.reportFormat,
677
- });
678
- },
679
- // ── review-published (LLM) ───────────────────────────
680
- reviewPublished: async (opts) => {
681
- return commandReviewPublished({
682
- config: opts.config,
683
- module: opts.module,
684
- adapter: opts.adapter,
685
- model: opts.model,
686
- showPrompt: opts.showPrompt,
687
- failOn: opts.failOn,
688
- output: opts.output,
689
- reportFormat: opts.reportFormat,
690
- });
691
- },
692
- // ── propose-overlays (LLM) ───────────────────────────
693
- proposeOverlays: async (opts) => {
694
- return commandProposeOverlays({
695
- config: opts.config,
696
- module: opts.module,
697
- adapter: opts.adapter,
698
- model: opts.model,
699
- showPrompt: opts.showPrompt,
700
- failOn: opts.failOn,
701
- output: opts.output,
702
- reportFormat: opts.reportFormat,
703
- });
704
- },
705
- // ── audit-guardrails (LLM) ───────────────────────────
706
- auditGuardrails: async (opts) => {
707
- return commandAuditGuardrails({
708
- config: opts.config,
709
- guardrails: opts.guardrails,
710
- adapter: opts.adapter,
711
- model: opts.model,
712
- showPrompt: opts.showPrompt,
713
- failOn: opts.failOn,
714
- output: opts.output,
715
- reportFormat: opts.reportFormat,
716
- });
717
- },
718
- };
719
- createProgram(handlers, pkg.version).parse();
720
- // =============================================================================
721
- // Helper functions (used by init, deps commands)
722
- // =============================================================================
723
- function processOpenAPIWithExtensions(openapiPath) {
724
- const content = fs.readFileSync(openapiPath, 'utf-8');
725
- const spec = yaml.parse(content);
726
- const stats = { servicesAdded: 0, methodsAdded: 0, services: new Set() };
727
- const httpMethods = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'];
728
- if (spec.paths) {
729
- for (const [pathKey, pathItem] of Object.entries(spec.paths)) {
730
- if (!pathItem || typeof pathItem !== 'object')
731
- continue;
732
- const service = inferServiceFromPath(pathKey);
733
- for (const method of httpMethods) {
734
- const operation = pathItem[method];
735
- if (!operation || typeof operation !== 'object')
736
- continue;
737
- const op = operation;
738
- if (!op['x-micro-contracts-service'] && service) {
739
- op['x-micro-contracts-service'] = service;
740
- stats.servicesAdded++;
741
- stats.services.add(service);
742
- }
743
- if (!op['x-micro-contracts-method']) {
744
- const methodName = op.operationId
745
- ? String(op.operationId)
746
- : inferMethodName(method, pathKey);
747
- op['x-micro-contracts-method'] = methodName;
748
- stats.methodsAdded++;
749
- }
750
- }
751
- }
752
- }
753
- const output = yaml.stringify(spec, { indent: 2 });
754
- return { yaml: output, stats: { ...stats, services: Array.from(stats.services) } };
755
- }
756
- function inferServiceFromPath(pathKey) {
757
- const normalized = pathKey.replace(/^\/api\//, '/').replace(/^\/v\d+\//, '/');
758
- const segments = normalized.split('/').filter(Boolean);
759
- if (segments.length === 0)
760
- return null;
761
- const firstSegment = segments[0];
762
- if (firstSegment.startsWith('{'))
763
- return null;
764
- const words = firstSegment.replace(/-/g, '_').split('_');
765
- const pascalCase = words
766
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
767
- .join('');
768
- if (pascalCase.endsWith('s') && !pascalCase.endsWith('ss')) {
769
- return pascalCase.slice(0, -1);
770
- }
771
- return pascalCase;
772
- }
773
- function inferMethodName(httpMethod, pathKey) {
774
- const segments = pathKey
775
- .replace(/^\/api\//, '/')
776
- .replace(/^\/v\d+\//, '/')
777
- .split('/')
778
- .filter(Boolean);
779
- const hasIdParam = segments.some(s => s.startsWith('{'));
780
- const resourceSegments = segments.filter(s => !s.startsWith('{'));
781
- const resourceName = resourceSegments
782
- .map((seg) => {
783
- const words = seg.replace(/-/g, '_').split('_');
784
- return words
785
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
786
- .join('');
787
- })
788
- .join('');
789
- const singularName = resourceName.endsWith('s') && !resourceName.endsWith('ss')
790
- ? resourceName.slice(0, -1)
791
- : resourceName;
792
- switch (httpMethod.toLowerCase()) {
793
- case 'get': return hasIdParam ? `get${singularName}ById` : `get${resourceName}`;
794
- case 'post': return `create${singularName}`;
795
- case 'put': return `update${singularName}`;
796
- case 'patch': return `patch${singularName}`;
797
- case 'delete': return `delete${singularName}`;
798
- default: return `${httpMethod.toLowerCase()}${resourceName}`;
799
- }
800
- }
801
- function generateDbTemplate() {
802
- return `import pg from 'pg';
803
- import { DBModel, PostgresDriver } from 'litedbmodel';
804
-
805
- const { Pool } = pg;
806
-
807
- let pool: pg.Pool | null = null;
808
-
809
- export function getPool(): pg.Pool {
810
- if (!pool) {
811
- pool = new Pool({
812
- connectionString: process.env.DATABASE_URL,
813
- max: 20,
814
- idleTimeoutMillis: 30000,
815
- connectionTimeoutMillis: 2000,
816
- });
817
- }
818
- return pool;
819
- }
820
-
821
- export async function initializeDb(): Promise<void> {
822
- const p = getPool();
823
- DBModel.setDriver(new PostgresDriver(p));
824
- const client = await p.connect();
825
- try {
826
- await client.query('SELECT 1');
827
- } finally {
828
- client.release();
829
- }
830
- }
831
-
832
- export async function closeDb(): Promise<void> {
833
- if (pool) {
834
- await pool.end();
835
- pool = null;
836
- }
837
- }
838
-
839
- export async function testConnection(): Promise<boolean> {
840
- try {
841
- const p = getPool();
842
- const client = await p.connect();
843
- try {
844
- await client.query('SELECT 1');
845
- return true;
846
- } finally {
847
- client.release();
848
- }
849
- } catch {
850
- return false;
851
- }
852
- }
853
- `;
854
- }
855
- function generateContainerTemplate(moduleName) {
856
- const pascalName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
857
- return `import { testConnection, closeDb } from './db.js';
858
-
859
- export interface ${pascalName}Services {
860
- // example: ExampleServiceApi;
861
- }
862
-
863
- export interface ${pascalName}ModuleContainer {
864
- services: ${pascalName}Services;
865
- testConnection: () => Promise<boolean>;
866
- close: () => Promise<void>;
867
- }
868
-
869
- export async function initialize${pascalName}Module(): Promise<${pascalName}ModuleContainer> {
870
- const services: ${pascalName}Services = {};
871
-
872
- return {
873
- services,
874
- testConnection,
875
- close: closeDb,
876
- };
877
- }
878
- `;
879
- }
880
- function generateConfigTemplate(moduleName) {
881
- return [
882
- '# micro-contracts Configuration', '',
883
- 'defaults:', ' contract:', ' output: packages/contract/{module}', '',
884
- ' contractPublic:', ' output: packages/contract-published/{module}', '',
885
- ' outputs:', ' server-routes:', ' output: server/src/{module}/routes.generated.ts',
886
- ' template: fastify-routes.hbs', ' config:',
887
- ' servicesPath: fastify.services.{module}', '',
888
- ' frontend-api:', ' output: frontend/src/{module}/api.generated.ts',
889
- ' template: fetch-client.hbs', '',
890
- ' shared-client:', ' output: frontend/src/shared/{module}.api.generated.ts',
891
- ' template: fetch-client.hbs', ' condition: hasPublicEndpoints', ' config:',
892
- ' contractPackage: "@project/contract-published/{module}"', '',
893
- ' overlays:', ' shared:', ' - spec/_shared/overlays/middleware.overlay.yaml',
894
- ' collision: error', '', ' docs:', ' enabled: true', '',
895
- 'modules:', ` ${moduleName}:`, ` openapi: spec/${moduleName}/openapi/${moduleName}.yaml`, '',
896
- ].join('\n');
897
- }
898
- function generateScreenConfigTemplate(moduleName) {
899
- return [
900
- '# micro-contracts Configuration (Screen Spec)', '',
901
- 'defaults:', ' contract:', ' output: packages/contract/{module}', '',
902
- ' docs:', ' enabled: false', '',
903
- 'modules:', ` ${moduleName}:`, ` openapi: spec/${moduleName}/openapi/${moduleName}.yaml`,
904
- ' screen: true', ' outputs:', ' screen-navigation:',
905
- ` output: frontend/src/screens/navigation.generated.ts`,
906
- ' template: screen-navigation.hbs', ' screen-events:',
907
- ` output: frontend/src/screens/events.generated.ts`,
908
- ' template: screen-events.hbs', '',
909
- ].join('\n');
910
- }
911
- function generateProblemDetailsSchema() {
912
- return `# RFC 9457 Problem Details
913
- components:
914
- schemas:
915
- ProblemDetails:
916
- type: object
917
- required: [type, title, status]
918
- properties:
919
- type:
920
- type: string
921
- format: uri
922
- title:
923
- type: string
924
- status:
925
- type: integer
926
- detail:
927
- type: string
928
- instance:
929
- type: string
930
- format: uri
931
- code:
932
- type: string
933
- traceId:
934
- type: string
935
- errors:
936
- type: array
937
- items:
938
- type: object
939
- properties:
940
- field:
941
- type: string
942
- message:
943
- type: string
944
- `;
945
- }
946
- function generateSpectralRules() {
947
- return `extends: ["spectral:oas"]
948
-
949
- rules:
950
- operation-service:
951
- description: "Operations must have x-micro-contracts-service"
952
- severity: error
953
- given: "$.paths[*][get,post,put,patch,delete]"
954
- then:
955
- field: x-micro-contracts-service
956
- function: truthy
957
-
958
- operation-method:
959
- description: "Operations must have x-micro-contracts-method"
960
- severity: error
961
- given: "$.paths[*][get,post,put,patch,delete]"
962
- then:
963
- field: x-micro-contracts-method
964
- function: truthy
965
-
966
- operation-error-responses:
967
- description: "Operations should have 5XX or default error response"
968
- severity: warn
969
- given: "$.paths[*][get,post,put,patch,delete].responses"
970
- then:
971
- function: schema
972
- functionOptions:
973
- schema:
974
- anyOf:
975
- - required: ["500"]
976
- - required: ["5XX"]
977
- - required: ["default"]
978
-
979
- canonical-extension-prefix:
980
- description: "Use canonical x-micro-contracts-* extensions"
981
- severity: warn
982
- given: "$.paths[*][get,post,put,patch,delete]"
983
- then:
984
- - field: x-service
985
- function: falsy
986
- - field: x-method
987
- function: falsy
988
- - field: x-public
989
- function: falsy
990
- `;
991
- }
992
- function generateScreenSpecTemplate(moduleName) {
993
- const pascalName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1);
994
- return `openapi: '3.1.0'
995
- info:
996
- title: ${pascalName} Screen Specification
997
- version: '0.1.0'
998
- description: |
999
- Screen contract for ${moduleName} domain.
1000
- servers:
1001
- - url: /
1002
- description: Screen routes (client-side)
1003
- paths:
1004
- /home:
1005
- get:
1006
- operationId: renderHomePage
1007
- security: [{session: []}]
1008
- x-screen-const: HOME
1009
- x-screen-id: SCR-001
1010
- x-screen-name: HomePage
1011
- x-back-navigation: false
1012
- summary: Render home page
1013
- responses:
1014
- '200':
1015
- description: Home page ViewModel
1016
- content:
1017
- application/json:
1018
- schema:
1019
- \$ref: '#/components/schemas/HomePageViewModel'
1020
- links:
1021
- goToSettings:
1022
- operationId: renderSettingsPage
1023
- /settings:
1024
- get:
1025
- operationId: renderSettingsPage
1026
- security: [{session: []}]
1027
- x-screen-const: SETTINGS
1028
- x-screen-id: SCR-002
1029
- x-screen-name: SettingsPage
1030
- x-back-navigation: true
1031
- summary: Render settings page
1032
- responses:
1033
- '200':
1034
- description: Settings page ViewModel
1035
- content:
1036
- application/json:
1037
- schema:
1038
- \$ref: '#/components/schemas/SettingsPageViewModel'
1039
- links:
1040
- goToHome:
1041
- operationId: renderHomePage
1042
- components:
1043
- securitySchemes:
1044
- session:
1045
- type: apiKey
1046
- in: cookie
1047
- name: session
1048
- schemas:
1049
- HomePageViewModel:
1050
- type: object
1051
- required: [greeting]
1052
- properties:
1053
- greeting:
1054
- type: string
1055
- SettingsPageViewModel:
1056
- type: object
1057
- required: [theme]
1058
- properties:
1059
- theme:
1060
- type: string
1061
- enum: [light, dark]
1062
- `;
1063
- }
1064
- function outputDependencyGraph(moduleDeps) {
1065
- console.log('```mermaid');
1066
- console.log('graph LR');
1067
- for (const [moduleName, { deps }] of moduleDeps) {
1068
- const moduleTargets = new Set();
1069
- for (const dep of deps) {
1070
- const parts = dep.split('.');
1071
- if (parts.length >= 1)
1072
- moduleTargets.add(parts[0]);
1073
- }
1074
- for (const target of moduleTargets) {
1075
- console.log(` ${moduleName} --> ${target}`);
1076
- }
1077
- }
1078
- console.log('```');
1079
- }
1080
- function outputImpactAnalysis(moduleDeps, ref) {
1081
- console.log(`Impacted by changes to ${ref}:\n`);
1082
- const impacted = [];
1083
- for (const [moduleName, { deps }] of moduleDeps) {
1084
- if (deps.includes(ref))
1085
- impacted.push(moduleName);
1086
- }
1087
- if (impacted.length === 0) {
1088
- console.log(' No modules depend on this API.');
1089
- }
1090
- else {
1091
- for (const m of impacted)
1092
- console.log(` - ${m}`);
1093
- }
1094
- }
1095
- function outputWhoDependsOn(moduleDeps, ref) {
1096
- console.log(`Modules that depend on ${ref}:\n`);
1097
- const dependent = [];
1098
- for (const [moduleName, { deps }] of moduleDeps) {
1099
- if (deps.some(d => d.startsWith(ref)))
1100
- dependent.push(moduleName);
1101
- }
1102
- if (dependent.length === 0) {
1103
- console.log(' None found.');
1104
- }
1105
- else {
1106
- for (const m of dependent)
1107
- console.log(` - ${m}`);
1108
- }
1109
- }
1110
- function validateDependencies(moduleDeps) {
1111
- let hasErrors = false;
1112
- for (const [moduleName, { openApiDeps, configDeps }] of moduleDeps) {
1113
- for (const dep of configDeps) {
1114
- if (!openApiDeps.includes(dep)) {
1115
- console.error(`ERROR: ${moduleName}.dependsOn includes '${dep}'`);
1116
- console.error(` but it's not declared in OpenAPI x-micro-contracts-depend-on`);
1117
- hasErrors = true;
1118
- }
1119
- }
1120
- }
1121
- if (!hasErrors) {
1122
- console.log('✓ All dependencies are valid');
1123
- }
1124
- else {
1125
- process.exit(1);
1126
- }
1127
- }
1128
- function outputAllDependencies(moduleDeps) {
1129
- console.log('Module Dependencies:\n');
1130
- for (const [moduleName, { deps }] of moduleDeps) {
1131
- console.log(`${moduleName}:`);
1132
- if (deps.length === 0) {
1133
- console.log(' (no dependencies)');
1134
- }
1135
- else {
1136
- for (const dep of deps)
1137
- console.log(` - ${dep}`);
1138
- }
1139
- console.log();
1140
- }
1141
- }
1142
- //# sourceMappingURL=cli.js.map