@omnifyjp/omnify 5.7.0 → 5.8.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/package.json +6 -6
- package/ts-dist/php/controller-generator.js +12 -6
- package/ts-dist/php/model-generator.js +33 -16
- package/ts-dist/php/policy-generator.js +30 -16
- package/ts-dist/php/request-generator.js +31 -19
- package/ts-dist/php/resource-generator.js +30 -18
- package/ts-dist/php/service-generator.js +38 -19
- package/ts-dist/php/types.d.ts +23 -0
- package/ts-dist/php/types.js +25 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnifyjp/omnify",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.8.1",
|
|
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.
|
|
40
|
-
"@omnifyjp/omnify-darwin-x64": "5.
|
|
41
|
-
"@omnifyjp/omnify-linux-x64": "5.
|
|
42
|
-
"@omnifyjp/omnify-linux-arm64": "5.
|
|
43
|
-
"@omnifyjp/omnify-win32-x64": "5.
|
|
39
|
+
"@omnifyjp/omnify-darwin-arm64": "5.8.1",
|
|
40
|
+
"@omnifyjp/omnify-darwin-x64": "5.8.1",
|
|
41
|
+
"@omnifyjp/omnify-linux-x64": "5.8.1",
|
|
42
|
+
"@omnifyjp/omnify-linux-arm64": "5.8.1",
|
|
43
|
+
"@omnifyjp/omnify-win32-x64": "5.8.1"
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Generates base + editable controller classes for schemas with `options.api`.
|
|
3
3
|
*/
|
|
4
4
|
import { toPascalCase } from './naming-helper.js';
|
|
5
|
-
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass } from './types.js';
|
|
5
|
+
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass, nestByGroup } from './types.js';
|
|
6
6
|
import { buildControllerMethodAttributes, openApiControllerImports } from './openapi-generator.js';
|
|
7
7
|
const DEFAULT_ACTIONS = ['index', 'store', 'show', 'update', 'destroy'];
|
|
8
8
|
/** Generate controller classes for all schemas with api config. */
|
|
@@ -157,13 +157,19 @@ abstract class ${modelName}ControllerBase extends Controller
|
|
|
157
157
|
${methods.join('\n')}
|
|
158
158
|
}
|
|
159
159
|
`;
|
|
160
|
-
// Issue #96
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
// Issue #96 + #98 v5.8+: legacy honors flatBase + group-by-folder.
|
|
161
|
+
let baseDescriptor;
|
|
162
|
+
if (config.structure === 'modular') {
|
|
163
|
+
baseDescriptor = {
|
|
163
164
|
className: `${modelName}ControllerBase`,
|
|
164
165
|
path: resolveModularBasePath(config, name, 'Controllers', '', config.controllers.basePath).replace(/\/$/, ''),
|
|
165
|
-
}
|
|
166
|
-
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
const raw = resolveBaseClass(config.controllers, `${modelName}Controller`, 'Base');
|
|
170
|
+
const nested = nestByGroup({ path: raw.path, namespace: '' }, schema.group);
|
|
171
|
+
baseDescriptor = { className: raw.className, path: nested.path };
|
|
172
|
+
}
|
|
167
173
|
const finalContent = content.replace(new RegExp(`abstract class ${modelName}ControllerBase\\b`, 'g'), `abstract class ${baseDescriptor.className}`);
|
|
168
174
|
return baseFile(`${baseDescriptor.path}/${baseDescriptor.className}.php`, finalContent);
|
|
169
175
|
}
|
|
@@ -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, } from './types.js';
|
|
7
|
+
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveSharedBaseNamespace, resolveGlobalTraitNamespace, resolveGlobalEnumNamespace, resolveBaseClass, resolveEditableClass, nestByGroup, } 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). */
|
|
@@ -29,7 +29,7 @@ function generateForSchema(name, schema, reader, config) {
|
|
|
29
29
|
const hasNestedSet = detectNestedSet(options, properties);
|
|
30
30
|
return [
|
|
31
31
|
generateBaseModel(name, schema, reader, config),
|
|
32
|
-
generateUserModel(name, config, hasNestedSet),
|
|
32
|
+
generateUserModel(name, schema, config, hasNestedSet),
|
|
33
33
|
];
|
|
34
34
|
}
|
|
35
35
|
function generateBaseModel(name, schema, reader, config) {
|
|
@@ -40,16 +40,24 @@ function generateBaseModel(name, schema, reader, config) {
|
|
|
40
40
|
// isolates base from editable). `baseDescriptor` is the resolved
|
|
41
41
|
// {className, fqn, path} for the base class — `baseClass` (further
|
|
42
42
|
// below) is the PARENT class string (BaseModel / Authenticatable).
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
// Issue #98 v5.8+: apply group-by-folder nesting in legacy structure
|
|
44
|
+
// so 100+ schemas produce a navigable tree mirroring schemas/<group>/.
|
|
45
|
+
let baseDescriptor;
|
|
46
|
+
let baseNamespace;
|
|
47
|
+
if (config.structure === 'modular') {
|
|
48
|
+
baseDescriptor = {
|
|
45
49
|
className: `${modelName}BaseModel`,
|
|
46
50
|
fqn: `${resolveModularBaseNamespace(config, name, 'Models', config.models.baseNamespace)}\\${modelName}BaseModel`,
|
|
47
51
|
path: resolveModularBasePath(config, name, 'Models', '', config.models.basePath).replace(/\/$/, ''),
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
};
|
|
53
|
+
baseNamespace = resolveModularBaseNamespace(config, name, 'Models', config.models.baseNamespace);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
const raw = resolveBaseClass(config.models, modelName, 'BaseModel');
|
|
57
|
+
const nested = nestByGroup({ path: raw.path, namespace: raw.fqn.substring(0, raw.fqn.lastIndexOf('\\')) }, schema.group);
|
|
58
|
+
baseDescriptor = { className: raw.className, path: nested.path, fqn: `${nested.namespace}\\${raw.className}` };
|
|
59
|
+
baseNamespace = nested.namespace;
|
|
60
|
+
}
|
|
53
61
|
const localesNamespace = resolveModularBaseNamespace(config, name, 'Locales', config.models.baseNamespace + '\\Locales');
|
|
54
62
|
// Issue #33: HasLocalizedDisplayName is a global Omnify trait, not a per-shared module file.
|
|
55
63
|
const traitsNamespace = resolveGlobalTraitNamespace(config, config.models.baseNamespace + '\\Traits');
|
|
@@ -207,18 +215,27 @@ ${auditLogProperties}${relations}${accessors}${fileAccessors}${auditSection}${ne
|
|
|
207
215
|
: `${baseDescriptor.path}/${baseDescriptor.className}.php`;
|
|
208
216
|
return baseFile(filePath, finalContent);
|
|
209
217
|
}
|
|
210
|
-
function generateUserModel(name, config, hasNestedSet = false) {
|
|
218
|
+
function generateUserModel(name, schema, config, hasNestedSet = false) {
|
|
211
219
|
const modelName = toPascalCase(name);
|
|
212
220
|
// Issue #96: editable stub may live at a different path/namespace
|
|
213
221
|
// than the base (e.g. `app/Models/<Name>.php` while base is at
|
|
214
222
|
// `app/Omnify/Models/<Name>.php`). resolveEditableClass +
|
|
215
223
|
// resolveBaseClass keep the extends-clause + use-import in sync.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
:
|
|
224
|
+
// Issue #98 v5.8+: same group-by-folder nesting on the editable side.
|
|
225
|
+
let editable;
|
|
226
|
+
let base;
|
|
227
|
+
if (config.structure === 'modular') {
|
|
228
|
+
editable = { fileName: `${modelName}.php`, namespace: config.models.namespace, path: config.models.path, fqn: `${config.models.namespace}\\${modelName}`, className: modelName };
|
|
229
|
+
base = { className: `${modelName}BaseModel`, fqn: `${resolveModularBaseNamespace(config, name, 'Models', config.models.baseNamespace)}\\${modelName}BaseModel` };
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
const editRaw = resolveEditableClass(config.models, modelName);
|
|
233
|
+
const editNested = nestByGroup({ path: editRaw.path, namespace: editRaw.namespace }, schema.group);
|
|
234
|
+
editable = { className: editRaw.className, fileName: editRaw.fileName, namespace: editNested.namespace, path: editNested.path, fqn: `${editNested.namespace}\\${editRaw.className}` };
|
|
235
|
+
const baseRaw = resolveBaseClass(config.models, modelName, 'BaseModel');
|
|
236
|
+
const baseNs = nestByGroup({ path: baseRaw.path, namespace: baseRaw.fqn.substring(0, baseRaw.fqn.lastIndexOf('\\')) }, schema.group).namespace;
|
|
237
|
+
base = { className: baseRaw.className, fqn: `${baseNs}\\${baseRaw.className}` };
|
|
238
|
+
}
|
|
222
239
|
const factoryNamespace = config.factories.namespace;
|
|
223
240
|
const nestedSetNamespace = config.nestedset.namespace;
|
|
224
241
|
// Issue #98: when flatBase + userEditablePath both set, base.className
|
|
@@ -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 } from './types.js';
|
|
8
|
+
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass, nestByGroup } from './types.js';
|
|
9
9
|
// ============================================================================
|
|
10
10
|
// Action mapping: Omnify → Laravel
|
|
11
11
|
// ============================================================================
|
|
@@ -40,7 +40,7 @@ export function generatePolicies(reader, config) {
|
|
|
40
40
|
function generateForSchema(name, schema, reader, config) {
|
|
41
41
|
return [
|
|
42
42
|
generateBasePolicyClass(name, schema, reader, config),
|
|
43
|
-
generateUserPolicyClass(name, config),
|
|
43
|
+
generateUserPolicyClass(name, schema, config),
|
|
44
44
|
];
|
|
45
45
|
}
|
|
46
46
|
function generateBasePolicyClass(name, schema, reader, config) {
|
|
@@ -89,28 +89,42 @@ class ${modelName}PolicyBase
|
|
|
89
89
|
${methods.join('\n')}${cidrHelper}
|
|
90
90
|
}
|
|
91
91
|
`;
|
|
92
|
-
// Issue #96
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
// Issue #96 + #98 v5.8+: legacy honors flatBase + group-by-folder.
|
|
93
|
+
let baseDescriptor;
|
|
94
|
+
if (config.structure === 'modular') {
|
|
95
|
+
baseDescriptor = {
|
|
95
96
|
className: `${modelName}PolicyBase`,
|
|
96
97
|
path: resolveModularBasePath(config, name, 'Policies', '', config.policies.basePath).replace(/\/$/, ''),
|
|
97
|
-
}
|
|
98
|
-
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const raw = resolveBaseClass(config.policies, `${modelName}Policy`, 'Base');
|
|
102
|
+
const nested = nestByGroup({ path: raw.path, namespace: '' }, schema.group);
|
|
103
|
+
baseDescriptor = { className: raw.className, path: nested.path };
|
|
104
|
+
}
|
|
99
105
|
const finalContent = content.replace(new RegExp(`class ${modelName}PolicyBase\\b`, 'g'), `class ${baseDescriptor.className}`);
|
|
100
106
|
return baseFile(`${baseDescriptor.path}/${baseDescriptor.className}.php`, finalContent);
|
|
101
107
|
}
|
|
102
|
-
function generateUserPolicyClass(name, config) {
|
|
108
|
+
function generateUserPolicyClass(name, schema, config) {
|
|
103
109
|
const modelName = toPascalCase(name);
|
|
104
|
-
// Issue #96
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
// Issue #96 + #98 v5.8+: split paths + flatBase + group-by-folder.
|
|
111
|
+
let baseDescriptor;
|
|
112
|
+
let editable;
|
|
113
|
+
if (config.structure === 'modular') {
|
|
114
|
+
baseDescriptor = {
|
|
107
115
|
className: `${modelName}PolicyBase`,
|
|
108
116
|
fqn: `${resolveModularBaseNamespace(config, name, 'Policies', config.policies.baseNamespace)}\\${modelName}PolicyBase`,
|
|
109
|
-
}
|
|
110
|
-
:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
};
|
|
118
|
+
editable = { className: `${modelName}Policy`, fileName: `${modelName}Policy.php`, namespace: config.policies.namespace, path: config.policies.path };
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
const baseRaw = resolveBaseClass(config.policies, `${modelName}Policy`, 'Base');
|
|
122
|
+
const baseNs = nestByGroup({ path: baseRaw.path, namespace: baseRaw.fqn.substring(0, baseRaw.fqn.lastIndexOf('\\')) }, schema.group).namespace;
|
|
123
|
+
baseDescriptor = { className: baseRaw.className, fqn: `${baseNs}\\${baseRaw.className}` };
|
|
124
|
+
const editRaw = resolveEditableClass(config.policies, modelName, 'Policy');
|
|
125
|
+
const editNested = nestByGroup({ path: editRaw.path, namespace: editRaw.namespace }, schema.group);
|
|
126
|
+
editable = { className: editRaw.className, fileName: editRaw.fileName, namespace: editNested.namespace, path: editNested.path };
|
|
127
|
+
}
|
|
114
128
|
// Issue #98: alias on class-name collision (flatBase + userEditablePath).
|
|
115
129
|
const baseAlias = baseDescriptor.className === editable.className
|
|
116
130
|
? `${baseDescriptor.className}Base`
|
|
@@ -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 } from './types.js';
|
|
6
|
+
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass, nestByGroup } 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.
|
|
@@ -36,8 +36,8 @@ function generateForSchema(name, schema, reader, config) {
|
|
|
36
36
|
return [
|
|
37
37
|
generateBaseRequest(name, schema, reader, config, 'Store'),
|
|
38
38
|
generateBaseRequest(name, schema, reader, config, 'Update'),
|
|
39
|
-
generateUserRequest(name, config, 'Store'),
|
|
40
|
-
generateUserRequest(name, config, 'Update'),
|
|
39
|
+
generateUserRequest(name, schema, config, 'Store'),
|
|
40
|
+
generateUserRequest(name, schema, config, 'Update'),
|
|
41
41
|
];
|
|
42
42
|
}
|
|
43
43
|
function generateBaseRequest(name, schema, reader, config, action) {
|
|
@@ -257,30 +257,42 @@ ${attributeKeysList}
|
|
|
257
257
|
}
|
|
258
258
|
}
|
|
259
259
|
`;
|
|
260
|
-
// Issue #96
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
? {
|
|
260
|
+
// Issue #96 + #98 v5.8+: legacy honors flatBase + group-by-folder.
|
|
261
|
+
let baseDescriptor;
|
|
262
|
+
if (config.structure === 'modular') {
|
|
263
|
+
baseDescriptor = {
|
|
265
264
|
className: `${modelName}${action}RequestBase`,
|
|
266
265
|
path: resolveModularBasePath(config, name, 'Requests', '', config.requests.basePath).replace(/\/$/, ''),
|
|
267
|
-
}
|
|
268
|
-
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
const raw = resolveBaseClass(config.requests, `${modelName}${action}Request`, 'Base');
|
|
270
|
+
const nested = nestByGroup({ path: raw.path, namespace: '' }, schema.group);
|
|
271
|
+
baseDescriptor = { className: raw.className, path: nested.path };
|
|
272
|
+
}
|
|
269
273
|
const finalContent = content.replace(new RegExp(`class ${modelName}${action}RequestBase\\b`, 'g'), `class ${baseDescriptor.className}`).replace(new RegExp(`abstract class ${modelName}${action}RequestBase\\b`, 'g'), `abstract class ${baseDescriptor.className}`);
|
|
270
274
|
return baseFile(`${baseDescriptor.path}/${baseDescriptor.className}.php`, finalContent);
|
|
271
275
|
}
|
|
272
|
-
function generateUserRequest(name, config, action) {
|
|
276
|
+
function generateUserRequest(name, schema, config, action) {
|
|
273
277
|
const modelName = toPascalCase(name);
|
|
274
|
-
// Issue #96
|
|
275
|
-
|
|
276
|
-
|
|
278
|
+
// Issue #96 + #98 v5.8+: split paths + flatBase + group-by-folder.
|
|
279
|
+
let baseDescriptor;
|
|
280
|
+
let editable;
|
|
281
|
+
if (config.structure === 'modular') {
|
|
282
|
+
baseDescriptor = {
|
|
277
283
|
className: `${modelName}${action}RequestBase`,
|
|
278
284
|
fqn: `${resolveModularBaseNamespace(config, name, 'Requests', config.requests.baseNamespace)}\\${modelName}${action}RequestBase`,
|
|
279
|
-
}
|
|
280
|
-
:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
285
|
+
};
|
|
286
|
+
editable = { className: `${modelName}${action}Request`, fileName: `${modelName}${action}Request.php`, namespace: config.requests.namespace, path: config.requests.path };
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
const baseRaw = resolveBaseClass(config.requests, `${modelName}${action}Request`, 'Base');
|
|
290
|
+
const baseNs = nestByGroup({ path: baseRaw.path, namespace: baseRaw.fqn.substring(0, baseRaw.fqn.lastIndexOf('\\')) }, schema.group).namespace;
|
|
291
|
+
baseDescriptor = { className: baseRaw.className, fqn: `${baseNs}\\${baseRaw.className}` };
|
|
292
|
+
const editRaw = resolveEditableClass(config.requests, modelName, `${action}Request`);
|
|
293
|
+
const editNested = nestByGroup({ path: editRaw.path, namespace: editRaw.namespace }, schema.group);
|
|
294
|
+
editable = { className: editRaw.className, fileName: editRaw.fileName, namespace: editNested.namespace, path: editNested.path };
|
|
295
|
+
}
|
|
284
296
|
// Issue #98: alias on class-name collision (flatBase + userEditablePath).
|
|
285
297
|
const baseAlias = baseDescriptor.className === editable.className
|
|
286
298
|
? `${baseDescriptor.className}Base`
|
|
@@ -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 } from './types.js';
|
|
6
|
+
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass, nestByGroup } 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 = [];
|
|
@@ -11,7 +11,7 @@ export function generateResources(reader, config) {
|
|
|
11
11
|
if (schema.kind === 'extend')
|
|
12
12
|
continue;
|
|
13
13
|
files.push(generateBaseResource(name, schema, reader, config));
|
|
14
|
-
files.push(generateUserResource(name, config));
|
|
14
|
+
files.push(generateUserResource(name, schema, config));
|
|
15
15
|
}
|
|
16
16
|
return files;
|
|
17
17
|
}
|
|
@@ -119,30 +119,42 @@ ${fieldsContent}
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
`;
|
|
122
|
-
// Issue #96
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
// Issue #96 + #98 v5.8+: legacy honors flatBase + group-by-folder.
|
|
123
|
+
let baseDescriptor;
|
|
124
|
+
if (config.structure === 'modular') {
|
|
125
|
+
baseDescriptor = {
|
|
126
126
|
className: `${modelName}ResourceBase`,
|
|
127
127
|
path: resolveModularBasePath(config, name, 'Resources', '', config.resources.basePath).replace(/\/$/, ''),
|
|
128
|
-
}
|
|
129
|
-
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const raw = resolveBaseClass(config.resources, `${modelName}Resource`, 'Base');
|
|
132
|
+
const nested = nestByGroup({ path: raw.path, namespace: '' }, schema.group);
|
|
133
|
+
baseDescriptor = { className: raw.className, path: nested.path };
|
|
134
|
+
}
|
|
130
135
|
const finalContent = content.replace(new RegExp(`class ${modelName}ResourceBase\\b`, 'g'), `class ${baseDescriptor.className}`);
|
|
131
136
|
return baseFile(`${baseDescriptor.path}/${baseDescriptor.className}.php`, finalContent);
|
|
132
137
|
}
|
|
133
|
-
function generateUserResource(name, config) {
|
|
138
|
+
function generateUserResource(name, schema, config) {
|
|
134
139
|
const modelName = toPascalCase(name);
|
|
135
|
-
// Issue #96
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
// Issue #96 + #98 v5.8+: split paths + flatBase + group-by-folder nesting.
|
|
141
|
+
let baseDescriptor;
|
|
142
|
+
let editable;
|
|
143
|
+
if (config.structure === 'modular') {
|
|
144
|
+
baseDescriptor = {
|
|
139
145
|
className: `${modelName}ResourceBase`,
|
|
140
146
|
fqn: `${resolveModularBaseNamespace(config, name, 'Resources', config.resources.baseNamespace)}\\${modelName}ResourceBase`,
|
|
141
|
-
}
|
|
142
|
-
:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
};
|
|
148
|
+
editable = { className: `${modelName}Resource`, fileName: `${modelName}Resource.php`, namespace: config.resources.namespace, path: config.resources.path };
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const baseRaw = resolveBaseClass(config.resources, `${modelName}Resource`, 'Base');
|
|
152
|
+
const baseNs = nestByGroup({ path: baseRaw.path, namespace: baseRaw.fqn.substring(0, baseRaw.fqn.lastIndexOf('\\')) }, schema.group).namespace;
|
|
153
|
+
baseDescriptor = { className: baseRaw.className, fqn: `${baseNs}\\${baseRaw.className}` };
|
|
154
|
+
const editRaw = resolveEditableClass(config.resources, modelName, 'Resource');
|
|
155
|
+
const editNested = nestByGroup({ path: editRaw.path, namespace: editRaw.namespace }, schema.group);
|
|
156
|
+
editable = { className: editRaw.className, fileName: editRaw.fileName, namespace: editNested.namespace, path: editNested.path };
|
|
157
|
+
}
|
|
146
158
|
// Issue #98: alias on class-name collision (flatBase + userEditablePath).
|
|
147
159
|
const baseAlias = baseDescriptor.className === editable.className
|
|
148
160
|
? `${baseDescriptor.className}Base`
|
|
@@ -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 } from './types.js';
|
|
16
|
+
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveBaseClass, resolveEditableClass, nestByGroup } from './types.js';
|
|
17
17
|
// ============================================================================
|
|
18
18
|
// Public entry point
|
|
19
19
|
// ============================================================================
|
|
@@ -593,15 +593,24 @@ class ${modelName}ServiceBase
|
|
|
593
593
|
${sections.join('\n')}
|
|
594
594
|
}
|
|
595
595
|
`;
|
|
596
|
-
// Issue #96
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
596
|
+
// Issue #96 + #98 v5.8+: legacy structure honors flatBase + group-by-folder.
|
|
597
|
+
let baseDescriptor;
|
|
598
|
+
if (config.structure === 'modular') {
|
|
599
|
+
baseDescriptor = {
|
|
600
600
|
className: `${modelName}ServiceBase`,
|
|
601
601
|
fileName: `${modelName}ServiceBase.php`,
|
|
602
602
|
path: resolveModularBasePath(config, name, 'Services', '', config.services.basePath).replace(/\/$/, ''),
|
|
603
|
-
}
|
|
604
|
-
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
const raw = resolveBaseClass(config.services, `${modelName}Service`, 'Base');
|
|
607
|
+
const nested = nestByGroup({ path: raw.path, namespace: '' }, schema.group);
|
|
608
|
+
baseDescriptor = {
|
|
609
|
+
className: raw.className,
|
|
610
|
+
fileName: `${raw.className}.php`,
|
|
611
|
+
path: nested.path,
|
|
612
|
+
};
|
|
613
|
+
}
|
|
605
614
|
const finalContent = content.replace(new RegExp(`class ${modelName}ServiceBase\\b`, 'g'), `class ${baseDescriptor.className}`);
|
|
606
615
|
return baseFile(`${baseDescriptor.path}/${baseDescriptor.fileName}`, finalContent);
|
|
607
616
|
}
|
|
@@ -1749,21 +1758,31 @@ function buildFlushTranslationsMethod(modelName) {
|
|
|
1749
1758
|
function generateUserService(name, schema, config) {
|
|
1750
1759
|
const modelName = toPascalCase(name);
|
|
1751
1760
|
const group = schema.group ? toPascalCase(schema.group) : null;
|
|
1752
|
-
// Issue #96
|
|
1761
|
+
// Issue #96 + #98 v5.8+: route editable stub via resolveEditableClass +
|
|
1753
1762
|
// resolveBaseClass so userEditablePath / userEditableNamespace and
|
|
1754
|
-
// flatBase (drops ServiceBase suffix) are honored
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1763
|
+
// flatBase (drops ServiceBase suffix) are honored, then nest by group.
|
|
1764
|
+
let baseDescriptor;
|
|
1765
|
+
let editable;
|
|
1766
|
+
if (config.structure === 'modular') {
|
|
1767
|
+
baseDescriptor = {
|
|
1758
1768
|
className: `${modelName}ServiceBase`,
|
|
1759
1769
|
fqn: `${resolveModularBaseNamespace(config, name, 'Services', config.services.baseNamespace)}\\${modelName}ServiceBase`,
|
|
1760
|
-
}
|
|
1761
|
-
:
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1770
|
+
};
|
|
1771
|
+
editable = { className: `${modelName}Service`, fileName: `${modelName}Service.php`, namespace: config.services.namespace, path: config.services.path };
|
|
1772
|
+
}
|
|
1773
|
+
else {
|
|
1774
|
+
const baseRaw = resolveBaseClass(config.services, `${modelName}Service`, 'Base');
|
|
1775
|
+
const baseNs = nestByGroup({ path: baseRaw.path, namespace: baseRaw.fqn.substring(0, baseRaw.fqn.lastIndexOf('\\')) }, schema.group).namespace;
|
|
1776
|
+
baseDescriptor = { className: baseRaw.className, fqn: `${baseNs}\\${baseRaw.className}` };
|
|
1777
|
+
const editRaw = resolveEditableClass(config.services, modelName, 'Service');
|
|
1778
|
+
const editNested = nestByGroup({ path: editRaw.path, namespace: editRaw.namespace }, schema.group);
|
|
1779
|
+
editable = { className: editRaw.className, fileName: editRaw.fileName, namespace: editNested.namespace, path: editNested.path };
|
|
1780
|
+
}
|
|
1781
|
+
// Group nesting already applied via nestByGroup above; no additional
|
|
1782
|
+
// <Group>/ subfolder needed here. (Pre-#98 had explicit group var; now
|
|
1783
|
+
// baked into the nested editable.path/namespace.)
|
|
1784
|
+
const userNs = editable.namespace;
|
|
1785
|
+
const userPath = editable.path;
|
|
1767
1786
|
// Issue #98: alias on class-name collision (flatBase + userEditablePath)
|
|
1768
1787
|
// so `class BannerService extends BannerService` becomes
|
|
1769
1788
|
// `class BannerService extends BannerServiceBase` via aliased import.
|
package/ts-dist/php/types.d.ts
CHANGED
|
@@ -295,6 +295,29 @@ export interface PhpConfig {
|
|
|
295
295
|
securityScheme: string;
|
|
296
296
|
};
|
|
297
297
|
}
|
|
298
|
+
/**
|
|
299
|
+
* Apply group-by-folder nesting (issue #98 v5.8+). When the schema's
|
|
300
|
+
* `group:` field is non-empty, append `<PascalGroup>` as a subfolder
|
|
301
|
+
* AND a sub-namespace. Schemas without `group:` are unchanged (no
|
|
302
|
+
* synthetic `Default/` / `Misc/` folder per the issue thread).
|
|
303
|
+
*
|
|
304
|
+
* Used by every generator's base + editable path resolution so files
|
|
305
|
+
* mirror the schema directory layout. With 100+ schemas across many
|
|
306
|
+
* groups, the flat output is unusable; nesting matches what devs
|
|
307
|
+
* already expect since the schemas themselves live at
|
|
308
|
+
* `schemas/<group>/<Name>.yaml`.
|
|
309
|
+
*
|
|
310
|
+
* Only applies to LEGACY structure; modular structure already nests
|
|
311
|
+
* per-Schema (`app/Omnify/Modules/<Schema>/<Layer>/`) so adding group
|
|
312
|
+
* on top would be redundant.
|
|
313
|
+
*/
|
|
314
|
+
export declare function nestByGroup(base: {
|
|
315
|
+
path: string;
|
|
316
|
+
namespace: string;
|
|
317
|
+
}, group: string | undefined): {
|
|
318
|
+
path: string;
|
|
319
|
+
namespace: string;
|
|
320
|
+
};
|
|
298
321
|
/**
|
|
299
322
|
* Class-name + file-name resolver for the BASE class of a layer. Returns
|
|
300
323
|
* the bare schema name when `layer.flatBase` is true (e.g. `Banner.php`
|
package/ts-dist/php/types.js
CHANGED
|
@@ -101,6 +101,31 @@ export function resolveGlobalTraitNamespace(config, legacyNamespace) {
|
|
|
101
101
|
}
|
|
102
102
|
return legacyNamespace;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Apply group-by-folder nesting (issue #98 v5.8+). When the schema's
|
|
106
|
+
* `group:` field is non-empty, append `<PascalGroup>` as a subfolder
|
|
107
|
+
* AND a sub-namespace. Schemas without `group:` are unchanged (no
|
|
108
|
+
* synthetic `Default/` / `Misc/` folder per the issue thread).
|
|
109
|
+
*
|
|
110
|
+
* Used by every generator's base + editable path resolution so files
|
|
111
|
+
* mirror the schema directory layout. With 100+ schemas across many
|
|
112
|
+
* groups, the flat output is unusable; nesting matches what devs
|
|
113
|
+
* already expect since the schemas themselves live at
|
|
114
|
+
* `schemas/<group>/<Name>.yaml`.
|
|
115
|
+
*
|
|
116
|
+
* Only applies to LEGACY structure; modular structure already nests
|
|
117
|
+
* per-Schema (`app/Omnify/Modules/<Schema>/<Layer>/`) so adding group
|
|
118
|
+
* on top would be redundant.
|
|
119
|
+
*/
|
|
120
|
+
export function nestByGroup(base, group) {
|
|
121
|
+
if (!group)
|
|
122
|
+
return base;
|
|
123
|
+
const pascal = group.charAt(0).toUpperCase() + group.slice(1).replace(/[-_]([a-z])/g, (_, c) => c.toUpperCase());
|
|
124
|
+
return {
|
|
125
|
+
path: `${base.path}/${pascal}`,
|
|
126
|
+
namespace: `${base.namespace}\\${pascal}`,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
104
129
|
/** Convert a file path to a PHP namespace (e.g., app/Models/Omnify → App\Models\Omnify). */
|
|
105
130
|
function pathToNamespace(p) {
|
|
106
131
|
return p
|