speckeeper 0.2.1 → 0.3.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/README.md +13 -5
- package/dist/cli.js +49 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +58 -8
- package/dist/index.js +26 -1
- package/dist/index.js.map +1 -1
- package/dist/templates/init/design/index.ts +4 -5
- package/dist/templates/init/design/requirements.ts +5 -4
- package/dist/templates/init/speckeeper.config.ts +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,21 +69,23 @@ npx speckeeper scaffold --source requirements.md
|
|
|
69
69
|
|
|
70
70
|
This generates:
|
|
71
71
|
- `design/_models/` — Model classes with Zod schemas, lint rules, and exporters derived from your flowchart
|
|
72
|
+
- `design/*.ts` — Spec data files using `defineSpecs()` for each model
|
|
73
|
+
- `design/index.ts` — Entry point that aggregates all spec modules via `mergeSpecs()`
|
|
72
74
|
- `design/_checkers/` — External checker skeletons for `implements` edges (e.g. OpenAPI, DDL)
|
|
73
|
-
- `design/_models/index.ts` — Re-exports and `allModels` array
|
|
74
|
-
- `speckeeper.config.ts` — Configuration wired to the generated models
|
|
75
75
|
|
|
76
76
|
See [Scaffold Mermaid Specification](./docs/scaffold-mermaid-spec.md) for the full input format and built-in node mappings.
|
|
77
77
|
|
|
78
78
|
### 3. Fill in your specifications
|
|
79
79
|
|
|
80
|
-
Edit files in `design/` to add your actual specification data:
|
|
80
|
+
Edit spec data files in `design/` to add your actual specification data. Each file uses `defineSpecs()` to pair Model instances with data:
|
|
81
81
|
|
|
82
82
|
```typescript
|
|
83
83
|
// design/requirements.ts
|
|
84
|
-
import
|
|
84
|
+
import { defineSpecs } from 'speckeeper';
|
|
85
|
+
import type { Requirement } from './_models/requirement';
|
|
86
|
+
import { FunctionalRequirementModel } from './_models/requirement';
|
|
85
87
|
|
|
86
|
-
|
|
88
|
+
const requirements: Requirement[] = [
|
|
87
89
|
{
|
|
88
90
|
id: 'FR-001',
|
|
89
91
|
name: 'User Authentication',
|
|
@@ -96,8 +98,14 @@ export const requirements: Requirement[] = [
|
|
|
96
98
|
],
|
|
97
99
|
},
|
|
98
100
|
];
|
|
101
|
+
|
|
102
|
+
export default defineSpecs(
|
|
103
|
+
[FunctionalRequirementModel.instance, requirements],
|
|
104
|
+
);
|
|
99
105
|
```
|
|
100
106
|
|
|
107
|
+
`design/index.ts` aggregates all spec files, and `speckeeper.config.ts` imports the result — no manual wiring needed beyond adding your spec file to `design/index.ts`.
|
|
108
|
+
|
|
101
109
|
### 4. Run validation
|
|
102
110
|
|
|
103
111
|
```bash
|
package/dist/cli.js
CHANGED
|
@@ -152,6 +152,12 @@ function findModelTypeBySpecId(specId) {
|
|
|
152
152
|
}
|
|
153
153
|
return null;
|
|
154
154
|
}
|
|
155
|
+
function registerSpecsFromConfig(specs) {
|
|
156
|
+
if (!specs) return;
|
|
157
|
+
for (const entry of specs) {
|
|
158
|
+
entry.model.register(entry.data);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
155
161
|
|
|
156
162
|
// src/cli/build.ts
|
|
157
163
|
async function buildCommand(options) {
|
|
@@ -166,6 +172,7 @@ async function buildCommand(options) {
|
|
|
166
172
|
try {
|
|
167
173
|
console.log(chalk4.blue(" Loading models..."));
|
|
168
174
|
registerModelsFromConfig(config.models || []);
|
|
175
|
+
registerSpecsFromConfig(config.specs);
|
|
169
176
|
console.log("");
|
|
170
177
|
ensureDir(join(cwd, config.docsDir));
|
|
171
178
|
ensureDir(join(cwd, config.specsDir));
|
|
@@ -243,6 +250,7 @@ async function lintCommand(options) {
|
|
|
243
250
|
try {
|
|
244
251
|
console.log(chalk4.blue(" Loading models..."));
|
|
245
252
|
registerModelsFromConfig(config.models || []);
|
|
253
|
+
registerSpecsFromConfig(config.specs);
|
|
246
254
|
const models = getAllModels();
|
|
247
255
|
console.log(chalk4.gray(` Loaded: ${models.length} models`));
|
|
248
256
|
console.log("");
|
|
@@ -325,6 +333,7 @@ async function driftCommand(options) {
|
|
|
325
333
|
try {
|
|
326
334
|
console.log(chalk4.blue(" Loading models..."));
|
|
327
335
|
registerModelsFromConfig(config.models || []);
|
|
336
|
+
registerSpecsFromConfig(config.specs);
|
|
328
337
|
const results = [];
|
|
329
338
|
const models = getAllModels();
|
|
330
339
|
for (const model of models) {
|
|
@@ -416,6 +425,7 @@ async function checkCommand(type, options) {
|
|
|
416
425
|
try {
|
|
417
426
|
console.log(chalk4.blue(" Loading models..."));
|
|
418
427
|
registerModelsFromConfig(config.models || []);
|
|
428
|
+
registerSpecsFromConfig(config.specs);
|
|
419
429
|
const results = [];
|
|
420
430
|
const models = getAllModels();
|
|
421
431
|
if (options.verbose) {
|
|
@@ -654,6 +664,7 @@ async function impactCommand(targetId, options) {
|
|
|
654
664
|
try {
|
|
655
665
|
console.log(chalk4.blue(" Loading models..."));
|
|
656
666
|
registerModelsFromConfig(config.models || []);
|
|
667
|
+
registerSpecsFromConfig(config.specs);
|
|
657
668
|
const store = getSpecStore();
|
|
658
669
|
const targetType = findModelTypeBySpecId(targetId);
|
|
659
670
|
if (!targetType) {
|
|
@@ -772,14 +783,14 @@ async function runInit(options = {}) {
|
|
|
772
783
|
console.log(" Next steps:");
|
|
773
784
|
if (packageJsonCreated) {
|
|
774
785
|
console.log(chalk4.gray(" 1. Run `npm install` to install dependencies"));
|
|
775
|
-
console.log(chalk4.gray(" 2.
|
|
776
|
-
console.log(chalk4.gray(" 3.
|
|
786
|
+
console.log(chalk4.gray(" 2. Add spec data in design/*.ts using defineSpecs()"));
|
|
787
|
+
console.log(chalk4.gray(" 3. Import new spec files in design/index.ts"));
|
|
777
788
|
console.log(chalk4.gray(" 4. Run `npx speckeeper lint` to validate"));
|
|
778
789
|
} else {
|
|
779
790
|
console.log(chalk4.gray(" 1. Add speckeeper and zod to your package.json dependencies"));
|
|
780
791
|
console.log(chalk4.gray(' 2. Ensure "type": "module" is set in package.json'));
|
|
781
|
-
console.log(chalk4.gray(" 3.
|
|
782
|
-
console.log(chalk4.gray(" 4.
|
|
792
|
+
console.log(chalk4.gray(" 3. Add spec data in design/*.ts using defineSpecs()"));
|
|
793
|
+
console.log(chalk4.gray(" 4. Import new spec files in design/index.ts"));
|
|
783
794
|
console.log(chalk4.gray(" 5. Run `npx speckeeper lint` to validate"));
|
|
784
795
|
}
|
|
785
796
|
}
|
|
@@ -2271,14 +2282,17 @@ function generateSpecDataFile(node) {
|
|
|
2271
2282
|
*
|
|
2272
2283
|
* Generated by speckeeper scaffold \u2014 add your specs below.
|
|
2273
2284
|
*/
|
|
2285
|
+
import { defineSpecs } from 'speckeeper';
|
|
2274
2286
|
import type { ${typeName} } from './_models/${fileName}';
|
|
2275
2287
|
import { ${className} } from './_models/${fileName}';
|
|
2276
2288
|
|
|
2277
|
-
|
|
2289
|
+
const ${varName}: ${typeName}[] = [
|
|
2278
2290
|
// { id: '${templateInfo.defaultIdPrefix}-001', name: '...', description: '...' },
|
|
2279
2291
|
];
|
|
2280
2292
|
|
|
2281
|
-
|
|
2293
|
+
export default defineSpecs(
|
|
2294
|
+
[${className}.instance, ${varName}],
|
|
2295
|
+
);
|
|
2282
2296
|
`;
|
|
2283
2297
|
return {
|
|
2284
2298
|
relativePath: `${fileName}.ts`,
|
|
@@ -2288,6 +2302,34 @@ ${className}.instance.register(${varName});
|
|
|
2288
2302
|
function toCamelCase3(pascalCase) {
|
|
2289
2303
|
return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
|
|
2290
2304
|
}
|
|
2305
|
+
function generateDesignIndex(speckeeperNodes) {
|
|
2306
|
+
const generated = /* @__PURE__ */ new Set();
|
|
2307
|
+
const specFiles = [];
|
|
2308
|
+
for (const node of speckeeperNodes) {
|
|
2309
|
+
const templateInfo = resolveModelTemplate(node.id);
|
|
2310
|
+
const key = templateInfo.templateName === "base" ? `base:${node.id}` : templateInfo.templateName;
|
|
2311
|
+
if (generated.has(key)) continue;
|
|
2312
|
+
generated.add(key);
|
|
2313
|
+
specFiles.push({
|
|
2314
|
+
varName: toCamelCase3(templateInfo.modelName),
|
|
2315
|
+
fileName: templateInfo.fileName
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
const imports = specFiles.map((f) => `import ${f.varName} from './${f.fileName}';`).join("\n");
|
|
2319
|
+
const args = specFiles.map((f) => f.varName).join(", ");
|
|
2320
|
+
const content = `/**
|
|
2321
|
+
* Design entry point \u2014 generated by speckeeper scaffold
|
|
2322
|
+
*/
|
|
2323
|
+
import { mergeSpecs } from 'speckeeper';
|
|
2324
|
+
${imports}
|
|
2325
|
+
|
|
2326
|
+
export default mergeSpecs(${args});
|
|
2327
|
+
`;
|
|
2328
|
+
return {
|
|
2329
|
+
relativePath: "index.ts",
|
|
2330
|
+
content
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2291
2333
|
function generateAllModelFiles(speckeeperNodes, resolvedEdges) {
|
|
2292
2334
|
const files = [];
|
|
2293
2335
|
const generated = /* @__PURE__ */ new Set();
|
|
@@ -2301,6 +2343,7 @@ function generateAllModelFiles(speckeeperNodes, resolvedEdges) {
|
|
|
2301
2343
|
files.push(generateModelFile(node));
|
|
2302
2344
|
files.push(generateSpecDataFile(node));
|
|
2303
2345
|
}
|
|
2346
|
+
files.push(generateDesignIndex(speckeeperNodes));
|
|
2304
2347
|
return files;
|
|
2305
2348
|
}
|
|
2306
2349
|
|