@contractspec/tool.create-contractspec-plugin 1.57.0 → 1.58.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.
@@ -1,26 +1,29 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from "commander";
3
- import { input, select } from "@inquirer/prompts";
4
- import chalk from "chalk";
5
- import { execSync } from "child_process";
6
- import { existsSync, mkdirSync, writeFileSync } from "fs";
7
- import { dirname, join } from "path";
8
- import * as mustache from "mustache";
9
2
 
10
- //#region src/utils.ts
3
+ // src/utils.ts
4
+ import * as mustache from "mustache";
11
5
  function renderTemplate(template, data) {
12
- return mustache.render(template, data);
6
+ return mustache.render(template, data);
7
+ }
8
+ function formatDate(date = new Date) {
9
+ return date.toISOString().split("T")[0] ?? "";
10
+ }
11
+ function capitalize(str) {
12
+ return str.charAt(0).toUpperCase() + str.slice(1);
13
+ }
14
+ function kebabToPascal(str) {
15
+ return str.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
16
+ }
17
+ function kebabToCamel(str) {
18
+ const pascal = kebabToPascal(str);
19
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
13
20
  }
14
21
 
15
- //#endregion
16
- //#region src/templates/example-generator.ts
17
- /**
18
- * Template for the example-generator plugin
19
- * Creates a markdown documentation generator from ContractSpec specs
20
- */
22
+ // src/templates/example-generator.ts
21
23
  function createExampleGeneratorTemplate() {
22
- return { files: {
23
- "package.json": `{
24
+ return {
25
+ files: {
26
+ "package.json": `{
24
27
  "name": "{{integrationPackageName}}",
25
28
  "version": "{{version}}",
26
29
  "description": "{{description}}",
@@ -50,10 +53,12 @@ function createExampleGeneratorTemplate() {
50
53
  "scripts": {
51
54
  "publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
52
55
  "publish:pkg:canary": "bun publish:pkg --tag canary",
53
- "build": "bun build:types && bun build:bundle",
54
- "build:bundle": "tsdown",
55
- "build:types": "tsc --noEmit",
56
- "dev": "bun build:bundle --watch",
56
+ "prebuild": "contractspec-bun-build prebuild",
57
+ "build": "bun run prebuild && bun run build:bundle && bun run build:types",
58
+ "build:bundle": "contractspec-bun-build transpile",
59
+ "build:types": "contractspec-bun-build types",
60
+ "typecheck": "tsc --noEmit",
61
+ "dev": "contractspec-bun-build dev",
57
62
  "clean": "rimraf dist .turbo",
58
63
  "lint": "bun lint:fix",
59
64
  "lint:fix": "eslint src --fix",
@@ -69,9 +74,8 @@ function createExampleGeneratorTemplate() {
69
74
  "zod": "catalog:"
70
75
  },
71
76
  "devDependencies": {
72
- "@contractspec/tool.tsdown": "workspace:*",
77
+ "@contractspec/tool.bun": "workspace:*",
73
78
  "@contractspec/tool.typescript": "workspace:*",
74
- "tsdown": "catalog:build",
75
79
  "typescript": "catalog:",
76
80
  "@types/node": "^22.0.0",
77
81
  "rimraf": "^6.0.1"
@@ -102,7 +106,7 @@ function createExampleGeneratorTemplate() {
102
106
  "bun": ">=1.0.0"
103
107
  }
104
108
  }`,
105
- "README.md": `# {{integrationPackageName}}
109
+ "README.md": `# {{integrationPackageName}}
106
110
 
107
111
  {{description}}
108
112
 
@@ -112,12 +116,12 @@ This is a ContractSpec plugin that generates markdown documentation from Contrac
112
116
 
113
117
  ## Features
114
118
 
115
- - 🚀 **Spec-First Generation**: Converts ContractSpec specs to markdown automatically
116
- - 📝 **Rich Formatting**: Supports tables, lists, and detail views
117
- - 🔧 **Configurable Output**: Customize formatting, field selection, and styling
118
- - 📊 **Data Integration**: Works with schema models and instance data
119
- - 🎯 **Type Safe**: Full TypeScript support with proper type definitions
120
- - 🧪 **Well Tested**: Comprehensive test suite included
119
+ - \uD83D\uDE80 **Spec-First Generation**: Converts ContractSpec specs to markdown automatically
120
+ - \uD83D\uDCDD **Rich Formatting**: Supports tables, lists, and detail views
121
+ - \uD83D\uDD27 **Configurable Output**: Customize formatting, field selection, and styling
122
+ - \uD83D\uDCCA **Data Integration**: Works with schema models and instance data
123
+ - \uD83C\uDFAF **Type Safe**: Full TypeScript support with proper type definitions
124
+ - \uD83E\uDDEA **Well Tested**: Comprehensive test suite included
121
125
 
122
126
  ## Installation
123
127
 
@@ -250,10 +254,10 @@ MIT © {{author}}
250
254
 
251
255
  ## Support
252
256
 
253
- - 📖 [Documentation](https://contractspec.io/docs)
254
- - 🐛 [Issues](https://github.com/lssm-tech/contractspec/issues)
255
- - 💬 [Discussions](https://github.com/lssm-tech/contractspec/discussions)`,
256
- "src/index.ts": `/**
257
+ - \uD83D\uDCD6 [Documentation](https://contractspec.io/docs)
258
+ - \uD83D\uDC1B [Issues](https://github.com/lssm-tech/contractspec/issues)
259
+ - \uD83D\uDCAC [Discussions](https://github.com/lssm-tech/contractspec/discussions)`,
260
+ "src/index.ts": `/**
257
261
  * {{integrationPackageName}}
258
262
  * {{description}}
259
263
 
@@ -262,7 +266,7 @@ MIT © {{author}}
262
266
  export { {{className}} } from "./generator.js";
263
267
  export type { {{className}}Config, GeneratorResult } from "./types.js";
264
268
  export { defaultConfig } from "./config.js";`,
265
- "src/types.ts": `import type { AnySchemaModel } from "@contractspec/lib.schema";
269
+ "src/types.ts": `import type { AnySchemaModel } from "@contractspec/lib.schema";
266
270
  import type { SpecDefinition } from "@contractspec/lib.contracts";
267
271
 
268
272
  /**
@@ -377,7 +381,7 @@ export class GenerationError extends {{className}}Error {
377
381
  this.name = "GenerationError";
378
382
  }
379
383
  }`,
380
- "src/config.ts": `import type { {{className}}Config } from "./types.js";
384
+ "src/config.ts": `import type { {{className}}Config } from "./types.js";
381
385
 
382
386
  /**
383
387
  * Default configuration for the {{className}} plugin
@@ -420,7 +424,7 @@ export function validateConfig(config: {{className}}Config): void {
420
424
  throw new Error("maxDepth must be greater than 0");
421
425
  }
422
426
  }`,
423
- "src/generator.ts": `import { existsSync, mkdirSync, writeFileSync } from "fs";
427
+ "src/generator.ts": `import { existsSync, mkdirSync, writeFileSync } from "fs";
424
428
  import { join, dirname } from "path";
425
429
  import type {
426
430
  {{className}}Config,
@@ -590,7 +594,7 @@ export class {{className}} {
590
594
  // No resources to cleanup for this plugin
591
595
  }
592
596
  }`,
593
- "src/utils/test-utils.ts": `
597
+ "src/utils/test-utils.ts": `
594
598
 
595
599
  import type { AnySchemaModel } from "@contractspec/lib.schema";
596
600
  import type { {{className}}Config } from "../types.js";
@@ -643,7 +647,7 @@ export function createTestConfig(overrides: Partial<Config> = {}): {{className}}
643
647
  ...overrides,
644
648
  };
645
649
  }`,
646
- "tests/generator.test.ts": `import { describe, it, expect, beforeEach, afterEach } from "bun:test";
650
+ "tests/generator.test.ts": `import { describe, it, expect, beforeEach, afterEach } from "bun:test";
647
651
  import { existsSync, unlinkSync, readFileSync } from "fs";
648
652
  import { join } from "path";
649
653
  import { {{className}}, ConfigurationError, GenerationError } from "../src/generator.js";
@@ -787,7 +791,7 @@ describe("{{className}}", () => {
787
791
  });
788
792
  });
789
793
  });`,
790
- "tests/utils.test.ts": `import { describe, it, expect } from "bun:test";
794
+ "tests/utils.test.ts": `import { describe, it, expect } from "bun:test";
791
795
  import { createMockSchema, createMockData, createTestConfig } from "../src/utils/test-utils.js";
792
796
  import type { {{className}}Config } from "../src/types.js";
793
797
 
@@ -856,7 +860,7 @@ describe("Test Utils", () => {
856
860
  });
857
861
  });
858
862
  });`,
859
- ".github/workflows/ci.yml": `name: CI
863
+ ".github/workflows/ci.yml": `name: CI
860
864
 
861
865
  on:
862
866
  push:
@@ -934,7 +938,7 @@ jobs:
934
938
  run: bun run publish:pkg
935
939
  env:
936
940
  NPM_TOKEN: \${{ secrets.NPM_TOKEN }}`,
937
- "tests/smoke.test.ts": `import { describe, it, expect, beforeAll, afterAll } from "bun:test";
941
+ "tests/smoke.test.ts": `import { describe, it, expect, beforeAll, afterAll } from "bun:test";
938
942
  import { existsSync, mkdirSync, rmSync } from "fs";
939
943
  import { {{className}} } from "../src/generator.js";
940
944
 
@@ -1009,7 +1013,7 @@ describe("{{className}} Smoke Test", () => {
1009
1013
  await expect(generator.cleanup()).resolves.not.toThrow();
1010
1014
  });
1011
1015
  });`,
1012
- ".eslintrc.json": `{
1016
+ ".eslintrc.json": `{
1013
1017
  "extends": [
1014
1018
  "@contractspec/eslint-config-typescript"
1015
1019
  ],
@@ -1021,7 +1025,7 @@ describe("{{className}} Smoke Test", () => {
1021
1025
  "@typescript-eslint/no-explicit-any": "warn"
1022
1026
  }
1023
1027
  }`,
1024
- "tsconfig.json": `{
1028
+ "tsconfig.json": `{
1025
1029
  "extends": "@contractspec/tsconfig-base",
1026
1030
  "compilerOptions": {
1027
1031
  "outDir": "./dist",
@@ -1039,16 +1043,13 @@ describe("{{className}} Smoke Test", () => {
1039
1043
  "tests"
1040
1044
  ]
1041
1045
  }`,
1042
- "tsdown.config.js": `import { defineConfig } from "tsdown";
1046
+ "tsdown.config.js": `import { defineConfig, nodeLib } from "@contractspec/tool.bun";
1043
1047
 
1044
- export default defineConfig({
1048
+ export default defineConfig(() => ({
1049
+ ...nodeLib,
1045
1050
  entry: ["src/index.ts"],
1046
- format: ["esm"],
1047
- dts: true,
1048
- clean: true,
1049
- sourcemap: true,
1050
- });`,
1051
- LICENSE: `MIT License
1051
+ }));`,
1052
+ LICENSE: `MIT License
1052
1053
 
1053
1054
  Copyright (c) {{currentYear}} {{author}}
1054
1055
 
@@ -1069,13 +1070,13 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1069
1070
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1070
1071
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1071
1072
  SOFTWARE.`,
1072
- "src/templates/index.ts": `/**
1073
+ "src/templates/index.ts": `/**
1073
1074
  * Template registry for the create-contractspec-plugin tool
1074
1075
  */
1075
1076
 
1076
1077
  export { createExampleGeneratorTemplate } from "./example-generator.js";
1077
1078
  export type { Template, TemplateFile } from "./types.js";`,
1078
- "src/templates/types.ts": `/**
1079
+ "src/templates/types.ts": `/**
1079
1080
  * Template types
1080
1081
  */
1081
1082
 
@@ -1092,140 +1093,146 @@ export interface Template {
1092
1093
  devDependencies?: string[];
1093
1094
  }
1094
1095
  `
1095
- } };
1096
+ }
1097
+ };
1096
1098
  }
1097
1099
 
1098
- //#endregion
1099
- //#region src/index.ts
1100
- /**
1101
- * ContractSpec Plugin Creator CLI
1102
- *
1103
- * A CLI tool for scaffolding ContractSpec plugins from templates.
1104
- * Supports various plugin types with proper structure and configuration.
1105
- */
1106
- const program = new Command();
1100
+ // src/index.ts
1101
+ import { Command } from "commander";
1102
+ import { input, select } from "@inquirer/prompts";
1103
+ import chalk from "chalk";
1104
+ import { execSync } from "child_process";
1105
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
1106
+ import { dirname, join } from "path";
1107
+ var program = new Command;
1107
1108
  program.name("create-contractspec-plugin").description("Create a new ContractSpec plugin from templates").version("1.48.0");
1108
1109
  program.command("create").description("Create a new plugin").option("-n, --name <name>", "Plugin name").option("-t, --type <type>", "Plugin type (generator, transformer, validator)", "generator").option("-d, --description <description>", "Plugin description").option("-a, --author <author>", "Plugin author").option("--template <template>", "Template to use", "example-generator").option("--dry-run", "Show what would be created without creating files").action(async (options) => {
1109
- try {
1110
- const config = await collectPluginConfig(options);
1111
- const template = selectTemplate(config.template);
1112
- if (options.dryRun) {
1113
- console.log(chalk.blue("🔍 Dry run - showing what would be created:"));
1114
- console.log(JSON.stringify(config, null, 2));
1115
- return;
1116
- }
1117
- await createPlugin(config, template);
1118
- console.log(chalk.green("✅ Plugin created successfully!"));
1119
- console.log(chalk.cyan("\nNext steps:"));
1120
- console.log(` cd ${config.packageDir}`);
1121
- console.log(" npm install");
1122
- console.log(" npm test");
1123
- } catch (error) {
1124
- console.error(chalk.red("❌ Error creating plugin:"), error);
1125
- process.exit(1);
1126
- }
1110
+ try {
1111
+ const config = await collectPluginConfig(options);
1112
+ const template = selectTemplate(config.template);
1113
+ if (options.dryRun) {
1114
+ console.log(chalk.blue("\uD83D\uDD0D Dry run - showing what would be created:"));
1115
+ console.log(JSON.stringify(config, null, 2));
1116
+ return;
1117
+ }
1118
+ await createPlugin(config, template);
1119
+ console.log(chalk.green("✅ Plugin created successfully!"));
1120
+ console.log(chalk.cyan(`
1121
+ Next steps:`));
1122
+ console.log(` cd ${config.packageDir}`);
1123
+ console.log(" npm install");
1124
+ console.log(" npm test");
1125
+ } catch (error) {
1126
+ console.error(chalk.red("❌ Error creating plugin:"), error);
1127
+ process.exit(1);
1128
+ }
1127
1129
  });
1128
1130
  program.command("list-templates").description("List available templates").action(() => {
1129
- console.log(chalk.blue("Available templates:"));
1130
- console.log(" example-generator - Markdown documentation generator");
1131
- console.log(" api-transformer - API response transformer");
1132
- console.log(" schema-validator - JSON schema validator");
1133
- console.log(" custom-event - Custom event handler");
1131
+ console.log(chalk.blue("Available templates:"));
1132
+ console.log(" example-generator - Markdown documentation generator");
1133
+ console.log(" api-transformer - API response transformer");
1134
+ console.log(" schema-validator - JSON schema validator");
1135
+ console.log(" custom-event - Custom event handler");
1134
1136
  });
1135
1137
  async function collectPluginConfig(options) {
1136
- const name = options.name ?? await input({
1137
- message: "Plugin name (kebab-case):",
1138
- validate: (input) => {
1139
- if (!input.trim()) return "Plugin name is required";
1140
- if (!/^[a-z][a-z0-9-]*$/.test(input)) return "Plugin name must be kebab-case and start with a letter";
1141
- return true;
1142
- }
1143
- });
1144
- const description = options.description ?? await input({
1145
- message: "Plugin description:",
1146
- validate: (input) => input.trim().length > 0 || "Description is required"
1147
- });
1148
- const defaultAuthor = (() => {
1149
- try {
1150
- return execSync("git config user.name", { encoding: "utf8" }).trim();
1151
- } catch (_err) {
1152
- return "";
1153
- }
1154
- })();
1155
- const author = options.author ?? await input({
1156
- message: "Author name:",
1157
- default: defaultAuthor
1158
- });
1159
- const type = await select({
1160
- message: "Plugin type:",
1161
- choices: [
1162
- {
1163
- name: "Generator - Creates artifacts from specs",
1164
- value: "generator"
1165
- },
1166
- {
1167
- name: "Transformer - Transforms data between formats",
1168
- value: "transformer"
1169
- },
1170
- {
1171
- name: "Validator - Validates specs and data",
1172
- value: "validator"
1173
- },
1174
- {
1175
- name: "Event Handler - Processes events",
1176
- value: "event"
1177
- }
1178
- ],
1179
- default: options.type
1180
- });
1181
- const template = options.template;
1182
- return {
1183
- name,
1184
- packageName: `@contractspec/plugin.${name}`,
1185
- integrationPackageName: `@contractspec/integration.${name}`,
1186
- className: name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("") + "Plugin",
1187
- description,
1188
- author,
1189
- type,
1190
- template,
1191
- packageDir: join(process.cwd(), name),
1192
- version: "1.0.0",
1193
- currentYear: (/* @__PURE__ */ new Date()).getFullYear()
1194
- };
1138
+ const name = options.name ?? await input({
1139
+ message: "Plugin name (kebab-case):",
1140
+ validate: (input2) => {
1141
+ if (!input2.trim())
1142
+ return "Plugin name is required";
1143
+ if (!/^[a-z][a-z0-9-]*$/.test(input2)) {
1144
+ return "Plugin name must be kebab-case and start with a letter";
1145
+ }
1146
+ return true;
1147
+ }
1148
+ });
1149
+ const description = options.description ?? await input({
1150
+ message: "Plugin description:",
1151
+ validate: (input2) => input2.trim().length > 0 || "Description is required"
1152
+ });
1153
+ const defaultAuthor = (() => {
1154
+ try {
1155
+ return execSync("git config user.name", {
1156
+ encoding: "utf8"
1157
+ }).trim();
1158
+ } catch (_err) {
1159
+ return "";
1160
+ }
1161
+ })();
1162
+ const author = options.author ?? await input({
1163
+ message: "Author name:",
1164
+ default: defaultAuthor
1165
+ });
1166
+ const type = await select({
1167
+ message: "Plugin type:",
1168
+ choices: [
1169
+ { name: "Generator - Creates artifacts from specs", value: "generator" },
1170
+ {
1171
+ name: "Transformer - Transforms data between formats",
1172
+ value: "transformer"
1173
+ },
1174
+ { name: "Validator - Validates specs and data", value: "validator" },
1175
+ { name: "Event Handler - Processes events", value: "event" }
1176
+ ],
1177
+ default: options.type
1178
+ });
1179
+ const template = options.template;
1180
+ const packageName = `@contractspec/plugin.${name}`;
1181
+ const integrationPackageName = `@contractspec/integration.${name}`;
1182
+ const className = name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("") + "Plugin";
1183
+ const packageDir = join(process.cwd(), name);
1184
+ return {
1185
+ name,
1186
+ packageName,
1187
+ integrationPackageName,
1188
+ className,
1189
+ description,
1190
+ author,
1191
+ type,
1192
+ template,
1193
+ packageDir,
1194
+ version: "1.0.0",
1195
+ currentYear: new Date().getFullYear()
1196
+ };
1195
1197
  }
1196
1198
  function selectTemplate(templateName) {
1197
- const templateFactory = { "example-generator": createExampleGeneratorTemplate }[templateName];
1198
- if (!templateFactory) throw new Error(`Unknown template: ${templateName}`);
1199
- return templateFactory();
1199
+ const templates = {
1200
+ "example-generator": createExampleGeneratorTemplate
1201
+ };
1202
+ const templateFactory = templates[templateName];
1203
+ if (!templateFactory) {
1204
+ throw new Error(`Unknown template: ${templateName}`);
1205
+ }
1206
+ return templateFactory();
1200
1207
  }
1201
1208
  async function createPlugin(config, template) {
1202
- console.log(chalk.blue(`📦 Creating plugin: ${config.packageName}`));
1203
- for (const dir of [
1204
- "",
1205
- "src",
1206
- "src/types",
1207
- "src/utils",
1208
- "src/templates",
1209
- "tests",
1210
- "docs",
1211
- ".github/workflows"
1212
- ]) {
1213
- const fullPath = join(config.packageDir, dir);
1214
- if (!existsSync(fullPath)) {
1215
- mkdirSync(fullPath, { recursive: true });
1216
- console.log(chalk.gray(`Created directory: ${fullPath}`));
1217
- }
1218
- }
1219
- for (const [file, content] of Object.entries(template.files)) {
1220
- const filePath = join(config.packageDir, file);
1221
- const renderedContent = renderTemplate(content, config);
1222
- const dir = dirname(filePath);
1223
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
1224
- writeFileSync(filePath, renderedContent, "utf8");
1225
- console.log(chalk.gray(`Created file: ${filePath}`));
1226
- }
1209
+ console.log(chalk.blue(`\uD83D\uDCE6 Creating plugin: ${config.packageName}`));
1210
+ const directories = [
1211
+ "",
1212
+ "src",
1213
+ "src/types",
1214
+ "src/utils",
1215
+ "src/templates",
1216
+ "tests",
1217
+ "docs",
1218
+ ".github/workflows"
1219
+ ];
1220
+ for (const dir of directories) {
1221
+ const fullPath = join(config.packageDir, dir);
1222
+ if (!existsSync(fullPath)) {
1223
+ mkdirSync(fullPath, { recursive: true });
1224
+ console.log(chalk.gray(`Created directory: ${fullPath}`));
1225
+ }
1226
+ }
1227
+ for (const [file, content] of Object.entries(template.files)) {
1228
+ const filePath = join(config.packageDir, file);
1229
+ const renderedContent = renderTemplate(content, config);
1230
+ const dir = dirname(filePath);
1231
+ if (!existsSync(dir)) {
1232
+ mkdirSync(dir, { recursive: true });
1233
+ }
1234
+ writeFileSync(filePath, renderedContent, "utf8");
1235
+ console.log(chalk.gray(`Created file: ${filePath}`));
1236
+ }
1227
1237
  }
1228
1238
  program.parse();
1229
-
1230
- //#endregion
1231
- export { };