@omnifyjp/omnify 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/package.json +9 -3
- package/ts-dist/cli.d.ts +13 -0
- package/ts-dist/cli.js +133 -0
- package/ts-dist/enum-generator.d.ts +28 -0
- package/ts-dist/enum-generator.js +253 -0
- package/ts-dist/generator.d.ts +19 -0
- package/ts-dist/generator.js +330 -0
- package/ts-dist/i18n-generator.d.ts +10 -0
- package/ts-dist/i18n-generator.js +143 -0
- package/ts-dist/index.d.ts +10 -0
- package/ts-dist/index.js +9 -0
- package/ts-dist/interface-generator.d.ts +31 -0
- package/ts-dist/interface-generator.js +341 -0
- package/ts-dist/types.d.ts +196 -0
- package/ts-dist/types.js +6 -0
- package/ts-dist/zod-generator.d.ts +32 -0
- package/ts-dist/zod-generator.js +428 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnifyjp/omnify",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Schema-driven code generation for Laravel, TypeScript, and SQL",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"url": "https://github.com/omnifyjp/omnify-go"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
|
-
"omnify": "bin/omnify"
|
|
11
|
+
"omnify": "bin/omnify",
|
|
12
|
+
"omnify-ts": "ts-dist/cli.js"
|
|
12
13
|
},
|
|
13
14
|
"types": "types/index.d.ts",
|
|
14
15
|
"exports": {
|
|
@@ -21,7 +22,12 @@
|
|
|
21
22
|
"./omnify-schema.json": "./omnify-schema.json",
|
|
22
23
|
"./omnify-config-schema.json": "./omnify-config-schema.json"
|
|
23
24
|
},
|
|
24
|
-
"files": ["bin/", "types/", "omnify-schema.json", "omnify-config-schema.json", "README.md"],
|
|
25
|
+
"files": ["bin/", "ts-dist/", "types/", "omnify-schema.json", "omnify-config-schema.json", "README.md"],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"commander": "^13.0.0",
|
|
28
|
+
"yaml": "^2.7.0",
|
|
29
|
+
"zod": "^3.24.0"
|
|
30
|
+
},
|
|
25
31
|
"optionalDependencies": {
|
|
26
32
|
"@omnifyjp/omnify-darwin-arm64": "0.1.0",
|
|
27
33
|
"@omnifyjp/omnify-darwin-x64": "0.1.0",
|
package/ts-dist/cli.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @omnify/ts CLI
|
|
4
|
+
*
|
|
5
|
+
* Reads omnify.yaml to resolve schemas.json input and TypeScript output paths.
|
|
6
|
+
* Falls back to explicit --input / --output flags.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* omnify-ts # reads omnify.yaml in cwd
|
|
10
|
+
* omnify-ts --config path/to/omnify.yaml
|
|
11
|
+
* omnify-ts --input schemas.json --output ./types # explicit override
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
package/ts-dist/cli.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @omnify/ts CLI
|
|
4
|
+
*
|
|
5
|
+
* Reads omnify.yaml to resolve schemas.json input and TypeScript output paths.
|
|
6
|
+
* Falls back to explicit --input / --output flags.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* omnify-ts # reads omnify.yaml in cwd
|
|
10
|
+
* omnify-ts --config path/to/omnify.yaml
|
|
11
|
+
* omnify-ts --input schemas.json --output ./types # explicit override
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
14
|
+
import { resolve, dirname, join } from 'node:path';
|
|
15
|
+
import { Command } from 'commander';
|
|
16
|
+
import { parse as parseYaml } from 'yaml';
|
|
17
|
+
import { generateTypeScript } from './generator.js';
|
|
18
|
+
function resolveFromConfig(configPath) {
|
|
19
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
20
|
+
const config = parseYaml(raw);
|
|
21
|
+
const configDir = dirname(configPath);
|
|
22
|
+
// Find default connection
|
|
23
|
+
const defaultName = config.default ?? 'default';
|
|
24
|
+
const connections = config.connections ?? {};
|
|
25
|
+
const conn = connections[defaultName];
|
|
26
|
+
if (!conn) {
|
|
27
|
+
throw new Error(`Connection "${defaultName}" not found in ${configPath}`);
|
|
28
|
+
}
|
|
29
|
+
// Resolve schemas.json input path
|
|
30
|
+
const schemasPath = conn.output?.laravel?.schemasPath;
|
|
31
|
+
if (!schemasPath) {
|
|
32
|
+
throw new Error(`No output.laravel.schemasPath found for connection "${defaultName}" in ${configPath}.\n` +
|
|
33
|
+
`Either add schemasPath to your config, or use --input explicitly.`);
|
|
34
|
+
}
|
|
35
|
+
// Resolve typescript output path
|
|
36
|
+
const tsPath = conn.output?.typescript?.path;
|
|
37
|
+
if (!tsPath) {
|
|
38
|
+
throw new Error(`No output.typescript.path found for connection "${defaultName}" in ${configPath}.\n` +
|
|
39
|
+
`Add this to your omnify.yaml:\n\n` +
|
|
40
|
+
` connections:\n` +
|
|
41
|
+
` ${defaultName}:\n` +
|
|
42
|
+
` output:\n` +
|
|
43
|
+
` typescript:\n` +
|
|
44
|
+
` path: resources/js/types/models\n\n` +
|
|
45
|
+
`Or use --output explicitly.`);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
input: resolve(configDir, schemasPath),
|
|
49
|
+
output: resolve(configDir, tsPath),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// CLI
|
|
54
|
+
// ============================================================================
|
|
55
|
+
const program = new Command();
|
|
56
|
+
program
|
|
57
|
+
.name('omnify-ts')
|
|
58
|
+
.description('Generate TypeScript types from Omnify schemas.json')
|
|
59
|
+
.option('-c, --config <path>', 'Path to omnify.yaml (default: ./omnify.yaml)')
|
|
60
|
+
.option('-i, --input <path>', 'Path to schemas.json (overrides config)')
|
|
61
|
+
.option('-o, --output <path>', 'Output directory (overrides config)')
|
|
62
|
+
.option('--force', 'Overwrite user-editable model files', false)
|
|
63
|
+
.action((opts) => {
|
|
64
|
+
let inputPath;
|
|
65
|
+
let outputDir;
|
|
66
|
+
if (opts.input && opts.output) {
|
|
67
|
+
// Explicit flags — skip config
|
|
68
|
+
inputPath = resolve(opts.input);
|
|
69
|
+
outputDir = resolve(opts.output);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Read from omnify.yaml
|
|
73
|
+
const configPath = resolve(opts.config ?? 'omnify.yaml');
|
|
74
|
+
if (!existsSync(configPath)) {
|
|
75
|
+
console.error(`Error: Config not found: ${configPath}\n` +
|
|
76
|
+
`Run from a directory with omnify.yaml, or use --config / --input + --output.`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
const resolved = resolveFromConfig(configPath);
|
|
80
|
+
inputPath = opts.input ? resolve(opts.input) : resolved.input;
|
|
81
|
+
outputDir = opts.output ? resolve(opts.output) : resolved.output;
|
|
82
|
+
}
|
|
83
|
+
// Read schemas.json
|
|
84
|
+
if (!existsSync(inputPath)) {
|
|
85
|
+
console.error(`Error: schemas.json not found: ${inputPath}\nRun "omnify generate" first.`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
const raw = readFileSync(inputPath, 'utf-8');
|
|
89
|
+
const input = JSON.parse(raw);
|
|
90
|
+
console.log(`Reading schemas from ${inputPath}`);
|
|
91
|
+
console.log(`Output directory: ${outputDir}`);
|
|
92
|
+
// Generate files
|
|
93
|
+
const files = generateTypeScript(input);
|
|
94
|
+
// Ensure output directories exist
|
|
95
|
+
mkdirSync(join(outputDir, 'base'), { recursive: true });
|
|
96
|
+
mkdirSync(join(outputDir, 'enum'), { recursive: true });
|
|
97
|
+
// Write files
|
|
98
|
+
let created = 0;
|
|
99
|
+
let overwritten = 0;
|
|
100
|
+
let skipped = 0;
|
|
101
|
+
for (const file of files) {
|
|
102
|
+
// Route files to correct subdirectory
|
|
103
|
+
let filePath;
|
|
104
|
+
if (file.category === 'enum' || file.category === 'plugin-enum') {
|
|
105
|
+
filePath = join(outputDir, 'enum', file.filePath);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
filePath = join(outputDir, file.filePath);
|
|
109
|
+
}
|
|
110
|
+
// Ensure parent directory exists
|
|
111
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
112
|
+
// Skip user-editable files if they already exist (unless --force)
|
|
113
|
+
if (!file.overwrite && existsSync(filePath) && !opts.force) {
|
|
114
|
+
skipped++;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
writeFileSync(filePath, file.content, 'utf-8');
|
|
118
|
+
if (file.overwrite || !existsSync(filePath)) {
|
|
119
|
+
overwritten++;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
created++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
console.log(`\nGeneration complete:`);
|
|
126
|
+
console.log(` ${overwritten} files written (auto-generated)`);
|
|
127
|
+
if (created > 0)
|
|
128
|
+
console.log(` ${created} files created (user-editable)`);
|
|
129
|
+
if (skipped > 0)
|
|
130
|
+
console.log(` ${skipped} files skipped (already exist)`);
|
|
131
|
+
console.log(`\nTotal: ${files.length} files`);
|
|
132
|
+
});
|
|
133
|
+
program.parse();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @omnify/ts — TypeScript Enum Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates TypeScript enums with helper methods from schema enum definitions,
|
|
5
|
+
* plugin enums (customTypes.enums), and inline property enums.
|
|
6
|
+
*/
|
|
7
|
+
import type { SchemaDefinition, TSEnum, TSTypeAlias, GeneratorOptions } from './types.js';
|
|
8
|
+
/** Convert a string to PascalCase. */
|
|
9
|
+
export declare function toPascalCase(value: string): string;
|
|
10
|
+
/** Convert enum value to valid TypeScript enum member name. */
|
|
11
|
+
export declare function toEnumMemberName(value: string): string;
|
|
12
|
+
/** Generate TSEnum from a schema enum definition. */
|
|
13
|
+
export declare function schemaToEnum(schema: SchemaDefinition, options: GeneratorOptions): TSEnum | null;
|
|
14
|
+
/** Generate enums from all schema enums. */
|
|
15
|
+
export declare function generateEnums(schemas: Record<string, SchemaDefinition>, options: GeneratorOptions): TSEnum[];
|
|
16
|
+
/** Generate enums from plugin enums (customTypes.enums in schemas.json). */
|
|
17
|
+
export declare function generatePluginEnums(pluginEnums: Record<string, string[]>, _options: GeneratorOptions): TSEnum[];
|
|
18
|
+
/** Format a TypeScript enum with helpers (Values array, type guard, label getter). */
|
|
19
|
+
export declare function formatEnum(enumDef: TSEnum): string;
|
|
20
|
+
/** Format a TypeScript type alias with helpers. */
|
|
21
|
+
export declare function formatTypeAlias(alias: TSTypeAlias): string;
|
|
22
|
+
/** Result of extracting inline enums. */
|
|
23
|
+
export interface ExtractedInlineEnum {
|
|
24
|
+
typeAlias?: TSTypeAlias;
|
|
25
|
+
enum?: TSEnum;
|
|
26
|
+
}
|
|
27
|
+
/** Extract inline enums from Enum/Select properties. */
|
|
28
|
+
export declare function extractInlineEnums(schemas: Record<string, SchemaDefinition>, options: GeneratorOptions): ExtractedInlineEnum[];
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @omnify/ts — TypeScript Enum Generator
|
|
3
|
+
*
|
|
4
|
+
* Generates TypeScript enums with helper methods from schema enum definitions,
|
|
5
|
+
* plugin enums (customTypes.enums), and inline property enums.
|
|
6
|
+
*/
|
|
7
|
+
/** Convert a string to PascalCase. */
|
|
8
|
+
export function toPascalCase(value) {
|
|
9
|
+
const normalized = value.replace(/([a-z])([A-Z])/g, '$1_$2');
|
|
10
|
+
return normalized
|
|
11
|
+
.split(/[-_\s]+/)
|
|
12
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
13
|
+
.join('');
|
|
14
|
+
}
|
|
15
|
+
/** Convert enum value to valid TypeScript enum member name. */
|
|
16
|
+
export function toEnumMemberName(value) {
|
|
17
|
+
let result = value
|
|
18
|
+
.split(/[-_\s]+/)
|
|
19
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
20
|
+
.join('')
|
|
21
|
+
.replace(/[^a-zA-Z0-9]/g, '');
|
|
22
|
+
if (/^\d/.test(result)) {
|
|
23
|
+
result = '_' + result;
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
/** Parse an enum value from schema (string or object with value/label). */
|
|
28
|
+
function parseEnumValue(value, options) {
|
|
29
|
+
if (typeof value === 'string') {
|
|
30
|
+
return { name: toEnumMemberName(value), value };
|
|
31
|
+
}
|
|
32
|
+
let label;
|
|
33
|
+
if (value.label !== undefined) {
|
|
34
|
+
if (typeof value.label === 'object') {
|
|
35
|
+
// Multi-locale: keep all locales
|
|
36
|
+
label = value.label;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
label = value.label;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
name: toEnumMemberName(value.value),
|
|
44
|
+
value: value.value,
|
|
45
|
+
label,
|
|
46
|
+
extra: value.extra,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Generate TSEnum from a schema enum definition. */
|
|
50
|
+
export function schemaToEnum(schema, options) {
|
|
51
|
+
if (schema.kind !== 'enum' || !schema.values)
|
|
52
|
+
return null;
|
|
53
|
+
const values = schema.values.map(v => parseEnumValue(v, options));
|
|
54
|
+
const displayName = resolveString(schema.displayName, options);
|
|
55
|
+
return {
|
|
56
|
+
name: schema.name,
|
|
57
|
+
values,
|
|
58
|
+
comment: displayName ?? schema.name,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Generate enums from all schema enums. */
|
|
62
|
+
export function generateEnums(schemas, options) {
|
|
63
|
+
const enums = [];
|
|
64
|
+
for (const schema of Object.values(schemas)) {
|
|
65
|
+
if (schema.kind === 'enum') {
|
|
66
|
+
const enumDef = schemaToEnum(schema, options);
|
|
67
|
+
if (enumDef)
|
|
68
|
+
enums.push(enumDef);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return enums;
|
|
72
|
+
}
|
|
73
|
+
/** Generate enums from plugin enums (customTypes.enums in schemas.json). */
|
|
74
|
+
export function generatePluginEnums(pluginEnums, _options) {
|
|
75
|
+
const enums = [];
|
|
76
|
+
for (const [name, values] of Object.entries(pluginEnums)) {
|
|
77
|
+
enums.push({
|
|
78
|
+
name,
|
|
79
|
+
values: values.map(v => ({
|
|
80
|
+
name: toEnumMemberName(v),
|
|
81
|
+
value: v,
|
|
82
|
+
})),
|
|
83
|
+
comment: name,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return enums;
|
|
87
|
+
}
|
|
88
|
+
/** Check if label is multi-locale. */
|
|
89
|
+
function isMultiLocaleLabel(label) {
|
|
90
|
+
return label !== undefined && typeof label === 'object';
|
|
91
|
+
}
|
|
92
|
+
/** Lowercase first character. */
|
|
93
|
+
function lowerFirst(str) {
|
|
94
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
95
|
+
}
|
|
96
|
+
/** Escape single quotes in strings. */
|
|
97
|
+
function escapeString(str) {
|
|
98
|
+
return str.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
99
|
+
}
|
|
100
|
+
/** Format a TypeScript enum with helpers (Values array, type guard, label getter). */
|
|
101
|
+
export function formatEnum(enumDef) {
|
|
102
|
+
const { name, values, comment } = enumDef;
|
|
103
|
+
const parts = [];
|
|
104
|
+
if (comment) {
|
|
105
|
+
parts.push(`/**\n * ${comment}\n */\n`);
|
|
106
|
+
}
|
|
107
|
+
// Enum definition
|
|
108
|
+
const enumValues = values.map(v => ` ${v.name} = '${v.value}',`).join('\n');
|
|
109
|
+
parts.push(`export enum ${name} {\n${enumValues}\n}\n\n`);
|
|
110
|
+
// Values array
|
|
111
|
+
parts.push(`/** All ${name} values */\n`);
|
|
112
|
+
parts.push(`export const ${name}Values = Object.values(${name}) as ${name}[];\n\n`);
|
|
113
|
+
// Type guard
|
|
114
|
+
parts.push(`/** Type guard for ${name} */\n`);
|
|
115
|
+
parts.push(`export function is${name}(value: unknown): value is ${name} {\n`);
|
|
116
|
+
parts.push(` return ${name}Values.includes(value as ${name});\n`);
|
|
117
|
+
parts.push(`}\n\n`);
|
|
118
|
+
// Labels
|
|
119
|
+
const hasLabels = values.some(v => v.label !== undefined);
|
|
120
|
+
const hasMultiLocale = values.some(v => isMultiLocaleLabel(v.label));
|
|
121
|
+
if (hasLabels) {
|
|
122
|
+
if (hasMultiLocale) {
|
|
123
|
+
const labelEntries = values
|
|
124
|
+
.filter(v => v.label !== undefined)
|
|
125
|
+
.map(v => {
|
|
126
|
+
if (isMultiLocaleLabel(v.label)) {
|
|
127
|
+
const locales = Object.entries(v.label)
|
|
128
|
+
.map(([locale, text]) => `'${locale}': '${escapeString(text)}'`)
|
|
129
|
+
.join(', ');
|
|
130
|
+
return ` [${name}.${v.name}]: { ${locales} },`;
|
|
131
|
+
}
|
|
132
|
+
return ` [${name}.${v.name}]: { default: '${escapeString(String(v.label))}' },`;
|
|
133
|
+
})
|
|
134
|
+
.join('\n');
|
|
135
|
+
parts.push(`const ${lowerFirst(name)}Labels: Partial<Record<${name}, Record<string, string>>> = {\n${labelEntries}\n};\n\n`);
|
|
136
|
+
parts.push(`/** Get label for ${name} value with locale support */\n`);
|
|
137
|
+
parts.push(`export function get${name}Label(value: ${name}, locale?: string): string {\n`);
|
|
138
|
+
parts.push(` const labels = ${lowerFirst(name)}Labels[value];\n`);
|
|
139
|
+
parts.push(` if (!labels) return value;\n`);
|
|
140
|
+
parts.push(` if (locale && labels[locale]) return labels[locale];\n`);
|
|
141
|
+
parts.push(` return labels['ja'] ?? labels['en'] ?? Object.values(labels)[0] ?? value;\n`);
|
|
142
|
+
parts.push(`}\n\n`);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
const labelEntries = values
|
|
146
|
+
.filter(v => v.label !== undefined)
|
|
147
|
+
.map(v => ` [${name}.${v.name}]: '${escapeString(String(v.label))}',`)
|
|
148
|
+
.join('\n');
|
|
149
|
+
parts.push(`const ${lowerFirst(name)}Labels: Partial<Record<${name}, string>> = {\n${labelEntries}\n};\n\n`);
|
|
150
|
+
parts.push(`/** Get label for ${name} value */\n`);
|
|
151
|
+
parts.push(`export function get${name}Label(value: ${name}): string {\n`);
|
|
152
|
+
parts.push(` return ${lowerFirst(name)}Labels[value] ?? value;\n`);
|
|
153
|
+
parts.push(`}\n\n`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
parts.push(`/** Get label for ${name} value */\n`);
|
|
158
|
+
parts.push(`export function get${name}Label(value: ${name}): string {\n`);
|
|
159
|
+
parts.push(` return value;\n`);
|
|
160
|
+
parts.push(`}\n\n`);
|
|
161
|
+
}
|
|
162
|
+
// Extra
|
|
163
|
+
const hasExtra = values.some(v => v.extra !== undefined);
|
|
164
|
+
if (hasExtra) {
|
|
165
|
+
const extraEntries = values
|
|
166
|
+
.filter(v => v.extra !== undefined)
|
|
167
|
+
.map(v => ` [${name}.${v.name}]: ${JSON.stringify(v.extra)},`)
|
|
168
|
+
.join('\n');
|
|
169
|
+
parts.push(`const ${lowerFirst(name)}Extra: Partial<Record<${name}, Record<string, unknown>>> = {\n${extraEntries}\n};\n\n`);
|
|
170
|
+
parts.push(`/** Get extra metadata for ${name} value */\n`);
|
|
171
|
+
parts.push(`export function get${name}Extra(value: ${name}): Record<string, unknown> | undefined {\n`);
|
|
172
|
+
parts.push(` return ${lowerFirst(name)}Extra[value];\n`);
|
|
173
|
+
parts.push(`}`);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
parts.push(`/** Get extra metadata for ${name} value */\n`);
|
|
177
|
+
parts.push(`export function get${name}Extra(_value: ${name}): Record<string, unknown> | undefined {\n`);
|
|
178
|
+
parts.push(` return undefined;\n`);
|
|
179
|
+
parts.push(`}`);
|
|
180
|
+
}
|
|
181
|
+
return parts.join('');
|
|
182
|
+
}
|
|
183
|
+
/** Format a TypeScript type alias with helpers. */
|
|
184
|
+
export function formatTypeAlias(alias) {
|
|
185
|
+
const { name, type, comment } = alias;
|
|
186
|
+
const parts = [];
|
|
187
|
+
if (comment) {
|
|
188
|
+
parts.push(`/**\n * ${comment}\n */\n`);
|
|
189
|
+
}
|
|
190
|
+
parts.push(`export type ${name} = ${type};\n\n`);
|
|
191
|
+
const values = type.split(' | ').map(v => v.trim());
|
|
192
|
+
parts.push(`/** All ${name} values */\n`);
|
|
193
|
+
parts.push(`export const ${name}Values: ${name}[] = [${values.join(', ')}];\n\n`);
|
|
194
|
+
parts.push(`/** Type guard for ${name} */\n`);
|
|
195
|
+
parts.push(`export function is${name}(value: unknown): value is ${name} {\n`);
|
|
196
|
+
parts.push(` return ${name}Values.includes(value as ${name});\n`);
|
|
197
|
+
parts.push(`}\n\n`);
|
|
198
|
+
parts.push(`/** Get label for ${name} value */\n`);
|
|
199
|
+
parts.push(`export function get${name}Label(value: ${name}): string {\n`);
|
|
200
|
+
parts.push(` return value;\n`);
|
|
201
|
+
parts.push(`}\n\n`);
|
|
202
|
+
parts.push(`/** Get extra metadata for ${name} value */\n`);
|
|
203
|
+
parts.push(`export function get${name}Extra(_value: ${name}): Record<string, unknown> | undefined {\n`);
|
|
204
|
+
parts.push(` return undefined;\n`);
|
|
205
|
+
parts.push(`}`);
|
|
206
|
+
return parts.join('');
|
|
207
|
+
}
|
|
208
|
+
/** Extract inline enums from Enum/Select properties. */
|
|
209
|
+
export function extractInlineEnums(schemas, options) {
|
|
210
|
+
const results = [];
|
|
211
|
+
for (const schema of Object.values(schemas)) {
|
|
212
|
+
if (schema.kind === 'enum' || !schema.properties)
|
|
213
|
+
continue;
|
|
214
|
+
for (const [propName, property] of Object.entries(schema.properties)) {
|
|
215
|
+
if (property.type === 'Enum' && Array.isArray(property.enum) && property.enum.length > 0) {
|
|
216
|
+
const typeName = `${schema.name}${toPascalCase(propName)}`;
|
|
217
|
+
const displayName = resolveString(schema.displayName, options);
|
|
218
|
+
// Check if values have labels (i.e., are objects not strings)
|
|
219
|
+
const enumValues = property.enum;
|
|
220
|
+
const hasLabels = enumValues.some(v => typeof v !== 'string' && v.label !== undefined);
|
|
221
|
+
if (hasLabels) {
|
|
222
|
+
const values = enumValues.map(v => parseEnumValue(v, options));
|
|
223
|
+
results.push({
|
|
224
|
+
enum: {
|
|
225
|
+
name: typeName,
|
|
226
|
+
values,
|
|
227
|
+
comment: displayName ?? `${schema.name} ${propName} enum`,
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
const values = enumValues.map(v => typeof v === 'string' ? v : v.value);
|
|
233
|
+
results.push({
|
|
234
|
+
typeAlias: {
|
|
235
|
+
name: typeName,
|
|
236
|
+
type: values.map(v => `'${v}'`).join(' | '),
|
|
237
|
+
comment: displayName ?? `${schema.name} ${propName} enum`,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return results;
|
|
245
|
+
}
|
|
246
|
+
/** Resolve a LocalizedString to a single string. */
|
|
247
|
+
function resolveString(value, options) {
|
|
248
|
+
if (value === undefined)
|
|
249
|
+
return undefined;
|
|
250
|
+
if (typeof value === 'string')
|
|
251
|
+
return value;
|
|
252
|
+
return value[options.defaultLocale] ?? value[options.fallbackLocale] ?? value['en'] ?? Object.values(value)[0];
|
|
253
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @omnify/ts — Main Generator
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates TypeScript code generation from schemas.json.
|
|
5
|
+
* Reads the JSON, builds options, calls sub-generators, and assembles output files.
|
|
6
|
+
*
|
|
7
|
+
* Output structure:
|
|
8
|
+
* base/{SchemaName}.ts — Auto-generated interfaces + Zod + i18n (always overwritten)
|
|
9
|
+
* enum/{EnumName}.ts — Auto-generated enums (always overwritten)
|
|
10
|
+
* common.ts — Shared types (DateTimeString, etc.)
|
|
11
|
+
* i18n.ts — Validation messages + locale helpers
|
|
12
|
+
* {SchemaName}.ts — User-editable models extending base (created once, never overwritten)
|
|
13
|
+
* index.ts — Re-exports (always overwritten)
|
|
14
|
+
*/
|
|
15
|
+
import type { SchemasJson, TypeScriptFile } from './types.js';
|
|
16
|
+
/**
|
|
17
|
+
* Generate all TypeScript files from schemas.json input.
|
|
18
|
+
*/
|
|
19
|
+
export declare function generateTypeScript(input: SchemasJson): TypeScriptFile[];
|