@contractspec/tool.create-contractspec-plugin 1.57.0 → 1.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1239 -0
- package/dist/{index.mjs → node/index.js} +184 -177
- package/dist/node/templates/example-generator.js +1080 -0
- package/dist/node/utils.js +25 -0
- package/dist/templates/example-generator.d.ts +26 -0
- package/dist/templates/example-generator.d.ts.map +1 -0
- package/dist/templates/example-generator.js +1081 -0
- package/dist/utils.d.ts +22 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +26 -0
- package/package.json +39 -18
|
@@ -0,0 +1,1081 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/templates/example-generator.ts
|
|
3
|
+
function createExampleGeneratorTemplate() {
|
|
4
|
+
return {
|
|
5
|
+
files: {
|
|
6
|
+
"package.json": `{
|
|
7
|
+
"name": "{{integrationPackageName}}",
|
|
8
|
+
"version": "{{version}}",
|
|
9
|
+
"description": "{{description}}",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"contractspec",
|
|
12
|
+
"plugin",
|
|
13
|
+
"generator",
|
|
14
|
+
"markdown",
|
|
15
|
+
"documentation",
|
|
16
|
+
"typescript"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": "./dist/index.js",
|
|
23
|
+
"./types": "./dist/types.js",
|
|
24
|
+
"./generator": "./dist/generator.js",
|
|
25
|
+
"./config": "./dist/config.js",
|
|
26
|
+
"./*": "./*"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
|
|
35
|
+
"publish:pkg:canary": "bun publish:pkg --tag canary",
|
|
36
|
+
"prebuild": "contractspec-bun-build prebuild",
|
|
37
|
+
"build": "bun run prebuild && bun run build:bundle && bun run build:types",
|
|
38
|
+
"build:bundle": "contractspec-bun-build transpile",
|
|
39
|
+
"build:types": "contractspec-bun-build types",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"dev": "contractspec-bun-build dev",
|
|
42
|
+
"clean": "rimraf dist .turbo",
|
|
43
|
+
"lint": "bun lint:fix",
|
|
44
|
+
"lint:fix": "eslint src --fix",
|
|
45
|
+
"lint:check": "eslint src",
|
|
46
|
+
"test": "bun test",
|
|
47
|
+
"test:watch": "bun test --watch",
|
|
48
|
+
"test:coverage": "bun test --coverage",
|
|
49
|
+
"test:smoke": "bun test/smoke.test.ts"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@contractspec/lib.contracts": "workspace:*",
|
|
53
|
+
"@contractspec/lib.schema": "workspace:*",
|
|
54
|
+
"zod": "catalog:"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@contractspec/tool.bun": "workspace:*",
|
|
58
|
+
"@contractspec/tool.typescript": "workspace:*",
|
|
59
|
+
"typescript": "catalog:",
|
|
60
|
+
"@types/node": "^22.0.0",
|
|
61
|
+
"rimraf": "^6.0.1"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"@contractspec/lib.contracts": "^1.0.0",
|
|
65
|
+
"@contractspec/lib.schema": "^1.0.0"
|
|
66
|
+
},
|
|
67
|
+
"publishConfig": {
|
|
68
|
+
"access": "public",
|
|
69
|
+
"registry": "https://registry.npmjs.org/"
|
|
70
|
+
},
|
|
71
|
+
"license": "MIT",
|
|
72
|
+
"repository": {
|
|
73
|
+
"type": "git",
|
|
74
|
+
"url": "https://github.com/lssm-tech/contractspec.git",
|
|
75
|
+
"directory": "packages/integrations/{{name}}"
|
|
76
|
+
},
|
|
77
|
+
"homepage": "https://contractspec.io",
|
|
78
|
+
"bugs": {
|
|
79
|
+
"url": "https://github.com/lssm-tech/contractspec/issues"
|
|
80
|
+
},
|
|
81
|
+
"author": {
|
|
82
|
+
"name": "{{author}}"
|
|
83
|
+
},
|
|
84
|
+
"engines": {
|
|
85
|
+
"node": ">=18.0.0",
|
|
86
|
+
"bun": ">=1.0.0"
|
|
87
|
+
}
|
|
88
|
+
}`,
|
|
89
|
+
"README.md": `# {{integrationPackageName}}
|
|
90
|
+
|
|
91
|
+
{{description}}
|
|
92
|
+
|
|
93
|
+
## Overview
|
|
94
|
+
|
|
95
|
+
This is a ContractSpec plugin that generates markdown documentation from ContractSpec specifications. It transforms structured spec definitions into human-readable documentation that can be used for API docs, user guides, or technical specifications.
|
|
96
|
+
|
|
97
|
+
## Features
|
|
98
|
+
|
|
99
|
+
- \uD83D\uDE80 **Spec-First Generation**: Converts ContractSpec specs to markdown automatically
|
|
100
|
+
- \uD83D\uDCDD **Rich Formatting**: Supports tables, lists, and detail views
|
|
101
|
+
- \uD83D\uDD27 **Configurable Output**: Customize formatting, field selection, and styling
|
|
102
|
+
- \uD83D\uDCCA **Data Integration**: Works with schema models and instance data
|
|
103
|
+
- \uD83C\uDFAF **Type Safe**: Full TypeScript support with proper type definitions
|
|
104
|
+
- \uD83E\uDDEA **Well Tested**: Comprehensive test suite included
|
|
105
|
+
|
|
106
|
+
## Installation
|
|
107
|
+
|
|
108
|
+
\`\`\`bash
|
|
109
|
+
npm install {{integrationPackageName}}
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
## Usage
|
|
113
|
+
|
|
114
|
+
### Basic Usage
|
|
115
|
+
|
|
116
|
+
\`\`\`typescript
|
|
117
|
+
import { {{className}} } from "{{integrationPackageName}}";
|
|
118
|
+
|
|
119
|
+
const generator = new {{className}}({
|
|
120
|
+
outputDir: "./docs",
|
|
121
|
+
format: "table", // or "list", "detail", "auto"
|
|
122
|
+
includeFields: ["id", "name", "description"],
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Generate markdown from specs
|
|
126
|
+
await generator.generateFromSpec(specPath, outputPath);
|
|
127
|
+
\`\`\`
|
|
128
|
+
|
|
129
|
+
### Advanced Configuration
|
|
130
|
+
|
|
131
|
+
\`\`\`typescript
|
|
132
|
+
import { {{className}} } from "{{integrationPackageName}}";
|
|
133
|
+
|
|
134
|
+
const generator = new {{className}}({
|
|
135
|
+
outputDir: "./docs",
|
|
136
|
+
format: "auto",
|
|
137
|
+
title: "API Documentation",
|
|
138
|
+
description: "Auto-generated API documentation",
|
|
139
|
+
maxItems: 100,
|
|
140
|
+
maxDepth: 3,
|
|
141
|
+
fieldLabels: {
|
|
142
|
+
id: "ID",
|
|
143
|
+
createdAt: "Created Date",
|
|
144
|
+
updatedAt: "Last Modified"
|
|
145
|
+
},
|
|
146
|
+
summaryFields: ["id", "name", "status"],
|
|
147
|
+
excludeFields: ["internalNotes", "metadata"]
|
|
148
|
+
});
|
|
149
|
+
\`\`\`
|
|
150
|
+
|
|
151
|
+
## Configuration Options
|
|
152
|
+
|
|
153
|
+
| Option | Type | Default | Description |
|
|
154
|
+
|--------|------|---------|-------------|
|
|
155
|
+
| \`outputDir\` | \`string\` | \`"./docs"\` | Directory for generated files |
|
|
156
|
+
| \`format\` | \`string\` | \`"auto"\` | Output format: \`"table"\`, \`"list"\`, \`"detail"\`, \`"auto"\` |
|
|
157
|
+
| \`title\` | \`string\` | undefined | Document title |
|
|
158
|
+
| \`description\` | \`string\` | undefined | Document description |
|
|
159
|
+
| \`maxItems\` | \`number\` | \`100\` | Maximum items to render in tables |
|
|
160
|
+
| \`maxDepth\` | \`number\` | \`2\` | Maximum nesting depth for objects |
|
|
161
|
+
| \`includeFields\` | \`string[]\` | undefined | Only include these fields |
|
|
162
|
+
| \`excludeFields\` | \`string[]\` | \`[]\` | Exclude these fields from output |
|
|
163
|
+
| \`fieldLabels\` | \`Record<string, string>\` | undefined | Custom field labels |
|
|
164
|
+
| \`summaryFields\` | \`string[]\` | undefined | Fields for list summaries |
|
|
165
|
+
|
|
166
|
+
## Plugin Interface
|
|
167
|
+
|
|
168
|
+
This plugin implements the ContractSpec generator interface:
|
|
169
|
+
|
|
170
|
+
\`\`\`typescript
|
|
171
|
+
interface GeneratorPlugin {
|
|
172
|
+
readonly id: string;
|
|
173
|
+
readonly name: string;
|
|
174
|
+
readonly version: string;
|
|
175
|
+
|
|
176
|
+
initialize(config: GeneratorConfig): Promise<void>;
|
|
177
|
+
generate(spec: SpecDefinition, context: GeneratorContext): Promise<GeneratorResult>;
|
|
178
|
+
cleanup(): Promise<void>;
|
|
179
|
+
}
|
|
180
|
+
\`\`\`
|
|
181
|
+
|
|
182
|
+
## Development
|
|
183
|
+
|
|
184
|
+
### Setup
|
|
185
|
+
|
|
186
|
+
\`\`\`bash
|
|
187
|
+
# Clone the repository
|
|
188
|
+
git clone https://github.com/lssm-tech/contractspec.git
|
|
189
|
+
cd contractspec/packages/libs/plugins/{{name}}
|
|
190
|
+
|
|
191
|
+
# Install dependencies
|
|
192
|
+
bun install
|
|
193
|
+
|
|
194
|
+
# Run tests
|
|
195
|
+
bun test
|
|
196
|
+
|
|
197
|
+
# Build the plugin
|
|
198
|
+
bun run build
|
|
199
|
+
\`\`\`
|
|
200
|
+
|
|
201
|
+
### Testing
|
|
202
|
+
|
|
203
|
+
\`\`\`bash
|
|
204
|
+
# Run all tests
|
|
205
|
+
bun test
|
|
206
|
+
|
|
207
|
+
# Run tests in watch mode
|
|
208
|
+
bun test:watch
|
|
209
|
+
|
|
210
|
+
# Run tests with coverage
|
|
211
|
+
bun test:coverage
|
|
212
|
+
\`\`\`
|
|
213
|
+
|
|
214
|
+
### Building
|
|
215
|
+
|
|
216
|
+
\`\`\`bash
|
|
217
|
+
# Build the plugin
|
|
218
|
+
bun run build
|
|
219
|
+
|
|
220
|
+
# Build types only
|
|
221
|
+
bun run build:types
|
|
222
|
+
|
|
223
|
+
# Build bundle only
|
|
224
|
+
bun run build:bundle
|
|
225
|
+
\`\`\`
|
|
226
|
+
|
|
227
|
+
## Contributing
|
|
228
|
+
|
|
229
|
+
We welcome contributions! Please see our [Contributing Guide](../../CONTRIBUTING.md) for details.
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
MIT \xA9 {{author}}
|
|
234
|
+
|
|
235
|
+
## Support
|
|
236
|
+
|
|
237
|
+
- \uD83D\uDCD6 [Documentation](https://contractspec.io/docs)
|
|
238
|
+
- \uD83D\uDC1B [Issues](https://github.com/lssm-tech/contractspec/issues)
|
|
239
|
+
- \uD83D\uDCAC [Discussions](https://github.com/lssm-tech/contractspec/discussions)`,
|
|
240
|
+
"src/index.ts": `/**
|
|
241
|
+
* {{integrationPackageName}}
|
|
242
|
+
* {{description}}
|
|
243
|
+
|
|
244
|
+
*/
|
|
245
|
+
|
|
246
|
+
export { {{className}} } from "./generator.js";
|
|
247
|
+
export type { {{className}}Config, GeneratorResult } from "./types.js";
|
|
248
|
+
export { defaultConfig } from "./config.js";`,
|
|
249
|
+
"src/types.ts": `import type { AnySchemaModel } from "@contractspec/lib.schema";
|
|
250
|
+
import type { SpecDefinition } from "@contractspec/lib.contracts";
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Configuration for the {{className}} plugin
|
|
254
|
+
*/
|
|
255
|
+
export interface {{className}}Config {
|
|
256
|
+
/** Directory where markdown files will be generated */
|
|
257
|
+
outputDir: string;
|
|
258
|
+
/** Output format: table, list, detail, or auto */
|
|
259
|
+
format?: "table" | "list" | "detail" | "auto";
|
|
260
|
+
/** Title for the generated documentation */
|
|
261
|
+
title?: string;
|
|
262
|
+
/** Description to include below the title */
|
|
263
|
+
description?: string;
|
|
264
|
+
/** Maximum number of items to render in tables */
|
|
265
|
+
maxItems?: number;
|
|
266
|
+
/** Maximum nesting depth for nested objects */
|
|
267
|
+
maxDepth?: number;
|
|
268
|
+
/** Only include these fields (if not specified, all fields are included) */
|
|
269
|
+
includeFields?: string[];
|
|
270
|
+
/** Exclude these fields from output */
|
|
271
|
+
excludeFields?: string[];
|
|
272
|
+
/** Custom field labels (field name -> display label) */
|
|
273
|
+
fieldLabels?: Record<string, string>;
|
|
274
|
+
/** Fields to use for summary in list format */
|
|
275
|
+
summaryFields?: string[];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Context provided during generation
|
|
280
|
+
*/
|
|
281
|
+
export interface GeneratorContext {
|
|
282
|
+
/** The spec definition being processed */
|
|
283
|
+
spec: SpecDefinition;
|
|
284
|
+
/** Schema models from the spec */
|
|
285
|
+
schemas: Record<string, AnySchemaModel>;
|
|
286
|
+
/** Instance data (optional) */
|
|
287
|
+
data?: unknown;
|
|
288
|
+
/** Additional metadata */
|
|
289
|
+
metadata?: Record<string, unknown>;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Result of generation
|
|
294
|
+
*/
|
|
295
|
+
export interface GeneratorResult {
|
|
296
|
+
/** Path to the generated file */
|
|
297
|
+
outputPath: string;
|
|
298
|
+
/** Number of items processed */
|
|
299
|
+
itemCount: number;
|
|
300
|
+
/** Generation metadata */
|
|
301
|
+
metadata: {
|
|
302
|
+
specId: string;
|
|
303
|
+
generatedAt: Date;
|
|
304
|
+
format: string;
|
|
305
|
+
config: Partial<{{className}}Config>;
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Plugin metadata
|
|
311
|
+
*/
|
|
312
|
+
export interface PluginMetadata {
|
|
313
|
+
readonly id: string;
|
|
314
|
+
readonly name: string;
|
|
315
|
+
readonly version: string;
|
|
316
|
+
readonly description: string;
|
|
317
|
+
readonly author: string;
|
|
318
|
+
readonly homepage?: string;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Error types for the plugin
|
|
323
|
+
*/
|
|
324
|
+
export class {{className}}Error extends Error {
|
|
325
|
+
constructor(
|
|
326
|
+
message: string,
|
|
327
|
+
public readonly code: string,
|
|
328
|
+
public readonly details?: unknown
|
|
329
|
+
) {
|
|
330
|
+
super(message);
|
|
331
|
+
this.name = "{{className}}Error";
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Validation errors
|
|
337
|
+
*/
|
|
338
|
+
export class ValidationError extends {{className}}Error {
|
|
339
|
+
constructor(message: string, details?: unknown) {
|
|
340
|
+
super(message, "VALIDATION_ERROR", details);
|
|
341
|
+
this.name = "ValidationError";
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Configuration errors
|
|
347
|
+
*/
|
|
348
|
+
export class ConfigurationError extends {{className}}Error {
|
|
349
|
+
constructor(message: string, details?: unknown) {
|
|
350
|
+
super(message, "CONFIGURATION_ERROR", details);
|
|
351
|
+
this.name = "ConfigurationError";
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Generation errors
|
|
357
|
+
*/
|
|
358
|
+
export class GenerationError extends {{className}}Error {
|
|
359
|
+
constructor(message: string, details?: unknown) {
|
|
360
|
+
super(message, "GENERATION_ERROR", details);
|
|
361
|
+
this.name = "GenerationError";
|
|
362
|
+
}
|
|
363
|
+
}`,
|
|
364
|
+
"src/config.ts": `import type { {{className}}Config } from "./types.js";
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Default configuration for the {{className}} plugin
|
|
368
|
+
*/
|
|
369
|
+
export const defaultConfig: {{className}}Config = {
|
|
370
|
+
outputDir: "./docs",
|
|
371
|
+
format: "auto",
|
|
372
|
+
maxItems: 100,
|
|
373
|
+
maxDepth: 2,
|
|
374
|
+
excludeFields: [],
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Merge user config with defaults
|
|
379
|
+
*/
|
|
380
|
+
export function mergeConfig(userConfig: Partial<{{className}}Config>): {{className}}Config {
|
|
381
|
+
return {
|
|
382
|
+
...defaultConfig,
|
|
383
|
+
...userConfig,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Validate configuration
|
|
389
|
+
*/
|
|
390
|
+
export function validateConfig(config: {{className}}Config): void {
|
|
391
|
+
if (!config.outputDir) {
|
|
392
|
+
throw new Error("outputDir is required");
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (config.format && !["table", "list", "detail", "auto"].includes(config.format)) {
|
|
396
|
+
throw new Error("format must be one of: table, list, detail, auto");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (config.maxItems !== undefined && config.maxItems < 1) {
|
|
400
|
+
throw new Error("maxItems must be greater than 0");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (config.maxDepth !== undefined && config.maxDepth < 1) {
|
|
404
|
+
throw new Error("maxDepth must be greater than 0");
|
|
405
|
+
}
|
|
406
|
+
}`,
|
|
407
|
+
"src/generator.ts": `import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
408
|
+
import { join, dirname } from "path";
|
|
409
|
+
import type {
|
|
410
|
+
{{className}}Config,
|
|
411
|
+
GeneratorContext,
|
|
412
|
+
GeneratorResult,
|
|
413
|
+
PluginMetadata,
|
|
414
|
+
ConfigurationError,
|
|
415
|
+
GenerationError
|
|
416
|
+
} from "./types.js";
|
|
417
|
+
import { schemaToMarkdown } from "@contractspec/lib.contracts";
|
|
418
|
+
import { validateConfig, mergeConfig } from "./config.js";
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* {{className}} - Markdown Documentation Generator
|
|
422
|
+
*
|
|
423
|
+
* Generates markdown documentation from ContractSpec specifications.
|
|
424
|
+
*/
|
|
425
|
+
export class {{className}} {
|
|
426
|
+
private readonly metadata: PluginMetadata;
|
|
427
|
+
private config: {{className}}Config;
|
|
428
|
+
|
|
429
|
+
constructor(config: Partial<{{className}}Config> = {}) {
|
|
430
|
+
this.metadata = {
|
|
431
|
+
id: "{{name}}",
|
|
432
|
+
name: "{{integrationPackageName}}",
|
|
433
|
+
version: "{{version}}",
|
|
434
|
+
description: "{{description}}",
|
|
435
|
+
author: "{{author}}",
|
|
436
|
+
homepage: "https://contractspec.io",
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
this.config = mergeConfig(config);
|
|
440
|
+
validateConfig(this.config);
|
|
441
|
+
|
|
442
|
+
// Ensure output directory exists
|
|
443
|
+
if (!existsSync(this.config.outputDir)) {
|
|
444
|
+
mkdirSync(this.config.outputDir, { recursive: true });
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Get plugin metadata
|
|
450
|
+
*/
|
|
451
|
+
getMetadata(): PluginMetadata {
|
|
452
|
+
return this.metadata;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Generate markdown documentation from a spec and optional data
|
|
457
|
+
*/
|
|
458
|
+
async generate(context: GeneratorContext): Promise<GeneratorResult> {
|
|
459
|
+
try {
|
|
460
|
+
const { spec, schemas, data } = context;
|
|
461
|
+
|
|
462
|
+
if (!spec) {
|
|
463
|
+
throw new GenerationError("Spec definition is required");
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Find the primary schema (first one by convention)
|
|
467
|
+
const schemaNames = Object.keys(schemas);
|
|
468
|
+
if (schemaNames.length === 0) {
|
|
469
|
+
throw new GenerationError("No schemas found in spec");
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const primarySchemaName = schemaNames[0];
|
|
473
|
+
const primarySchema = schemas[primarySchemaName];
|
|
474
|
+
|
|
475
|
+
// Generate markdown
|
|
476
|
+
const markdown = schemaToMarkdown(primarySchema, data, {
|
|
477
|
+
title: this.config.title,
|
|
478
|
+
description: this.config.description,
|
|
479
|
+
format: this.config.format,
|
|
480
|
+
maxItems: this.config.maxItems,
|
|
481
|
+
maxDepth: this.config.maxDepth,
|
|
482
|
+
includeFields: this.config.includeFields,
|
|
483
|
+
excludeFields: this.config.excludeFields,
|
|
484
|
+
fieldLabels: this.config.fieldLabels,
|
|
485
|
+
summaryFields: this.config.summaryFields,
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// Determine output file path
|
|
489
|
+
const fileName = this.generateFileName(spec.id || primarySchemaName);
|
|
490
|
+
const outputPath = join(this.config.outputDir, fileName);
|
|
491
|
+
|
|
492
|
+
// Write markdown file
|
|
493
|
+
writeFileSync(outputPath, markdown, "utf8");
|
|
494
|
+
|
|
495
|
+
const itemCount = Array.isArray(data) ? data.length : data ? 1 : 0;
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
outputPath,
|
|
499
|
+
itemCount,
|
|
500
|
+
metadata: {
|
|
501
|
+
specId: spec.id || "unknown",
|
|
502
|
+
generatedAt: new Date(),
|
|
503
|
+
format: this.config.format || "auto",
|
|
504
|
+
config: {
|
|
505
|
+
format: this.config.format,
|
|
506
|
+
maxItems: this.config.maxItems,
|
|
507
|
+
maxDepth: this.config.maxDepth,
|
|
508
|
+
},
|
|
509
|
+
},
|
|
510
|
+
};
|
|
511
|
+
} catch (error) {
|
|
512
|
+
if (error instanceof GenerationError) {
|
|
513
|
+
throw error;
|
|
514
|
+
}
|
|
515
|
+
throw new GenerationError("Failed to generate documentation", error);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Generate documentation from a spec file path
|
|
521
|
+
*/
|
|
522
|
+
async generateFromSpec(specPath: string, outputPath?: string): Promise<GeneratorResult> {
|
|
523
|
+
try {
|
|
524
|
+
// This would typically load the spec from file
|
|
525
|
+
// For now, we'll throw an error indicating this needs implementation
|
|
526
|
+
throw new Error("generateFromSpec needs to be implemented with spec loading logic");
|
|
527
|
+
} catch (error) {
|
|
528
|
+
if (error instanceof GenerationError) {
|
|
529
|
+
throw error;
|
|
530
|
+
}
|
|
531
|
+
throw new GenerationError("Failed to generate from spec file", error);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Generate appropriate filename for the output
|
|
537
|
+
*/
|
|
538
|
+
private generateFileName(specId: string): string {
|
|
539
|
+
// Convert spec ID to a filename-friendly format
|
|
540
|
+
const fileName = specId
|
|
541
|
+
.replace(/[^a-zA-Z0-9-_]/g, "-")
|
|
542
|
+
.replace(/-+/g, "-")
|
|
543
|
+
.toLowerCase();
|
|
544
|
+
|
|
545
|
+
return fileName.endsWith(".md")
|
|
546
|
+
? fileName
|
|
547
|
+
: fileName + ".md";
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Update configuration
|
|
552
|
+
*/
|
|
553
|
+
updateConfig(newConfig: Partial<{{className}}Config>): void {
|
|
554
|
+
this.config = mergeConfig({ ...this.config, ...newConfig });
|
|
555
|
+
validateConfig(this.config);
|
|
556
|
+
|
|
557
|
+
// Ensure output directory exists if it changed
|
|
558
|
+
if (!existsSync(this.config.outputDir)) {
|
|
559
|
+
mkdirSync(this.config.outputDir, { recursive: true });
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Get current configuration
|
|
565
|
+
*/
|
|
566
|
+
getConfig(): {{className}}Config {
|
|
567
|
+
return { ...this.config };
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Cleanup resources (no-op for this plugin)
|
|
572
|
+
*/
|
|
573
|
+
async cleanup(): Promise<void> {
|
|
574
|
+
// No resources to cleanup for this plugin
|
|
575
|
+
}
|
|
576
|
+
}`,
|
|
577
|
+
"src/utils/test-utils.ts": `
|
|
578
|
+
|
|
579
|
+
import type { AnySchemaModel } from "@contractspec/lib.schema";
|
|
580
|
+
import type { {{className}}Config } from "../types.js";
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Create a mock schema for testing
|
|
584
|
+
*/
|
|
585
|
+
export function createMockSchema(overrides: Partial<AnySchemaModel> = {}): AnySchemaModel {
|
|
586
|
+
return {
|
|
587
|
+
config: {
|
|
588
|
+
fields: {
|
|
589
|
+
id: { type: "string", isOptional: false },
|
|
590
|
+
name: { type: "string", isOptional: false },
|
|
591
|
+
description: { type: "string", isOptional: true },
|
|
592
|
+
status: { type: "string", isOptional: true },
|
|
593
|
+
createdAt: { type: "date", isOptional: false },
|
|
594
|
+
updatedAt: { type: "date", isOptional: true },
|
|
595
|
+
},
|
|
596
|
+
...overrides.config,
|
|
597
|
+
},
|
|
598
|
+
...overrides,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Create mock data for testing
|
|
604
|
+
*/
|
|
605
|
+
export function createMockData(count: number = 3) {
|
|
606
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
607
|
+
id: "item-" + (i + 1),
|
|
608
|
+
name: "Test Item " + (i + 1),
|
|
609
|
+
description: "Description for test item " + (i + 1),
|
|
610
|
+
status: i % 2 === 0 ? "active" : "inactive",
|
|
611
|
+
createdAt: new Date(2024, 0, i + 1),
|
|
612
|
+
updatedAt: i % 3 === 0 ? undefined : new Date(2024, 0, i + 2),
|
|
613
|
+
}));
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Create test configuration
|
|
619
|
+
*/
|
|
620
|
+
export function createTestConfig(overrides: Partial<Config> = {}): {{className}}Config {
|
|
621
|
+
return {
|
|
622
|
+
outputDir: "./test-docs",
|
|
623
|
+
format: "table",
|
|
624
|
+
maxItems: 10,
|
|
625
|
+
maxDepth: 2,
|
|
626
|
+
excludeFields: [],
|
|
627
|
+
...overrides,
|
|
628
|
+
};
|
|
629
|
+
}`,
|
|
630
|
+
"tests/generator.test.ts": `import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
631
|
+
import { existsSync, unlinkSync, readFileSync } from "fs";
|
|
632
|
+
import { join } from "path";
|
|
633
|
+
import { {{className}}, ConfigurationError, GenerationError } from "../src/generator.js";
|
|
634
|
+
import type { {{className}}Config } from "../src/types.js";
|
|
635
|
+
import { createMockSchema, createMockData, createTestConfig } from "../src/utils/test-utils.js";
|
|
636
|
+
|
|
637
|
+
describe("{{className}}", () => {
|
|
638
|
+
let generator: {{className}};
|
|
639
|
+
let testConfig: {{className}}Config;
|
|
640
|
+
const testOutputDir = "./test-output";
|
|
641
|
+
|
|
642
|
+
beforeEach(() => {
|
|
643
|
+
testConfig = createTestConfig({ outputDir: testOutputDir });
|
|
644
|
+
generator = new {{className}}(testConfig);
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
afterEach(() => {
|
|
648
|
+
// Clean up test files
|
|
649
|
+
if (existsSync(testOutputDir)) {
|
|
650
|
+
// In a real implementation, you'd recursively delete the directory
|
|
651
|
+
console.log("Cleaning up test directory: " + testOutputDir);
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
describe("constructor", () => {
|
|
656
|
+
it("should create a generator with default config", () => {
|
|
657
|
+
const gen = new {{className}}();
|
|
658
|
+
const config = gen.getConfig();
|
|
659
|
+
|
|
660
|
+
expect(config.outputDir).toBe("./docs");
|
|
661
|
+
expect(config.format).toBe("auto");
|
|
662
|
+
expect(config.maxItems).toBe(100);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it("should create a generator with custom config", () => {
|
|
666
|
+
const customConfig = {
|
|
667
|
+
outputDir: "./custom-docs",
|
|
668
|
+
format: "table" as const,
|
|
669
|
+
maxItems: 50,
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
const gen = new {{className}}(customConfig);
|
|
673
|
+
const config = gen.getConfig();
|
|
674
|
+
|
|
675
|
+
expect(config.outputDir).toBe("./custom-docs");
|
|
676
|
+
expect(config.format).toBe("table");
|
|
677
|
+
expect(config.maxItems).toBe(50);
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
it("should throw ConfigurationError for invalid config", () => {
|
|
681
|
+
expect(() => {
|
|
682
|
+
new {{className}}({ outputDir: "" });
|
|
683
|
+
}).toThrow(ConfigurationError);
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
describe("getMetadata", () => {
|
|
688
|
+
it("should return correct plugin metadata", () => {
|
|
689
|
+
const metadata = generator.getMetadata();
|
|
690
|
+
|
|
691
|
+
expect(metadata.id).toBe("{{name}}");
|
|
692
|
+
expect(metadata.name).toBe("{{integrationPackageName}}");
|
|
693
|
+
expect(metadata.description).toBe("{{description}}");
|
|
694
|
+
expect(metadata.author).toBe("{{author}}");
|
|
695
|
+
});
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
describe("generate", () => {
|
|
699
|
+
it("should generate markdown from schema and data", async () => {
|
|
700
|
+
const schema = createMockSchema();
|
|
701
|
+
const data = createMockData(3);
|
|
702
|
+
|
|
703
|
+
const result = await generator.generate({
|
|
704
|
+
spec: { id: "test-spec" } as any,
|
|
705
|
+
schemas: { TestItem: schema },
|
|
706
|
+
data,
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
expect(result.outputPath).toContain(testOutputDir);
|
|
710
|
+
expect(result.itemCount).toBe(3);
|
|
711
|
+
expect(existsSync(result.outputPath)).toBe(true);
|
|
712
|
+
|
|
713
|
+
// Check generated content
|
|
714
|
+
const content = readFileSync(result.outputPath, "utf8");
|
|
715
|
+
expect(content).toContain("item-1");
|
|
716
|
+
expect(content).toContain("Test Item 1");
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
it("should handle empty data", async () => {
|
|
720
|
+
const schema = createMockSchema();
|
|
721
|
+
|
|
722
|
+
const result = await generator.generate({
|
|
723
|
+
spec: { id: "test-spec" } as any,
|
|
724
|
+
schemas: { TestItem: schema },
|
|
725
|
+
data: [],
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
expect(result.itemCount).toBe(0);
|
|
729
|
+
expect(existsSync(result.outputPath)).toBe(true);
|
|
730
|
+
});
|
|
731
|
+
|
|
732
|
+
it("should throw GenerationError for missing spec", async () => {
|
|
733
|
+
await expect(
|
|
734
|
+
generator.generate({
|
|
735
|
+
spec: null as any,
|
|
736
|
+
schemas: {},
|
|
737
|
+
data: [],
|
|
738
|
+
})
|
|
739
|
+
).rejects.toThrow(GenerationError);
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
it("should throw GenerationError for missing schemas", async () => {
|
|
743
|
+
await expect(
|
|
744
|
+
generator.generate({
|
|
745
|
+
spec: { id: "test-spec" } as any,
|
|
746
|
+
schemas: {},
|
|
747
|
+
data: [],
|
|
748
|
+
})
|
|
749
|
+
).rejects.toThrow(GenerationError);
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
describe("updateConfig", () => {
|
|
754
|
+
it("should update configuration", () => {
|
|
755
|
+
generator.updateConfig({ maxItems: 25 });
|
|
756
|
+
|
|
757
|
+
const config = generator.getConfig();
|
|
758
|
+
expect(config.maxItems).toBe(25);
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
it("should throw ConfigurationError for invalid update", () => {
|
|
762
|
+
expect(() => {
|
|
763
|
+
generator.updateConfig({ format: "invalid" as any });
|
|
764
|
+
}).toThrow(ConfigurationError);
|
|
765
|
+
});
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
describe("cleanup", () => {
|
|
769
|
+
it("should cleanup successfully", async () => {
|
|
770
|
+
await expect(generator.cleanup()).resolves.not.toThrow();
|
|
771
|
+
});
|
|
772
|
+
});
|
|
773
|
+
});`,
|
|
774
|
+
"tests/utils.test.ts": `import { describe, it, expect } from "bun:test";
|
|
775
|
+
import { createMockSchema, createMockData, createTestConfig } from "../src/utils/test-utils.js";
|
|
776
|
+
import type { {{className}}Config } from "../src/types.js";
|
|
777
|
+
|
|
778
|
+
describe("Test Utils", () => {
|
|
779
|
+
describe("createMockSchema", () => {
|
|
780
|
+
it("should create a mock schema with default fields", () => {
|
|
781
|
+
const schema = createMockSchema();
|
|
782
|
+
|
|
783
|
+
expect(schema.config.fields).toBeDefined();
|
|
784
|
+
expect(schema.config.fields.id).toBeDefined();
|
|
785
|
+
expect(schema.config.fields.name).toBeDefined();
|
|
786
|
+
expect(schema.config.fields.description).toBeDefined();
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
it("should apply overrides", () => {
|
|
790
|
+
const overrides = {
|
|
791
|
+
config: {
|
|
792
|
+
fields: {
|
|
793
|
+
customField: { type: "number", isOptional: false },
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
const schema = createMockSchema(overrides);
|
|
799
|
+
|
|
800
|
+
expect(schema.config.fields.customField).toBeDefined();
|
|
801
|
+
expect(schema.config.fields.customField.type).toBe("number");
|
|
802
|
+
});
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
describe("createMockData", () => {
|
|
806
|
+
it("should create mock data with specified count", () => {
|
|
807
|
+
const data = createMockData(5);
|
|
808
|
+
|
|
809
|
+
expect(data).toHaveLength(5);
|
|
810
|
+
expect(data[0].id).toBe("item-1");
|
|
811
|
+
expect(data[4].id).toBe("item-5");
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
it("should create default count when not specified", () => {
|
|
815
|
+
const data = createMockData();
|
|
816
|
+
|
|
817
|
+
expect(data).toHaveLength(3);
|
|
818
|
+
});
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
describe("createTestConfig", () => {
|
|
822
|
+
it("should create test config with defaults", () => {
|
|
823
|
+
const config = createTestConfig();
|
|
824
|
+
|
|
825
|
+
expect(config.outputDir).toBe("./test-docs");
|
|
826
|
+
expect(config.format).toBe("table");
|
|
827
|
+
expect(config.maxItems).toBe(10);
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
it("should apply overrides", () => {
|
|
831
|
+
const overrides: Partial<{{className}}Config> = {
|
|
832
|
+
format: "list",
|
|
833
|
+
maxItems: 20,
|
|
834
|
+
};
|
|
835
|
+
|
|
836
|
+
const config = createTestConfig(overrides);
|
|
837
|
+
|
|
838
|
+
expect(config.format).toBe("list");
|
|
839
|
+
expect(config.maxItems).toBe(20);
|
|
840
|
+
});
|
|
841
|
+
});
|
|
842
|
+
});`,
|
|
843
|
+
".github/workflows/ci.yml": `name: CI
|
|
844
|
+
|
|
845
|
+
on:
|
|
846
|
+
push:
|
|
847
|
+
branches: [ main, develop ]
|
|
848
|
+
pull_request:
|
|
849
|
+
branches: [ main ]
|
|
850
|
+
|
|
851
|
+
jobs:
|
|
852
|
+
smoke-test:
|
|
853
|
+
runs-on: ubuntu-latest
|
|
854
|
+
steps:
|
|
855
|
+
- uses: actions/checkout@v4
|
|
856
|
+
|
|
857
|
+
- name: Setup Bun
|
|
858
|
+
uses: oven-sh/setup-bun@v1
|
|
859
|
+
with:
|
|
860
|
+
bun-version: latest
|
|
861
|
+
|
|
862
|
+
- name: Install dependencies
|
|
863
|
+
run: bun install
|
|
864
|
+
|
|
865
|
+
- name: Run smoke test
|
|
866
|
+
run: bun run test:smoke
|
|
867
|
+
|
|
868
|
+
test:
|
|
869
|
+
runs-on: ubuntu-latest
|
|
870
|
+
|
|
871
|
+
strategy:
|
|
872
|
+
matrix:
|
|
873
|
+
node-version: [18, 20]
|
|
874
|
+
|
|
875
|
+
steps:
|
|
876
|
+
- uses: actions/checkout@v4
|
|
877
|
+
|
|
878
|
+
- name: Setup Bun
|
|
879
|
+
uses: oven-sh/setup-bun@v1
|
|
880
|
+
with:
|
|
881
|
+
bun-version: latest
|
|
882
|
+
|
|
883
|
+
- name: Install dependencies
|
|
884
|
+
run: bun install
|
|
885
|
+
|
|
886
|
+
- name: Run tests
|
|
887
|
+
run: bun test
|
|
888
|
+
|
|
889
|
+
- name: Run tests with coverage
|
|
890
|
+
run: bun test --coverage
|
|
891
|
+
|
|
892
|
+
- name: Build
|
|
893
|
+
run: bun run build
|
|
894
|
+
|
|
895
|
+
- name: Lint
|
|
896
|
+
run: bun run lint:check
|
|
897
|
+
|
|
898
|
+
publish:
|
|
899
|
+
needs: test
|
|
900
|
+
runs-on: ubuntu-latest
|
|
901
|
+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
902
|
+
|
|
903
|
+
steps:
|
|
904
|
+
- uses: actions/checkout@v4
|
|
905
|
+
|
|
906
|
+
- name: Setup Bun
|
|
907
|
+
uses: oven-sh/setup-bun@v1
|
|
908
|
+
with:
|
|
909
|
+
bun-version: latest
|
|
910
|
+
|
|
911
|
+
- name: Install dependencies
|
|
912
|
+
run: bun install
|
|
913
|
+
|
|
914
|
+
- name: Build
|
|
915
|
+
run: bun run build
|
|
916
|
+
|
|
917
|
+
- name: Publish to NPM
|
|
918
|
+
run: bun run publish:pkg
|
|
919
|
+
env:
|
|
920
|
+
NPM_TOKEN: \${{ secrets.NPM_TOKEN }}`,
|
|
921
|
+
"tests/smoke.test.ts": `import { describe, it, expect, beforeAll, afterAll } from "bun:test";
|
|
922
|
+
import { existsSync, mkdirSync, rmSync } from "fs";
|
|
923
|
+
import { {{className}} } from "../src/generator.js";
|
|
924
|
+
|
|
925
|
+
const SMOKE_OUTPUT_DIR = "./.smoke-test-output";
|
|
926
|
+
|
|
927
|
+
describe("{{className}} Smoke Test", () => {
|
|
928
|
+
beforeAll(() => {
|
|
929
|
+
if (!existsSync(SMOKE_OUTPUT_DIR)) {
|
|
930
|
+
mkdirSync(SMOKE_OUTPUT_DIR, { recursive: true });
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
afterAll(() => {
|
|
935
|
+
if (existsSync(SMOKE_OUTPUT_DIR)) {
|
|
936
|
+
rmSync(SMOKE_OUTPUT_DIR, { recursive: true, force: true });
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
it("should instantiate without errors", () => {
|
|
941
|
+
const generator = new {{className}}({ outputDir: SMOKE_OUTPUT_DIR });
|
|
942
|
+
expect(generator).toBeDefined();
|
|
943
|
+
expect(typeof generator.generate).toBe("function");
|
|
944
|
+
expect(typeof generator.getMetadata).toBe("function");
|
|
945
|
+
expect(typeof generator.getConfig).toBe("function");
|
|
946
|
+
expect(typeof generator.cleanup).toBe("function");
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
it("should generate output file", async () => {
|
|
950
|
+
const generator = new {{className}}({ outputDir: SMOKE_OUTPUT_DIR });
|
|
951
|
+
|
|
952
|
+
const result = await generator.generate({
|
|
953
|
+
spec: { id: "smoke-test-spec" } as any,
|
|
954
|
+
schemas: {
|
|
955
|
+
TestEntity: {
|
|
956
|
+
config: {
|
|
957
|
+
fields: {
|
|
958
|
+
id: { type: "string", isOptional: false },
|
|
959
|
+
name: { type: "string", isOptional: false },
|
|
960
|
+
},
|
|
961
|
+
},
|
|
962
|
+
},
|
|
963
|
+
},
|
|
964
|
+
data: [{ id: "test-1", name: "Smoke Test Entity" }],
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
expect(result.outputPath).toBeDefined();
|
|
968
|
+
expect(result.itemCount).toBe(1);
|
|
969
|
+
expect(existsSync(result.outputPath)).toBe(true);
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
it("should handle config updates", () => {
|
|
973
|
+
const generator = new {{className}}();
|
|
974
|
+
generator.updateConfig({ format: "list", maxItems: 50 });
|
|
975
|
+
const config = generator.getConfig();
|
|
976
|
+
expect(config.format).toBe("list");
|
|
977
|
+
expect(config.maxItems).toBe(50);
|
|
978
|
+
});
|
|
979
|
+
|
|
980
|
+
it("should provide valid metadata", () => {
|
|
981
|
+
const generator = new {{className}}();
|
|
982
|
+
const metadata = generator.getMetadata();
|
|
983
|
+
expect(metadata.id).toBeDefined();
|
|
984
|
+
expect(metadata.name).toBeDefined();
|
|
985
|
+
expect(metadata.version).toBeDefined();
|
|
986
|
+
expect(typeof metadata.id).toBe("string");
|
|
987
|
+
expect(typeof metadata.name).toBe("string");
|
|
988
|
+
expect(typeof metadata.version).toBe("string");
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
it("should cleanup without errors", async () => {
|
|
992
|
+
const generator = new {{className}}({ outputDir: SMOKE_OUTPUT_DIR });
|
|
993
|
+
await expect(generator.cleanup()).resolves.not.toThrow();
|
|
994
|
+
});
|
|
995
|
+
});`,
|
|
996
|
+
".eslintrc.json": `{
|
|
997
|
+
"extends": [
|
|
998
|
+
"@contractspec/eslint-config-typescript"
|
|
999
|
+
],
|
|
1000
|
+
"parser": "@typescript-eslint/parser",
|
|
1001
|
+
"plugins": ["@typescript-eslint"],
|
|
1002
|
+
"rules": {
|
|
1003
|
+
"@typescript-eslint/no-unused-vars": "error",
|
|
1004
|
+
"@typescript-eslint/explicit-function-return-type": "warn",
|
|
1005
|
+
"@typescript-eslint/no-explicit-any": "warn"
|
|
1006
|
+
}
|
|
1007
|
+
}`,
|
|
1008
|
+
"tsconfig.json": `{
|
|
1009
|
+
"extends": "@contractspec/tsconfig-base",
|
|
1010
|
+
"compilerOptions": {
|
|
1011
|
+
"outDir": "./dist",
|
|
1012
|
+
"rootDir": "./src",
|
|
1013
|
+
"declaration": true,
|
|
1014
|
+
"declarationMap": true,
|
|
1015
|
+
"sourceMap": true
|
|
1016
|
+
},
|
|
1017
|
+
"include": [
|
|
1018
|
+
"src/**/*"
|
|
1019
|
+
],
|
|
1020
|
+
"exclude": [
|
|
1021
|
+
"node_modules",
|
|
1022
|
+
"dist",
|
|
1023
|
+
"tests"
|
|
1024
|
+
]
|
|
1025
|
+
}`,
|
|
1026
|
+
"tsdown.config.js": `import { defineConfig, nodeLib } from "@contractspec/tool.bun";
|
|
1027
|
+
|
|
1028
|
+
export default defineConfig(() => ({
|
|
1029
|
+
...nodeLib,
|
|
1030
|
+
entry: ["src/index.ts"],
|
|
1031
|
+
}));`,
|
|
1032
|
+
LICENSE: `MIT License
|
|
1033
|
+
|
|
1034
|
+
Copyright (c) {{currentYear}} {{author}}
|
|
1035
|
+
|
|
1036
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1037
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
1038
|
+
in the Software without restriction, including without limitation the rights
|
|
1039
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
1040
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
1041
|
+
furnished to do so, subject to the following conditions:
|
|
1042
|
+
|
|
1043
|
+
The above copyright notice and this permission notice shall be included in all
|
|
1044
|
+
copies or substantial portions of the Software.
|
|
1045
|
+
|
|
1046
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
1047
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1048
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
1049
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
1050
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
1051
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
1052
|
+
SOFTWARE.`,
|
|
1053
|
+
"src/templates/index.ts": `/**
|
|
1054
|
+
* Template registry for the create-contractspec-plugin tool
|
|
1055
|
+
*/
|
|
1056
|
+
|
|
1057
|
+
export { createExampleGeneratorTemplate } from "./example-generator.js";
|
|
1058
|
+
export type { Template, TemplateFile } from "./types.js";`,
|
|
1059
|
+
"src/templates/types.ts": `/**
|
|
1060
|
+
* Template types
|
|
1061
|
+
*/
|
|
1062
|
+
|
|
1063
|
+
export interface TemplateFile {
|
|
1064
|
+
content: string;
|
|
1065
|
+
path: string;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
export interface Template {
|
|
1069
|
+
name: string;
|
|
1070
|
+
description: string;
|
|
1071
|
+
files: Record<string, string>;
|
|
1072
|
+
dependencies?: string[];
|
|
1073
|
+
devDependencies?: string[];
|
|
1074
|
+
}
|
|
1075
|
+
`
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
export {
|
|
1080
|
+
createExampleGeneratorTemplate
|
|
1081
|
+
};
|