hekireki 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/README.md +71 -78
- package/dist/cli/index.d.ts +14 -0
- package/dist/cli/index.js +164 -0
- package/dist/{fsp-DYtOxLN_.js → fsp-FOfmtPYd.js} +4 -4
- package/dist/generator/arktype/index.js +32 -5
- package/dist/generator/dbml/index.d.ts +4 -1
- package/dist/generator/dbml/index.js +192 -20
- package/dist/generator/docs/index.d.ts +1 -0
- package/dist/generator/docs/index.js +1103 -0
- package/dist/generator/ecto/index.js +2 -2
- package/dist/generator/effect/index.js +32 -5
- package/dist/generator/mermaid-er/index.js +1 -1
- package/dist/generator/valibot/index.js +2 -3
- package/dist/generator/zod/index.js +2 -3
- package/dist/{utils-CXBzdZih.js → utils-BHGDO4qk.js} +12 -1
- package/package.json +8 -3
- package/dist/dbml-content-D3ioOw2D.js +0 -151
- package/dist/generator/svg/index.d.ts +0 -6
- package/dist/generator/svg/index.js +0 -74
- package/dist/relations-CxeKj9KD.js +0 -12
package/README.md
CHANGED
|
@@ -373,84 +373,77 @@ Output: `docs/er-diagram.png`
|
|
|
373
373
|
|
|
374
374
|
## Configuration
|
|
375
375
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
| Generator | Prefix | Example |
|
|
449
|
-
|----------------|--------|------------------------------------------------------------|
|
|
450
|
-
| Zod | `@z.` | `/// @z.uuid()` |
|
|
451
|
-
| Valibot | `@v.` | `/// @v.pipe(v.string(), v.uuid())` |
|
|
452
|
-
| ArkType | `@a.` | `/// @a."string.uuid"` |
|
|
453
|
-
| Effect Schema | `@e.` | `/// @e.Schema.UUID` |
|
|
376
|
+
Configure each generator directly in your `schema.prisma` file:
|
|
377
|
+
|
|
378
|
+
```prisma
|
|
379
|
+
// Zod Generator
|
|
380
|
+
generator Hekireki-Zod {
|
|
381
|
+
provider = "hekireki-zod"
|
|
382
|
+
output = "./zod" // Output directory (default: ./zod)
|
|
383
|
+
file = "index.ts" // File name (default: index.ts)
|
|
384
|
+
type = true // Generate TypeScript types (default: false)
|
|
385
|
+
comment = true // Include schema documentation (default: false)
|
|
386
|
+
zod = "v4" // Zod import: "v4", "mini", or "@hono/zod-openapi" (default: v4)
|
|
387
|
+
relation = true // Generate relation schemas (default: false)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Valibot Generator
|
|
391
|
+
generator Hekireki-Valibot {
|
|
392
|
+
provider = "hekireki-valibot"
|
|
393
|
+
output = "./valibot" // Output directory (default: ./valibot)
|
|
394
|
+
file = "index.ts" // File name (default: index.ts)
|
|
395
|
+
type = true // Generate TypeScript types (default: false)
|
|
396
|
+
comment = true // Include schema documentation (default: false)
|
|
397
|
+
relation = true // Generate relation schemas (default: false)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ArkType Generator
|
|
401
|
+
generator Hekireki-ArkType {
|
|
402
|
+
provider = "hekireki-arktype"
|
|
403
|
+
output = "./arktype" // Output directory (default: ./arktype)
|
|
404
|
+
file = "index.ts" // File name (default: index.ts)
|
|
405
|
+
type = true // Generate TypeScript types (default: false)
|
|
406
|
+
comment = true // Include schema documentation (default: false)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Effect Schema Generator
|
|
410
|
+
generator Hekireki-Effect {
|
|
411
|
+
provider = "hekireki-effect"
|
|
412
|
+
output = "./effect" // Output directory (default: ./effect)
|
|
413
|
+
file = "index.ts" // File name (default: index.ts)
|
|
414
|
+
type = true // Generate TypeScript types (default: false)
|
|
415
|
+
comment = true // Include schema documentation (default: false)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Mermaid ER Generator
|
|
419
|
+
generator Hekireki-ER {
|
|
420
|
+
provider = "hekireki-mermaid-er"
|
|
421
|
+
output = "./mermaid-er" // Output directory (default: ./mermaid-er)
|
|
422
|
+
file = "ER.md" // File name (default: ER.md)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Ecto Generator
|
|
426
|
+
generator Hekireki-Ecto {
|
|
427
|
+
provider = "hekireki-ecto"
|
|
428
|
+
output = "./ecto" // Output directory (default: ./ecto)
|
|
429
|
+
app = "MyApp" // App name (default: MyApp)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// DBML Generator
|
|
433
|
+
generator Hekireki-DBML {
|
|
434
|
+
provider = "hekireki-dbml"
|
|
435
|
+
output = "./dbml" // Output directory (default: ./dbml)
|
|
436
|
+
file = "schema.dbml" // File name (default: schema.dbml)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// SVG/PNG Generator
|
|
440
|
+
generator Hekireki-SVG {
|
|
441
|
+
provider = "hekireki-svg"
|
|
442
|
+
output = "./docs" // Output directory (default: ./docs)
|
|
443
|
+
file = "er-diagram" // File name without extension (default: er-diagram)
|
|
444
|
+
format = "png" // Output format: "png", "svg", or "dot" (default: png)
|
|
445
|
+
}
|
|
446
|
+
```
|
|
454
447
|
|
|
455
448
|
## License
|
|
456
449
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/cli/index.d.ts
|
|
2
|
+
type Result<T> = {
|
|
3
|
+
readonly ok: true;
|
|
4
|
+
readonly value: T;
|
|
5
|
+
} | {
|
|
6
|
+
readonly ok: false;
|
|
7
|
+
readonly error: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Main CLI entry point for hekireki.
|
|
11
|
+
*/
|
|
12
|
+
declare const hekireki: () => Result<string>;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { hekireki };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { serve } from "@hono/node-server";
|
|
5
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
6
|
+
import { Hono } from "hono";
|
|
7
|
+
|
|
8
|
+
//#region src/cli/index.ts
|
|
9
|
+
/**
|
|
10
|
+
* CLI module for hekireki.
|
|
11
|
+
*
|
|
12
|
+
* Provides the main entry point for the hekireki CLI tool.
|
|
13
|
+
*
|
|
14
|
+
* @module cli
|
|
15
|
+
*/
|
|
16
|
+
const HELP_TEXT = `⚡️ hekireki - Prisma schema tools
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
hekireki <command> [options]
|
|
20
|
+
|
|
21
|
+
Commands:
|
|
22
|
+
docs serve Start a local server to view the documentation
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
-p, --port <port> Specify the port (default: 5858)
|
|
26
|
+
-h, --help Show help
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
hekireki docs serve
|
|
30
|
+
hekireki docs serve -p 3000`;
|
|
31
|
+
const DOCS_HELP_TEXT = `⚡️ hekireki docs - Documentation tools
|
|
32
|
+
|
|
33
|
+
Usage:
|
|
34
|
+
hekireki docs serve [options]
|
|
35
|
+
|
|
36
|
+
Commands:
|
|
37
|
+
serve Start a local server to view the documentation
|
|
38
|
+
|
|
39
|
+
Options:
|
|
40
|
+
-p, --port <port> Specify the port (default: 5858)
|
|
41
|
+
-h, --help Show help
|
|
42
|
+
|
|
43
|
+
Examples:
|
|
44
|
+
hekireki docs serve
|
|
45
|
+
hekireki docs serve -p 3000`;
|
|
46
|
+
/**
|
|
47
|
+
* Parse port from CLI arguments.
|
|
48
|
+
*/
|
|
49
|
+
const parsePort = (args) => {
|
|
50
|
+
const portIndex = args.findIndex((arg) => arg === "-p" || arg === "--port");
|
|
51
|
+
if (portIndex === -1) return {
|
|
52
|
+
ok: true,
|
|
53
|
+
value: 5858
|
|
54
|
+
};
|
|
55
|
+
const portStr = args[portIndex + 1];
|
|
56
|
+
if (!portStr || portStr.startsWith("-")) return {
|
|
57
|
+
ok: false,
|
|
58
|
+
error: "❌ Error: --port requires a number"
|
|
59
|
+
};
|
|
60
|
+
const port = parseInt(portStr, 10);
|
|
61
|
+
if (isNaN(port)) return {
|
|
62
|
+
ok: false,
|
|
63
|
+
error: `❌ Error: Invalid port number: ${portStr}`
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
ok: true,
|
|
67
|
+
value: port
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Parse CLI arguments for docs serve command.
|
|
72
|
+
*/
|
|
73
|
+
const parseDocsServeArgs = (args) => {
|
|
74
|
+
const portResult = parsePort(args);
|
|
75
|
+
if (!portResult.ok) return portResult;
|
|
76
|
+
return {
|
|
77
|
+
ok: true,
|
|
78
|
+
value: { port: portResult.value }
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Start the documentation server.
|
|
83
|
+
*/
|
|
84
|
+
const startDocsServer = (options) => {
|
|
85
|
+
const absolutePath = path.resolve("./docs");
|
|
86
|
+
if (!fs.existsSync(absolutePath)) return {
|
|
87
|
+
ok: false,
|
|
88
|
+
error: `❌ Error: Directory not found: ${absolutePath}\n Run "prisma generate" first to generate the documentation.`
|
|
89
|
+
};
|
|
90
|
+
const indexPath = path.join(absolutePath, "index.html");
|
|
91
|
+
if (!fs.existsSync(indexPath)) return {
|
|
92
|
+
ok: false,
|
|
93
|
+
error: `❌ Error: index.html not found in ${absolutePath}\n Run "prisma generate" first to generate the documentation.`
|
|
94
|
+
};
|
|
95
|
+
const app = new Hono();
|
|
96
|
+
app.get("/", (c) => {
|
|
97
|
+
const html = fs.readFileSync(indexPath, "utf-8");
|
|
98
|
+
return c.html(html);
|
|
99
|
+
});
|
|
100
|
+
app.use("/*", serveStatic({ root: absolutePath }));
|
|
101
|
+
const server = serve({
|
|
102
|
+
fetch: app.fetch,
|
|
103
|
+
port: options.port
|
|
104
|
+
});
|
|
105
|
+
process.on("SIGTERM", () => {
|
|
106
|
+
server.close();
|
|
107
|
+
process.exit(0);
|
|
108
|
+
});
|
|
109
|
+
process.on("SIGINT", () => {
|
|
110
|
+
server.close();
|
|
111
|
+
process.exit(0);
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
ok: true,
|
|
115
|
+
value: `⚡️ Hekireki Docs Server started at http://localhost:${options.port}\n📂 Serving documentation from: ${absolutePath}`
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Handle docs subcommand.
|
|
120
|
+
*/
|
|
121
|
+
const handleDocs = (args) => {
|
|
122
|
+
const subcommand = args[0];
|
|
123
|
+
if (!subcommand || subcommand === "-h" || subcommand === "--help") return {
|
|
124
|
+
ok: true,
|
|
125
|
+
value: DOCS_HELP_TEXT
|
|
126
|
+
};
|
|
127
|
+
if (subcommand !== "serve") return {
|
|
128
|
+
ok: false,
|
|
129
|
+
error: `❌ Unknown command: docs ${subcommand}\n\n${DOCS_HELP_TEXT}`
|
|
130
|
+
};
|
|
131
|
+
const parseResult = parseDocsServeArgs(args.slice(1));
|
|
132
|
+
if (!parseResult.ok) return parseResult;
|
|
133
|
+
return startDocsServer(parseResult.value);
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Command handlers map.
|
|
137
|
+
*/
|
|
138
|
+
const commands = { docs: handleDocs };
|
|
139
|
+
/**
|
|
140
|
+
* Main CLI entry point for hekireki.
|
|
141
|
+
*/
|
|
142
|
+
const hekireki = () => {
|
|
143
|
+
const args = process.argv.slice(2);
|
|
144
|
+
const command = args[0];
|
|
145
|
+
if (!command || command === "-h" || command === "--help") return {
|
|
146
|
+
ok: true,
|
|
147
|
+
value: HELP_TEXT
|
|
148
|
+
};
|
|
149
|
+
const handler = commands[command];
|
|
150
|
+
if (!handler) return {
|
|
151
|
+
ok: false,
|
|
152
|
+
error: `❌ Unknown command: ${command}\n\n${HELP_TEXT}`
|
|
153
|
+
};
|
|
154
|
+
return handler(args.slice(1));
|
|
155
|
+
};
|
|
156
|
+
const result = hekireki();
|
|
157
|
+
if (result.ok) console.log(result.value);
|
|
158
|
+
else {
|
|
159
|
+
console.error(result.error);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
//#endregion
|
|
164
|
+
export { hekireki };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
2
|
|
|
3
3
|
//#region src/shared/fsp/index.ts
|
|
4
4
|
/**
|
|
@@ -9,7 +9,7 @@ import fsp from "node:fs/promises";
|
|
|
9
9
|
*/
|
|
10
10
|
async function mkdir(dir) {
|
|
11
11
|
try {
|
|
12
|
-
await
|
|
12
|
+
await fs.mkdir(dir, { recursive: true });
|
|
13
13
|
return {
|
|
14
14
|
ok: true,
|
|
15
15
|
value: void 0
|
|
@@ -30,7 +30,7 @@ async function mkdir(dir) {
|
|
|
30
30
|
*/
|
|
31
31
|
async function writeFile(path, data) {
|
|
32
32
|
try {
|
|
33
|
-
await
|
|
33
|
+
await fs.writeFile(path, data, "utf-8");
|
|
34
34
|
return {
|
|
35
35
|
ok: true,
|
|
36
36
|
value: void 0
|
|
@@ -51,7 +51,7 @@ async function writeFile(path, data) {
|
|
|
51
51
|
*/
|
|
52
52
|
async function writeFileBinary(path, data) {
|
|
53
53
|
try {
|
|
54
|
-
await
|
|
54
|
+
await fs.writeFile(path, data);
|
|
55
55
|
return {
|
|
56
56
|
ok: true,
|
|
57
57
|
value: void 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as
|
|
3
|
-
import { n as writeFile, t as mkdir } from "../../fsp-
|
|
2
|
+
import { a as getBool, i as collectRelationProps, n as validationSchemas, o as getString, r as parseDocumentWithoutAnnotations, s as fmt, t as schemaFromFields } from "../../utils-BHGDO4qk.js";
|
|
3
|
+
import { n as writeFile, t as mkdir } from "../../fsp-FOfmtPYd.js";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import pkg from "@prisma/generator-helper";
|
|
6
6
|
import { makeValidationExtractor } from "utils-lab";
|
|
@@ -15,6 +15,19 @@ import { makeValidationExtractor } from "utils-lab";
|
|
|
15
15
|
function schema(modelName, fields) {
|
|
16
16
|
return `export const ${modelName}Schema = type({\n${fields}\n})`;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Make ArkType relations schema for a model.
|
|
20
|
+
* @param model - The DMMF model
|
|
21
|
+
* @param relProps - The relation properties
|
|
22
|
+
* @param options - Options for the relation generation
|
|
23
|
+
* @returns The generated ArkType relations schema or null if no relations
|
|
24
|
+
*/
|
|
25
|
+
function makeArktypeRelations(model, relProps, options) {
|
|
26
|
+
if (relProps.length === 0) return null;
|
|
27
|
+
const fields = `${`...${model.name}Schema.t,`}${relProps.map((r) => `${r.key}:${r.isMany ? `${r.targetModel}Schema.array()` : `${r.targetModel}Schema`},`).join("")}`;
|
|
28
|
+
const typeLine = options?.includeType ? `\n\nexport type ${model.name}Relations = typeof ${model.name}RelationsSchema.infer` : "";
|
|
29
|
+
return `export const ${model.name}RelationsSchema = type({${fields}})${typeLine}`;
|
|
30
|
+
}
|
|
18
31
|
|
|
19
32
|
//#endregion
|
|
20
33
|
//#region src/generator/arktype/generator/schemas.ts
|
|
@@ -69,17 +82,31 @@ function arktype(models, type, comment) {
|
|
|
69
82
|
//#endregion
|
|
70
83
|
//#region src/generator/arktype/index.ts
|
|
71
84
|
const { generatorHandler } = pkg;
|
|
72
|
-
const
|
|
85
|
+
const buildRelationsOnly = (dmmf, includeType) => {
|
|
86
|
+
const models = dmmf.datamodel.models;
|
|
87
|
+
const relIndex = collectRelationProps(models);
|
|
88
|
+
const relByModel = {};
|
|
89
|
+
for (const r of relIndex) {
|
|
90
|
+
const existing = relByModel[r.model] ?? [];
|
|
91
|
+
relByModel[r.model] = [...existing, r];
|
|
92
|
+
}
|
|
93
|
+
return models.map((model) => makeArktypeRelations(model, (relByModel[model.name] ?? []).map(({ key, targetModel, isMany }) => ({
|
|
94
|
+
key,
|
|
95
|
+
targetModel,
|
|
96
|
+
isMany
|
|
97
|
+
})), { includeType })).filter((code) => Boolean(code)).join("\n\n");
|
|
98
|
+
};
|
|
99
|
+
const emit = async (options, enableRelation) => {
|
|
73
100
|
const outDir = options.generator.output?.value ?? "./arktype";
|
|
74
101
|
const file = getString(options.generator.config?.file, "index.ts") ?? "index.ts";
|
|
75
|
-
const fmtResult = await fmt(arktype(options.dmmf.datamodel.models, getBool(options.generator.config?.type), getBool(options.generator.config?.comment)));
|
|
102
|
+
const fmtResult = await fmt([arktype(options.dmmf.datamodel.models, getBool(options.generator.config?.type), getBool(options.generator.config?.comment)), enableRelation ? buildRelationsOnly(options.dmmf, getBool(options.generator.config?.type)) : ""].filter(Boolean).join("\n\n"));
|
|
76
103
|
if (!fmtResult.ok) throw new Error(`Format error: ${fmtResult.error}`);
|
|
77
104
|
const mkdirResult = await mkdir(outDir);
|
|
78
105
|
if (!mkdirResult.ok) throw new Error(`Failed to create directory: ${mkdirResult.error}`);
|
|
79
106
|
const writeResult = await writeFile(path.join(outDir, file), fmtResult.value);
|
|
80
107
|
if (!writeResult.ok) throw new Error(`Failed to write file: ${writeResult.error}`);
|
|
81
108
|
};
|
|
82
|
-
const onGenerate = (options) => emit(options);
|
|
109
|
+
const onGenerate = (options) => emit(options, options.generator.config?.relation === "true" || Array.isArray(options.generator.config?.relation) && options.generator.config?.relation[0] === "true");
|
|
83
110
|
generatorHandler({
|
|
84
111
|
onManifest() {
|
|
85
112
|
return {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { GeneratorOptions } from "@prisma/generator-helper";
|
|
2
2
|
|
|
3
3
|
//#region src/generator/dbml/index.d.ts
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Main generator function
|
|
6
|
+
*/
|
|
7
|
+
declare const main: (options: GeneratorOptions) => Promise<void>;
|
|
5
8
|
//#endregion
|
|
6
9
|
export { main };
|