@tailor-platform/erp-kit 0.0.1 → 0.1.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 +196 -28
- package/dist/cli.js +894 -0
- package/package.json +65 -8
- package/rules/app-compose/backend/auth.md +78 -0
- package/rules/app-compose/frontend/auth.md +55 -0
- package/rules/app-compose/frontend/component.md +55 -0
- package/rules/app-compose/frontend/page.md +86 -0
- package/rules/app-compose/frontend/screen-detailview.md +112 -0
- package/rules/app-compose/frontend/screen-form.md +145 -0
- package/rules/app-compose/frontend/screen-listview.md +159 -0
- package/rules/app-compose/structure.md +32 -0
- package/rules/module-development/commands.md +54 -0
- package/rules/module-development/cross-module-type-injection.md +28 -0
- package/rules/module-development/dependency-modules.md +24 -0
- package/rules/module-development/errors.md +12 -0
- package/rules/module-development/executors.md +67 -0
- package/rules/module-development/exports.md +13 -0
- package/rules/module-development/models.md +34 -0
- package/rules/module-development/structure.md +27 -0
- package/rules/module-development/sync-vs-async-operations.md +83 -0
- package/rules/module-development/testing.md +43 -0
- package/rules/sdk-best-practices/db-relations.md +74 -0
- package/rules/sdk-best-practices/sdk-docs.md +14 -0
- package/schemas/app-compose/actors.yml +34 -0
- package/schemas/app-compose/business-flow.yml +50 -0
- package/schemas/app-compose/requirements.yml +33 -0
- package/schemas/app-compose/resolver.yml +47 -0
- package/schemas/app-compose/screen.yml +81 -0
- package/schemas/app-compose/story.yml +67 -0
- package/schemas/module/command.yml +52 -0
- package/schemas/module/feature.yml +58 -0
- package/schemas/module/model.yml +70 -0
- package/schemas/module/module.yml +50 -0
- package/skills/1-module-docs/SKILL.md +107 -0
- package/skills/2-module-feature-breakdown/SKILL.md +66 -0
- package/skills/3-module-doc-review/SKILL.md +230 -0
- package/skills/4-module-tdd-implementation/SKILL.md +56 -0
- package/skills/5-module-implementation-review/SKILL.md +400 -0
- package/skills/app-compose-1-requirement-analysis/SKILL.md +85 -0
- package/skills/app-compose-2-requirements-breakdown/SKILL.md +88 -0
- package/skills/app-compose-3-doc-review/SKILL.md +112 -0
- package/skills/app-compose-4-design-mock/SKILL.md +248 -0
- package/skills/app-compose-5-design-mock-review/SKILL.md +283 -0
- package/skills/app-compose-6-implementation-spec/SKILL.md +122 -0
- package/skills/mock-scenario/SKILL.md +118 -0
- package/src/app.ts +1 -0
- package/src/cli.ts +120 -0
- package/src/commands/check.test.ts +30 -0
- package/src/commands/check.ts +66 -0
- package/src/commands/init.test.ts +77 -0
- package/src/commands/init.ts +87 -0
- package/src/commands/mock/index.ts +53 -0
- package/src/commands/mock/start.ts +179 -0
- package/src/commands/mock/validate.test.ts +185 -0
- package/src/commands/mock/validate.ts +198 -0
- package/src/commands/scaffold.test.ts +76 -0
- package/src/commands/scaffold.ts +119 -0
- package/src/commands/sync-check.test.ts +125 -0
- package/src/commands/sync-check.ts +182 -0
- package/src/integration.test.ts +63 -0
- package/src/mdschema.ts +48 -0
- package/src/mockServer.ts +55 -0
- package/src/module.ts +86 -0
- package/src/modules/accounting/.gitkeep +0 -0
- package/src/modules/coa-management/.gitkeep +0 -0
- package/src/modules/inventory/.gitkeep +0 -0
- package/src/modules/manufacturing/.gitkeep +0 -0
- package/src/modules/primitives/README.md +39 -0
- package/src/modules/primitives/command/activateCategory.test.ts +75 -0
- package/src/modules/primitives/command/activateCategory.ts +50 -0
- package/src/modules/primitives/command/activateCurrency.test.ts +70 -0
- package/src/modules/primitives/command/activateCurrency.ts +50 -0
- package/src/modules/primitives/command/activateUnit.test.ts +53 -0
- package/src/modules/primitives/command/activateUnit.ts +50 -0
- package/src/modules/primitives/command/convertAmount.test.ts +275 -0
- package/src/modules/primitives/command/convertAmount.ts +126 -0
- package/src/modules/primitives/command/convertQuantity.test.ts +219 -0
- package/src/modules/primitives/command/convertQuantity.ts +73 -0
- package/src/modules/primitives/command/createCategory.test.ts +126 -0
- package/src/modules/primitives/command/createCategory.ts +89 -0
- package/src/modules/primitives/command/createCurrency.test.ts +191 -0
- package/src/modules/primitives/command/createCurrency.ts +77 -0
- package/src/modules/primitives/command/createExchangeRate.test.ts +216 -0
- package/src/modules/primitives/command/createExchangeRate.ts +91 -0
- package/src/modules/primitives/command/createUnit.test.ts +214 -0
- package/src/modules/primitives/command/createUnit.ts +88 -0
- package/src/modules/primitives/command/deactivateCategory.test.ts +97 -0
- package/src/modules/primitives/command/deactivateCategory.ts +62 -0
- package/src/modules/primitives/command/deactivateCurrency.test.ts +85 -0
- package/src/modules/primitives/command/deactivateCurrency.ts +55 -0
- package/src/modules/primitives/command/deactivateUnit.test.ts +78 -0
- package/src/modules/primitives/command/deactivateUnit.ts +62 -0
- package/src/modules/primitives/command/setBaseCurrency.test.ts +98 -0
- package/src/modules/primitives/command/setBaseCurrency.ts +74 -0
- package/src/modules/primitives/command/setReferenceUnit.test.ts +108 -0
- package/src/modules/primitives/command/setReferenceUnit.ts +84 -0
- package/src/modules/primitives/db/currency.ts +30 -0
- package/src/modules/primitives/db/exchangeRate.ts +28 -0
- package/src/modules/primitives/db/unit.ts +32 -0
- package/src/modules/primitives/db/uomCategory.ts +32 -0
- package/src/modules/primitives/docs/commands/ActivateCategory.md +34 -0
- package/src/modules/primitives/docs/commands/ActivateCurrency.md +33 -0
- package/src/modules/primitives/docs/commands/ActivateUnit.md +34 -0
- package/src/modules/primitives/docs/commands/ConvertAmount.md +50 -0
- package/src/modules/primitives/docs/commands/ConvertQuantity.md +43 -0
- package/src/modules/primitives/docs/commands/CreateCategory.md +44 -0
- package/src/modules/primitives/docs/commands/CreateCurrency.md +47 -0
- package/src/modules/primitives/docs/commands/CreateExchangeRate.md +48 -0
- package/src/modules/primitives/docs/commands/CreateUnit.md +48 -0
- package/src/modules/primitives/docs/commands/DeactivateCategory.md +38 -0
- package/src/modules/primitives/docs/commands/DeactivateCurrency.md +38 -0
- package/src/modules/primitives/docs/commands/DeactivateUnit.md +38 -0
- package/src/modules/primitives/docs/commands/SetBaseCurrency.md +39 -0
- package/src/modules/primitives/docs/commands/SetReferenceUnit.md +43 -0
- package/src/modules/primitives/docs/features/currency-definitions.md +55 -0
- package/src/modules/primitives/docs/features/exchange-rates.md +61 -0
- package/src/modules/primitives/docs/features/unit-conversion.md +66 -0
- package/src/modules/primitives/docs/features/uom-categories.md +52 -0
- package/src/modules/primitives/docs/models/Currency.md +45 -0
- package/src/modules/primitives/docs/models/ExchangeRate.md +33 -0
- package/src/modules/primitives/docs/models/Unit.md +46 -0
- package/src/modules/primitives/docs/models/UoMCategory.md +44 -0
- package/src/modules/primitives/generated/kysely-tailordb.ts +95 -0
- package/src/modules/primitives/index.ts +40 -0
- package/src/modules/primitives/lib/errors.ts +138 -0
- package/src/modules/primitives/lib/types.ts +20 -0
- package/src/modules/primitives/module.ts +66 -0
- package/src/modules/primitives/permissions.ts +18 -0
- package/src/modules/primitives/tailor.config.ts +11 -0
- package/src/modules/primitives/testing/fixtures.ts +161 -0
- package/src/modules/product-management/.gitkeep +0 -0
- package/src/modules/purchase/.gitkeep +0 -0
- package/src/modules/sales/.gitkeep +0 -0
- package/src/modules/shared/createContext.test.ts +39 -0
- package/src/modules/shared/createContext.ts +15 -0
- package/src/modules/shared/defineCommand.test.ts +42 -0
- package/src/modules/shared/defineCommand.ts +19 -0
- package/src/modules/shared/definePermissions.test.ts +146 -0
- package/src/modules/shared/definePermissions.ts +94 -0
- package/src/modules/shared/entityTypes.ts +15 -0
- package/src/modules/shared/errors.ts +22 -0
- package/src/modules/shared/index.ts +1 -0
- package/src/modules/shared/internal.ts +13 -0
- package/src/modules/shared/requirePermission.test.ts +47 -0
- package/src/modules/shared/requirePermission.ts +8 -0
- package/src/modules/shared/types.ts +4 -0
- package/src/modules/supplier-management/.gitkeep +0 -0
- package/src/modules/supplier-portal/.gitkeep +0 -0
- package/src/modules/testing/index.ts +120 -0
- package/src/modules/user-management/README.md +38 -0
- package/src/modules/user-management/command/activateUser.test.ts +112 -0
- package/src/modules/user-management/command/activateUser.ts +67 -0
- package/src/modules/user-management/command/assignPermissionToRole.test.ts +119 -0
- package/src/modules/user-management/command/assignPermissionToRole.ts +87 -0
- package/src/modules/user-management/command/assignRoleToUser.test.ts +162 -0
- package/src/modules/user-management/command/assignRoleToUser.ts +93 -0
- package/src/modules/user-management/command/createPermission.test.ts +143 -0
- package/src/modules/user-management/command/createPermission.ts +66 -0
- package/src/modules/user-management/command/createRole.test.ts +115 -0
- package/src/modules/user-management/command/createRole.ts +52 -0
- package/src/modules/user-management/command/createUser.test.ts +198 -0
- package/src/modules/user-management/command/createUser.ts +85 -0
- package/src/modules/user-management/command/deactivateUser.test.ts +112 -0
- package/src/modules/user-management/command/deactivateUser.ts +67 -0
- package/src/modules/user-management/command/logAuditEvent.test.ts +179 -0
- package/src/modules/user-management/command/logAuditEvent.ts +59 -0
- package/src/modules/user-management/command/reactivateUser.test.ts +115 -0
- package/src/modules/user-management/command/reactivateUser.ts +67 -0
- package/src/modules/user-management/command/revokePermissionFromRole.test.ts +112 -0
- package/src/modules/user-management/command/revokePermissionFromRole.ts +81 -0
- package/src/modules/user-management/command/revokeRoleFromUser.test.ts +112 -0
- package/src/modules/user-management/command/revokeRoleFromUser.ts +81 -0
- package/src/modules/user-management/db/auditEvent.ts +47 -0
- package/src/modules/user-management/db/permission.ts +31 -0
- package/src/modules/user-management/db/role.ts +28 -0
- package/src/modules/user-management/db/rolePermission.ts +44 -0
- package/src/modules/user-management/db/user.ts +38 -0
- package/src/modules/user-management/db/userRole.ts +44 -0
- package/src/modules/user-management/docs/commands/ActivateUser.md +36 -0
- package/src/modules/user-management/docs/commands/AssignPermissionToRole.md +39 -0
- package/src/modules/user-management/docs/commands/AssignRoleToUser.md +43 -0
- package/src/modules/user-management/docs/commands/CreatePermission.md +35 -0
- package/src/modules/user-management/docs/commands/CreateRole.md +35 -0
- package/src/modules/user-management/docs/commands/CreateUser.md +41 -0
- package/src/modules/user-management/docs/commands/DeactivateUser.md +38 -0
- package/src/modules/user-management/docs/commands/LogAuditEvent.md +37 -0
- package/src/modules/user-management/docs/commands/ReactivateUser.md +37 -0
- package/src/modules/user-management/docs/commands/RevokePermissionFromRole.md +40 -0
- package/src/modules/user-management/docs/commands/RevokeRoleFromUser.md +40 -0
- package/src/modules/user-management/docs/features/audit-trail.md +80 -0
- package/src/modules/user-management/docs/features/role-based-access-control.md +76 -0
- package/src/modules/user-management/docs/features/user-account-management.md +64 -0
- package/src/modules/user-management/docs/models/AuditEvent.md +34 -0
- package/src/modules/user-management/docs/models/Permission.md +31 -0
- package/src/modules/user-management/docs/models/Role.md +31 -0
- package/src/modules/user-management/docs/models/RolePermission.md +33 -0
- package/src/modules/user-management/docs/models/User.md +47 -0
- package/src/modules/user-management/docs/models/UserRole.md +34 -0
- package/src/modules/user-management/docs/plans/2026-01-30-flattened-permissions-design.md +52 -0
- package/src/modules/user-management/executor/recomputeOnRolePermissionChange.ts +61 -0
- package/src/modules/user-management/generated/enums.ts +24 -0
- package/src/modules/user-management/generated/kysely-tailordb.ts +112 -0
- package/src/modules/user-management/index.ts +32 -0
- package/src/modules/user-management/lib/errors.ts +81 -0
- package/src/modules/user-management/lib/recomputeUserPermissions.ts +53 -0
- package/src/modules/user-management/lib/types.ts +31 -0
- package/src/modules/user-management/module.ts +77 -0
- package/src/modules/user-management/permissions.ts +15 -0
- package/src/modules/user-management/tailor.config.ts +11 -0
- package/src/modules/user-management/testing/fixtures.ts +98 -0
- package/src/schemas.ts +25 -0
- package/src/testing.ts +10 -0
- package/src/util.ts +3 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mock-scenario
|
|
3
|
+
description: Scaffold a new Mockoon mock scenario with CRUD routes, error scenarios, and registry updates
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Mock Scenario Scaffolder
|
|
7
|
+
|
|
8
|
+
Generate a complete Mockoon mock API config for a new provider scenario and wire it into the registry.
|
|
9
|
+
|
|
10
|
+
## Workflow
|
|
11
|
+
|
|
12
|
+
### Step 1: Identify target API
|
|
13
|
+
|
|
14
|
+
Ask the user for:
|
|
15
|
+
|
|
16
|
+
- **Provider name** (e.g. `stripe`, `github`, `twilio`)
|
|
17
|
+
- **Scenario name** (e.g. `admin-api`, `payments-api`, `product-sync`)
|
|
18
|
+
- **Key resources to mock** (e.g. customers, invoices, messages)
|
|
19
|
+
- **Scenario to test** (e.g. initial full sync, incremental sync, webhook-triggered update, error recovery)
|
|
20
|
+
|
|
21
|
+
### Step 2: Research real API
|
|
22
|
+
|
|
23
|
+
Use web search to find:
|
|
24
|
+
|
|
25
|
+
- Base URL and API version
|
|
26
|
+
- Authentication scheme (API key, OAuth, bearer token)
|
|
27
|
+
- Endpoint patterns and HTTP methods
|
|
28
|
+
- Response shapes for the key resources
|
|
29
|
+
- Error response format (status codes, error body structure)
|
|
30
|
+
- Rate limit headers
|
|
31
|
+
|
|
32
|
+
### Step 3: Generate Mockoon JSON
|
|
33
|
+
|
|
34
|
+
Create `mocks/{provider}/{scenario}/mock.json` following the Shopify admin-api mock as the canonical reference (`mocks/shopify/admin-api/mock.json`).
|
|
35
|
+
|
|
36
|
+
The config must include:
|
|
37
|
+
|
|
38
|
+
- **uuid**: a valid UUID v4
|
|
39
|
+
- **name**: Human-readable API name
|
|
40
|
+
- **endpointPrefix**: Match the real API's base path
|
|
41
|
+
- **port**: `3000` (overridden at runtime by the reverse proxy launcher)
|
|
42
|
+
- **hostname**: `0.0.0.0`
|
|
43
|
+
- **latency**: `0`
|
|
44
|
+
- **folders**: `[]`
|
|
45
|
+
- **rootChildren**: `[]`
|
|
46
|
+
- **proxyMode**: `false`
|
|
47
|
+
- **proxyHost**: `""`
|
|
48
|
+
- **proxyRemovePrefix**: `false`
|
|
49
|
+
- **tlsOptions**: `{ "enabled": false, "type": "CERT", "pfxPath": "", "certPath": "", "keyPath": "", "caPath": "", "passphrase": "" }`
|
|
50
|
+
- **cors**: `true`
|
|
51
|
+
- **headers**: `[{ "key": "Content-Type", "value": "application/json" }]`
|
|
52
|
+
- **proxyReqHeaders**: `[]`
|
|
53
|
+
- **proxyResHeaders**: `[]`
|
|
54
|
+
- **callbacks**: `[]`
|
|
55
|
+
|
|
56
|
+
All UUIDs (environment, routes, responses, data buckets) must be valid UUID v4 values.
|
|
57
|
+
|
|
58
|
+
**Data buckets** — for each stateful/CRUD resource:
|
|
59
|
+
|
|
60
|
+
- 2–3 seed records with realistic field values
|
|
61
|
+
- Use the `id` field matching Mockoon's `"id"` property for CRUD lookup
|
|
62
|
+
|
|
63
|
+
**Routes:**
|
|
64
|
+
|
|
65
|
+
1. **CRUD routes** — for mutable resources:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"type": "crud",
|
|
70
|
+
"endpoint": "{resource}",
|
|
71
|
+
"responses": [
|
|
72
|
+
{
|
|
73
|
+
"label": "CRUD {Resource}",
|
|
74
|
+
"statusCode": 200,
|
|
75
|
+
"headers": [{ "key": "Content-Type", "value": "application/json" }],
|
|
76
|
+
"bodyType": "DATABUCKET",
|
|
77
|
+
"databucketID": "{resource-bucket-id}"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
2. **Static routes** — for read-only endpoints:
|
|
84
|
+
- List endpoints returning JSON arrays
|
|
85
|
+
- Single-item endpoints using `{{urlParam 'id'}}` interpolation
|
|
86
|
+
|
|
87
|
+
3. **Error scenarios** — triggered via `X-Test-Scenario` header:
|
|
88
|
+
- `unauthorized` → 401
|
|
89
|
+
- `not-found` → 404
|
|
90
|
+
- `rate-limit` → 429 with `Retry-After` header
|
|
91
|
+
- `server-error` → 500
|
|
92
|
+
|
|
93
|
+
4. **Catch-all** — `*` endpoint returning 500 when `X-Test-Scenario: server-error`
|
|
94
|
+
|
|
95
|
+
**Every route must have:** `type`, `documentation`, `responseMode: null`, `streamingMode: null`, `streamingInterval: 0`
|
|
96
|
+
|
|
97
|
+
**Every response must have:** `latency: 0`, `bodyType`, `databucketID`, `filePath: ""`, `sendFileAsBody: false`, `rules`, `rulesOperator: "OR"`, `disableTemplating: false`, `fallbackTo404: false`, `default`, `crudKey: "id"`, `callbacks: []`, a `Content-Type` header, and a non-empty `label`
|
|
98
|
+
|
|
99
|
+
**Every rule must have:** `target`, `modifier`, `value`, `operator`, `invert: false`
|
|
100
|
+
|
|
101
|
+
### Step 4: Generate scenario README
|
|
102
|
+
|
|
103
|
+
Create `mocks/{provider}/{scenario}/README.md` with:
|
|
104
|
+
|
|
105
|
+
- Title and one-line description
|
|
106
|
+
- Quick start with both methods: `erp-kit mock start` (proxy) and direct `npx @mockoon/cli start`
|
|
107
|
+
- Endpoints table (method, path, scenarios)
|
|
108
|
+
- CRUD workflow example with curl commands using proxy URL (`http://localhost:3000/{provider}/{scenario}/...`)
|
|
109
|
+
- Error scenario examples
|
|
110
|
+
- Test data description
|
|
111
|
+
|
|
112
|
+
### Step 5: Validate
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
erp-kit mock validate
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Fix any failures before finishing.
|
package/src/app.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createContext } from "./modules/shared/createContext";
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { defineCommand, runMain, arg } from "politty";
|
|
5
|
+
import { runCheck } from "./commands/check.js";
|
|
6
|
+
import { runSyncCheck, formatSyncCheckReport } from "./commands/sync-check.js";
|
|
7
|
+
import { runScaffold, ALL_TYPES, isModuleType, type ScaffoldType } from "./commands/scaffold.js";
|
|
8
|
+
import { runInit } from "./commands/init.js";
|
|
9
|
+
import { mockCommand } from "./commands/mock/index.js";
|
|
10
|
+
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
|
|
13
|
+
const rootArgs = z.object({
|
|
14
|
+
modulesRoot: arg(z.string().optional(), {
|
|
15
|
+
alias: "m",
|
|
16
|
+
description: "Path to modules directory",
|
|
17
|
+
}),
|
|
18
|
+
appRoot: arg(z.string().optional(), {
|
|
19
|
+
alias: "a",
|
|
20
|
+
description: "Path to app-compose directory (apps/ or examples/)",
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
function requireRoot(args: { modulesRoot?: string; appRoot?: string }) {
|
|
25
|
+
const paths = { modulesRoot: args.modulesRoot, appRoot: args.appRoot };
|
|
26
|
+
if (!paths.modulesRoot && !paths.appRoot) {
|
|
27
|
+
console.error("At least one of --modules-root or --app-root is required.");
|
|
28
|
+
process.exit(2);
|
|
29
|
+
}
|
|
30
|
+
return paths;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const checkCommand = defineCommand({
|
|
34
|
+
name: "check",
|
|
35
|
+
description: "Validate docs against schemas",
|
|
36
|
+
args: rootArgs,
|
|
37
|
+
run: async (args) => {
|
|
38
|
+
const paths = requireRoot(args);
|
|
39
|
+
const exitCode = await runCheck(paths, cwd);
|
|
40
|
+
process.exit(exitCode);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const syncCheckCommand = defineCommand({
|
|
45
|
+
name: "sync-check",
|
|
46
|
+
description: "Validate source <-> doc correspondence",
|
|
47
|
+
args: rootArgs,
|
|
48
|
+
run: async (args) => {
|
|
49
|
+
const paths = requireRoot(args);
|
|
50
|
+
const result = await runSyncCheck(paths, cwd);
|
|
51
|
+
console.log(formatSyncCheckReport(result));
|
|
52
|
+
process.exit(result.exitCode);
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const scaffoldCommand = defineCommand({
|
|
57
|
+
name: "scaffold",
|
|
58
|
+
description: "Generate doc file from schema template",
|
|
59
|
+
args: rootArgs.extend({
|
|
60
|
+
type: arg(z.enum(ALL_TYPES as unknown as [string, ...string[]]), {
|
|
61
|
+
positional: true,
|
|
62
|
+
description: `Scaffold type (${ALL_TYPES.join(", ")})`,
|
|
63
|
+
}),
|
|
64
|
+
parent: arg(z.string(), {
|
|
65
|
+
positional: true,
|
|
66
|
+
description: "Parent name (module or app name)",
|
|
67
|
+
}),
|
|
68
|
+
name: arg(z.string().optional(), {
|
|
69
|
+
positional: true,
|
|
70
|
+
description: "Item name (required for most types)",
|
|
71
|
+
}),
|
|
72
|
+
}),
|
|
73
|
+
run: async (args) => {
|
|
74
|
+
const paths = requireRoot(args);
|
|
75
|
+
const root = isModuleType(args.type) ? paths.modulesRoot : paths.appRoot;
|
|
76
|
+
if (!root) {
|
|
77
|
+
console.error(
|
|
78
|
+
`--${isModuleType(args.type) ? "modules-root" : "app-root"} is required for scaffold type "${args.type}".`,
|
|
79
|
+
);
|
|
80
|
+
process.exit(2);
|
|
81
|
+
}
|
|
82
|
+
const exitCode = await runScaffold(
|
|
83
|
+
args.type as ScaffoldType,
|
|
84
|
+
args.parent,
|
|
85
|
+
args.name,
|
|
86
|
+
root,
|
|
87
|
+
cwd,
|
|
88
|
+
);
|
|
89
|
+
process.exit(exitCode);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const initCommand = defineCommand({
|
|
94
|
+
name: "init",
|
|
95
|
+
description: "Set up consumer repo (skills, rules)",
|
|
96
|
+
args: z.object({
|
|
97
|
+
force: arg(z.boolean().default(false), {
|
|
98
|
+
alias: "f",
|
|
99
|
+
description: "Overwrite existing skills and rules",
|
|
100
|
+
}),
|
|
101
|
+
}),
|
|
102
|
+
run: (args) => {
|
|
103
|
+
const exitCode = runInit(cwd, args.force);
|
|
104
|
+
process.exit(exitCode);
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const main = defineCommand({
|
|
109
|
+
name: "erp-kit",
|
|
110
|
+
description: "Documentation validation and scaffolding tool",
|
|
111
|
+
subCommands: {
|
|
112
|
+
check: checkCommand,
|
|
113
|
+
"sync-check": syncCheckCommand,
|
|
114
|
+
scaffold: scaffoldCommand,
|
|
115
|
+
init: initCommand,
|
|
116
|
+
mock: mockCommand,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
void runMain(main);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { buildCheckTargets } from "./check.js";
|
|
3
|
+
|
|
4
|
+
describe("buildCheckTargets", () => {
|
|
5
|
+
it("generates module targets when modulesRoot is set", () => {
|
|
6
|
+
const targets = buildCheckTargets({ modulesRoot: "modules", appRoot: undefined });
|
|
7
|
+
expect(targets).toEqual([
|
|
8
|
+
{ glob: "modules/[a-zA-Z]*/docs/features/*.md", schemaKey: "feature" },
|
|
9
|
+
{ glob: "modules/[a-zA-Z]*/docs/commands/*.md", schemaKey: "command" },
|
|
10
|
+
{ glob: "modules/[a-zA-Z]*/docs/models/*.md", schemaKey: "model" },
|
|
11
|
+
{ glob: "modules/[a-zA-Z]*/README.md", schemaKey: "module" },
|
|
12
|
+
]);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("generates app-compose targets when appRoot is set", () => {
|
|
16
|
+
const targets = buildCheckTargets({ modulesRoot: undefined, appRoot: "apps" });
|
|
17
|
+
expect(targets[0].glob).toBe("apps/[a-zA-Z]*/README.md");
|
|
18
|
+
expect(targets).toHaveLength(6);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("generates both when both roots are set", () => {
|
|
22
|
+
const targets = buildCheckTargets({ modulesRoot: "modules", appRoot: "examples" });
|
|
23
|
+
expect(targets).toHaveLength(10);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("returns empty when neither root is set", () => {
|
|
27
|
+
const targets = buildCheckTargets({ modulesRoot: undefined, appRoot: undefined });
|
|
28
|
+
expect(targets).toHaveLength(0);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { runMdschema } from "../mdschema.js";
|
|
2
|
+
import { ALL_SCHEMAS } from "../schemas.js";
|
|
3
|
+
export interface CheckTarget {
|
|
4
|
+
glob: string;
|
|
5
|
+
schemaKey: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function buildCheckTargets(config: {
|
|
9
|
+
modulesRoot?: string;
|
|
10
|
+
appRoot?: string;
|
|
11
|
+
}): CheckTarget[] {
|
|
12
|
+
const targets: CheckTarget[] = [];
|
|
13
|
+
|
|
14
|
+
if (config.modulesRoot) {
|
|
15
|
+
const m = config.modulesRoot;
|
|
16
|
+
targets.push(
|
|
17
|
+
{ glob: `${m}/[a-zA-Z]*/docs/features/*.md`, schemaKey: "feature" },
|
|
18
|
+
{ glob: `${m}/[a-zA-Z]*/docs/commands/*.md`, schemaKey: "command" },
|
|
19
|
+
{ glob: `${m}/[a-zA-Z]*/docs/models/*.md`, schemaKey: "model" },
|
|
20
|
+
{ glob: `${m}/[a-zA-Z]*/README.md`, schemaKey: "module" },
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (config.appRoot) {
|
|
25
|
+
const a = config.appRoot;
|
|
26
|
+
targets.push(
|
|
27
|
+
{ glob: `${a}/[a-zA-Z]*/README.md`, schemaKey: "requirements" },
|
|
28
|
+
{ glob: `${a}/[a-zA-Z]*/docs/actors/*.md`, schemaKey: "actors" },
|
|
29
|
+
{ glob: `${a}/[a-zA-Z]*/docs/business-flow/*/README.md`, schemaKey: "business-flow" },
|
|
30
|
+
{ glob: `${a}/[a-zA-Z]*/docs/business-flow/*/story/*.md`, schemaKey: "story" },
|
|
31
|
+
{ glob: `${a}/[a-zA-Z]*/docs/screen/*.md`, schemaKey: "screen" },
|
|
32
|
+
{ glob: `${a}/[a-zA-Z]*/docs/resolver/*.md`, schemaKey: "resolver" },
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return targets;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function runCheck(
|
|
40
|
+
config: { modulesRoot?: string; appRoot?: string },
|
|
41
|
+
cwd: string,
|
|
42
|
+
): Promise<number> {
|
|
43
|
+
const targets = buildCheckTargets(config);
|
|
44
|
+
|
|
45
|
+
const results = await Promise.all(
|
|
46
|
+
targets.map(async (target) => {
|
|
47
|
+
const schemaPath = ALL_SCHEMAS[target.schemaKey];
|
|
48
|
+
if (!schemaPath) {
|
|
49
|
+
console.error(`Unknown schema key: ${target.schemaKey}`);
|
|
50
|
+
return 2;
|
|
51
|
+
}
|
|
52
|
+
const { exitCode, stdout, stderr } = await runMdschema(
|
|
53
|
+
["check", target.glob, "--schema", schemaPath],
|
|
54
|
+
cwd,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (stdout.trim()) console.log(stdout);
|
|
58
|
+
if (stderr.trim()) console.error(stderr);
|
|
59
|
+
|
|
60
|
+
return exitCode;
|
|
61
|
+
}),
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (results.includes(2)) return 2;
|
|
65
|
+
return results.some((code) => code !== 0) ? 1 : 0;
|
|
66
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
5
|
+
import { runInit } from "./init.js";
|
|
6
|
+
|
|
7
|
+
describe("runInit", () => {
|
|
8
|
+
let tmpDir: string;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "init-test-"));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("copies framework skills to .agents/skills/", () => {
|
|
19
|
+
runInit(tmpDir, false);
|
|
20
|
+
const skillPath = path.join(tmpDir, ".agents", "skills", "1-module-docs", "SKILL.md");
|
|
21
|
+
expect(fs.existsSync(skillPath)).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("does not overwrite project-specific skills", () => {
|
|
25
|
+
const customSkillDir = path.join(tmpDir, ".agents", "skills", "my-custom-skill");
|
|
26
|
+
fs.mkdirSync(customSkillDir, { recursive: true });
|
|
27
|
+
fs.writeFileSync(path.join(customSkillDir, "SKILL.md"), "# Custom");
|
|
28
|
+
runInit(tmpDir, false);
|
|
29
|
+
const content = fs.readFileSync(path.join(customSkillDir, "SKILL.md"), "utf-8");
|
|
30
|
+
expect(content).toBe("# Custom");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("does not overwrite existing framework skills", () => {
|
|
34
|
+
const skillDir = path.join(tmpDir, ".agents", "skills", "1-module-docs");
|
|
35
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
36
|
+
fs.writeFileSync(path.join(skillDir, "SKILL.md"), "# Customized");
|
|
37
|
+
runInit(tmpDir, false);
|
|
38
|
+
const content = fs.readFileSync(path.join(skillDir, "SKILL.md"), "utf-8");
|
|
39
|
+
expect(content).toBe("# Customized");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("overwrites existing framework skills with --force", () => {
|
|
43
|
+
const skillDir = path.join(tmpDir, ".agents", "skills", "1-module-docs");
|
|
44
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
45
|
+
fs.writeFileSync(path.join(skillDir, "SKILL.md"), "# Customized");
|
|
46
|
+
runInit(tmpDir, true);
|
|
47
|
+
const content = fs.readFileSync(path.join(skillDir, "SKILL.md"), "utf-8");
|
|
48
|
+
expect(content).not.toBe("# Customized");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("copies framework rules to .agents/rules/", () => {
|
|
52
|
+
runInit(tmpDir, false);
|
|
53
|
+
const rulePath = path.join(tmpDir, ".agents", "rules", "module-development", "structure.md");
|
|
54
|
+
expect(fs.existsSync(rulePath)).toBe(true);
|
|
55
|
+
// Also check nested subdirectory
|
|
56
|
+
const nestedRule = path.join(tmpDir, ".agents", "rules", "app-compose", "frontend", "auth.md");
|
|
57
|
+
expect(fs.existsSync(nestedRule)).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("does not overwrite existing rules", () => {
|
|
61
|
+
const ruleDir = path.join(tmpDir, ".agents", "rules", "module-development");
|
|
62
|
+
fs.mkdirSync(ruleDir, { recursive: true });
|
|
63
|
+
fs.writeFileSync(path.join(ruleDir, "structure.md"), "# Custom Rule");
|
|
64
|
+
runInit(tmpDir, false);
|
|
65
|
+
const content = fs.readFileSync(path.join(ruleDir, "structure.md"), "utf-8");
|
|
66
|
+
expect(content).toBe("# Custom Rule");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("overwrites existing rules with --force", () => {
|
|
70
|
+
const ruleDir = path.join(tmpDir, ".agents", "rules", "module-development");
|
|
71
|
+
fs.mkdirSync(ruleDir, { recursive: true });
|
|
72
|
+
fs.writeFileSync(path.join(ruleDir, "structure.md"), "# Custom Rule");
|
|
73
|
+
runInit(tmpDir, true);
|
|
74
|
+
const content = fs.readFileSync(path.join(ruleDir, "structure.md"), "utf-8");
|
|
75
|
+
expect(content).not.toBe("# Custom Rule");
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { PACKAGE_ROOT } from "../util.js";
|
|
5
|
+
|
|
6
|
+
const SKILLS_SRC = path.join(PACKAGE_ROOT, "skills");
|
|
7
|
+
const RULES_SRC = path.join(PACKAGE_ROOT, "rules");
|
|
8
|
+
|
|
9
|
+
const FRAMEWORK_SKILLS = [
|
|
10
|
+
"1-module-docs",
|
|
11
|
+
"2-module-feature-breakdown",
|
|
12
|
+
"3-module-doc-review",
|
|
13
|
+
"4-module-tdd-implementation",
|
|
14
|
+
"5-module-implementation-review",
|
|
15
|
+
"app-compose-1-requirement-analysis",
|
|
16
|
+
"app-compose-2-requirements-breakdown",
|
|
17
|
+
"app-compose-3-doc-review",
|
|
18
|
+
"app-compose-4-design-mock",
|
|
19
|
+
"app-compose-5-design-mock-review",
|
|
20
|
+
"app-compose-6-implementation-spec",
|
|
21
|
+
"mock-scenario",
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export function runInit(cwd: string, force: boolean): number {
|
|
25
|
+
console.log(chalk.bold("erp-kit init\n"));
|
|
26
|
+
|
|
27
|
+
const skillsDest = path.join(cwd, ".agents", "skills");
|
|
28
|
+
let copiedCount = 0;
|
|
29
|
+
let skippedCount = 0;
|
|
30
|
+
for (const skill of FRAMEWORK_SKILLS) {
|
|
31
|
+
const srcSkill = path.join(SKILLS_SRC, skill, "SKILL.md");
|
|
32
|
+
const destDir = path.join(skillsDest, skill);
|
|
33
|
+
const destSkill = path.join(destDir, "SKILL.md");
|
|
34
|
+
|
|
35
|
+
if (!fs.existsSync(srcSkill)) continue;
|
|
36
|
+
if (!force && fs.existsSync(destSkill)) {
|
|
37
|
+
console.log(chalk.yellow(` Skipped ${skill}/SKILL.md (already exists)`));
|
|
38
|
+
skippedCount++;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
43
|
+
fs.copyFileSync(srcSkill, destSkill);
|
|
44
|
+
copiedCount++;
|
|
45
|
+
}
|
|
46
|
+
console.log(chalk.green(` Copied ${copiedCount} framework skills to .agents/skills/`));
|
|
47
|
+
if (skippedCount > 0) {
|
|
48
|
+
console.log(
|
|
49
|
+
chalk.yellow(` Skipped ${skippedCount} existing skills (use --force to overwrite)`),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const rulesDest = path.join(cwd, ".agents", "rules");
|
|
54
|
+
let rulesCount = 0;
|
|
55
|
+
let rulesSkipped = 0;
|
|
56
|
+
if (fs.existsSync(RULES_SRC)) {
|
|
57
|
+
const copyRulesRecursive = (srcDir: string, destDir: string) => {
|
|
58
|
+
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
59
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
60
|
+
const destPath = path.join(destDir, entry.name);
|
|
61
|
+
if (entry.isDirectory()) {
|
|
62
|
+
copyRulesRecursive(srcPath, destPath);
|
|
63
|
+
} else {
|
|
64
|
+
if (!force && fs.existsSync(destPath)) {
|
|
65
|
+
const rel = path.relative(rulesDest, destPath);
|
|
66
|
+
console.log(chalk.yellow(` Skipped rule ${rel} (already exists)`));
|
|
67
|
+
rulesSkipped++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
71
|
+
fs.copyFileSync(srcPath, destPath);
|
|
72
|
+
rulesCount++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
copyRulesRecursive(RULES_SRC, rulesDest);
|
|
77
|
+
}
|
|
78
|
+
console.log(chalk.green(` Copied ${rulesCount} framework rules to .agents/rules/`));
|
|
79
|
+
if (rulesSkipped > 0) {
|
|
80
|
+
console.log(
|
|
81
|
+
chalk.yellow(` Skipped ${rulesSkipped} existing rules (use --force to overwrite)`),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log(chalk.bold.green("\nDone! Run `erp-kit check` to validate your docs."));
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineCommand, arg } from "politty";
|
|
3
|
+
import { runMockStart } from "./start.js";
|
|
4
|
+
import { runMockValidate } from "./validate.js";
|
|
5
|
+
|
|
6
|
+
const startCommand = defineCommand({
|
|
7
|
+
name: "start",
|
|
8
|
+
description: "Start mock API servers with reverse proxy",
|
|
9
|
+
args: z.object({
|
|
10
|
+
mocksRoot: arg(z.string().default("./mocks"), {
|
|
11
|
+
description: "Path to mocks directory",
|
|
12
|
+
}),
|
|
13
|
+
port: arg(z.coerce.number().default(3000), {
|
|
14
|
+
alias: "p",
|
|
15
|
+
description: "Reverse proxy port",
|
|
16
|
+
}),
|
|
17
|
+
filter: arg(z.array(z.string()).default([]), {
|
|
18
|
+
positional: true,
|
|
19
|
+
description: "Filter by provider or provider/scenario",
|
|
20
|
+
}),
|
|
21
|
+
}),
|
|
22
|
+
run: async (args) => {
|
|
23
|
+
const exitCode = await runMockStart(args.mocksRoot, args.filter, args.port);
|
|
24
|
+
process.exit(exitCode);
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const validateCommand = defineCommand({
|
|
29
|
+
name: "validate",
|
|
30
|
+
description: "Validate mock scenario configs",
|
|
31
|
+
args: z.object({
|
|
32
|
+
mocksRoot: arg(z.string().default("./mocks"), {
|
|
33
|
+
description: "Path to mocks directory",
|
|
34
|
+
}),
|
|
35
|
+
paths: arg(z.array(z.string()).default([]), {
|
|
36
|
+
positional: true,
|
|
37
|
+
description: "Specific scenario paths to validate",
|
|
38
|
+
}),
|
|
39
|
+
}),
|
|
40
|
+
run: async (args) => {
|
|
41
|
+
const exitCode = await runMockValidate(args.mocksRoot, args.paths);
|
|
42
|
+
process.exit(exitCode);
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const mockCommand = defineCommand({
|
|
47
|
+
name: "mock",
|
|
48
|
+
description: "Mock API server management",
|
|
49
|
+
subCommands: {
|
|
50
|
+
start: startCommand,
|
|
51
|
+
validate: validateCommand,
|
|
52
|
+
},
|
|
53
|
+
});
|