@omnifyjp/omnify 5.8.2 → 5.8.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/omnify-config-schema.json +5 -0
- package/package.json +6 -6
- package/ts-dist/php/controller-generator.js +22 -5
- package/ts-dist/php/factory-generator.js +9 -2
- package/ts-dist/php/model-generator.js +22 -5
- package/ts-dist/php/policy-generator.js +9 -3
- package/ts-dist/php/request-generator.js +2 -2
- package/ts-dist/php/resource-generator.js +2 -2
- package/ts-dist/php/schema-config-generator.js +9 -2
- package/ts-dist/php/service-generator.js +11 -3
- package/ts-dist/php/service-provider-generator.js +12 -4
- package/ts-dist/php/translation-model-generator.js +43 -11
- package/ts-dist/php/types.d.ts +44 -0
- package/ts-dist/php/types.js +28 -2
- package/types/config.d.ts +10 -0
|
@@ -474,6 +474,11 @@
|
|
|
474
474
|
"type": "boolean",
|
|
475
475
|
"default": false,
|
|
476
476
|
"description": "Drop the `Base/` (or `OmnifyBase/`) subfolder + `*Base*` class suffix. Base lives at `path` with class name `<Schema>` (models) or `<Schema><Layer>` (service / resource / etc.). Editable stub extends the bare base FQN. Recommended when `path` already isolates generated code (e.g. `app/Omnify/Models/`). Issue #96, v5.4+."
|
|
477
|
+
},
|
|
478
|
+
"userEditableGroupByFolder": {
|
|
479
|
+
"type": "boolean",
|
|
480
|
+
"default": false,
|
|
481
|
+
"description": "Mirror the schema's group folder onto the USER-EDITABLE layer's path + namespace (issue #98 v5.8.5). Default false — Laravel-canonical flat editable (e.g. `app/Models/User.php`, `App\\Models\\User`); group is encoded only in the base layer + the `use ... as Base` import. Set true to restore v5.8.x grouped editable shape (e.g. `app/Models/Auth/User.php`, `App\\Models\\Auth\\User`). Per layer."
|
|
477
482
|
}
|
|
478
483
|
}
|
|
479
484
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnifyjp/omnify",
|
|
3
|
-
"version": "5.8.
|
|
3
|
+
"version": "5.8.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": "5.8.
|
|
40
|
-
"@omnifyjp/omnify-darwin-x64": "5.8.
|
|
41
|
-
"@omnifyjp/omnify-linux-x64": "5.8.
|
|
42
|
-
"@omnifyjp/omnify-linux-arm64": "5.8.
|
|
43
|
-
"@omnifyjp/omnify-win32-x64": "5.8.
|
|
39
|
+
"@omnifyjp/omnify-darwin-arm64": "5.8.4",
|
|
40
|
+
"@omnifyjp/omnify-darwin-x64": "5.8.4",
|
|
41
|
+
"@omnifyjp/omnify-linux-x64": "5.8.4",
|
|
42
|
+
"@omnifyjp/omnify-linux-arm64": "5.8.4",
|
|
43
|
+
"@omnifyjp/omnify-win32-x64": "5.8.4"
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -26,11 +26,28 @@ function generateBaseController(name, schema, reader, config) {
|
|
|
26
26
|
const lookup = api.lookup ?? true;
|
|
27
27
|
const bulkDelete = api.bulkDelete ?? false;
|
|
28
28
|
const restore = api.restore ?? (schema.options?.softDelete ?? false);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
// Issue #98 v5.8.4 / v5.8.5: cross-layer imports must mirror where
|
|
30
|
+
// the imported file actually lives. BASE layers (Models, Services
|
|
31
|
+
// base, Controllers base) are ALWAYS group-nested in legacy structure.
|
|
32
|
+
// EDITABLE layers (Requests, Resources) are nested only when the
|
|
33
|
+
// target layer's `userEditableGroupByFolder` is `true` — Laravel-
|
|
34
|
+
// canonical defaults to flat. Modular structure already isolates
|
|
35
|
+
// per-schema so all paths skip group nesting.
|
|
36
|
+
const groupNestBase = (ns) => config.structure === 'modular'
|
|
37
|
+
? ns
|
|
38
|
+
: nestByGroup({ path: '', namespace: ns }, schema.group).namespace;
|
|
39
|
+
const groupNestEditable = (ns, layer) => config.structure === 'modular'
|
|
40
|
+
? ns
|
|
41
|
+
: (layer.userEditableGroupByFolder ? nestByGroup({ path: '', namespace: ns }, schema.group).namespace : ns);
|
|
42
|
+
const baseNs = config.structure === 'modular'
|
|
43
|
+
? resolveModularBaseNamespace(config, name, 'Controllers', config.controllers.baseNamespace)
|
|
44
|
+
: groupNestBase(config.controllers.baseNamespace);
|
|
45
|
+
const modelNs = groupNestBase(config.models.namespace);
|
|
46
|
+
const serviceBaseNs = config.structure === 'modular'
|
|
47
|
+
? resolveModularBaseNamespace(config, name, 'Services', config.services.baseNamespace)
|
|
48
|
+
: groupNestBase(config.services.baseNamespace);
|
|
49
|
+
const requestNs = groupNestEditable(config.requests.namespace, config.requests);
|
|
50
|
+
const resourceNs = groupNestEditable(config.resources.namespace, config.resources);
|
|
34
51
|
// Build imports
|
|
35
52
|
const imports = [
|
|
36
53
|
`use App\\Http\\Controllers\\Controller;`,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { toPascalCase } from './naming-helper.js';
|
|
5
5
|
import { toFaker, compoundFaker, associationFaker } from './faker-mapper.js';
|
|
6
|
-
import { userFile } from './types.js';
|
|
6
|
+
import { userFile, nestByGroup } from './types.js';
|
|
7
7
|
/** Generate Factory classes for all project-owned visible object schemas. */
|
|
8
8
|
export function generateFactories(reader, config) {
|
|
9
9
|
const files = [];
|
|
@@ -17,7 +17,14 @@ export function generateFactories(reader, config) {
|
|
|
17
17
|
}
|
|
18
18
|
function generateFactory(name, schema, reader, schemas, config) {
|
|
19
19
|
const modelName = toPascalCase(name);
|
|
20
|
-
|
|
20
|
+
// Issue #98 v5.8.4: legacy structure groups Models by folder, so the
|
|
21
|
+
// factory's `use ${modelNamespace}\\${modelName}` import must include
|
|
22
|
+
// the schema's group too — otherwise the factory references a flat
|
|
23
|
+
// path that no longer holds the model file. Modular structure
|
|
24
|
+
// already isolates per-schema so it skips the nesting.
|
|
25
|
+
const modelNamespace = config.structure === 'modular'
|
|
26
|
+
? config.models.namespace
|
|
27
|
+
: nestByGroup({ path: '', namespace: config.models.namespace }, schema.group).namespace;
|
|
21
28
|
const factoryNamespace = config.factories.namespace;
|
|
22
29
|
const properties = (schema.properties ?? {});
|
|
23
30
|
const expandedProperties = reader.getExpandedProperties(name);
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { toPascalCase, toSnakeCase, toCamelCase, pluralize } from './naming-helper.js';
|
|
5
5
|
import { toCast, toPhpDocType, isHiddenByDefault } from './type-mapper.js';
|
|
6
6
|
import { buildRelation } from './relation-builder.js';
|
|
7
|
-
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveSharedBaseNamespace, resolveGlobalTraitNamespace, resolveGlobalEnumNamespace, resolveBaseClass, resolveEditableClass, nestByGroup, } from './types.js';
|
|
7
|
+
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveSharedBaseNamespace, resolveGlobalTraitNamespace, resolveGlobalEnumNamespace, resolveBaseClass, resolveEditableClass, nestByGroup, nestEditableByGroup, } from './types.js';
|
|
8
8
|
import { enumClassName } from './enum-generator.js';
|
|
9
9
|
/** Generate base model and user model for all project-owned object schemas,
|
|
10
10
|
* plus user models for package schemas (extending the package model). */
|
|
@@ -42,6 +42,13 @@ function generateBaseModel(name, schema, reader, config) {
|
|
|
42
42
|
// below) is the PARENT class string (BaseModel / Authenticatable).
|
|
43
43
|
// Issue #98 v5.8+: apply group-by-folder nesting in legacy structure
|
|
44
44
|
// so 100+ schemas produce a navigable tree mirroring schemas/<group>/.
|
|
45
|
+
// v5.8.5: pivot schemas (`kind: pivot`) override group → all pivot
|
|
46
|
+
// models live under a dedicated `Pivot/` subfolder regardless of the
|
|
47
|
+
// YAML group, mirroring the dedicated `Translation/` subfolder. Devs
|
|
48
|
+
// rarely interact with pivot models directly (Eloquent uses them
|
|
49
|
+
// implicitly), so consolidating them keeps the main model namespace
|
|
50
|
+
// clean.
|
|
51
|
+
const isPivot = schema.kind === 'pivot';
|
|
45
52
|
let baseDescriptor;
|
|
46
53
|
let baseNamespace;
|
|
47
54
|
if (config.structure === 'modular') {
|
|
@@ -54,7 +61,9 @@ function generateBaseModel(name, schema, reader, config) {
|
|
|
54
61
|
}
|
|
55
62
|
else {
|
|
56
63
|
const raw = resolveBaseClass(config.models, modelName, 'BaseModel');
|
|
57
|
-
const nested =
|
|
64
|
+
const nested = isPivot
|
|
65
|
+
? { path: `${raw.path}/Pivot`, namespace: `${raw.fqn.substring(0, raw.fqn.lastIndexOf('\\'))}\\Pivot` }
|
|
66
|
+
: nestByGroup({ path: raw.path, namespace: raw.fqn.substring(0, raw.fqn.lastIndexOf('\\')) }, schema.group);
|
|
58
67
|
baseDescriptor = { className: raw.className, path: nested.path, fqn: `${nested.namespace}\\${raw.className}` };
|
|
59
68
|
baseNamespace = nested.namespace;
|
|
60
69
|
}
|
|
@@ -221,7 +230,11 @@ function generateUserModel(name, schema, config, hasNestedSet = false) {
|
|
|
221
230
|
// than the base (e.g. `app/Models/<Name>.php` while base is at
|
|
222
231
|
// `app/Omnify/Models/<Name>.php`). resolveEditableClass +
|
|
223
232
|
// resolveBaseClass keep the extends-clause + use-import in sync.
|
|
224
|
-
// Issue #98 v5.8
|
|
233
|
+
// Issue #98 v5.8.5: pivot schemas always nest under `Pivot/` on
|
|
234
|
+
// both base AND editable sides regardless of `userEditableGroupByFolder`.
|
|
235
|
+
// Pivots are infrastructure — keeping them out of the main namespace
|
|
236
|
+
// mirrors how Laravel devs treat `Illuminate\Database\Eloquent\Relations\Pivot`.
|
|
237
|
+
const isPivot = schema.kind === 'pivot';
|
|
225
238
|
let editable;
|
|
226
239
|
let base;
|
|
227
240
|
if (config.structure === 'modular') {
|
|
@@ -230,10 +243,14 @@ function generateUserModel(name, schema, config, hasNestedSet = false) {
|
|
|
230
243
|
}
|
|
231
244
|
else {
|
|
232
245
|
const editRaw = resolveEditableClass(config.models, modelName);
|
|
233
|
-
const editNested =
|
|
246
|
+
const editNested = isPivot
|
|
247
|
+
? { path: `${editRaw.path}/Pivot`, namespace: `${editRaw.namespace}\\Pivot` }
|
|
248
|
+
: nestEditableByGroup(config.models, { path: editRaw.path, namespace: editRaw.namespace }, schema.group);
|
|
234
249
|
editable = { className: editRaw.className, fileName: editRaw.fileName, namespace: editNested.namespace, path: editNested.path, fqn: `${editNested.namespace}\\${editRaw.className}` };
|
|
235
250
|
const baseRaw = resolveBaseClass(config.models, modelName, 'BaseModel');
|
|
236
|
-
const baseNs =
|
|
251
|
+
const baseNs = isPivot
|
|
252
|
+
? `${baseRaw.fqn.substring(0, baseRaw.fqn.lastIndexOf('\\'))}\\Pivot`
|
|
253
|
+
: nestByGroup({ path: baseRaw.path, namespace: baseRaw.fqn.substring(0, baseRaw.fqn.lastIndexOf('\\')) }, schema.group).namespace;
|
|
237
254
|
base = { className: baseRaw.className, fqn: `${baseNs}\\${baseRaw.className}` };
|
|
238
255
|
}
|
|
239
256
|
const factoryNamespace = config.factories.namespace;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Only generates for schemas that have `policies` defined.
|
|
6
6
|
*/
|
|
7
7
|
import { toPascalCase, toSnakeCase, toCamelCase } from './naming-helper.js';
|
|
8
|
-
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass, nestByGroup } from './types.js';
|
|
8
|
+
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass, nestByGroup, nestEditableByGroup } from './types.js';
|
|
9
9
|
// ============================================================================
|
|
10
10
|
// Action mapping: Omnify → Laravel
|
|
11
11
|
// ============================================================================
|
|
@@ -46,7 +46,13 @@ function generateForSchema(name, schema, reader, config) {
|
|
|
46
46
|
function generateBasePolicyClass(name, schema, reader, config) {
|
|
47
47
|
const modelName = toPascalCase(name);
|
|
48
48
|
const baseNamespace = resolveModularBaseNamespace(config, name, 'Policies', config.policies.baseNamespace);
|
|
49
|
-
|
|
49
|
+
// Issue #98 v5.8.4: legacy structure groups Models by folder. The
|
|
50
|
+
// policy's `use ${modelNamespace}\\${modelName}` import must include
|
|
51
|
+
// the schema's group too, otherwise the policy fails to autoload at
|
|
52
|
+
// runtime when Laravel resolves it from the gate registration.
|
|
53
|
+
const modelNamespace = config.structure === 'modular'
|
|
54
|
+
? config.models.namespace
|
|
55
|
+
: nestByGroup({ path: '', namespace: config.models.namespace }, schema.group).namespace;
|
|
50
56
|
const policies = schema.policies;
|
|
51
57
|
const hasSoftDelete = schema.options?.softDelete ?? false;
|
|
52
58
|
const needsCidrHelper = policiesUseCidr(policies);
|
|
@@ -122,7 +128,7 @@ function generateUserPolicyClass(name, schema, config) {
|
|
|
122
128
|
const baseNs = nestByGroup({ path: baseRaw.path, namespace: baseRaw.fqn.substring(0, baseRaw.fqn.lastIndexOf('\\')) }, schema.group).namespace;
|
|
123
129
|
baseDescriptor = { className: baseRaw.className, fqn: `${baseNs}\\${baseRaw.className}` };
|
|
124
130
|
const editRaw = resolveEditableClass(config.policies, modelName, 'Policy');
|
|
125
|
-
const editNested =
|
|
131
|
+
const editNested = nestEditableByGroup(config.policies, { path: editRaw.path, namespace: editRaw.namespace }, schema.group);
|
|
126
132
|
editable = { className: editRaw.className, fileName: editRaw.fileName, namespace: editNested.namespace, path: editNested.path };
|
|
127
133
|
}
|
|
128
134
|
// Issue #98: alias on class-name collision (flatBase + userEditablePath).
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { toPascalCase, toSnakeCase, toCamelCase } from './naming-helper.js';
|
|
5
5
|
import { toStoreRules, toUpdateRules, formatRules, hasRuleObject } from './type-mapper.js';
|
|
6
|
-
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass, nestByGroup } from './types.js';
|
|
6
|
+
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass, nestByGroup, nestEditableByGroup } from './types.js';
|
|
7
7
|
/**
|
|
8
8
|
* Resolve the Laravel validation rule type ('integer', 'uuid', or 'string') for
|
|
9
9
|
* a foreign key column based on the target schema's primary key type.
|
|
@@ -294,7 +294,7 @@ function generateUserRequest(name, schema, config, action) {
|
|
|
294
294
|
const baseNs = nestByGroup({ path: baseRaw.path, namespace: baseRaw.fqn.substring(0, baseRaw.fqn.lastIndexOf('\\')) }, schema.group).namespace;
|
|
295
295
|
baseDescriptor = { className: baseRaw.className, fqn: `${baseNs}\\${baseRaw.className}` };
|
|
296
296
|
const editRaw = resolveEditableClass(config.requests, modelName, `${action}Request`);
|
|
297
|
-
const editNested =
|
|
297
|
+
const editNested = nestEditableByGroup(config.requests, { path: editRaw.path, namespace: editRaw.namespace }, schema.group);
|
|
298
298
|
editable = { className: editRaw.className, fileName: editRaw.fileName, namespace: editNested.namespace, path: editNested.path };
|
|
299
299
|
}
|
|
300
300
|
// Issue #98: alias on class-name collision (flatBase + userEditablePath).
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { toPascalCase, toSnakeCase, toCamelCase } from './naming-helper.js';
|
|
5
5
|
import { isHiddenByDefault, toResourceExpression } from './type-mapper.js';
|
|
6
|
-
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass, nestByGroup } from './types.js';
|
|
6
|
+
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass, nestByGroup, nestEditableByGroup } from './types.js';
|
|
7
7
|
/** Generate Resource classes for all project-owned visible object schemas. */
|
|
8
8
|
export function generateResources(reader, config) {
|
|
9
9
|
const files = [];
|
|
@@ -158,7 +158,7 @@ function generateUserResource(name, schema, config) {
|
|
|
158
158
|
const baseNs = nestByGroup({ path: baseRaw.path, namespace: baseRaw.fqn.substring(0, baseRaw.fqn.lastIndexOf('\\')) }, schema.group).namespace;
|
|
159
159
|
baseDescriptor = { className: baseRaw.className, fqn: `${baseNs}\\${baseRaw.className}` };
|
|
160
160
|
const editRaw = resolveEditableClass(config.resources, modelName, 'Resource');
|
|
161
|
-
const editNested =
|
|
161
|
+
const editNested = nestEditableByGroup(config.resources, { path: editRaw.path, namespace: editRaw.namespace }, schema.group);
|
|
162
162
|
editable = { className: editRaw.className, fileName: editRaw.fileName, namespace: editNested.namespace, path: editNested.path };
|
|
163
163
|
}
|
|
164
164
|
// Issue #98: alias on class-name collision (flatBase + userEditablePath).
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Only includes properties that have searchable/filterable/sortable flags.
|
|
6
6
|
*/
|
|
7
7
|
import { toPascalCase, toSnakeCase } from './naming-helper.js';
|
|
8
|
-
import { baseFile } from './types.js';
|
|
8
|
+
import { baseFile, nestByGroup } from './types.js';
|
|
9
9
|
/** Generate the omnify-schemas config file. */
|
|
10
10
|
export function generateSchemaConfig(reader, config) {
|
|
11
11
|
const apiSchemas = reader.getSchemasWithApi();
|
|
@@ -64,9 +64,16 @@ function generateSchemaEntry(name, schema, reader, config) {
|
|
|
64
64
|
const propsBlock = propLines.length > 0
|
|
65
65
|
? `\n 'properties' => [\n${propLines.join('\n')}\n ],`
|
|
66
66
|
: `\n 'properties' => [],`;
|
|
67
|
+
// Issue #98 v5.8.4: Query Engine resolves `model` via reflection, so
|
|
68
|
+
// the FQN must reflect the actual file location (which now includes
|
|
69
|
+
// the schema's group folder for legacy structure). Modular structure
|
|
70
|
+
// already isolates per-schema so it skips the nesting.
|
|
71
|
+
const modelNs = config.structure === 'modular'
|
|
72
|
+
? config.models.namespace
|
|
73
|
+
: nestByGroup({ path: '', namespace: config.models.namespace }, schema.group).namespace;
|
|
67
74
|
return ` '${modelName}' => [
|
|
68
75
|
'table' => '${tableName}',
|
|
69
|
-
'model' => \\${
|
|
76
|
+
'model' => \\${modelNs}\\${modelName}::class,
|
|
70
77
|
'softDelete' => ${softDelete ? 'true' : 'false'},
|
|
71
78
|
'perPage' => ${perPage},${propsBlock}
|
|
72
79
|
],
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* eagerCount}` keys are deprecated and emit warnings at generate time.
|
|
14
14
|
*/
|
|
15
15
|
import { toPascalCase, toSnakeCase } from './naming-helper.js';
|
|
16
|
-
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass, nestByGroup } from './types.js';
|
|
16
|
+
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass, nestByGroup, nestEditableByGroup } from './types.js';
|
|
17
17
|
// ============================================================================
|
|
18
18
|
// Public entry point
|
|
19
19
|
// ============================================================================
|
|
@@ -510,7 +510,15 @@ function generateBaseService(name, schema, reader, config) {
|
|
|
510
510
|
const baseNs = config.structure === 'modular'
|
|
511
511
|
? resolveModularBaseNamespace(config, name, 'Services', config.services.baseNamespace)
|
|
512
512
|
: nestByGroup({ path: '', namespace: config.services.baseNamespace }, schema.group).namespace;
|
|
513
|
-
|
|
513
|
+
// Issue #98 v5.8.4: cross-layer Model imports must include the
|
|
514
|
+
// schema's group folder so they resolve to where Models actually
|
|
515
|
+
// live now. Pre-fix the service base emitted
|
|
516
|
+
// `use App\Omnify\Models\Banner;` while the model file was at
|
|
517
|
+
// `app/Omnify/Models/Cms/Banner.php` — autoload failed at runtime
|
|
518
|
+
// for every consumer (TypeError + missing-file include).
|
|
519
|
+
const modelNs = config.structure === 'modular'
|
|
520
|
+
? config.models.namespace
|
|
521
|
+
: nestByGroup({ path: '', namespace: config.models.namespace }, schema.group).namespace;
|
|
514
522
|
// Build imports
|
|
515
523
|
const imports = [
|
|
516
524
|
`use ${modelNs}\\${modelName};`,
|
|
@@ -1784,7 +1792,7 @@ function generateUserService(name, schema, config) {
|
|
|
1784
1792
|
const baseNs = nestByGroup({ path: baseRaw.path, namespace: baseRaw.fqn.substring(0, baseRaw.fqn.lastIndexOf('\\')) }, schema.group).namespace;
|
|
1785
1793
|
baseDescriptor = { className: baseRaw.className, fqn: `${baseNs}\\${baseRaw.className}` };
|
|
1786
1794
|
const editRaw = resolveEditableClass(config.services, modelName, 'Service');
|
|
1787
|
-
const editNested =
|
|
1795
|
+
const editNested = nestEditableByGroup(config.services, { path: editRaw.path, namespace: editRaw.namespace }, schema.group);
|
|
1788
1796
|
editable = { className: editRaw.className, fileName: editRaw.fileName, namespace: editNested.namespace, path: editNested.path };
|
|
1789
1797
|
}
|
|
1790
1798
|
// Group nesting already applied via nestByGroup above; no additional
|
|
@@ -2,15 +2,23 @@
|
|
|
2
2
|
* Port of AppServiceProviderGenerator.php — generates OmnifyServiceProvider.
|
|
3
3
|
*/
|
|
4
4
|
import { toPascalCase } from './naming-helper.js';
|
|
5
|
-
import { baseFile } from './types.js';
|
|
5
|
+
import { baseFile, nestByGroup } from './types.js';
|
|
6
6
|
/** Generate the OmnifyServiceProvider for the application. */
|
|
7
7
|
export function generateServiceProvider(reader, config) {
|
|
8
8
|
const modelNamespace = config.models.namespace;
|
|
9
|
-
// Build morph map entries (project-owned schemas only)
|
|
9
|
+
// Build morph map entries (project-owned schemas only). Issue #98 v5.8.4:
|
|
10
|
+
// each schema's actual model FQN includes its group folder, so the
|
|
11
|
+
// morph map must echo that — pre-fix every entry pointed to the flat
|
|
12
|
+
// `App\Omnify\Models\<Name>::class` and Laravel's morphMap resolved to
|
|
13
|
+
// a non-existent class for every grouped schema, breaking polymorphic
|
|
14
|
+
// relationships project-wide.
|
|
10
15
|
const morphEntries = [];
|
|
11
|
-
for (const name of Object.
|
|
16
|
+
for (const [name, schema] of Object.entries(reader.getProjectVisibleObjectSchemas())) {
|
|
12
17
|
const modelName = toPascalCase(name);
|
|
13
|
-
|
|
18
|
+
const ns = config.structure === 'modular'
|
|
19
|
+
? modelNamespace
|
|
20
|
+
: nestByGroup({ path: '', namespace: modelNamespace }, schema.group).namespace;
|
|
21
|
+
morphEntries.push(` '${modelName}' => \\${ns}\\${modelName}::class,`);
|
|
14
22
|
}
|
|
15
23
|
// Add File to morph map when any schema uses File type
|
|
16
24
|
if (reader.hasFileProperties()) {
|
|
@@ -64,6 +64,12 @@ function generateTranslationBaseModel(name, translatableFields, enforceLanguageF
|
|
|
64
64
|
// Astrotomic's `translation_model_namespace` once globally instead
|
|
65
65
|
// of per-model `$translationModel` overrides. Modular structure
|
|
66
66
|
// already isolates per-Schema so this nesting only applies to legacy.
|
|
67
|
+
// Issue #98 v5.8.3: when `flatBase: true`, the model layer drops
|
|
68
|
+
// the `Base/` subfolder + `BaseModel` suffix everywhere else; the
|
|
69
|
+
// translation base must follow the same convention so naming
|
|
70
|
+
// stays consistent across layers (a class extending `Model` named
|
|
71
|
+
// `CategoryTranslationBaseModel` while siblings drop the suffix is
|
|
72
|
+
// the bug the reporter flagged).
|
|
67
73
|
const baseNamespace = config.structure === 'modular'
|
|
68
74
|
? resolveModularBaseNamespace(config, name, 'Models', config.models.baseNamespace)
|
|
69
75
|
: `${config.models.baseNamespace}\\Translation`;
|
|
@@ -93,6 +99,12 @@ function generateTranslationBaseModel(name, translatableFields, enforceLanguageF
|
|
|
93
99
|
protected $casts = [
|
|
94
100
|
${castEntries.join('\n')}
|
|
95
101
|
];`;
|
|
102
|
+
// Issue #98 v5.8.3: honor flatBase. With flatBase=true the base
|
|
103
|
+
// class drops the `BaseModel` suffix and lives at the bare name,
|
|
104
|
+
// matching how every other Models-layer base behaves under flatBase.
|
|
105
|
+
// Modular structure ignores flatBase (per-Schema isolation already).
|
|
106
|
+
const flatBase = config.structure !== 'modular' && config.models.flatBase === true;
|
|
107
|
+
const baseClassName = flatBase ? `${modelName}Translation` : `${modelName}TranslationBaseModel`;
|
|
96
108
|
const content = `<?php
|
|
97
109
|
|
|
98
110
|
namespace ${baseNamespace};
|
|
@@ -107,9 +119,9 @@ namespace ${baseNamespace};
|
|
|
107
119
|
use Illuminate\\Database\\Eloquent\\Model;
|
|
108
120
|
|
|
109
121
|
/**
|
|
110
|
-
* ${
|
|
122
|
+
* ${baseClassName}
|
|
111
123
|
*/
|
|
112
|
-
class ${
|
|
124
|
+
class ${baseClassName} extends Model
|
|
113
125
|
{
|
|
114
126
|
/**
|
|
115
127
|
* The table associated with the model.
|
|
@@ -130,10 +142,11 @@ ${fillableLines}
|
|
|
130
142
|
}
|
|
131
143
|
`;
|
|
132
144
|
// Issue #98 v5.8.2: legacy structure routes translation bases to
|
|
133
|
-
// `<modelsBasePath>/Translation/<Name>TranslationBaseModel.php
|
|
145
|
+
// `<modelsBasePath>/Translation/<Name>TranslationBaseModel.php`
|
|
146
|
+
// (or `${modelName}Translation.php` when flatBase drops the suffix).
|
|
134
147
|
const filePath = config.structure === 'modular'
|
|
135
|
-
? resolveModularBasePath(config, name, 'Models', `${
|
|
136
|
-
: `${config.models.basePath}/Translation/${
|
|
148
|
+
? resolveModularBasePath(config, name, 'Models', `${baseClassName}.php`, config.models.basePath)
|
|
149
|
+
: `${config.models.basePath}/Translation/${baseClassName}.php`;
|
|
137
150
|
return baseFile(filePath, content);
|
|
138
151
|
}
|
|
139
152
|
function generateTranslationUserModel(name, config) {
|
|
@@ -141,32 +154,51 @@ function generateTranslationUserModel(name, config) {
|
|
|
141
154
|
// Issue #98 v5.8.2: editable translation stub also lives under
|
|
142
155
|
// `Translation/` to mirror the base layout. Astrotomic finds it via
|
|
143
156
|
// the project-set `translation_model_namespace`.
|
|
157
|
+
// Issue #98 v5.8.3 (Bug 1): when `userEditablePath` points elsewhere
|
|
158
|
+
// (e.g. canonical Laravel `app/Models/`), the namespace must match
|
|
159
|
+
// the FILE LOCATION, not the base namespace — otherwise PSR-4 fails
|
|
160
|
+
// at runtime. Use `userEditableNamespace` (resolved from the config
|
|
161
|
+
// override) instead of the base `models.namespace`.
|
|
144
162
|
const modelNamespace = config.structure === 'modular'
|
|
145
163
|
? config.models.namespace
|
|
146
|
-
: `${config.models.
|
|
164
|
+
: `${config.models.userEditableNamespace}\\Translation`;
|
|
147
165
|
const baseNamespace = config.structure === 'modular'
|
|
148
166
|
? resolveModularBaseNamespace(config, name, 'Models', config.models.baseNamespace)
|
|
149
167
|
: `${config.models.baseNamespace}\\Translation`;
|
|
168
|
+
// Issue #98 v5.8.3 (Bug 2): honor flatBase + alias on class-name
|
|
169
|
+
// collision. With flatBase the base class is also named
|
|
170
|
+
// `<Model>Translation` (no suffix); the editable stub then needs
|
|
171
|
+
// an alias to avoid a self-collision when extending. Mirrors the
|
|
172
|
+
// pattern used by resource / service / request user generators.
|
|
173
|
+
const flatBase = config.structure !== 'modular' && config.models.flatBase === true;
|
|
174
|
+
const baseClassName = flatBase ? `${modelName}Translation` : `${modelName}TranslationBaseModel`;
|
|
175
|
+
const editableClassName = `${modelName}Translation`;
|
|
176
|
+
const baseAlias = baseClassName === editableClassName
|
|
177
|
+
? `${editableClassName}Base`
|
|
178
|
+
: baseClassName;
|
|
179
|
+
const baseImport = baseAlias === baseClassName
|
|
180
|
+
? `use ${baseNamespace}\\${baseClassName};`
|
|
181
|
+
: `use ${baseNamespace}\\${baseClassName} as ${baseAlias};`;
|
|
150
182
|
const content = `<?php
|
|
151
183
|
|
|
152
184
|
namespace ${modelNamespace};
|
|
153
185
|
|
|
154
|
-
|
|
186
|
+
${baseImport}
|
|
155
187
|
|
|
156
188
|
/**
|
|
157
|
-
* ${
|
|
189
|
+
* ${editableClassName} Model
|
|
158
190
|
*
|
|
159
191
|
* This file is generated once and can be customized.
|
|
160
192
|
* Add your custom methods and logic here.
|
|
161
193
|
*/
|
|
162
|
-
class ${
|
|
194
|
+
class ${editableClassName} extends ${baseAlias}
|
|
163
195
|
{
|
|
164
196
|
// Add your custom methods here
|
|
165
197
|
}
|
|
166
198
|
`;
|
|
167
199
|
// Editable file path mirrors the namespace nesting.
|
|
168
200
|
const filePath = config.structure === 'modular'
|
|
169
|
-
? `${config.models.path}/${
|
|
170
|
-
: `${config.models.userEditablePath}/Translation/${
|
|
201
|
+
? `${config.models.path}/${editableClassName}.php`
|
|
202
|
+
: `${config.models.userEditablePath}/Translation/${editableClassName}.php`;
|
|
171
203
|
return userFile(filePath, content);
|
|
172
204
|
}
|
package/ts-dist/php/types.d.ts
CHANGED
|
@@ -113,6 +113,26 @@ export interface LaravelPathOverride {
|
|
|
113
113
|
* isolates generated code (e.g. `app/Omnify/Models/`).
|
|
114
114
|
*/
|
|
115
115
|
flatBase?: boolean;
|
|
116
|
+
/**
|
|
117
|
+
* Issue #98 v5.8.5: per-layer toggle for whether the USER-EDITABLE
|
|
118
|
+
* stub is grouped by the schema's parent folder. The base layer is
|
|
119
|
+
* always grouped (canonical layout — domain-organized regenerated
|
|
120
|
+
* code), but Laravel ecosystem code (`app/Models/`, `app/Services/`,
|
|
121
|
+
* `app/Http/Requests/`, `app/Http/Resources/`) is canonically FLAT —
|
|
122
|
+
* `App\Models\User`, not `App\Models\Auth\User`. `config/auth.php`,
|
|
123
|
+
* Sanctum traits, factory discovery and dozens of ecosystem packages
|
|
124
|
+
* assume the flat shape.
|
|
125
|
+
*
|
|
126
|
+
* Default: `false` (Laravel-canonical flat editable). The grouped
|
|
127
|
+
* base + flat editable layout is what new `composer create-project
|
|
128
|
+
* laravel/laravel` apps produce, and the use-statement in the editable
|
|
129
|
+
* stub still aliases the GROUPED base FQN — group is encoded in the
|
|
130
|
+
* `use ... as ...Base` import, not the editable's namespace.
|
|
131
|
+
*
|
|
132
|
+
* Projects that adopted v5.8.x and want to keep their grouped editable
|
|
133
|
+
* layout flip this to `true` per layer.
|
|
134
|
+
*/
|
|
135
|
+
userEditableGroupByFolder?: boolean;
|
|
116
136
|
}
|
|
117
137
|
/** Nested set package configuration. */
|
|
118
138
|
export interface NestedSetOverride {
|
|
@@ -225,6 +245,15 @@ export interface BaseEditableLayer {
|
|
|
225
245
|
* extends `\{namespace}\{Schema}` (no `BaseModel` indirection). Issue #96.
|
|
226
246
|
*/
|
|
227
247
|
flatBase: boolean;
|
|
248
|
+
/**
|
|
249
|
+
* Issue #98 v5.8.5: when `true`, the user-editable layer is also
|
|
250
|
+
* grouped by the schema's parent folder (mirrors the base layer).
|
|
251
|
+
* Default `false` — Laravel-canonical flat layout (`app/Models/User.php`,
|
|
252
|
+
* not `app/Models/Auth/User.php`). Group is encoded only in the base
|
|
253
|
+
* layer + the `use ... as Base` import inside the editable stub.
|
|
254
|
+
* The base layer is unaffected — it is always grouped.
|
|
255
|
+
*/
|
|
256
|
+
userEditableGroupByFolder: boolean;
|
|
228
257
|
}
|
|
229
258
|
/** PHP codegen configuration (resolved with defaults). */
|
|
230
259
|
export interface PhpConfig {
|
|
@@ -347,6 +376,21 @@ export declare function resolveEditableClass(layer: BaseEditableLayer, schemaNam
|
|
|
347
376
|
path: string;
|
|
348
377
|
fqn: string;
|
|
349
378
|
};
|
|
379
|
+
/**
|
|
380
|
+
* Issue #98 v5.8.5: gate group nesting for the user-editable layer on
|
|
381
|
+
* `layer.userEditableGroupByFolder`. Default flat (Laravel-canonical) —
|
|
382
|
+
* `app/Models/User.php`, `App\Models\User`. Opt-in to mirror-base via
|
|
383
|
+
* `userEditableGroupByFolder: true` per layer. Auxiliary subfolders
|
|
384
|
+
* (Translation/, Pivot/, Enum/) are imposed elsewhere and are NOT
|
|
385
|
+
* affected by this flag — they always nest.
|
|
386
|
+
*/
|
|
387
|
+
export declare function nestEditableByGroup(layer: BaseEditableLayer, loc: {
|
|
388
|
+
path: string;
|
|
389
|
+
namespace: string;
|
|
390
|
+
}, group: string | undefined): {
|
|
391
|
+
path: string;
|
|
392
|
+
namespace: string;
|
|
393
|
+
};
|
|
350
394
|
/**
|
|
351
395
|
* Derive full PHP config from optional overrides.
|
|
352
396
|
* All paths and namespaces fall back to sensible defaults.
|
package/ts-dist/php/types.js
CHANGED
|
@@ -184,11 +184,24 @@ nsFromPath) {
|
|
|
184
184
|
const baseNamespace = flatBase ? namespace : `${namespace}\\${baseSubfolder}`;
|
|
185
185
|
// User-editable: defaults to the same dir as base (legacy behavior),
|
|
186
186
|
// overridable to e.g. canonical Laravel paths (`app/Models/`).
|
|
187
|
+
// Issue #98 v5.8.3: when only `userEditablePath` is overridden,
|
|
188
|
+
// derive the namespace from the editable PATH (not the base
|
|
189
|
+
// namespace) so the file's `namespace ...;` declaration matches
|
|
190
|
+
// its physical location and PSR-4 autoload doesn't break at
|
|
191
|
+
// runtime. Reporter hit this with `userEditablePath: app/Models`
|
|
192
|
+
// and the base path defaulting to `app/Omnify/Models/`: the
|
|
193
|
+
// editable stub claimed the base namespace and Laravel couldn't
|
|
194
|
+
// load it.
|
|
187
195
|
const userEditablePath = override?.userEditablePath
|
|
188
196
|
? withRoot(rootPath, override.userEditablePath)
|
|
189
197
|
: path;
|
|
190
|
-
const userEditableNamespace = override?.userEditableNamespace
|
|
191
|
-
|
|
198
|
+
const userEditableNamespace = override?.userEditableNamespace
|
|
199
|
+
?? (override?.userEditablePath ? nsFromPath(userEditablePath) : namespace);
|
|
200
|
+
// Issue #98 v5.8.5: the user-editable layer is FLAT (Laravel-canonical)
|
|
201
|
+
// by default. Project explicitly opts back into the v5.8.x grouped
|
|
202
|
+
// editable shape via `userEditableGroupByFolder: true` per layer.
|
|
203
|
+
const userEditableGroupByFolder = override?.userEditableGroupByFolder ?? false;
|
|
204
|
+
return { namespace, baseNamespace, path, basePath, userEditablePath, userEditableNamespace, flatBase, userEditableGroupByFolder };
|
|
192
205
|
}
|
|
193
206
|
/**
|
|
194
207
|
* Class-name + file-name resolver for the BASE class of a layer. Returns
|
|
@@ -225,6 +238,19 @@ export function resolveEditableClass(layer, schemaName, classSuffix = '') {
|
|
|
225
238
|
fqn: `${layer.userEditableNamespace}\\${className}`,
|
|
226
239
|
};
|
|
227
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Issue #98 v5.8.5: gate group nesting for the user-editable layer on
|
|
243
|
+
* `layer.userEditableGroupByFolder`. Default flat (Laravel-canonical) —
|
|
244
|
+
* `app/Models/User.php`, `App\Models\User`. Opt-in to mirror-base via
|
|
245
|
+
* `userEditableGroupByFolder: true` per layer. Auxiliary subfolders
|
|
246
|
+
* (Translation/, Pivot/, Enum/) are imposed elsewhere and are NOT
|
|
247
|
+
* affected by this flag — they always nest.
|
|
248
|
+
*/
|
|
249
|
+
export function nestEditableByGroup(layer, loc, group) {
|
|
250
|
+
if (!layer.userEditableGroupByFolder)
|
|
251
|
+
return loc;
|
|
252
|
+
return nestByGroup(loc, group);
|
|
253
|
+
}
|
|
228
254
|
// Default paths.
|
|
229
255
|
//
|
|
230
256
|
// Hybrid projects (manual team code + Omnify codegen) need Omnify-generated
|
package/types/config.d.ts
CHANGED
|
@@ -68,6 +68,16 @@ export interface CodegenLaravelPathConfig {
|
|
|
68
68
|
* generated code (e.g. `app/Omnify/Models/`). Issue #96, v5.4+.
|
|
69
69
|
*/
|
|
70
70
|
flatBase?: boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Issue #98 v5.8.5: when `true`, the user-editable layer's path +
|
|
73
|
+
* namespace mirrors the schema's group folder (e.g.
|
|
74
|
+
* `app/Models/Auth/User.php`, namespace `App\Models\Auth`). Default
|
|
75
|
+
* `false` — Laravel-canonical FLAT layout (`app/Models/User.php`,
|
|
76
|
+
* namespace `App\Models`); group is encoded only in the base layer
|
|
77
|
+
* and the `use ... as Base` import inside the editable stub. Per
|
|
78
|
+
* layer (model / service / request / resource / policy / controller).
|
|
79
|
+
*/
|
|
80
|
+
userEditableGroupByFolder?: boolean;
|
|
71
81
|
}
|
|
72
82
|
|
|
73
83
|
/** Nested set package configuration. */
|