@tailor-platform/erp-kit 0.1.0 → 0.1.2
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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +77 -50
- package/dist/cli.js +693 -553
- package/package.json +4 -2
- package/schemas/module/command.yml +1 -0
- package/schemas/module/model.yml +9 -0
- package/schemas/module/query.yml +53 -0
- package/skills/1-module-docs/SKILL.md +4 -0
- package/{rules/module-development → skills/1-module-docs/references}/structure.md +2 -7
- package/skills/2-module-feature-breakdown/SKILL.md +6 -0
- package/{rules/module-development → skills/2-module-feature-breakdown/references}/commands.md +0 -6
- package/{rules/module-development → skills/2-module-feature-breakdown/references}/models.md +0 -5
- package/skills/2-module-feature-breakdown/references/structure.md +22 -0
- package/skills/3-module-doc-review/SKILL.md +6 -0
- package/skills/3-module-doc-review/references/commands.md +54 -0
- package/skills/3-module-doc-review/references/models.md +29 -0
- package/{rules/module-development → skills/3-module-doc-review/references}/testing.md +0 -6
- package/skills/4-module-tdd-implementation/SKILL.md +24 -6
- package/skills/4-module-tdd-implementation/references/commands.md +45 -0
- package/{rules/sdk-best-practices → skills/4-module-tdd-implementation/references}/db-relations.md +0 -5
- package/{rules/module-development → skills/4-module-tdd-implementation/references}/errors.md +0 -5
- package/{rules/module-development → skills/4-module-tdd-implementation/references}/exports.md +0 -5
- package/skills/4-module-tdd-implementation/references/models.md +30 -0
- package/skills/4-module-tdd-implementation/references/structure.md +22 -0
- package/skills/4-module-tdd-implementation/references/testing.md +37 -0
- package/skills/5-module-implementation-review/SKILL.md +8 -0
- package/skills/5-module-implementation-review/references/commands.md +45 -0
- package/skills/5-module-implementation-review/references/errors.md +7 -0
- package/skills/5-module-implementation-review/references/exports.md +8 -0
- package/skills/5-module-implementation-review/references/models.md +30 -0
- package/skills/5-module-implementation-review/references/testing.md +29 -0
- package/skills/app-compose-1-requirement-analysis/SKILL.md +4 -0
- package/{rules/app-compose → skills/app-compose-1-requirement-analysis/references}/structure.md +0 -5
- package/skills/app-compose-2-requirements-breakdown/SKILL.md +7 -0
- package/{rules/app-compose/frontend → skills/app-compose-2-requirements-breakdown/references}/screen-detailview.md +0 -6
- package/{rules/app-compose/frontend → skills/app-compose-2-requirements-breakdown/references}/screen-form.md +0 -6
- package/{rules/app-compose/frontend → skills/app-compose-2-requirements-breakdown/references}/screen-listview.md +0 -6
- package/skills/app-compose-2-requirements-breakdown/references/structure.md +27 -0
- package/skills/app-compose-3-doc-review/SKILL.md +4 -0
- package/skills/app-compose-3-doc-review/references/structure.md +27 -0
- package/skills/app-compose-4-design-mock/SKILL.md +8 -0
- package/{rules/app-compose/frontend → skills/app-compose-4-design-mock/references}/component.md +0 -5
- package/skills/app-compose-4-design-mock/references/screen-detailview.md +106 -0
- package/skills/app-compose-4-design-mock/references/screen-form.md +139 -0
- package/skills/app-compose-4-design-mock/references/screen-listview.md +153 -0
- package/skills/app-compose-4-design-mock/references/structure.md +27 -0
- package/skills/app-compose-5-design-mock-review/SKILL.md +7 -0
- package/skills/app-compose-5-design-mock-review/references/component.md +50 -0
- package/skills/app-compose-5-design-mock-review/references/screen-detailview.md +106 -0
- package/skills/app-compose-5-design-mock-review/references/screen-form.md +139 -0
- package/skills/app-compose-5-design-mock-review/references/screen-listview.md +153 -0
- package/skills/app-compose-6-implementation-spec/SKILL.md +5 -0
- package/{rules/app-compose/backend → skills/app-compose-6-implementation-spec/references}/auth.md +0 -6
- package/skills/app-compose-6-implementation-spec/references/structure.md +27 -0
- package/src/cli.ts +8 -90
- package/src/commands/app/index.ts +74 -0
- package/src/commands/check.test.ts +2 -1
- package/src/commands/check.ts +1 -0
- package/src/commands/init.test.ts +30 -19
- package/src/commands/init.ts +76 -43
- package/src/commands/module/index.ts +85 -0
- package/src/commands/module/list.test.ts +62 -0
- package/src/commands/module/list.ts +64 -0
- package/src/commands/scaffold.test.ts +5 -0
- package/src/commands/scaffold.ts +2 -1
- package/src/commands/sync-check.test.ts +28 -0
- package/src/commands/sync-check.ts +6 -0
- package/src/integration.test.ts +6 -8
- package/src/module.ts +4 -3
- package/src/modules/primitives/docs/models/Currency.md +4 -0
- package/src/modules/primitives/docs/models/ExchangeRate.md +4 -1
- package/src/modules/primitives/docs/models/Unit.md +4 -1
- package/src/modules/primitives/docs/models/UoMCategory.md +2 -0
- package/src/modules/primitives/index.ts +2 -2
- package/src/modules/primitives/module.ts +5 -3
- package/src/modules/primitives/permissions.ts +0 -2
- package/src/modules/primitives/{command → query}/convertAmount.test.ts +2 -19
- package/src/modules/primitives/query/convertAmount.ts +122 -0
- package/src/modules/primitives/{command → query}/convertQuantity.test.ts +2 -13
- package/src/modules/primitives/{command → query}/convertQuantity.ts +4 -6
- package/src/modules/shared/defineQuery.test.ts +28 -0
- package/src/modules/shared/defineQuery.ts +16 -0
- package/src/modules/shared/internal.ts +2 -1
- package/src/modules/shared/types.ts +8 -0
- package/src/modules/user-management/docs/models/AuditEvent.md +2 -0
- package/src/modules/user-management/docs/models/Permission.md +2 -0
- package/src/modules/user-management/docs/models/Role.md +2 -0
- package/src/modules/user-management/docs/models/RolePermission.md +2 -0
- package/src/modules/user-management/docs/models/User.md +2 -0
- package/src/modules/user-management/docs/models/UserRole.md +2 -0
- package/src/schemas.ts +1 -0
- package/rules/app-compose/frontend/auth.md +0 -55
- package/rules/app-compose/frontend/page.md +0 -86
- package/rules/module-development/cross-module-type-injection.md +0 -28
- package/rules/module-development/dependency-modules.md +0 -24
- package/rules/module-development/executors.md +0 -67
- package/rules/module-development/sync-vs-async-operations.md +0 -83
- package/rules/sdk-best-practices/sdk-docs.md +0 -14
- package/src/modules/primitives/command/convertAmount.ts +0 -126
- /package/src/modules/primitives/docs/{commands → queries}/ConvertAmount.md +0 -0
- /package/src/modules/primitives/docs/{commands → queries}/ConvertQuantity.md +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,421 +1,113 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { z as
|
|
5
|
-
import { defineCommand as
|
|
4
|
+
import { z as z4 } from "zod";
|
|
5
|
+
import { defineCommand as defineCommand4, runMain, arg as arg4 } from "politty";
|
|
6
6
|
|
|
7
|
-
// src/
|
|
8
|
-
import path from "path";
|
|
7
|
+
// src/commands/init.ts
|
|
9
8
|
import fs from "fs";
|
|
10
|
-
import { execFile } from "child_process";
|
|
11
|
-
import { createRequire } from "module";
|
|
12
|
-
var require2 = createRequire(import.meta.url);
|
|
13
|
-
function getMdschemaBin() {
|
|
14
|
-
const pkgPath = require2.resolve("@jackchuka/mdschema/package.json");
|
|
15
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
16
|
-
const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.mdschema;
|
|
17
|
-
if (!bin) {
|
|
18
|
-
throw new Error("Could not resolve mdschema binary from package.json bin field");
|
|
19
|
-
}
|
|
20
|
-
return path.join(path.dirname(pkgPath), bin);
|
|
21
|
-
}
|
|
22
|
-
function runMdschema(args, cwd2) {
|
|
23
|
-
return new Promise((resolve4) => {
|
|
24
|
-
execFile(
|
|
25
|
-
getMdschemaBin(),
|
|
26
|
-
args,
|
|
27
|
-
{ encoding: "utf-8", cwd: cwd2, timeout: 3e4 },
|
|
28
|
-
(error, stdout, stderr) => {
|
|
29
|
-
if (error) {
|
|
30
|
-
const execError = error;
|
|
31
|
-
resolve4({
|
|
32
|
-
exitCode: execError.code === "ENOENT" ? 127 : execError.status ?? 1,
|
|
33
|
-
stdout: stdout ?? "",
|
|
34
|
-
stderr: stderr ?? ""
|
|
35
|
-
});
|
|
36
|
-
} else {
|
|
37
|
-
resolve4({ exitCode: 0, stdout: stdout ?? "", stderr: stderr ?? "" });
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
);
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// src/schemas.ts
|
|
45
|
-
import path3 from "path";
|
|
46
|
-
|
|
47
|
-
// src/util.ts
|
|
48
9
|
import path2 from "path";
|
|
49
|
-
|
|
10
|
+
import chalk from "chalk";
|
|
50
11
|
|
|
51
|
-
// src/
|
|
52
|
-
|
|
53
|
-
var
|
|
54
|
-
module: path3.join(SCHEMAS_ROOT, "module", "module.yml"),
|
|
55
|
-
command: path3.join(SCHEMAS_ROOT, "module", "command.yml"),
|
|
56
|
-
model: path3.join(SCHEMAS_ROOT, "module", "model.yml"),
|
|
57
|
-
feature: path3.join(SCHEMAS_ROOT, "module", "feature.yml")
|
|
58
|
-
};
|
|
59
|
-
var APP_COMPOSE_SCHEMAS = {
|
|
60
|
-
requirements: path3.join(SCHEMAS_ROOT, "app-compose", "requirements.yml"),
|
|
61
|
-
actors: path3.join(SCHEMAS_ROOT, "app-compose", "actors.yml"),
|
|
62
|
-
"business-flow": path3.join(SCHEMAS_ROOT, "app-compose", "business-flow.yml"),
|
|
63
|
-
story: path3.join(SCHEMAS_ROOT, "app-compose", "story.yml"),
|
|
64
|
-
screen: path3.join(SCHEMAS_ROOT, "app-compose", "screen.yml"),
|
|
65
|
-
resolver: path3.join(SCHEMAS_ROOT, "app-compose", "resolver.yml")
|
|
66
|
-
};
|
|
67
|
-
var ALL_SCHEMAS = {
|
|
68
|
-
...MODULE_SCHEMAS,
|
|
69
|
-
...APP_COMPOSE_SCHEMAS
|
|
70
|
-
};
|
|
12
|
+
// src/util.ts
|
|
13
|
+
import path from "path";
|
|
14
|
+
var PACKAGE_ROOT = path.resolve(import.meta.dirname, "..");
|
|
71
15
|
|
|
72
|
-
// src/commands/
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (!schemaPath) {
|
|
103
|
-
console.error(`Unknown schema key: ${target.schemaKey}`);
|
|
104
|
-
return 2;
|
|
16
|
+
// src/commands/init.ts
|
|
17
|
+
var SKILLS_SRC = path2.join(PACKAGE_ROOT, "skills");
|
|
18
|
+
var FRAMEWORK_SKILLS = [
|
|
19
|
+
"1-module-docs",
|
|
20
|
+
"2-module-feature-breakdown",
|
|
21
|
+
"3-module-doc-review",
|
|
22
|
+
"4-module-tdd-implementation",
|
|
23
|
+
"5-module-implementation-review",
|
|
24
|
+
"app-compose-1-requirement-analysis",
|
|
25
|
+
"app-compose-2-requirements-breakdown",
|
|
26
|
+
"app-compose-3-doc-review",
|
|
27
|
+
"app-compose-4-design-mock",
|
|
28
|
+
"app-compose-5-design-mock-review",
|
|
29
|
+
"app-compose-6-implementation-spec",
|
|
30
|
+
"mock-scenario"
|
|
31
|
+
];
|
|
32
|
+
function copyDirectoryRecursive(srcDir, destDir, force) {
|
|
33
|
+
let copied = 0;
|
|
34
|
+
let skipped = 0;
|
|
35
|
+
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
36
|
+
const srcPath = path2.join(srcDir, entry.name);
|
|
37
|
+
const destPath = path2.join(destDir, entry.name);
|
|
38
|
+
if (entry.isDirectory()) {
|
|
39
|
+
const sub = copyDirectoryRecursive(srcPath, destPath, force);
|
|
40
|
+
copied += sub.copied;
|
|
41
|
+
skipped += sub.skipped;
|
|
42
|
+
} else {
|
|
43
|
+
if (!force && fs.existsSync(destPath)) {
|
|
44
|
+
skipped++;
|
|
45
|
+
continue;
|
|
105
46
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
);
|
|
110
|
-
if (stdout.trim()) console.log(stdout);
|
|
111
|
-
if (stderr.trim()) console.error(stderr);
|
|
112
|
-
return exitCode;
|
|
113
|
-
})
|
|
114
|
-
);
|
|
115
|
-
if (results.includes(2)) return 2;
|
|
116
|
-
return results.some((code) => code !== 0) ? 1 : 0;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// src/commands/sync-check.ts
|
|
120
|
-
import path4 from "path";
|
|
121
|
-
import fg from "fast-glob";
|
|
122
|
-
import chalk from "chalk";
|
|
123
|
-
function moduleCategories(root) {
|
|
124
|
-
return [
|
|
125
|
-
{
|
|
126
|
-
name: "command",
|
|
127
|
-
sourcePattern: `${root}/*/command/*.ts`,
|
|
128
|
-
docPattern: `${root}/*/docs/commands/*.md`,
|
|
129
|
-
exclusions: [/\.test\.ts$/]
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
name: "model",
|
|
133
|
-
sourcePattern: `${root}/*/db/*.ts`,
|
|
134
|
-
docPattern: `${root}/*/docs/models/*.md`,
|
|
135
|
-
exclusions: [/\.test\.ts$/, /^index\.ts$/]
|
|
47
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
48
|
+
fs.copyFileSync(srcPath, destPath);
|
|
49
|
+
copied++;
|
|
136
50
|
}
|
|
137
|
-
|
|
51
|
+
}
|
|
52
|
+
return { copied, skipped };
|
|
138
53
|
}
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
54
|
+
function runInit(cwd4, force) {
|
|
55
|
+
console.log(chalk.bold("erp-kit init\n"));
|
|
56
|
+
const skillsDest = path2.join(cwd4, ".agents", "skills");
|
|
57
|
+
let copiedCount = 0;
|
|
58
|
+
let skippedCount = 0;
|
|
59
|
+
for (const skill of FRAMEWORK_SKILLS) {
|
|
60
|
+
const srcSkillDir = path2.join(SKILLS_SRC, skill);
|
|
61
|
+
if (!fs.existsSync(srcSkillDir)) continue;
|
|
62
|
+
const destDir = path2.join(skillsDest, skill);
|
|
63
|
+
const result = copyDirectoryRecursive(srcSkillDir, destDir, force);
|
|
64
|
+
copiedCount += result.copied;
|
|
65
|
+
if (result.skipped > 0) {
|
|
66
|
+
console.log(chalk.yellow(` Skipped ${skill}/ (${result.skipped} existing files)`));
|
|
67
|
+
skippedCount += result.skipped;
|
|
146
68
|
}
|
|
147
|
-
];
|
|
148
|
-
}
|
|
149
|
-
function shouldExclude(fileName, exclusions) {
|
|
150
|
-
return exclusions.some((pattern) => pattern.test(fileName));
|
|
151
|
-
}
|
|
152
|
-
async function runSyncCheck(config, cwd2) {
|
|
153
|
-
const errors = [];
|
|
154
|
-
let totalSources = 0;
|
|
155
|
-
let totalDocs = 0;
|
|
156
|
-
const allCategories = [];
|
|
157
|
-
if (config.modulesRoot) {
|
|
158
|
-
allCategories.push(...moduleCategories(config.modulesRoot));
|
|
159
|
-
}
|
|
160
|
-
if (config.appRoot) {
|
|
161
|
-
allCategories.push(...appComposeCategories(config.appRoot));
|
|
162
69
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
for (const sourcePath of sources) {
|
|
169
|
-
const fileName = path4.basename(sourcePath);
|
|
170
|
-
if (shouldExclude(fileName, category.exclusions)) continue;
|
|
171
|
-
const basename2 = path4.basename(sourcePath, path4.extname(sourcePath));
|
|
172
|
-
sourceBasenames.set(basename2.toLowerCase(), sourcePath);
|
|
173
|
-
}
|
|
174
|
-
for (const docPath of docs) {
|
|
175
|
-
const basename2 = path4.basename(docPath, path4.extname(docPath));
|
|
176
|
-
docBasenames.set(basename2.toLowerCase(), docPath);
|
|
177
|
-
}
|
|
178
|
-
for (const [basename2, sourcePath] of sourceBasenames) {
|
|
179
|
-
if (!docBasenames.has(basename2)) {
|
|
180
|
-
errors.push({
|
|
181
|
-
type: "missing-doc",
|
|
182
|
-
category: category.name,
|
|
183
|
-
sourcePath,
|
|
184
|
-
expectedBasename: basename2
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
for (const [basename2, docPath] of docBasenames) {
|
|
189
|
-
if (!sourceBasenames.has(basename2)) {
|
|
190
|
-
errors.push({
|
|
191
|
-
type: "orphaned-doc",
|
|
192
|
-
category: category.name,
|
|
193
|
-
docPath,
|
|
194
|
-
expectedBasename: basename2
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
totalSources += sourceBasenames.size;
|
|
199
|
-
totalDocs += docBasenames.size;
|
|
70
|
+
console.log(chalk.green(` Copied ${copiedCount} skill files to .agents/skills/`));
|
|
71
|
+
if (skippedCount > 0) {
|
|
72
|
+
console.log(
|
|
73
|
+
chalk.yellow(` Skipped ${skippedCount} existing files (use --force to overwrite)`)
|
|
74
|
+
);
|
|
200
75
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
function formatSyncCheckReport(result) {
|
|
212
|
-
const lines = [];
|
|
213
|
-
lines.push(chalk.bold("docs-sync-check: Checking source-documentation correspondence...\n"));
|
|
214
|
-
if (result.errors.length > 0) {
|
|
215
|
-
lines.push(chalk.red.bold("Errors:\n"));
|
|
216
|
-
const byCategory = /* @__PURE__ */ new Map();
|
|
217
|
-
for (const error of result.errors) {
|
|
218
|
-
const existing = byCategory.get(error.category) ?? [];
|
|
219
|
-
existing.push(error);
|
|
220
|
-
byCategory.set(error.category, existing);
|
|
76
|
+
const claudeSkills = path2.join(cwd4, ".claude", "skills");
|
|
77
|
+
const relTarget = path2.relative(path2.dirname(claudeSkills), skillsDest);
|
|
78
|
+
const claudeSkillsExists = (() => {
|
|
79
|
+
try {
|
|
80
|
+
fs.lstatSync(claudeSkills);
|
|
81
|
+
return true;
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
221
84
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
85
|
+
})();
|
|
86
|
+
if (claudeSkillsExists) {
|
|
87
|
+
const stat2 = fs.lstatSync(claudeSkills);
|
|
88
|
+
if (stat2.isSymbolicLink()) {
|
|
89
|
+
const current = fs.readlinkSync(claudeSkills);
|
|
90
|
+
if (current === relTarget) {
|
|
91
|
+
console.log(chalk.green(" .claude/skills -> .agents/skills/ (already linked)"));
|
|
92
|
+
} else if (force) {
|
|
93
|
+
fs.unlinkSync(claudeSkills);
|
|
94
|
+
fs.symlinkSync(relTarget, claudeSkills);
|
|
95
|
+
console.log(chalk.green(" .claude/skills -> .agents/skills/ (relinked)"));
|
|
96
|
+
} else {
|
|
97
|
+
console.log(
|
|
98
|
+
chalk.yellow(` Skipped .claude/skills (symlink exists -> ${current}, use --force)`)
|
|
99
|
+
);
|
|
236
100
|
}
|
|
101
|
+
} else {
|
|
102
|
+
console.log(chalk.yellow(" Skipped .claude/skills (directory exists, not a symlink)"));
|
|
237
103
|
}
|
|
238
104
|
} else {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
lines.push(` Categories checked: ${result.summary.categoriesChecked}`);
|
|
243
|
-
lines.push(
|
|
244
|
-
` Source files: ${result.summary.totalSources}, Doc files: ${result.summary.totalDocs}`
|
|
245
|
-
);
|
|
246
|
-
if (result.errors.length > 0) {
|
|
247
|
-
lines.push(chalk.red(` Errors: ${result.errors.length}`));
|
|
248
|
-
lines.push("");
|
|
249
|
-
lines.push(chalk.red.bold(`docs-sync-check failed with ${result.errors.length} error(s).`));
|
|
250
|
-
} else {
|
|
251
|
-
lines.push(chalk.green(" Errors: 0"));
|
|
252
|
-
lines.push("");
|
|
253
|
-
lines.push(chalk.green.bold("docs-sync-check passed."));
|
|
105
|
+
fs.mkdirSync(path2.dirname(claudeSkills), { recursive: true });
|
|
106
|
+
fs.symlinkSync(relTarget, claudeSkills);
|
|
107
|
+
console.log(chalk.green(" .claude/skills -> .agents/skills/ (linked)"));
|
|
254
108
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
// src/commands/scaffold.ts
|
|
259
|
-
import path5 from "path";
|
|
260
|
-
import fs2 from "fs";
|
|
261
|
-
var MODULE_TYPES = ["module", "feature", "command", "model"];
|
|
262
|
-
var APP_TYPES = [
|
|
263
|
-
"requirements",
|
|
264
|
-
"actors",
|
|
265
|
-
"business-flow",
|
|
266
|
-
"story",
|
|
267
|
-
"screen",
|
|
268
|
-
"resolver"
|
|
269
|
-
];
|
|
270
|
-
var ALL_TYPES = [...MODULE_TYPES, ...APP_TYPES];
|
|
271
|
-
var MODULE_DIR_MAP = {
|
|
272
|
-
feature: "docs/features",
|
|
273
|
-
command: "docs/commands",
|
|
274
|
-
model: "docs/models"
|
|
275
|
-
};
|
|
276
|
-
var APP_DIR_MAP = {
|
|
277
|
-
actors: "docs/actors",
|
|
278
|
-
"business-flow": "docs/business-flow",
|
|
279
|
-
screen: "docs/screen",
|
|
280
|
-
resolver: "docs/resolver"
|
|
281
|
-
};
|
|
282
|
-
function isModuleType(type) {
|
|
283
|
-
return MODULE_TYPES.includes(type);
|
|
284
|
-
}
|
|
285
|
-
function resolveScaffoldPath(type, parentName, name, root) {
|
|
286
|
-
if (type === "module" || type === "requirements") {
|
|
287
|
-
return path5.join(root, parentName, "README.md");
|
|
288
|
-
}
|
|
289
|
-
if (!name) {
|
|
290
|
-
throw new Error(`Name is required for scaffold type "${type}"`);
|
|
291
|
-
}
|
|
292
|
-
if (type === "business-flow") {
|
|
293
|
-
return path5.join(root, parentName, "docs/business-flow", name, "README.md");
|
|
294
|
-
}
|
|
295
|
-
if (type === "story") {
|
|
296
|
-
const parts = name.split("/");
|
|
297
|
-
if (parts.length !== 2) {
|
|
298
|
-
throw new Error(
|
|
299
|
-
`Story name must be "<flow>/<story>" (e.g., "onboarding/admin--create-user")`
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
return path5.join(root, parentName, "docs/business-flow", parts[0], "story", `${parts[1]}.md`);
|
|
303
|
-
}
|
|
304
|
-
if (MODULE_DIR_MAP[type]) {
|
|
305
|
-
return path5.join(root, parentName, MODULE_DIR_MAP[type], `${name}.md`);
|
|
306
|
-
}
|
|
307
|
-
if (APP_DIR_MAP[type]) {
|
|
308
|
-
return path5.join(root, parentName, APP_DIR_MAP[type], `${name}.md`);
|
|
309
|
-
}
|
|
310
|
-
throw new Error(`Unknown scaffold type: ${type}`);
|
|
311
|
-
}
|
|
312
|
-
async function runScaffold(type, parentName, name, root, cwd2) {
|
|
313
|
-
const outputPath = resolveScaffoldPath(type, parentName, name, root);
|
|
314
|
-
const absoluteOutput = path5.resolve(cwd2, outputPath);
|
|
315
|
-
if (fs2.existsSync(absoluteOutput)) {
|
|
316
|
-
console.error(`File already exists: ${outputPath}`);
|
|
317
|
-
return 1;
|
|
318
|
-
}
|
|
319
|
-
const schemaPath = ALL_SCHEMAS[type];
|
|
320
|
-
if (!schemaPath) {
|
|
321
|
-
console.error(`No schema found for type: ${type}`);
|
|
322
|
-
return 2;
|
|
323
|
-
}
|
|
324
|
-
try {
|
|
325
|
-
fs2.mkdirSync(path5.dirname(absoluteOutput), { recursive: true });
|
|
326
|
-
} catch (err) {
|
|
327
|
-
console.error(
|
|
328
|
-
`Failed to create directory: ${err instanceof Error ? err.message : String(err)}`
|
|
329
|
-
);
|
|
330
|
-
return 1;
|
|
331
|
-
}
|
|
332
|
-
const { exitCode, stdout, stderr } = await runMdschema(
|
|
333
|
-
["generate", "--schema", schemaPath, "--output", absoluteOutput],
|
|
334
|
-
cwd2
|
|
335
|
-
);
|
|
336
|
-
if (stdout.trim()) console.log(stdout);
|
|
337
|
-
if (stderr.trim()) console.error(stderr);
|
|
338
|
-
return exitCode;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// src/commands/init.ts
|
|
342
|
-
import fs3 from "fs";
|
|
343
|
-
import path6 from "path";
|
|
344
|
-
import chalk2 from "chalk";
|
|
345
|
-
var SKILLS_SRC = path6.join(PACKAGE_ROOT, "skills");
|
|
346
|
-
var RULES_SRC = path6.join(PACKAGE_ROOT, "rules");
|
|
347
|
-
var FRAMEWORK_SKILLS = [
|
|
348
|
-
"1-module-docs",
|
|
349
|
-
"2-module-feature-breakdown",
|
|
350
|
-
"3-module-doc-review",
|
|
351
|
-
"4-module-tdd-implementation",
|
|
352
|
-
"5-module-implementation-review",
|
|
353
|
-
"app-compose-1-requirement-analysis",
|
|
354
|
-
"app-compose-2-requirements-breakdown",
|
|
355
|
-
"app-compose-3-doc-review",
|
|
356
|
-
"app-compose-4-design-mock",
|
|
357
|
-
"app-compose-5-design-mock-review",
|
|
358
|
-
"app-compose-6-implementation-spec",
|
|
359
|
-
"mock-scenario"
|
|
360
|
-
];
|
|
361
|
-
function runInit(cwd2, force) {
|
|
362
|
-
console.log(chalk2.bold("erp-kit init\n"));
|
|
363
|
-
const skillsDest = path6.join(cwd2, ".agents", "skills");
|
|
364
|
-
let copiedCount = 0;
|
|
365
|
-
let skippedCount = 0;
|
|
366
|
-
for (const skill of FRAMEWORK_SKILLS) {
|
|
367
|
-
const srcSkill = path6.join(SKILLS_SRC, skill, "SKILL.md");
|
|
368
|
-
const destDir = path6.join(skillsDest, skill);
|
|
369
|
-
const destSkill = path6.join(destDir, "SKILL.md");
|
|
370
|
-
if (!fs3.existsSync(srcSkill)) continue;
|
|
371
|
-
if (!force && fs3.existsSync(destSkill)) {
|
|
372
|
-
console.log(chalk2.yellow(` Skipped ${skill}/SKILL.md (already exists)`));
|
|
373
|
-
skippedCount++;
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
fs3.mkdirSync(destDir, { recursive: true });
|
|
377
|
-
fs3.copyFileSync(srcSkill, destSkill);
|
|
378
|
-
copiedCount++;
|
|
379
|
-
}
|
|
380
|
-
console.log(chalk2.green(` Copied ${copiedCount} framework skills to .agents/skills/`));
|
|
381
|
-
if (skippedCount > 0) {
|
|
382
|
-
console.log(
|
|
383
|
-
chalk2.yellow(` Skipped ${skippedCount} existing skills (use --force to overwrite)`)
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
const rulesDest = path6.join(cwd2, ".agents", "rules");
|
|
387
|
-
let rulesCount = 0;
|
|
388
|
-
let rulesSkipped = 0;
|
|
389
|
-
if (fs3.existsSync(RULES_SRC)) {
|
|
390
|
-
const copyRulesRecursive = (srcDir, destDir) => {
|
|
391
|
-
for (const entry of fs3.readdirSync(srcDir, { withFileTypes: true })) {
|
|
392
|
-
const srcPath = path6.join(srcDir, entry.name);
|
|
393
|
-
const destPath = path6.join(destDir, entry.name);
|
|
394
|
-
if (entry.isDirectory()) {
|
|
395
|
-
copyRulesRecursive(srcPath, destPath);
|
|
396
|
-
} else {
|
|
397
|
-
if (!force && fs3.existsSync(destPath)) {
|
|
398
|
-
const rel = path6.relative(rulesDest, destPath);
|
|
399
|
-
console.log(chalk2.yellow(` Skipped rule ${rel} (already exists)`));
|
|
400
|
-
rulesSkipped++;
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
fs3.mkdirSync(destDir, { recursive: true });
|
|
404
|
-
fs3.copyFileSync(srcPath, destPath);
|
|
405
|
-
rulesCount++;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
};
|
|
409
|
-
copyRulesRecursive(RULES_SRC, rulesDest);
|
|
410
|
-
}
|
|
411
|
-
console.log(chalk2.green(` Copied ${rulesCount} framework rules to .agents/rules/`));
|
|
412
|
-
if (rulesSkipped > 0) {
|
|
413
|
-
console.log(
|
|
414
|
-
chalk2.yellow(` Skipped ${rulesSkipped} existing rules (use --force to overwrite)`)
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
|
-
console.log(chalk2.bold.green("\nDone! Run `erp-kit check` to validate your docs."));
|
|
418
|
-
return 0;
|
|
109
|
+
console.log(chalk.bold.green("\nDone! Run `erp-kit check` to validate your docs."));
|
|
110
|
+
return 0;
|
|
419
111
|
}
|
|
420
112
|
|
|
421
113
|
// src/commands/mock/index.ts
|
|
@@ -600,12 +292,12 @@ Reverse proxy listening on http://localhost:${port}`);
|
|
|
600
292
|
// src/commands/mock/validate.ts
|
|
601
293
|
import { readdir, readFile, stat } from "fs/promises";
|
|
602
294
|
import { join as join2, resolve as resolve3, relative as relative2, dirname as dirname2, basename } from "path";
|
|
603
|
-
import
|
|
295
|
+
import chalk2 from "chalk";
|
|
604
296
|
function pass(msg) {
|
|
605
|
-
console.log(
|
|
297
|
+
console.log(chalk2.green(`\u2713 ${msg}`));
|
|
606
298
|
}
|
|
607
299
|
function fail(ctx, msg) {
|
|
608
|
-
console.log(
|
|
300
|
+
console.log(chalk2.red(`\u2717 ${msg}`));
|
|
609
301
|
ctx.failures++;
|
|
610
302
|
}
|
|
611
303
|
async function subdirs(dir) {
|
|
@@ -663,232 +355,680 @@ function checkResponseQuality(ctx, data, label) {
|
|
|
663
355
|
}
|
|
664
356
|
}
|
|
665
357
|
}
|
|
666
|
-
function checkDatabucketRefs(ctx, data, label) {
|
|
667
|
-
const bucketIds = new Set((data.data ?? []).map((d) => d.id));
|
|
668
|
-
for (const route of data.routes ?? []) {
|
|
669
|
-
for (const resp of route.responses ?? []) {
|
|
670
|
-
if (resp.bodyType === "DATABUCKET") {
|
|
671
|
-
const respLabel = `${label} \u2192 ${route.uuid}/${resp.uuid}`;
|
|
672
|
-
if (resp.databucketID && bucketIds.has(resp.databucketID)) {
|
|
673
|
-
pass(`${respLabel}: databucket "${resp.databucketID}" exists`);
|
|
358
|
+
function checkDatabucketRefs(ctx, data, label) {
|
|
359
|
+
const bucketIds = new Set((data.data ?? []).map((d) => d.id));
|
|
360
|
+
for (const route of data.routes ?? []) {
|
|
361
|
+
for (const resp of route.responses ?? []) {
|
|
362
|
+
if (resp.bodyType === "DATABUCKET") {
|
|
363
|
+
const respLabel = `${label} \u2192 ${route.uuid}/${resp.uuid}`;
|
|
364
|
+
if (resp.databucketID && bucketIds.has(resp.databucketID)) {
|
|
365
|
+
pass(`${respLabel}: databucket "${resp.databucketID}" exists`);
|
|
366
|
+
} else {
|
|
367
|
+
fail(ctx, `${respLabel}: databucketID "${resp.databucketID}" not found in data array`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async function discoverAllScenarios(mocksDir) {
|
|
374
|
+
const scenarios = [];
|
|
375
|
+
const providers = await subdirs(mocksDir);
|
|
376
|
+
for (const provider of providers) {
|
|
377
|
+
const providerDir = join2(mocksDir, provider);
|
|
378
|
+
for (const scenario of await subdirs(providerDir)) {
|
|
379
|
+
scenarios.push(`${provider}/${scenario}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return scenarios;
|
|
383
|
+
}
|
|
384
|
+
function resolveScenarioDir(arg5) {
|
|
385
|
+
const abs = resolve3(arg5);
|
|
386
|
+
if (basename(abs) === "mock.json") return dirname2(abs);
|
|
387
|
+
return abs;
|
|
388
|
+
}
|
|
389
|
+
async function validateScenario(ctx, scenarioDir, mocksDir) {
|
|
390
|
+
const label = relative2(mocksDir, scenarioDir);
|
|
391
|
+
console.log(chalk2.bold(`
|
|
392
|
+
\u2500\u2500 ${label} \u2500\u2500`));
|
|
393
|
+
let entries;
|
|
394
|
+
try {
|
|
395
|
+
entries = await readdir(scenarioDir);
|
|
396
|
+
} catch {
|
|
397
|
+
fail(ctx, `${label}: directory not found`);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const hasMock = checkStructure(ctx, entries, label);
|
|
401
|
+
if (!hasMock) return;
|
|
402
|
+
const mockPath = join2(scenarioDir, "mock.json");
|
|
403
|
+
let data;
|
|
404
|
+
try {
|
|
405
|
+
data = JSON.parse(await readFile(mockPath, "utf-8"));
|
|
406
|
+
} catch (err) {
|
|
407
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
408
|
+
fail(ctx, `${label}: invalid JSON \u2014 ${errMsg}`);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
await checkSchema(ctx, data, label);
|
|
412
|
+
checkResponseQuality(ctx, data, label);
|
|
413
|
+
checkDatabucketRefs(ctx, data, label);
|
|
414
|
+
}
|
|
415
|
+
async function runMockValidate(mocksRoot, paths) {
|
|
416
|
+
const ctx = { failures: 0 };
|
|
417
|
+
const mocksDir = resolve3(mocksRoot);
|
|
418
|
+
const targets = paths.length > 0 ? paths.map(resolveScenarioDir) : (await discoverAllScenarios(mocksDir)).map((s) => join2(mocksDir, s));
|
|
419
|
+
if (targets.length === 0) {
|
|
420
|
+
fail(ctx, "No scenarios found under mocks/");
|
|
421
|
+
return 1;
|
|
422
|
+
}
|
|
423
|
+
console.log(chalk2.bold("\nValidating mock configs...\n"));
|
|
424
|
+
for (const target of targets) {
|
|
425
|
+
await validateScenario(ctx, target, mocksDir);
|
|
426
|
+
}
|
|
427
|
+
console.log(chalk2.bold("\n\u2500\u2500 summary \u2500\u2500"));
|
|
428
|
+
if (ctx.failures === 0) {
|
|
429
|
+
console.log(chalk2.green("\u2713 All checks passed"));
|
|
430
|
+
} else {
|
|
431
|
+
console.log(chalk2.red(`\u2717 ${ctx.failures} check(s) failed`));
|
|
432
|
+
}
|
|
433
|
+
return ctx.failures === 0 ? 0 : 1;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// src/commands/mock/index.ts
|
|
437
|
+
var startCommand = defineCommand({
|
|
438
|
+
name: "start",
|
|
439
|
+
description: "Start mock API servers with reverse proxy",
|
|
440
|
+
args: z.object({
|
|
441
|
+
mocksRoot: arg(z.string().default("./mocks"), {
|
|
442
|
+
description: "Path to mocks directory"
|
|
443
|
+
}),
|
|
444
|
+
port: arg(z.coerce.number().default(3e3), {
|
|
445
|
+
alias: "p",
|
|
446
|
+
description: "Reverse proxy port"
|
|
447
|
+
}),
|
|
448
|
+
filter: arg(z.array(z.string()).default([]), {
|
|
449
|
+
positional: true,
|
|
450
|
+
description: "Filter by provider or provider/scenario"
|
|
451
|
+
})
|
|
452
|
+
}),
|
|
453
|
+
run: async (args) => {
|
|
454
|
+
const exitCode = await runMockStart(args.mocksRoot, args.filter, args.port);
|
|
455
|
+
process.exit(exitCode);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
var validateCommand = defineCommand({
|
|
459
|
+
name: "validate",
|
|
460
|
+
description: "Validate mock scenario configs",
|
|
461
|
+
args: z.object({
|
|
462
|
+
mocksRoot: arg(z.string().default("./mocks"), {
|
|
463
|
+
description: "Path to mocks directory"
|
|
464
|
+
}),
|
|
465
|
+
paths: arg(z.array(z.string()).default([]), {
|
|
466
|
+
positional: true,
|
|
467
|
+
description: "Specific scenario paths to validate"
|
|
468
|
+
})
|
|
469
|
+
}),
|
|
470
|
+
run: async (args) => {
|
|
471
|
+
const exitCode = await runMockValidate(args.mocksRoot, args.paths);
|
|
472
|
+
process.exit(exitCode);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
var mockCommand = defineCommand({
|
|
476
|
+
name: "mock",
|
|
477
|
+
description: "Mock API server management",
|
|
478
|
+
subCommands: {
|
|
479
|
+
start: startCommand,
|
|
480
|
+
validate: validateCommand
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// src/commands/module/index.ts
|
|
485
|
+
import { z as z2 } from "zod";
|
|
486
|
+
import { defineCommand as defineCommand2, arg as arg2 } from "politty";
|
|
487
|
+
|
|
488
|
+
// src/mdschema.ts
|
|
489
|
+
import path3 from "path";
|
|
490
|
+
import fs2 from "fs";
|
|
491
|
+
import { execFile } from "child_process";
|
|
492
|
+
import { createRequire } from "module";
|
|
493
|
+
var require2 = createRequire(import.meta.url);
|
|
494
|
+
function getMdschemaBin() {
|
|
495
|
+
const pkgPath = require2.resolve("@jackchuka/mdschema/package.json");
|
|
496
|
+
const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf-8"));
|
|
497
|
+
const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.mdschema;
|
|
498
|
+
if (!bin) {
|
|
499
|
+
throw new Error("Could not resolve mdschema binary from package.json bin field");
|
|
500
|
+
}
|
|
501
|
+
return path3.join(path3.dirname(pkgPath), bin);
|
|
502
|
+
}
|
|
503
|
+
function runMdschema(args, cwd4) {
|
|
504
|
+
return new Promise((resolve4) => {
|
|
505
|
+
execFile(
|
|
506
|
+
getMdschemaBin(),
|
|
507
|
+
args,
|
|
508
|
+
{ encoding: "utf-8", cwd: cwd4, timeout: 3e4 },
|
|
509
|
+
(error, stdout, stderr) => {
|
|
510
|
+
if (error) {
|
|
511
|
+
const execError = error;
|
|
512
|
+
resolve4({
|
|
513
|
+
exitCode: execError.code === "ENOENT" ? 127 : execError.status ?? 1,
|
|
514
|
+
stdout: stdout ?? "",
|
|
515
|
+
stderr: stderr ?? ""
|
|
516
|
+
});
|
|
517
|
+
} else {
|
|
518
|
+
resolve4({ exitCode: 0, stdout: stdout ?? "", stderr: stderr ?? "" });
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
);
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// src/schemas.ts
|
|
526
|
+
import path4 from "path";
|
|
527
|
+
var SCHEMAS_ROOT = path4.join(PACKAGE_ROOT, "schemas");
|
|
528
|
+
var MODULE_SCHEMAS = {
|
|
529
|
+
module: path4.join(SCHEMAS_ROOT, "module", "module.yml"),
|
|
530
|
+
command: path4.join(SCHEMAS_ROOT, "module", "command.yml"),
|
|
531
|
+
model: path4.join(SCHEMAS_ROOT, "module", "model.yml"),
|
|
532
|
+
feature: path4.join(SCHEMAS_ROOT, "module", "feature.yml"),
|
|
533
|
+
query: path4.join(SCHEMAS_ROOT, "module", "query.yml")
|
|
534
|
+
};
|
|
535
|
+
var APP_COMPOSE_SCHEMAS = {
|
|
536
|
+
requirements: path4.join(SCHEMAS_ROOT, "app-compose", "requirements.yml"),
|
|
537
|
+
actors: path4.join(SCHEMAS_ROOT, "app-compose", "actors.yml"),
|
|
538
|
+
"business-flow": path4.join(SCHEMAS_ROOT, "app-compose", "business-flow.yml"),
|
|
539
|
+
story: path4.join(SCHEMAS_ROOT, "app-compose", "story.yml"),
|
|
540
|
+
screen: path4.join(SCHEMAS_ROOT, "app-compose", "screen.yml"),
|
|
541
|
+
resolver: path4.join(SCHEMAS_ROOT, "app-compose", "resolver.yml")
|
|
542
|
+
};
|
|
543
|
+
var ALL_SCHEMAS = {
|
|
544
|
+
...MODULE_SCHEMAS,
|
|
545
|
+
...APP_COMPOSE_SCHEMAS
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
// src/commands/check.ts
|
|
549
|
+
function buildCheckTargets(config) {
|
|
550
|
+
const targets = [];
|
|
551
|
+
if (config.modulesRoot) {
|
|
552
|
+
const m = config.modulesRoot;
|
|
553
|
+
targets.push(
|
|
554
|
+
{ glob: `${m}/[a-zA-Z]*/docs/features/*.md`, schemaKey: "feature" },
|
|
555
|
+
{ glob: `${m}/[a-zA-Z]*/docs/commands/*.md`, schemaKey: "command" },
|
|
556
|
+
{ glob: `${m}/[a-zA-Z]*/docs/models/*.md`, schemaKey: "model" },
|
|
557
|
+
{ glob: `${m}/[a-zA-Z]*/docs/queries/*.md`, schemaKey: "query" },
|
|
558
|
+
{ glob: `${m}/[a-zA-Z]*/README.md`, schemaKey: "module" }
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
if (config.appRoot) {
|
|
562
|
+
const a = config.appRoot;
|
|
563
|
+
targets.push(
|
|
564
|
+
{ glob: `${a}/[a-zA-Z]*/README.md`, schemaKey: "requirements" },
|
|
565
|
+
{ glob: `${a}/[a-zA-Z]*/docs/actors/*.md`, schemaKey: "actors" },
|
|
566
|
+
{ glob: `${a}/[a-zA-Z]*/docs/business-flow/*/README.md`, schemaKey: "business-flow" },
|
|
567
|
+
{ glob: `${a}/[a-zA-Z]*/docs/business-flow/*/story/*.md`, schemaKey: "story" },
|
|
568
|
+
{ glob: `${a}/[a-zA-Z]*/docs/screen/*.md`, schemaKey: "screen" },
|
|
569
|
+
{ glob: `${a}/[a-zA-Z]*/docs/resolver/*.md`, schemaKey: "resolver" }
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
return targets;
|
|
573
|
+
}
|
|
574
|
+
async function runCheck(config, cwd4) {
|
|
575
|
+
const targets = buildCheckTargets(config);
|
|
576
|
+
const results = await Promise.all(
|
|
577
|
+
targets.map(async (target) => {
|
|
578
|
+
const schemaPath = ALL_SCHEMAS[target.schemaKey];
|
|
579
|
+
if (!schemaPath) {
|
|
580
|
+
console.error(`Unknown schema key: ${target.schemaKey}`);
|
|
581
|
+
return 2;
|
|
582
|
+
}
|
|
583
|
+
const { exitCode, stdout, stderr } = await runMdschema(
|
|
584
|
+
["check", target.glob, "--schema", schemaPath],
|
|
585
|
+
cwd4
|
|
586
|
+
);
|
|
587
|
+
if (stdout.trim()) console.log(stdout);
|
|
588
|
+
if (stderr.trim()) console.error(stderr);
|
|
589
|
+
return exitCode;
|
|
590
|
+
})
|
|
591
|
+
);
|
|
592
|
+
if (results.includes(2)) return 2;
|
|
593
|
+
return results.some((code) => code !== 0) ? 1 : 0;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// src/commands/sync-check.ts
|
|
597
|
+
import path5 from "path";
|
|
598
|
+
import fg from "fast-glob";
|
|
599
|
+
import chalk3 from "chalk";
|
|
600
|
+
function moduleCategories(root) {
|
|
601
|
+
return [
|
|
602
|
+
{
|
|
603
|
+
name: "command",
|
|
604
|
+
sourcePattern: `${root}/*/command/*.ts`,
|
|
605
|
+
docPattern: `${root}/*/docs/commands/*.md`,
|
|
606
|
+
exclusions: [/\.test\.ts$/]
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
name: "model",
|
|
610
|
+
sourcePattern: `${root}/*/db/*.ts`,
|
|
611
|
+
docPattern: `${root}/*/docs/models/*.md`,
|
|
612
|
+
exclusions: [/\.test\.ts$/, /^index\.ts$/]
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
name: "query",
|
|
616
|
+
sourcePattern: `${root}/*/query/*.ts`,
|
|
617
|
+
docPattern: `${root}/*/docs/queries/*.md`,
|
|
618
|
+
exclusions: [/\.test\.ts$/]
|
|
619
|
+
}
|
|
620
|
+
];
|
|
621
|
+
}
|
|
622
|
+
function appComposeCategories(root) {
|
|
623
|
+
return [
|
|
624
|
+
{
|
|
625
|
+
name: "resolver",
|
|
626
|
+
sourcePattern: `${root}/*/backend/src/modules/**/resolvers/*.ts`,
|
|
627
|
+
docPattern: `${root}/*/docs/resolver/*.md`,
|
|
628
|
+
exclusions: [/\.test\.ts$/, /^index\.ts$/]
|
|
629
|
+
}
|
|
630
|
+
];
|
|
631
|
+
}
|
|
632
|
+
function shouldExclude(fileName, exclusions) {
|
|
633
|
+
return exclusions.some((pattern) => pattern.test(fileName));
|
|
634
|
+
}
|
|
635
|
+
async function runSyncCheck(config, cwd4) {
|
|
636
|
+
const errors = [];
|
|
637
|
+
let totalSources = 0;
|
|
638
|
+
let totalDocs = 0;
|
|
639
|
+
const allCategories = [];
|
|
640
|
+
if (config.modulesRoot) {
|
|
641
|
+
allCategories.push(...moduleCategories(config.modulesRoot));
|
|
642
|
+
}
|
|
643
|
+
if (config.appRoot) {
|
|
644
|
+
allCategories.push(...appComposeCategories(config.appRoot));
|
|
645
|
+
}
|
|
646
|
+
for (const category of allCategories) {
|
|
647
|
+
const sources = await fg(category.sourcePattern, { cwd: cwd4 });
|
|
648
|
+
const docs = await fg(category.docPattern, { cwd: cwd4 });
|
|
649
|
+
const sourceBasenames = /* @__PURE__ */ new Map();
|
|
650
|
+
const docBasenames = /* @__PURE__ */ new Map();
|
|
651
|
+
for (const sourcePath of sources) {
|
|
652
|
+
const fileName = path5.basename(sourcePath);
|
|
653
|
+
if (shouldExclude(fileName, category.exclusions)) continue;
|
|
654
|
+
const basename2 = path5.basename(sourcePath, path5.extname(sourcePath));
|
|
655
|
+
sourceBasenames.set(basename2.toLowerCase(), sourcePath);
|
|
656
|
+
}
|
|
657
|
+
for (const docPath of docs) {
|
|
658
|
+
const basename2 = path5.basename(docPath, path5.extname(docPath));
|
|
659
|
+
docBasenames.set(basename2.toLowerCase(), docPath);
|
|
660
|
+
}
|
|
661
|
+
for (const [basename2, sourcePath] of sourceBasenames) {
|
|
662
|
+
if (!docBasenames.has(basename2)) {
|
|
663
|
+
errors.push({
|
|
664
|
+
type: "missing-doc",
|
|
665
|
+
category: category.name,
|
|
666
|
+
sourcePath,
|
|
667
|
+
expectedBasename: basename2
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
for (const [basename2, docPath] of docBasenames) {
|
|
672
|
+
if (!sourceBasenames.has(basename2)) {
|
|
673
|
+
errors.push({
|
|
674
|
+
type: "orphaned-doc",
|
|
675
|
+
category: category.name,
|
|
676
|
+
docPath,
|
|
677
|
+
expectedBasename: basename2
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
totalSources += sourceBasenames.size;
|
|
682
|
+
totalDocs += docBasenames.size;
|
|
683
|
+
}
|
|
684
|
+
return {
|
|
685
|
+
exitCode: errors.length > 0 ? 1 : 0,
|
|
686
|
+
errors,
|
|
687
|
+
summary: {
|
|
688
|
+
categoriesChecked: allCategories.length,
|
|
689
|
+
totalSources,
|
|
690
|
+
totalDocs
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function formatSyncCheckReport(result) {
|
|
695
|
+
const lines = [];
|
|
696
|
+
lines.push(chalk3.bold("docs-sync-check: Checking source-documentation correspondence...\n"));
|
|
697
|
+
if (result.errors.length > 0) {
|
|
698
|
+
lines.push(chalk3.red.bold("Errors:\n"));
|
|
699
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
700
|
+
for (const error of result.errors) {
|
|
701
|
+
const existing = byCategory.get(error.category) ?? [];
|
|
702
|
+
existing.push(error);
|
|
703
|
+
byCategory.set(error.category, existing);
|
|
704
|
+
}
|
|
705
|
+
for (const [category, categoryErrors] of byCategory) {
|
|
706
|
+
lines.push(chalk3.cyan(` Category: ${category}
|
|
707
|
+
`));
|
|
708
|
+
for (const error of categoryErrors) {
|
|
709
|
+
if (error.type === "missing-doc") {
|
|
710
|
+
lines.push(` ${chalk3.red(error.sourcePath)}`);
|
|
711
|
+
lines.push(` ${chalk3.yellow("Missing documentation for:")} ${error.expectedBasename}`);
|
|
674
712
|
} else {
|
|
675
|
-
|
|
713
|
+
lines.push(` ${chalk3.red(error.docPath)}`);
|
|
714
|
+
lines.push(
|
|
715
|
+
` ${chalk3.yellow("Orphaned documentation:")} no source file for ${error.expectedBasename}`
|
|
716
|
+
);
|
|
676
717
|
}
|
|
718
|
+
lines.push("");
|
|
677
719
|
}
|
|
678
720
|
}
|
|
721
|
+
} else {
|
|
722
|
+
lines.push(chalk3.green("All source files have corresponding documentation.\n"));
|
|
723
|
+
}
|
|
724
|
+
lines.push(chalk3.bold("Summary:"));
|
|
725
|
+
lines.push(` Categories checked: ${result.summary.categoriesChecked}`);
|
|
726
|
+
lines.push(
|
|
727
|
+
` Source files: ${result.summary.totalSources}, Doc files: ${result.summary.totalDocs}`
|
|
728
|
+
);
|
|
729
|
+
if (result.errors.length > 0) {
|
|
730
|
+
lines.push(chalk3.red(` Errors: ${result.errors.length}`));
|
|
731
|
+
lines.push("");
|
|
732
|
+
lines.push(chalk3.red.bold(`docs-sync-check failed with ${result.errors.length} error(s).`));
|
|
733
|
+
} else {
|
|
734
|
+
lines.push(chalk3.green(" Errors: 0"));
|
|
735
|
+
lines.push("");
|
|
736
|
+
lines.push(chalk3.green.bold("docs-sync-check passed."));
|
|
679
737
|
}
|
|
738
|
+
return lines.join("\n");
|
|
680
739
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
740
|
+
|
|
741
|
+
// src/commands/scaffold.ts
|
|
742
|
+
import path6 from "path";
|
|
743
|
+
import fs3 from "fs";
|
|
744
|
+
var MODULE_TYPES = ["module", "feature", "command", "model", "query"];
|
|
745
|
+
var APP_TYPES = [
|
|
746
|
+
"requirements",
|
|
747
|
+
"actors",
|
|
748
|
+
"business-flow",
|
|
749
|
+
"story",
|
|
750
|
+
"screen",
|
|
751
|
+
"resolver"
|
|
752
|
+
];
|
|
753
|
+
var ALL_TYPES = [...MODULE_TYPES, ...APP_TYPES];
|
|
754
|
+
var MODULE_DIR_MAP = {
|
|
755
|
+
feature: "docs/features",
|
|
756
|
+
command: "docs/commands",
|
|
757
|
+
model: "docs/models",
|
|
758
|
+
query: "docs/queries"
|
|
759
|
+
};
|
|
760
|
+
var APP_DIR_MAP = {
|
|
761
|
+
actors: "docs/actors",
|
|
762
|
+
"business-flow": "docs/business-flow",
|
|
763
|
+
screen: "docs/screen",
|
|
764
|
+
resolver: "docs/resolver"
|
|
765
|
+
};
|
|
766
|
+
function resolveScaffoldPath(type, parentName, name, root) {
|
|
767
|
+
if (type === "module" || type === "requirements") {
|
|
768
|
+
return path6.join(root, parentName, "README.md");
|
|
769
|
+
}
|
|
770
|
+
if (!name) {
|
|
771
|
+
throw new Error(`Name is required for scaffold type "${type}"`);
|
|
772
|
+
}
|
|
773
|
+
if (type === "business-flow") {
|
|
774
|
+
return path6.join(root, parentName, "docs/business-flow", name, "README.md");
|
|
775
|
+
}
|
|
776
|
+
if (type === "story") {
|
|
777
|
+
const parts = name.split("/");
|
|
778
|
+
if (parts.length !== 2) {
|
|
779
|
+
throw new Error(
|
|
780
|
+
`Story name must be "<flow>/<story>" (e.g., "onboarding/admin--create-user")`
|
|
781
|
+
);
|
|
688
782
|
}
|
|
783
|
+
return path6.join(root, parentName, "docs/business-flow", parts[0], "story", `${parts[1]}.md`);
|
|
689
784
|
}
|
|
690
|
-
|
|
691
|
-
}
|
|
692
|
-
function resolveScenarioDir(arg3) {
|
|
693
|
-
const abs = resolve3(arg3);
|
|
694
|
-
if (basename(abs) === "mock.json") return dirname2(abs);
|
|
695
|
-
return abs;
|
|
696
|
-
}
|
|
697
|
-
async function validateScenario(ctx, scenarioDir, mocksDir) {
|
|
698
|
-
const label = relative2(mocksDir, scenarioDir);
|
|
699
|
-
console.log(chalk3.bold(`
|
|
700
|
-
\u2500\u2500 ${label} \u2500\u2500`));
|
|
701
|
-
let entries;
|
|
702
|
-
try {
|
|
703
|
-
entries = await readdir(scenarioDir);
|
|
704
|
-
} catch {
|
|
705
|
-
fail(ctx, `${label}: directory not found`);
|
|
706
|
-
return;
|
|
785
|
+
if (MODULE_DIR_MAP[type]) {
|
|
786
|
+
return path6.join(root, parentName, MODULE_DIR_MAP[type], `${name}.md`);
|
|
707
787
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
const mockPath = join2(scenarioDir, "mock.json");
|
|
711
|
-
let data;
|
|
712
|
-
try {
|
|
713
|
-
data = JSON.parse(await readFile(mockPath, "utf-8"));
|
|
714
|
-
} catch (err) {
|
|
715
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
716
|
-
fail(ctx, `${label}: invalid JSON \u2014 ${errMsg}`);
|
|
717
|
-
return;
|
|
788
|
+
if (APP_DIR_MAP[type]) {
|
|
789
|
+
return path6.join(root, parentName, APP_DIR_MAP[type], `${name}.md`);
|
|
718
790
|
}
|
|
719
|
-
|
|
720
|
-
checkResponseQuality(ctx, data, label);
|
|
721
|
-
checkDatabucketRefs(ctx, data, label);
|
|
791
|
+
throw new Error(`Unknown scaffold type: ${type}`);
|
|
722
792
|
}
|
|
723
|
-
async function
|
|
724
|
-
const
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
fail(ctx, "No scenarios found under mocks/");
|
|
793
|
+
async function runScaffold(type, parentName, name, root, cwd4) {
|
|
794
|
+
const outputPath = resolveScaffoldPath(type, parentName, name, root);
|
|
795
|
+
const absoluteOutput = path6.resolve(cwd4, outputPath);
|
|
796
|
+
if (fs3.existsSync(absoluteOutput)) {
|
|
797
|
+
console.error(`File already exists: ${outputPath}`);
|
|
729
798
|
return 1;
|
|
730
799
|
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
800
|
+
const schemaPath = ALL_SCHEMAS[type];
|
|
801
|
+
if (!schemaPath) {
|
|
802
|
+
console.error(`No schema found for type: ${type}`);
|
|
803
|
+
return 2;
|
|
734
804
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
805
|
+
try {
|
|
806
|
+
fs3.mkdirSync(path6.dirname(absoluteOutput), { recursive: true });
|
|
807
|
+
} catch (err) {
|
|
808
|
+
console.error(
|
|
809
|
+
`Failed to create directory: ${err instanceof Error ? err.message : String(err)}`
|
|
810
|
+
);
|
|
811
|
+
return 1;
|
|
740
812
|
}
|
|
741
|
-
|
|
813
|
+
const { exitCode, stdout, stderr } = await runMdschema(
|
|
814
|
+
["generate", "--schema", schemaPath, "--output", absoluteOutput],
|
|
815
|
+
cwd4
|
|
816
|
+
);
|
|
817
|
+
if (stdout.trim()) console.log(stdout);
|
|
818
|
+
if (stderr.trim()) console.error(stderr);
|
|
819
|
+
return exitCode;
|
|
742
820
|
}
|
|
743
821
|
|
|
744
|
-
// src/commands/
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
822
|
+
// src/commands/module/list.ts
|
|
823
|
+
import { readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
|
|
824
|
+
import { join as join3 } from "path";
|
|
825
|
+
import chalk4 from "chalk";
|
|
826
|
+
var MODULES_DIR = join3(PACKAGE_ROOT, "src", "modules");
|
|
827
|
+
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["shared", "testing"]);
|
|
828
|
+
function countFiles(dir, pattern, exclusions) {
|
|
829
|
+
if (!existsSync2(dir)) return 0;
|
|
830
|
+
return readdirSync2(dir).filter((f) => pattern.test(f) && !exclusions.some((p) => p.test(f))).length;
|
|
831
|
+
}
|
|
832
|
+
function listModules() {
|
|
833
|
+
if (!existsSync2(MODULES_DIR)) return [];
|
|
834
|
+
return readdirSync2(MODULES_DIR, { withFileTypes: true }).filter((d) => d.isDirectory() && !EXCLUDED_DIRS.has(d.name)).map((d) => {
|
|
835
|
+
const modDir = join3(MODULES_DIR, d.name);
|
|
836
|
+
return {
|
|
837
|
+
name: d.name,
|
|
838
|
+
commands: countFiles(join3(modDir, "command"), /\.ts$/, [/\.test\.ts$/]),
|
|
839
|
+
models: countFiles(join3(modDir, "db"), /\.ts$/, [/\.test\.ts$/, /^index\.ts$/]),
|
|
840
|
+
features: countFiles(join3(modDir, "docs", "features"), /\.md$/, [])
|
|
841
|
+
};
|
|
842
|
+
}).sort((a, b) => a.name.localeCompare(b.name));
|
|
843
|
+
}
|
|
844
|
+
function formatModuleList(modules) {
|
|
845
|
+
if (modules.length === 0) return "No modules found.";
|
|
846
|
+
const lines = [];
|
|
847
|
+
lines.push(chalk4.bold("Modules:\n"));
|
|
848
|
+
const nameWidth = Math.max(...modules.map((m) => m.name.length), 4);
|
|
849
|
+
for (const mod of modules) {
|
|
850
|
+
const counts = [
|
|
851
|
+
`${mod.commands} commands`,
|
|
852
|
+
`${mod.models} models`,
|
|
853
|
+
`${mod.features} features`
|
|
854
|
+
].join(", ");
|
|
855
|
+
lines.push(` ${mod.name.padEnd(nameWidth)} ${counts}`);
|
|
856
|
+
}
|
|
857
|
+
lines.push("");
|
|
858
|
+
lines.push(`${modules.length} modules`);
|
|
859
|
+
return lines.join("\n");
|
|
860
|
+
}
|
|
861
|
+
function runModuleList() {
|
|
862
|
+
const modules = listModules();
|
|
863
|
+
console.log(formatModuleList(modules));
|
|
864
|
+
return 0;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// src/commands/module/index.ts
|
|
868
|
+
var cwd = process.cwd();
|
|
869
|
+
var rootArgs = z2.object({
|
|
870
|
+
root: arg2(z2.string(), {
|
|
871
|
+
alias: "r",
|
|
872
|
+
description: "Path to modules directory"
|
|
873
|
+
})
|
|
874
|
+
});
|
|
875
|
+
var listCommand = defineCommand2({
|
|
876
|
+
name: "list",
|
|
877
|
+
description: "List available modules",
|
|
878
|
+
run: () => {
|
|
879
|
+
const exitCode = runModuleList();
|
|
880
|
+
process.exit(exitCode);
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
var checkCommand = defineCommand2({
|
|
884
|
+
name: "check",
|
|
885
|
+
description: "Validate module docs against schemas",
|
|
886
|
+
args: rootArgs,
|
|
761
887
|
run: async (args) => {
|
|
762
|
-
const exitCode = await
|
|
888
|
+
const exitCode = await runCheck({ modulesRoot: args.root }, cwd);
|
|
763
889
|
process.exit(exitCode);
|
|
764
890
|
}
|
|
765
891
|
});
|
|
766
|
-
var
|
|
767
|
-
name: "
|
|
768
|
-
description: "Validate
|
|
769
|
-
args:
|
|
770
|
-
|
|
771
|
-
|
|
892
|
+
var syncCheckCommand = defineCommand2({
|
|
893
|
+
name: "sync-check",
|
|
894
|
+
description: "Validate source <-> doc correspondence",
|
|
895
|
+
args: rootArgs,
|
|
896
|
+
run: async (args) => {
|
|
897
|
+
const result = await runSyncCheck({ modulesRoot: args.root }, cwd);
|
|
898
|
+
console.log(formatSyncCheckReport(result));
|
|
899
|
+
process.exit(result.exitCode);
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
var scaffoldCommand = defineCommand2({
|
|
903
|
+
name: "scaffold",
|
|
904
|
+
description: "Generate module doc from schema template",
|
|
905
|
+
args: rootArgs.extend({
|
|
906
|
+
type: arg2(z2.enum(MODULE_TYPES), {
|
|
907
|
+
positional: true,
|
|
908
|
+
description: `Scaffold type (${MODULE_TYPES.join(", ")})`
|
|
772
909
|
}),
|
|
773
|
-
|
|
910
|
+
parent: arg2(z2.string(), {
|
|
774
911
|
positional: true,
|
|
775
|
-
description: "
|
|
912
|
+
description: "Module name"
|
|
913
|
+
}),
|
|
914
|
+
name: arg2(z2.string().optional(), {
|
|
915
|
+
positional: true,
|
|
916
|
+
description: "Item name (required for feature, command, model)"
|
|
776
917
|
})
|
|
777
918
|
}),
|
|
778
919
|
run: async (args) => {
|
|
779
|
-
const exitCode = await
|
|
920
|
+
const exitCode = await runScaffold(
|
|
921
|
+
args.type,
|
|
922
|
+
args.parent,
|
|
923
|
+
args.name,
|
|
924
|
+
args.root,
|
|
925
|
+
cwd
|
|
926
|
+
);
|
|
780
927
|
process.exit(exitCode);
|
|
781
928
|
}
|
|
782
929
|
});
|
|
783
|
-
var
|
|
784
|
-
name: "
|
|
785
|
-
description: "
|
|
930
|
+
var moduleCommand = defineCommand2({
|
|
931
|
+
name: "module",
|
|
932
|
+
description: "Module management",
|
|
786
933
|
subCommands: {
|
|
787
|
-
|
|
788
|
-
|
|
934
|
+
list: listCommand,
|
|
935
|
+
check: checkCommand,
|
|
936
|
+
"sync-check": syncCheckCommand,
|
|
937
|
+
scaffold: scaffoldCommand
|
|
789
938
|
}
|
|
790
939
|
});
|
|
791
940
|
|
|
792
|
-
// src/
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
alias: "a",
|
|
801
|
-
description: "Path to app-compose directory (apps/ or examples/)"
|
|
941
|
+
// src/commands/app/index.ts
|
|
942
|
+
import { z as z3 } from "zod";
|
|
943
|
+
import { defineCommand as defineCommand3, arg as arg3 } from "politty";
|
|
944
|
+
var cwd2 = process.cwd();
|
|
945
|
+
var rootArgs2 = z3.object({
|
|
946
|
+
root: arg3(z3.string(), {
|
|
947
|
+
alias: "r",
|
|
948
|
+
description: "Path to app-compose directory"
|
|
802
949
|
})
|
|
803
950
|
});
|
|
804
|
-
|
|
805
|
-
const paths = { modulesRoot: args.modulesRoot, appRoot: args.appRoot };
|
|
806
|
-
if (!paths.modulesRoot && !paths.appRoot) {
|
|
807
|
-
console.error("At least one of --modules-root or --app-root is required.");
|
|
808
|
-
process.exit(2);
|
|
809
|
-
}
|
|
810
|
-
return paths;
|
|
811
|
-
}
|
|
812
|
-
var checkCommand = defineCommand2({
|
|
951
|
+
var checkCommand2 = defineCommand3({
|
|
813
952
|
name: "check",
|
|
814
|
-
description: "Validate docs against schemas",
|
|
815
|
-
args:
|
|
953
|
+
description: "Validate app docs against schemas",
|
|
954
|
+
args: rootArgs2,
|
|
816
955
|
run: async (args) => {
|
|
817
|
-
const
|
|
818
|
-
const exitCode = await runCheck(paths, cwd);
|
|
956
|
+
const exitCode = await runCheck({ appRoot: args.root }, cwd2);
|
|
819
957
|
process.exit(exitCode);
|
|
820
958
|
}
|
|
821
959
|
});
|
|
822
|
-
var
|
|
960
|
+
var syncCheckCommand2 = defineCommand3({
|
|
823
961
|
name: "sync-check",
|
|
824
962
|
description: "Validate source <-> doc correspondence",
|
|
825
|
-
args:
|
|
963
|
+
args: rootArgs2,
|
|
826
964
|
run: async (args) => {
|
|
827
|
-
const
|
|
828
|
-
const result = await runSyncCheck(paths, cwd);
|
|
965
|
+
const result = await runSyncCheck({ appRoot: args.root }, cwd2);
|
|
829
966
|
console.log(formatSyncCheckReport(result));
|
|
830
967
|
process.exit(result.exitCode);
|
|
831
968
|
}
|
|
832
969
|
});
|
|
833
|
-
var
|
|
970
|
+
var scaffoldCommand2 = defineCommand3({
|
|
834
971
|
name: "scaffold",
|
|
835
|
-
description: "Generate doc
|
|
836
|
-
args:
|
|
837
|
-
type:
|
|
972
|
+
description: "Generate app doc from schema template",
|
|
973
|
+
args: rootArgs2.extend({
|
|
974
|
+
type: arg3(z3.enum(APP_TYPES), {
|
|
838
975
|
positional: true,
|
|
839
|
-
description: `Scaffold type (${
|
|
976
|
+
description: `Scaffold type (${APP_TYPES.join(", ")})`
|
|
840
977
|
}),
|
|
841
|
-
parent:
|
|
978
|
+
parent: arg3(z3.string(), {
|
|
842
979
|
positional: true,
|
|
843
|
-
description: "
|
|
980
|
+
description: "App name"
|
|
844
981
|
}),
|
|
845
|
-
name:
|
|
982
|
+
name: arg3(z3.string().optional(), {
|
|
846
983
|
positional: true,
|
|
847
984
|
description: "Item name (required for most types)"
|
|
848
985
|
})
|
|
849
986
|
}),
|
|
850
987
|
run: async (args) => {
|
|
851
|
-
const paths = requireRoot(args);
|
|
852
|
-
const root = isModuleType(args.type) ? paths.modulesRoot : paths.appRoot;
|
|
853
|
-
if (!root) {
|
|
854
|
-
console.error(
|
|
855
|
-
`--${isModuleType(args.type) ? "modules-root" : "app-root"} is required for scaffold type "${args.type}".`
|
|
856
|
-
);
|
|
857
|
-
process.exit(2);
|
|
858
|
-
}
|
|
859
988
|
const exitCode = await runScaffold(
|
|
860
989
|
args.type,
|
|
861
990
|
args.parent,
|
|
862
991
|
args.name,
|
|
863
|
-
root,
|
|
864
|
-
|
|
992
|
+
args.root,
|
|
993
|
+
cwd2
|
|
865
994
|
);
|
|
866
995
|
process.exit(exitCode);
|
|
867
996
|
}
|
|
868
997
|
});
|
|
869
|
-
var
|
|
998
|
+
var appCommand = defineCommand3({
|
|
999
|
+
name: "app",
|
|
1000
|
+
description: "App-compose management",
|
|
1001
|
+
subCommands: {
|
|
1002
|
+
check: checkCommand2,
|
|
1003
|
+
"sync-check": syncCheckCommand2,
|
|
1004
|
+
scaffold: scaffoldCommand2
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
// src/cli.ts
|
|
1009
|
+
var cwd3 = process.cwd();
|
|
1010
|
+
var initCommand = defineCommand4({
|
|
870
1011
|
name: "init",
|
|
871
|
-
description: "Set up consumer repo
|
|
872
|
-
args:
|
|
873
|
-
force:
|
|
1012
|
+
description: "Set up consumer repo with framework skills",
|
|
1013
|
+
args: z4.object({
|
|
1014
|
+
force: arg4(z4.boolean().default(false), {
|
|
874
1015
|
alias: "f",
|
|
875
|
-
description: "Overwrite existing skills
|
|
1016
|
+
description: "Overwrite existing skills"
|
|
876
1017
|
})
|
|
877
1018
|
}),
|
|
878
1019
|
run: (args) => {
|
|
879
|
-
const exitCode = runInit(
|
|
1020
|
+
const exitCode = runInit(cwd3, args.force);
|
|
880
1021
|
process.exit(exitCode);
|
|
881
1022
|
}
|
|
882
1023
|
});
|
|
883
|
-
var main =
|
|
1024
|
+
var main = defineCommand4({
|
|
884
1025
|
name: "erp-kit",
|
|
885
|
-
description: "
|
|
1026
|
+
description: "ERP module framework CLI",
|
|
886
1027
|
subCommands: {
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
init: initCommand
|
|
891
|
-
mock: mockCommand
|
|
1028
|
+
module: moduleCommand,
|
|
1029
|
+
app: appCommand,
|
|
1030
|
+
mock: mockCommand,
|
|
1031
|
+
init: initCommand
|
|
892
1032
|
}
|
|
893
1033
|
});
|
|
894
1034
|
void runMain(main);
|