@tailor-platform/erp-kit 0.5.0 → 0.5.1
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 +9 -0
- package/dist/cli.mjs +36 -12
- package/package.json +1 -1
- package/skills/erp-kit-app-5-impl-backend/SKILL.md +3 -1
- package/src/commands/init-module.test.ts +17 -3
- package/src/commands/init-module.ts +0 -12
- package/src/generator/generate-code-boilerplate.test.ts +9 -1
- package/src/generator/scaffold.ts +6 -2
- package/src/mdschema.ts +39 -3
- package/templates/scaffold/module/__dot__gitignore +3 -0
- package/templates/scaffold/module/eslint.config.js +31 -0
- package/templates/scaffold/module/generated/kysely-tailordb.ts +3 -0
- package/templates/scaffold/module/lib/types.ts +1 -6
- package/templates/scaffold/module/package.json +26 -0
- package/templates/scaffold/module/tsconfig.json +16 -0
- package/templates/scaffold/module/generated/.gitkeep +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @tailor-platform/erp-kit
|
|
2
2
|
|
|
3
|
+
## 0.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- fb40e4d: - Fix mdschema binary resolution to use platform-specific native binaries, enabling Windows support
|
|
8
|
+
- Fix init commands to be idempotent when directory already exists instead of erroring
|
|
9
|
+
- Add package.json and dev tooling to module template
|
|
10
|
+
- Update app skill to use erp-kit app generate seed
|
|
11
|
+
|
|
3
12
|
## 0.5.0
|
|
4
13
|
|
|
5
14
|
### Minor Changes
|
package/dist/cli.mjs
CHANGED
|
@@ -364,12 +364,35 @@ function runLicenseCheck(configPath) {
|
|
|
364
364
|
//#endregion
|
|
365
365
|
//#region src/mdschema.ts
|
|
366
366
|
const require = createRequire(import.meta.url);
|
|
367
|
+
const PLATFORMS = {
|
|
368
|
+
darwin: {
|
|
369
|
+
arm64: "@jackchuka/mdschema-darwin-arm64",
|
|
370
|
+
x64: "@jackchuka/mdschema-darwin-x64"
|
|
371
|
+
},
|
|
372
|
+
linux: {
|
|
373
|
+
arm64: "@jackchuka/mdschema-linux-arm64",
|
|
374
|
+
x64: "@jackchuka/mdschema-linux-x64"
|
|
375
|
+
},
|
|
376
|
+
win32: {
|
|
377
|
+
arm64: "@jackchuka/mdschema-windows-arm64",
|
|
378
|
+
x64: "@jackchuka/mdschema-windows-x64"
|
|
379
|
+
}
|
|
380
|
+
};
|
|
367
381
|
function getMdschemaBin() {
|
|
368
382
|
const pkgPath = require.resolve("@jackchuka/mdschema/package.json");
|
|
369
|
-
const
|
|
370
|
-
const
|
|
383
|
+
const pkgDir = path.dirname(pkgPath);
|
|
384
|
+
const mdschemaRequire = createRequire(pkgPath);
|
|
385
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
386
|
+
const pkg = PLATFORMS[process.platform]?.[process.arch];
|
|
387
|
+
if (pkg) try {
|
|
388
|
+
return mdschemaRequire.resolve(`${pkg}/bin/mdschema${ext}`);
|
|
389
|
+
} catch {}
|
|
390
|
+
const localBin = path.join(pkgDir, "bin", `mdschema${ext}`);
|
|
391
|
+
if (fs.existsSync(localBin)) return localBin;
|
|
392
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
393
|
+
const bin = typeof pkgJson.bin === "string" ? pkgJson.bin : pkgJson.bin?.mdschema;
|
|
371
394
|
if (!bin) throw new Error("Could not resolve mdschema binary from package.json bin field");
|
|
372
|
-
return path.join(
|
|
395
|
+
return path.join(pkgDir, bin);
|
|
373
396
|
}
|
|
374
397
|
function runMdschema(args, cwd) {
|
|
375
398
|
return new Promise((resolve) => {
|
|
@@ -552,10 +575,6 @@ async function runCheck(config, cwd) {
|
|
|
552
575
|
//#region src/commands/init-module.ts
|
|
553
576
|
function runInitModule(name, dir) {
|
|
554
577
|
const moduleDir = path.resolve(dir, name);
|
|
555
|
-
if (fs.existsSync(moduleDir)) {
|
|
556
|
-
console.error(`Directory already exists: ${moduleDir}`);
|
|
557
|
-
return 1;
|
|
558
|
-
}
|
|
559
578
|
fs.mkdirSync(moduleDir, { recursive: true });
|
|
560
579
|
return 0;
|
|
561
580
|
}
|
|
@@ -578,10 +597,6 @@ async function runInitModuleWithReadme(name, dir, cwd) {
|
|
|
578
597
|
}
|
|
579
598
|
function runInitApp(name, dir) {
|
|
580
599
|
const appDir = path.resolve(dir, name);
|
|
581
|
-
if (fs.existsSync(appDir)) {
|
|
582
|
-
console.error(`Directory already exists: ${appDir}`);
|
|
583
|
-
return 1;
|
|
584
|
-
}
|
|
585
600
|
fs.mkdirSync(appDir, { recursive: true });
|
|
586
601
|
return 0;
|
|
587
602
|
}
|
|
@@ -1129,7 +1144,16 @@ function copyTemplateDir(srcDir, destDir, replacements, placeholderFiles) {
|
|
|
1129
1144
|
}
|
|
1130
1145
|
}
|
|
1131
1146
|
function scaffoldModuleBoilerplate(moduleDir, moduleName) {
|
|
1132
|
-
|
|
1147
|
+
const templateDir = path.join(PACKAGE_ROOT, "templates", "scaffold", "module");
|
|
1148
|
+
const erpKitVersion = readErpKitVersion();
|
|
1149
|
+
copyTemplateDir(templateDir, moduleDir, {
|
|
1150
|
+
"template-module": moduleName,
|
|
1151
|
+
"\"workspace:*\"": `"${erpKitVersion}"`
|
|
1152
|
+
}, new Set([
|
|
1153
|
+
"package.json",
|
|
1154
|
+
"permissions.ts",
|
|
1155
|
+
"tailor.config.ts"
|
|
1156
|
+
]));
|
|
1133
1157
|
}
|
|
1134
1158
|
function scaffoldAppBoilerplate(appDir, appName) {
|
|
1135
1159
|
const templateDir = path.join(PACKAGE_ROOT, "templates", "scaffold", "app");
|
package/package.json
CHANGED
|
@@ -99,7 +99,9 @@ Do not directly mutate module-owned tables via Kysely — always use module comm
|
|
|
99
99
|
### Phase 5: Generated Files
|
|
100
100
|
|
|
101
101
|
- **Kysely types** (`src/generated/kysely-tailordb.ts`) — Auto-generated by `pnpm generate` after deployment. Before deployment, create a minimal placeholder so the codebase typechecks.
|
|
102
|
-
- **Seed data** (`seed/`) — `exec.mjs` runner + `data/*.jsonl` (records) and `data/*.schema.ts` (validation).
|
|
102
|
+
- **Seed data** (`seed/`) — `exec.mjs` runner + `data/*.jsonl` (records) and `data/*.schema.ts` (validation).
|
|
103
|
+
1. **Module seed** — Run `pnpm erp-kit app generate seed -p <app-path>` to generate module-provided defaults (currencies, units, UoM categories, exchange rates). This writes JSONL files to `seed/data/`, skipping files that already exist.
|
|
104
|
+
2. **App-specific seed** — Manually create seed records for permissions, roles, test users, and domain-specific data. Seeded users must have concrete permission keys required by the wired modules.
|
|
103
105
|
|
|
104
106
|
### Phase 6: Deploy Backend & Next Steps
|
|
105
107
|
|
|
@@ -14,12 +14,19 @@ describe("runInitModule", () => {
|
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
it("creates module directory", () => {
|
|
17
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "init-module-test-"));
|
|
18
|
+
const exitCode = runInitModule("my-module", tmpDir);
|
|
19
|
+
expect(exitCode).toBe(0);
|
|
20
|
+
expect(fs.existsSync(path.join(tmpDir, "my-module"))).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("succeeds if module directory already exists", () => {
|
|
17
24
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "init-module-test-"));
|
|
18
25
|
const moduleDir = path.join(tmpDir, "my-module");
|
|
19
26
|
fs.mkdirSync(moduleDir, { recursive: true });
|
|
20
27
|
|
|
21
28
|
const exitCode = runInitModule("my-module", tmpDir);
|
|
22
|
-
expect(exitCode).toBe(
|
|
29
|
+
expect(exitCode).toBe(0);
|
|
23
30
|
});
|
|
24
31
|
});
|
|
25
32
|
|
|
@@ -32,12 +39,19 @@ describe("runInitApp", () => {
|
|
|
32
39
|
}
|
|
33
40
|
});
|
|
34
41
|
|
|
35
|
-
it("
|
|
42
|
+
it("creates app directory", () => {
|
|
43
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "init-app-test-"));
|
|
44
|
+
const exitCode = runInitApp("my-app", tmpDir);
|
|
45
|
+
expect(exitCode).toBe(0);
|
|
46
|
+
expect(fs.existsSync(path.join(tmpDir, "my-app"))).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("succeeds if app directory already exists", () => {
|
|
36
50
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "init-app-test-"));
|
|
37
51
|
const appDir = path.join(tmpDir, "my-app");
|
|
38
52
|
fs.mkdirSync(appDir, { recursive: true });
|
|
39
53
|
|
|
40
54
|
const exitCode = runInitApp("my-app", tmpDir);
|
|
41
|
-
expect(exitCode).toBe(
|
|
55
|
+
expect(exitCode).toBe(0);
|
|
42
56
|
});
|
|
43
57
|
});
|
|
@@ -5,12 +5,6 @@ import { MODULE_SCHEMAS, APP_COMPOSE_SCHEMAS } from "../schemas";
|
|
|
5
5
|
|
|
6
6
|
export function runInitModule(name: string, dir: string): number {
|
|
7
7
|
const moduleDir = path.resolve(dir, name);
|
|
8
|
-
|
|
9
|
-
if (fs.existsSync(moduleDir)) {
|
|
10
|
-
console.error(`Directory already exists: ${moduleDir}`);
|
|
11
|
-
return 1;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
8
|
fs.mkdirSync(moduleDir, { recursive: true });
|
|
15
9
|
return 0;
|
|
16
10
|
}
|
|
@@ -40,12 +34,6 @@ export async function runInitModuleWithReadme(
|
|
|
40
34
|
|
|
41
35
|
export function runInitApp(name: string, dir: string): number {
|
|
42
36
|
const appDir = path.resolve(dir, name);
|
|
43
|
-
|
|
44
|
-
if (fs.existsSync(appDir)) {
|
|
45
|
-
console.error(`Directory already exists: ${appDir}`);
|
|
46
|
-
return 1;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
37
|
fs.mkdirSync(appDir, { recursive: true });
|
|
50
38
|
return 0;
|
|
51
39
|
}
|
|
@@ -24,7 +24,7 @@ describe("scaffoldModuleBoilerplate", () => {
|
|
|
24
24
|
expect(fs.existsSync(path.join(moduleDir, dir))).toBe(true);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
for (const dir of ["db", "command", "executor", "
|
|
27
|
+
for (const dir of ["db", "command", "executor", "query"]) {
|
|
28
28
|
expect(fs.existsSync(path.join(moduleDir, dir, ".gitkeep"))).toBe(true);
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -33,8 +33,12 @@ describe("scaffoldModuleBoilerplate", () => {
|
|
|
33
33
|
"index.ts",
|
|
34
34
|
"permissions.ts",
|
|
35
35
|
"tailor.config.ts",
|
|
36
|
+
"package.json",
|
|
37
|
+
"eslint.config.js",
|
|
38
|
+
"tsconfig.json",
|
|
36
39
|
"lib/types.ts",
|
|
37
40
|
"testing/fixtures.ts",
|
|
41
|
+
"generated/kysely-tailordb.ts",
|
|
38
42
|
];
|
|
39
43
|
for (const file of expectedFiles) {
|
|
40
44
|
expect(fs.existsSync(path.join(moduleDir, file))).toBe(true);
|
|
@@ -46,6 +50,10 @@ describe("scaffoldModuleBoilerplate", () => {
|
|
|
46
50
|
const config = fs.readFileSync(path.join(moduleDir, "tailor.config.ts"), "utf-8");
|
|
47
51
|
expect(config).toContain('name: "test-module"');
|
|
48
52
|
|
|
53
|
+
const pkg = fs.readFileSync(path.join(moduleDir, "package.json"), "utf-8");
|
|
54
|
+
expect(pkg).toContain('"name": "test-module"');
|
|
55
|
+
expect(pkg).not.toContain("workspace:*");
|
|
56
|
+
|
|
49
57
|
const moduleFile = fs.readFileSync(path.join(moduleDir, "module.ts"), "utf-8");
|
|
50
58
|
expect(moduleFile).toContain("defineModule");
|
|
51
59
|
expect(moduleFile).toContain("commands");
|
|
@@ -38,8 +38,12 @@ export function copyTemplateDir(
|
|
|
38
38
|
|
|
39
39
|
export function scaffoldModuleBoilerplate(moduleDir: string, moduleName: string): void {
|
|
40
40
|
const templateDir = path.join(PACKAGE_ROOT, "templates", "scaffold", "module");
|
|
41
|
-
const
|
|
42
|
-
const
|
|
41
|
+
const erpKitVersion = readErpKitVersion();
|
|
42
|
+
const replacements = {
|
|
43
|
+
"template-module": moduleName,
|
|
44
|
+
'"workspace:*"': `"${erpKitVersion}"`,
|
|
45
|
+
};
|
|
46
|
+
const placeholderFiles = new Set(["package.json", "permissions.ts", "tailor.config.ts"]);
|
|
43
47
|
copyTemplateDir(templateDir, moduleDir, replacements, placeholderFiles);
|
|
44
48
|
}
|
|
45
49
|
|
package/src/mdschema.ts
CHANGED
|
@@ -5,18 +5,54 @@ import { createRequire } from "node:module";
|
|
|
5
5
|
|
|
6
6
|
const require = createRequire(import.meta.url);
|
|
7
7
|
|
|
8
|
+
const PLATFORMS: Record<string, Record<string, string>> = {
|
|
9
|
+
darwin: {
|
|
10
|
+
arm64: "@jackchuka/mdschema-darwin-arm64",
|
|
11
|
+
x64: "@jackchuka/mdschema-darwin-x64",
|
|
12
|
+
},
|
|
13
|
+
linux: {
|
|
14
|
+
arm64: "@jackchuka/mdschema-linux-arm64",
|
|
15
|
+
x64: "@jackchuka/mdschema-linux-x64",
|
|
16
|
+
},
|
|
17
|
+
win32: {
|
|
18
|
+
arm64: "@jackchuka/mdschema-windows-arm64",
|
|
19
|
+
x64: "@jackchuka/mdschema-windows-x64",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
8
23
|
interface PackageJson {
|
|
9
24
|
bin?: string | Record<string, string>;
|
|
10
25
|
}
|
|
11
26
|
|
|
12
27
|
export function getMdschemaBin(): string {
|
|
13
28
|
const pkgPath = require.resolve("@jackchuka/mdschema/package.json");
|
|
14
|
-
const
|
|
15
|
-
|
|
29
|
+
const pkgDir = path.dirname(pkgPath);
|
|
30
|
+
|
|
31
|
+
// Resolve from mdschema's context so pnpm optionalDependencies are visible
|
|
32
|
+
const mdschemaRequire = createRequire(pkgPath);
|
|
33
|
+
const ext = process.platform === "win32" ? ".exe" : "";
|
|
34
|
+
const pkg = PLATFORMS[process.platform]?.[process.arch];
|
|
35
|
+
if (pkg) {
|
|
36
|
+
try {
|
|
37
|
+
return mdschemaRequire.resolve(`${pkg}/bin/mdschema${ext}`);
|
|
38
|
+
} catch {
|
|
39
|
+
// Platform package not installed, fall through
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Fallback: locally downloaded binary from install.js
|
|
44
|
+
const localBin = path.join(pkgDir, "bin", `mdschema${ext}`);
|
|
45
|
+
if (fs.existsSync(localBin)) {
|
|
46
|
+
return localBin;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Last resort: cli.js wrapper (requires shebang support, won't work on Windows)
|
|
50
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgPath, "utf-8")) as PackageJson;
|
|
51
|
+
const bin = typeof pkgJson.bin === "string" ? pkgJson.bin : pkgJson.bin?.mdschema;
|
|
16
52
|
if (!bin) {
|
|
17
53
|
throw new Error("Could not resolve mdschema binary from package.json bin field");
|
|
18
54
|
}
|
|
19
|
-
return path.join(
|
|
55
|
+
return path.join(pkgDir, bin);
|
|
20
56
|
}
|
|
21
57
|
|
|
22
58
|
export interface MdschemaResult {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import eslint from "@eslint/js";
|
|
2
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
3
|
+
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
|
|
4
|
+
import importX from "eslint-plugin-import-x";
|
|
5
|
+
import tseslint from "typescript-eslint";
|
|
6
|
+
|
|
7
|
+
export default defineConfig([
|
|
8
|
+
globalIgnores([".tailor-sdk/", "seed/", "generated/", "tailor.d.ts"]),
|
|
9
|
+
{
|
|
10
|
+
files: ["**/*.ts"],
|
|
11
|
+
extends: [
|
|
12
|
+
eslint.configs.recommended,
|
|
13
|
+
tseslint.configs.recommendedTypeChecked,
|
|
14
|
+
tseslint.configs.stylisticTypeChecked,
|
|
15
|
+
importX.flatConfigs.recommended,
|
|
16
|
+
importX.flatConfigs.typescript,
|
|
17
|
+
],
|
|
18
|
+
languageOptions: {
|
|
19
|
+
parserOptions: {
|
|
20
|
+
projectService: true,
|
|
21
|
+
tsconfigRootDir: import.meta.dirname,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
settings: {
|
|
25
|
+
"import-x/resolver-next": [createTypeScriptImportResolver()],
|
|
26
|
+
},
|
|
27
|
+
rules: {
|
|
28
|
+
"import-x/order": "error",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
]);
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
+
import type { InferSchema } from "@tailor-platform/erp-kit/module";
|
|
1
2
|
import type { DB } from "../generated/kysely-tailordb";
|
|
2
|
-
import type {
|
|
3
|
-
InferSchema,
|
|
4
|
-
Selectable,
|
|
5
|
-
Insertable,
|
|
6
|
-
Updateable,
|
|
7
|
-
} from "@tailor-platform/erp-kit/module";
|
|
8
3
|
|
|
9
4
|
export type Schema = InferSchema<DB>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "template-module",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"generate": "tailor-sdk generate",
|
|
7
|
+
"lint": "eslint --cache .",
|
|
8
|
+
"lint:fix": "eslint --cache --fix .",
|
|
9
|
+
"typecheck": "tsc --noEmit"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@tailor-platform/erp-kit": "workspace:*",
|
|
13
|
+
"@tailor-platform/sdk": "1.25.4"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@eslint/js": "10.0.1",
|
|
17
|
+
"@tailor-platform/function-types": "0.8.2",
|
|
18
|
+
"@types/node": "24.12.0",
|
|
19
|
+
"@typescript-eslint/parser": "^8.57.0",
|
|
20
|
+
"eslint": "10.0.3",
|
|
21
|
+
"eslint-import-resolver-typescript": "4.4.4",
|
|
22
|
+
"eslint-plugin-import-x": "4.16.2",
|
|
23
|
+
"typescript": "5.9.3",
|
|
24
|
+
"typescript-eslint": "8.57.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"allowSyntheticDefaultImports": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"types": ["node", "@tailor-platform/function-types"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["**/*.ts"]
|
|
16
|
+
}
|
|
File without changes
|