@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 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 pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
370
- const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.mdschema;
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(path.dirname(pkgPath), bin);
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
- copyTemplateDir(path.join(PACKAGE_ROOT, "templates", "scaffold", "module"), moduleDir, { "template-module": moduleName }, new Set(["permissions.ts", "tailor.config.ts"]));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tailor-platform/erp-kit",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "Opinionated ERP toolkit for building business applications on Tailor Platform",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -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). Create seed records for initial data (permissions, roles, test users, etc.). Seeded users must have concrete permission keys required by the wired modules.
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(1); // should fail — dir exists
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("errors if app directory already exists", () => {
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(1);
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", "generated", "query"]) {
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 replacements = { "template-module": moduleName };
42
- const placeholderFiles = new Set(["permissions.ts", "tailor.config.ts"]);
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 pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")) as PackageJson;
15
- const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.mdschema;
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(path.dirname(pkgPath), bin);
55
+ return path.join(pkgDir, bin);
20
56
  }
21
57
 
22
58
  export interface MdschemaResult {
@@ -0,0 +1,3 @@
1
+ node_modules/
2
+ .eslintcache
3
+ .tailor-sdk/
@@ -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
+ ]);
@@ -0,0 +1,3 @@
1
+ // Stub generated by scaffold template.
2
+ // Overwritten when running `tailor-sdk generate`.
3
+ export type DB = Record<string, never>;
@@ -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