@omnifyjp/omnify 0.2.2 → 0.2.4
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 +38 -25
- package/package.json +6 -6
- package/ts-dist/cli.js +108 -61
- package/ts-dist/php/base-model-generator.d.ts +7 -0
- package/ts-dist/php/base-model-generator.js +70 -0
- package/ts-dist/php/factory-generator.d.ts +7 -0
- package/ts-dist/php/factory-generator.js +95 -0
- package/ts-dist/php/faker-mapper.d.ts +12 -0
- package/ts-dist/php/faker-mapper.js +206 -0
- package/ts-dist/php/index.d.ts +18 -0
- package/ts-dist/php/index.js +40 -0
- package/ts-dist/php/locales-generator.d.ts +7 -0
- package/ts-dist/php/locales-generator.js +135 -0
- package/ts-dist/php/model-generator.d.ts +7 -0
- package/ts-dist/php/model-generator.js +396 -0
- package/ts-dist/php/naming-helper.d.ts +22 -0
- package/ts-dist/php/naming-helper.js +61 -0
- package/ts-dist/php/relation-builder.d.ts +12 -0
- package/ts-dist/php/relation-builder.js +147 -0
- package/ts-dist/php/request-generator.d.ts +7 -0
- package/ts-dist/php/request-generator.js +221 -0
- package/ts-dist/php/resource-generator.d.ts +7 -0
- package/ts-dist/php/resource-generator.js +178 -0
- package/ts-dist/php/schema-reader.d.ts +28 -0
- package/ts-dist/php/schema-reader.js +79 -0
- package/ts-dist/php/service-provider-generator.d.ts +7 -0
- package/ts-dist/php/service-provider-generator.js +64 -0
- package/ts-dist/php/trait-generator.d.ts +6 -0
- package/ts-dist/php/trait-generator.js +104 -0
- package/ts-dist/php/type-mapper.d.ts +25 -0
- package/ts-dist/php/type-mapper.js +217 -0
- package/ts-dist/php/types.d.ts +61 -0
- package/ts-dist/php/types.js +68 -0
- package/types/config.d.ts +50 -24
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ Define your database schemas in YAML, generate Laravel PHP migrations, raw SQL,
|
|
|
14
14
|
- **Laravel Migration Generation** — Complete PHP migration files with foreign keys, indexes, and comments
|
|
15
15
|
- **Raw SQL Generation** — MySQL, PostgreSQL, and SQLite
|
|
16
16
|
- **TypeScript Generation** — Interfaces, Zod schemas, and i18n types from `schemas.json`
|
|
17
|
+
- **PHP Codegen** — Eloquent models, form requests, API resources, and factories from `schemas.json`
|
|
17
18
|
- **Incremental Generation** — Lock file tracks changes; only regenerates what's needed
|
|
18
19
|
- **Compound Types** — Single property expands to multiple columns (e.g., `JapaneseName` → 4 columns)
|
|
19
20
|
- **Associations** — ManyToOne, OneToMany, ManyToMany (pivot tables), MorphMany/MorphTo
|
|
@@ -34,11 +35,6 @@ This installs:
|
|
|
34
35
|
|
|
35
36
|
Both are available in `node_modules/.bin/` after installation.
|
|
36
37
|
|
|
37
|
-
> **Note:** You can also install Go binary directly:
|
|
38
|
-
> ```bash
|
|
39
|
-
> go install github.com/famgia/omnify-go/cmd/omnify@latest
|
|
40
|
-
> ```
|
|
41
|
-
|
|
42
38
|
---
|
|
43
39
|
|
|
44
40
|
## Quick Start / クイックスタート
|
|
@@ -96,9 +92,10 @@ npx omnify generate
|
|
|
96
92
|
This generates:
|
|
97
93
|
- **Laravel migrations** → `database/migrations/omnify/`
|
|
98
94
|
- **SQL files** → configured output path
|
|
99
|
-
- **TypeScript types** →
|
|
95
|
+
- **TypeScript types** → if `codegen.typescript` configured (interfaces, Zod schemas, i18n)
|
|
96
|
+
- **PHP codegen** → if `codegen.laravel` configured (Eloquent models, requests, resources, factories)
|
|
100
97
|
- **schemas.json** → serialized schema data
|
|
101
|
-
- **Lock file** → `.omnify.
|
|
98
|
+
- **Lock file** → `.omnify/lock.json` (tracks schema state)
|
|
102
99
|
|
|
103
100
|
---
|
|
104
101
|
|
|
@@ -108,20 +105,18 @@ Create `omnify.yaml` in your project root:
|
|
|
108
105
|
|
|
109
106
|
```yaml
|
|
110
107
|
schemasDir: ./schemas
|
|
111
|
-
lockFilePath: .omnify.
|
|
108
|
+
lockFilePath: .omnify/lock.json
|
|
112
109
|
|
|
113
110
|
connections:
|
|
114
111
|
default:
|
|
115
112
|
driver: mysql
|
|
116
|
-
|
|
117
|
-
laravel
|
|
118
|
-
|
|
113
|
+
migrations:
|
|
114
|
+
- type: laravel
|
|
115
|
+
path: database/migrations/omnify
|
|
119
116
|
schemasPath: database/omnify/schemas.json
|
|
120
|
-
sql
|
|
117
|
+
- type: sql
|
|
121
118
|
path: migrations/sql
|
|
122
119
|
dialect: mysql
|
|
123
|
-
typescript:
|
|
124
|
-
path: resources/js/types/models # TypeScript types output directory
|
|
125
120
|
|
|
126
121
|
default: default
|
|
127
122
|
|
|
@@ -132,35 +127,53 @@ locale:
|
|
|
132
127
|
|
|
133
128
|
compoundTypes:
|
|
134
129
|
- japan # Enables JapaneseName, JapaneseAddress, JapaneseBankAccount
|
|
130
|
+
|
|
131
|
+
codegen:
|
|
132
|
+
typescript:
|
|
133
|
+
enable: true
|
|
134
|
+
modelsPath: resources/js/types/models # TS interfaces, Zod schemas, i18n
|
|
135
|
+
laravel:
|
|
136
|
+
enable: true
|
|
137
|
+
model: # Eloquent models
|
|
138
|
+
path: app/Models/Omnify
|
|
139
|
+
namespace: App\Models\Omnify
|
|
135
140
|
```
|
|
136
141
|
|
|
137
|
-
###
|
|
142
|
+
### Codegen / コード自動生成
|
|
138
143
|
|
|
139
|
-
When `
|
|
144
|
+
When `codegen` is configured, `omnify generate` automatically generates code after writing `schemas.json`. No separate step needed.
|
|
140
145
|
|
|
141
|
-
`
|
|
146
|
+
`codegen`を設定すると、`omnify generate`実行時にコードが自動生成されます。
|
|
142
147
|
|
|
143
|
-
|
|
148
|
+
#### TypeScript (`codegen.typescript`)
|
|
144
149
|
- `base/` — Auto-generated interfaces and Zod schemas (overwritten on each generation)
|
|
145
150
|
- `enum/` — Enum type definitions
|
|
146
151
|
- Model files — User-editable model files (created once, never overwritten)
|
|
147
152
|
|
|
153
|
+
#### Laravel PHP (`codegen.laravel`)
|
|
154
|
+
- Eloquent base models (`Base/`) — always overwritten
|
|
155
|
+
- User models — created once, never overwritten
|
|
156
|
+
- Form requests (Store + Update) — base + user files
|
|
157
|
+
- API resources — base + user files
|
|
158
|
+
- Factories — created once
|
|
159
|
+
- Service provider, traits, locales
|
|
160
|
+
|
|
148
161
|
### Multiple Connections / 複数接続
|
|
149
162
|
|
|
150
163
|
```yaml
|
|
151
164
|
connections:
|
|
152
165
|
default:
|
|
153
166
|
driver: mysql
|
|
154
|
-
|
|
155
|
-
laravel
|
|
156
|
-
|
|
167
|
+
migrations:
|
|
168
|
+
- type: laravel
|
|
169
|
+
path: database/migrations/omnify
|
|
157
170
|
|
|
158
171
|
product-db:
|
|
159
172
|
driver: pgsql
|
|
160
|
-
|
|
161
|
-
laravel
|
|
162
|
-
|
|
163
|
-
sql
|
|
173
|
+
migrations:
|
|
174
|
+
- type: laravel
|
|
175
|
+
path: database/migrations/product
|
|
176
|
+
- type: sql
|
|
164
177
|
path: output/sql/product
|
|
165
178
|
dialect: postgresql
|
|
166
179
|
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnifyjp/omnify",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Schema-driven code generation for Laravel, TypeScript, and SQL",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"zod": "^3.24.0"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
|
-
"@omnifyjp/omnify-darwin-arm64": "0.2.
|
|
40
|
-
"@omnifyjp/omnify-darwin-x64": "0.2.
|
|
41
|
-
"@omnifyjp/omnify-linux-x64": "0.2.
|
|
42
|
-
"@omnifyjp/omnify-linux-arm64": "0.2.
|
|
43
|
-
"@omnifyjp/omnify-win32-x64": "0.2.
|
|
39
|
+
"@omnifyjp/omnify-darwin-arm64": "0.2.4",
|
|
40
|
+
"@omnifyjp/omnify-darwin-x64": "0.2.4",
|
|
41
|
+
"@omnifyjp/omnify-linux-x64": "0.2.4",
|
|
42
|
+
"@omnifyjp/omnify-linux-arm64": "0.2.4",
|
|
43
|
+
"@omnifyjp/omnify-win32-x64": "0.2.4"
|
|
44
44
|
}
|
|
45
45
|
}
|
package/ts-dist/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import { resolve, dirname, join } from 'node:path';
|
|
|
15
15
|
import { Command } from 'commander';
|
|
16
16
|
import { parse as parseYaml } from 'yaml';
|
|
17
17
|
import { generateTypeScript } from './generator.js';
|
|
18
|
+
import { generatePhp } from './php/index.js';
|
|
18
19
|
function resolveFromConfig(configPath) {
|
|
19
20
|
const raw = readFileSync(configPath, 'utf-8');
|
|
20
21
|
const config = parseYaml(raw);
|
|
@@ -26,27 +27,38 @@ function resolveFromConfig(configPath) {
|
|
|
26
27
|
if (!conn) {
|
|
27
28
|
throw new Error(`Connection "${defaultName}" not found in ${configPath}`);
|
|
28
29
|
}
|
|
29
|
-
// Resolve schemas.json input path
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
// Resolve schemas.json input path from migrations[type=laravel].schemasPath
|
|
31
|
+
let schemasPath;
|
|
32
|
+
for (const m of conn.migrations ?? []) {
|
|
33
|
+
if (m.type === 'laravel' && m.schemasPath) {
|
|
34
|
+
schemasPath = m.schemasPath;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
34
37
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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.`);
|
|
38
|
+
if (!schemasPath) {
|
|
39
|
+
throw new Error(`No schemasPath found in laravel migration for connection "${defaultName}" in ${configPath}.\n` +
|
|
40
|
+
`Either add schemasPath to your laravel migration config, or use --input explicitly.`);
|
|
46
41
|
}
|
|
42
|
+
// TypeScript config (enable defaults to false)
|
|
43
|
+
const tsConfig = config.codegen?.typescript;
|
|
44
|
+
const tsEnabled = tsConfig?.enable === true && !!tsConfig?.modelsPath;
|
|
45
|
+
const tsOutput = tsEnabled ? resolve(configDir, tsConfig.modelsPath) : undefined;
|
|
46
|
+
// Laravel config (enable defaults to false)
|
|
47
|
+
const laravelConfig = config.codegen?.laravel;
|
|
48
|
+
const laravelEnabled = laravelConfig?.enable === true;
|
|
49
|
+
const laravelOverrides = laravelEnabled ? {
|
|
50
|
+
model: laravelConfig?.model,
|
|
51
|
+
request: laravelConfig?.request,
|
|
52
|
+
resource: laravelConfig?.resource,
|
|
53
|
+
factory: laravelConfig?.factory,
|
|
54
|
+
provider: laravelConfig?.provider,
|
|
55
|
+
} : undefined;
|
|
47
56
|
return {
|
|
48
57
|
input: resolve(configDir, schemasPath),
|
|
49
|
-
|
|
58
|
+
tsEnabled,
|
|
59
|
+
tsOutput,
|
|
60
|
+
laravelEnabled,
|
|
61
|
+
laravelOverrides,
|
|
50
62
|
};
|
|
51
63
|
}
|
|
52
64
|
// ============================================================================
|
|
@@ -55,22 +67,27 @@ function resolveFromConfig(configPath) {
|
|
|
55
67
|
const program = new Command();
|
|
56
68
|
program
|
|
57
69
|
.name('omnify-ts')
|
|
58
|
-
.description('Generate TypeScript types from Omnify schemas.json')
|
|
70
|
+
.description('Generate TypeScript types and Laravel PHP code from Omnify schemas.json')
|
|
59
71
|
.option('-c, --config <path>', 'Path to omnify.yaml (default: ./omnify.yaml)')
|
|
60
72
|
.option('-i, --input <path>', 'Path to schemas.json (overrides config)')
|
|
61
|
-
.option('-o, --output <path>', 'Output directory (overrides config)')
|
|
73
|
+
.option('-o, --output <path>', 'Output directory for TypeScript (overrides config)')
|
|
62
74
|
.option('--force', 'Overwrite user-editable model files', false)
|
|
63
75
|
.action((opts) => {
|
|
64
76
|
let inputPath;
|
|
65
|
-
let
|
|
77
|
+
let tsEnabled = true;
|
|
78
|
+
let tsOutput;
|
|
79
|
+
let laravelEnabled = false;
|
|
80
|
+
let laravelOverrides;
|
|
81
|
+
let configDir = process.cwd();
|
|
66
82
|
if (opts.input && opts.output) {
|
|
67
|
-
// Explicit flags — skip config
|
|
83
|
+
// Explicit flags — skip config, only TS generation
|
|
68
84
|
inputPath = resolve(opts.input);
|
|
69
|
-
|
|
85
|
+
tsOutput = resolve(opts.output);
|
|
70
86
|
}
|
|
71
87
|
else {
|
|
72
88
|
// Read from omnify.yaml
|
|
73
89
|
const configPath = resolve(opts.config ?? 'omnify.yaml');
|
|
90
|
+
configDir = dirname(configPath);
|
|
74
91
|
if (!existsSync(configPath)) {
|
|
75
92
|
console.error(`Error: Config not found: ${configPath}\n` +
|
|
76
93
|
`Run from a directory with omnify.yaml, or use --config / --input + --output.`);
|
|
@@ -78,7 +95,10 @@ program
|
|
|
78
95
|
}
|
|
79
96
|
const resolved = resolveFromConfig(configPath);
|
|
80
97
|
inputPath = opts.input ? resolve(opts.input) : resolved.input;
|
|
81
|
-
|
|
98
|
+
tsEnabled = resolved.tsEnabled;
|
|
99
|
+
tsOutput = opts.output ? resolve(opts.output) : resolved.tsOutput;
|
|
100
|
+
laravelEnabled = resolved.laravelEnabled;
|
|
101
|
+
laravelOverrides = resolved.laravelOverrides;
|
|
82
102
|
}
|
|
83
103
|
// Read schemas.json
|
|
84
104
|
if (!existsSync(inputPath)) {
|
|
@@ -88,46 +108,73 @@ program
|
|
|
88
108
|
const raw = readFileSync(inputPath, 'utf-8');
|
|
89
109
|
const input = JSON.parse(raw);
|
|
90
110
|
console.log(`Reading schemas from ${inputPath}`);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
filePath
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
111
|
+
// ---- TypeScript generation ----
|
|
112
|
+
if (tsEnabled && tsOutput) {
|
|
113
|
+
console.log(`\n[TypeScript] Output: ${tsOutput}`);
|
|
114
|
+
const files = generateTypeScript(input);
|
|
115
|
+
mkdirSync(join(tsOutput, 'base'), { recursive: true });
|
|
116
|
+
mkdirSync(join(tsOutput, 'enum'), { recursive: true });
|
|
117
|
+
let tsCreated = 0;
|
|
118
|
+
let tsOverwritten = 0;
|
|
119
|
+
let tsSkipped = 0;
|
|
120
|
+
for (const file of files) {
|
|
121
|
+
let filePath;
|
|
122
|
+
if (file.category === 'enum' || file.category === 'plugin-enum') {
|
|
123
|
+
filePath = join(tsOutput, 'enum', file.filePath);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
filePath = join(tsOutput, file.filePath);
|
|
127
|
+
}
|
|
128
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
129
|
+
if (!file.overwrite && existsSync(filePath) && !opts.force) {
|
|
130
|
+
tsSkipped++;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
writeFileSync(filePath, file.content, 'utf-8');
|
|
134
|
+
if (file.overwrite || !existsSync(filePath)) {
|
|
135
|
+
tsOverwritten++;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
tsCreated++;
|
|
139
|
+
}
|
|
116
140
|
}
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
141
|
+
console.log(` ${tsOverwritten} files written (auto-generated)`);
|
|
142
|
+
if (tsCreated > 0)
|
|
143
|
+
console.log(` ${tsCreated} files created (user-editable)`);
|
|
144
|
+
if (tsSkipped > 0)
|
|
145
|
+
console.log(` ${tsSkipped} files skipped (already exist)`);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
console.log(`\n[TypeScript] Skipped (disabled)`);
|
|
149
|
+
}
|
|
150
|
+
// ---- PHP/Laravel generation ----
|
|
151
|
+
if (laravelEnabled) {
|
|
152
|
+
console.log(`\n[Laravel] Generating PHP files`);
|
|
153
|
+
const phpFiles = generatePhp(input, laravelOverrides);
|
|
154
|
+
let phpCreated = 0;
|
|
155
|
+
let phpOverwritten = 0;
|
|
156
|
+
let phpSkipped = 0;
|
|
157
|
+
for (const file of phpFiles) {
|
|
158
|
+
const filePath = resolve(configDir, file.path);
|
|
159
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
160
|
+
if (!file.overwrite && existsSync(filePath) && !opts.force) {
|
|
161
|
+
phpSkipped++;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
writeFileSync(filePath, file.content, 'utf-8');
|
|
165
|
+
if (file.overwrite || !existsSync(filePath)) {
|
|
166
|
+
phpOverwritten++;
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
phpCreated++;
|
|
170
|
+
}
|
|
123
171
|
}
|
|
172
|
+
console.log(` ${phpOverwritten} files written (auto-generated)`);
|
|
173
|
+
if (phpCreated > 0)
|
|
174
|
+
console.log(` ${phpCreated} files created (user-editable)`);
|
|
175
|
+
if (phpSkipped > 0)
|
|
176
|
+
console.log(` ${phpSkipped} files skipped (already exist)`);
|
|
124
177
|
}
|
|
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`);
|
|
178
|
+
console.log(`\nGeneration complete.`);
|
|
132
179
|
});
|
|
133
180
|
program.parse();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port of BaseModelGenerator.php — generates abstract BaseModel with morph map.
|
|
3
|
+
*/
|
|
4
|
+
import { SchemaReader } from './schema-reader.js';
|
|
5
|
+
import type { GeneratedFile, PhpConfig } from './types.js';
|
|
6
|
+
/** Generate the abstract BaseModel class with morph map. */
|
|
7
|
+
export declare function generateBaseModel(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port of BaseModelGenerator.php — generates abstract BaseModel with morph map.
|
|
3
|
+
*/
|
|
4
|
+
import { toPascalCase } from './naming-helper.js';
|
|
5
|
+
import { baseFile } from './types.js';
|
|
6
|
+
/** Generate the abstract BaseModel class with morph map. */
|
|
7
|
+
export function generateBaseModel(reader, config) {
|
|
8
|
+
const baseNamespace = config.models.baseNamespace;
|
|
9
|
+
const modelNamespace = config.models.namespace;
|
|
10
|
+
// Build morph map entries from all visible object schemas
|
|
11
|
+
const morphMap = {};
|
|
12
|
+
for (const name of Object.keys(reader.getVisibleObjectSchemas())) {
|
|
13
|
+
const modelName = toPascalCase(name);
|
|
14
|
+
morphMap[modelName] = `\\${modelNamespace}\\${modelName}::class`;
|
|
15
|
+
}
|
|
16
|
+
// Sort for consistent output
|
|
17
|
+
const sortedKeys = Object.keys(morphMap).sort();
|
|
18
|
+
const morphEntries = sortedKeys
|
|
19
|
+
.map(k => ` '${k}' => ${morphMap[k]},`)
|
|
20
|
+
.join('\n');
|
|
21
|
+
const content = `<?php
|
|
22
|
+
|
|
23
|
+
namespace ${baseNamespace};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Base model class for all Omnify-generated models.
|
|
27
|
+
* Contains model mapping for polymorphic relations.
|
|
28
|
+
*
|
|
29
|
+
* DO NOT EDIT - This file is auto-generated by Omnify.
|
|
30
|
+
* Any changes will be overwritten on next generation.
|
|
31
|
+
*
|
|
32
|
+
* @generated by omnify
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
use Illuminate\\Database\\Eloquent\\Model;
|
|
36
|
+
use Illuminate\\Database\\Eloquent\\Relations\\Relation;
|
|
37
|
+
|
|
38
|
+
abstract class BaseModel extends Model
|
|
39
|
+
{
|
|
40
|
+
/**
|
|
41
|
+
* Model class map for polymorphic relations.
|
|
42
|
+
*/
|
|
43
|
+
protected static array $modelMap = [
|
|
44
|
+
${morphEntries}
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Boot the model and register morph map.
|
|
49
|
+
*/
|
|
50
|
+
protected static function boot(): void
|
|
51
|
+
{
|
|
52
|
+
parent::boot();
|
|
53
|
+
|
|
54
|
+
// Register morph map for polymorphic relations
|
|
55
|
+
Relation::enforceMorphMap(static::$modelMap);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get the model class for a given morph type.
|
|
60
|
+
*/
|
|
61
|
+
public static function getModelClass(string $morphType): ?string
|
|
62
|
+
{
|
|
63
|
+
return static::$modelMap[$morphType] ?? null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
`;
|
|
67
|
+
return [
|
|
68
|
+
baseFile(`${config.models.basePath}/BaseModel.php`, content),
|
|
69
|
+
];
|
|
70
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port of FactoryGenerator.php — generates Model Factory classes.
|
|
3
|
+
*/
|
|
4
|
+
import { SchemaReader } from './schema-reader.js';
|
|
5
|
+
import type { GeneratedFile, PhpConfig } from './types.js';
|
|
6
|
+
/** Generate Factory classes for all visible object schemas. */
|
|
7
|
+
export declare function generateFactories(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port of FactoryGenerator.php — generates Model Factory classes.
|
|
3
|
+
*/
|
|
4
|
+
import { toPascalCase } from './naming-helper.js';
|
|
5
|
+
import { toFaker, compoundFaker, associationFaker } from './faker-mapper.js';
|
|
6
|
+
import { userFile } from './types.js';
|
|
7
|
+
/** Generate Factory classes for all visible object schemas. */
|
|
8
|
+
export function generateFactories(reader, config) {
|
|
9
|
+
const files = [];
|
|
10
|
+
const schemas = reader.getSchemas();
|
|
11
|
+
for (const [name, schema] of Object.entries(reader.getVisibleObjectSchemas())) {
|
|
12
|
+
const file = generateFactory(name, schema, reader, schemas, config);
|
|
13
|
+
if (file)
|
|
14
|
+
files.push(file);
|
|
15
|
+
}
|
|
16
|
+
return files;
|
|
17
|
+
}
|
|
18
|
+
function generateFactory(name, schema, reader, schemas, config) {
|
|
19
|
+
const modelName = toPascalCase(name);
|
|
20
|
+
const modelNamespace = config.models.namespace;
|
|
21
|
+
const factoryNamespace = config.factories.namespace;
|
|
22
|
+
const properties = (schema.properties ?? {});
|
|
23
|
+
const expandedProperties = reader.getExpandedProperties(name);
|
|
24
|
+
const propertyOrder = reader.getPropertyOrder(name);
|
|
25
|
+
const attributes = [];
|
|
26
|
+
const imports = [];
|
|
27
|
+
for (const propName of propertyOrder) {
|
|
28
|
+
const prop = properties[propName];
|
|
29
|
+
if (!prop)
|
|
30
|
+
continue;
|
|
31
|
+
const type = prop['type'] ?? 'String';
|
|
32
|
+
// Handle associations (foreign keys)
|
|
33
|
+
if (type === 'Association') {
|
|
34
|
+
const result = associationFaker(propName, prop, modelNamespace, name);
|
|
35
|
+
if (result) {
|
|
36
|
+
attributes.push(result.fake);
|
|
37
|
+
if (result.import)
|
|
38
|
+
imports.push(result.import);
|
|
39
|
+
}
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
// Handle compound types
|
|
43
|
+
if (expandedProperties[propName]) {
|
|
44
|
+
const expansion = expandedProperties[propName];
|
|
45
|
+
const sourceType = expansion.sourceType ?? '';
|
|
46
|
+
const fakes = compoundFaker(propName, expansion, sourceType);
|
|
47
|
+
attributes.push(...fakes);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// Handle regular properties
|
|
51
|
+
const fake = toFaker(propName, prop, schemas);
|
|
52
|
+
if (fake)
|
|
53
|
+
attributes.push(fake);
|
|
54
|
+
}
|
|
55
|
+
const attributesStr = attributes.length > 0
|
|
56
|
+
? attributes.map(a => ` ${a}`).join('\n')
|
|
57
|
+
: '';
|
|
58
|
+
const uniqueImports = [...new Set(imports)];
|
|
59
|
+
const importsStr = uniqueImports.length > 0
|
|
60
|
+
? '\n' + uniqueImports.join('\n')
|
|
61
|
+
: '';
|
|
62
|
+
const content = `<?php
|
|
63
|
+
|
|
64
|
+
namespace ${factoryNamespace};
|
|
65
|
+
|
|
66
|
+
use ${modelNamespace}\\${modelName};
|
|
67
|
+
use Illuminate\\Database\\Eloquent\\Factories\\Factory;
|
|
68
|
+
${importsStr}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* ${modelName} Factory
|
|
72
|
+
*
|
|
73
|
+
* SAFE TO EDIT - This file is never overwritten by Omnify.
|
|
74
|
+
*
|
|
75
|
+
* @extends Factory<${modelName}>
|
|
76
|
+
*/
|
|
77
|
+
class ${modelName}Factory extends Factory
|
|
78
|
+
{
|
|
79
|
+
protected $model = ${modelName}::class;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Define the model's default state.
|
|
83
|
+
*
|
|
84
|
+
* @return array<string, mixed>
|
|
85
|
+
*/
|
|
86
|
+
public function definition(): array
|
|
87
|
+
{
|
|
88
|
+
return [
|
|
89
|
+
${attributesStr}
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
return userFile(`${config.factories.path}/${modelName}Factory.php`, content);
|
|
95
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Port of FakerMapper.php — generates Faker expressions for factories.
|
|
3
|
+
*/
|
|
4
|
+
/** Generate faker expression for a property. */
|
|
5
|
+
export declare function toFaker(propName: string, property: Record<string, unknown>, schemas?: Record<string, Record<string, unknown>>): string | null;
|
|
6
|
+
/** Generate faker expressions for compound type expanded columns. */
|
|
7
|
+
export declare function compoundFaker(propName: string, expansion: Record<string, unknown>, sourceType: string): string[];
|
|
8
|
+
/** Generate faker for association (foreign key). */
|
|
9
|
+
export declare function associationFaker(propName: string, property: Record<string, unknown>, modelNamespace: string, currentSchema: string): {
|
|
10
|
+
fake: string;
|
|
11
|
+
import: string | null;
|
|
12
|
+
} | null;
|