@omnifyjp/ts 3.0.1 → 3.1.0

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/dist/cli.js CHANGED
@@ -47,12 +47,16 @@ function resolveFromConfig(configPath) {
47
47
  const laravelConfig = config.codegen?.laravel;
48
48
  const laravelEnabled = laravelConfig?.enable === true;
49
49
  const laravelOverrides = laravelEnabled ? {
50
+ structure: laravelConfig?.structure,
50
51
  model: laravelConfig?.model,
51
52
  request: laravelConfig?.request,
52
53
  resource: laravelConfig?.resource,
53
54
  factory: laravelConfig?.factory,
54
55
  provider: laravelConfig?.provider,
55
56
  policy: laravelConfig?.policy,
57
+ controller: laravelConfig?.controller,
58
+ service: laravelConfig?.service,
59
+ route: laravelConfig?.route,
56
60
  nestedset: laravelConfig?.nestedset,
57
61
  } : undefined;
58
62
  return {
@@ -2,13 +2,13 @@
2
2
  * Generates the BaseModel class.
3
3
  * Morph map registration is handled exclusively by OmnifyServiceProvider.
4
4
  */
5
- import { baseFile } from './types.js';
5
+ import { baseFile, resolveSharedBasePath, resolveSharedBaseNamespace } from './types.js';
6
6
  /** Generate the BaseModel class. */
7
7
  export function generateBaseModel(config) {
8
- const baseNamespace = config.models.baseNamespace;
8
+ const ns = resolveSharedBaseNamespace(config, 'Models', config.models.baseNamespace);
9
9
  const content = `<?php
10
10
 
11
- namespace ${baseNamespace};
11
+ namespace ${ns};
12
12
 
13
13
  /**
14
14
  * Base model class for all Omnify-generated models.
@@ -25,7 +25,8 @@ class BaseModel extends Model
25
25
  {
26
26
  }
27
27
  `;
28
+ const path = resolveSharedBasePath(config, 'Models', 'BaseModel.php', config.models.basePath);
28
29
  return [
29
- baseFile(`${config.models.basePath}/BaseModel.php`, content),
30
+ baseFile(path, content),
30
31
  ];
31
32
  }
@@ -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 } from './types.js';
5
+ import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace } from './types.js';
6
6
  const DEFAULT_ACTIONS = ['index', 'store', 'show', 'update', 'destroy'];
7
7
  /** Generate controller classes for all schemas with api config. */
8
8
  export function generateControllers(reader, config) {
@@ -25,9 +25,9 @@ function generateBaseController(name, schema, reader, config) {
25
25
  const lookup = api.lookup ?? true;
26
26
  const bulkDelete = api.bulkDelete ?? false;
27
27
  const restore = api.restore ?? (schema.options?.softDelete ?? false);
28
- const baseNs = config.controllers.baseNamespace;
28
+ const baseNs = resolveModularBaseNamespace(config, name, 'Controllers', config.controllers.baseNamespace);
29
29
  const modelNs = config.models.namespace;
30
- const serviceBaseNs = config.services.baseNamespace;
30
+ const serviceBaseNs = resolveModularBaseNamespace(config, name, 'Services', config.services.baseNamespace);
31
31
  const requestNs = config.requests.namespace;
32
32
  const resourceNs = config.resources.namespace;
33
33
  // Build imports
@@ -145,12 +145,12 @@ abstract class ${modelName}ControllerBase extends Controller
145
145
  ${methods.join('\n')}
146
146
  }
147
147
  `;
148
- return baseFile(`${config.controllers.basePath}/${modelName}ControllerBase.php`, content);
148
+ return baseFile(resolveModularBasePath(config, name, 'Controllers', `${modelName}ControllerBase.php`, config.controllers.basePath), content);
149
149
  }
150
150
  function generateUserController(name, schema, config) {
151
151
  const modelName = toPascalCase(name);
152
152
  const group = schema.group ? toPascalCase(schema.group) : null;
153
- const baseNs = config.controllers.baseNamespace;
153
+ const baseNs = resolveModularBaseNamespace(config, name, 'Controllers', config.controllers.baseNamespace);
154
154
  const userNs = group
155
155
  ? `${config.controllers.namespace}\\Api\\V1\\${group}`
156
156
  : `${config.controllers.namespace}\\Api\\V1`;
@@ -5,7 +5,7 @@
5
5
  * - FileStatusEnum.php (base, always overwritten)
6
6
  * - FileLocales.php (base, always overwritten)
7
7
  */
8
- import { baseFile, userFile } from './types.js';
8
+ import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveSharedBaseNamespace } from './types.js';
9
9
  /** Generate all File model infrastructure files. */
10
10
  export function generateFileModels(reader, config) {
11
11
  if (!reader.hasFileProperties())
@@ -22,7 +22,11 @@ export function generateFileModels(reader, config) {
22
22
  return files;
23
23
  }
24
24
  function generateFileBaseModel(config, defaultDisk) {
25
- const baseNamespace = config.models.baseNamespace;
25
+ const baseNamespace = resolveModularBaseNamespace(config, 'File', 'Models', config.models.baseNamespace);
26
+ const localesNamespace = resolveModularBaseNamespace(config, 'File', 'Locales', config.models.baseNamespace + '\\Locales');
27
+ const traitsNamespace = resolveSharedBaseNamespace(config, 'Traits', config.models.baseNamespace + '\\Traits');
28
+ const sharedModelsNamespace = resolveSharedBaseNamespace(config, 'Models', config.models.baseNamespace);
29
+ const enumNamespace = resolveModularBaseNamespace(config, 'File', 'Enums', config.models.namespace);
26
30
  const modelNamespace = config.models.namespace;
27
31
  const content = `<?php
28
32
 
@@ -39,8 +43,8 @@ use Illuminate\\Database\\Eloquent\\Concerns\\HasUuids;
39
43
  use Illuminate\\Database\\Eloquent\\Relations\\MorphTo;
40
44
  use Illuminate\\Database\\Eloquent\\SoftDeletes;
41
45
  use Illuminate\\Database\\Eloquent\\Builder;
42
- use ${baseNamespace}\\Locales\\FileLocales;
43
- use ${baseNamespace}\\Traits\\HasLocalizedDisplayName;
46
+ use ${localesNamespace}\\FileLocales;
47
+ use ${traitsNamespace}\\HasLocalizedDisplayName;${sharedModelsNamespace !== baseNamespace ? `\nuse ${sharedModelsNamespace}\\BaseModel;` : ''}
44
48
 
45
49
  /**
46
50
  * FileBaseModel
@@ -55,7 +59,7 @@ use ${baseNamespace}\\Traits\\HasLocalizedDisplayName;
55
59
  * @property string $original_name
56
60
  * @property string $mime_type
57
61
  * @property int $size
58
- * @property \\${modelNamespace}\\FileStatusEnum $status
62
+ * @property \\${enumNamespace}\\FileStatusEnum $status
59
63
  * @property \\Illuminate\\Support\\Carbon|null $expires_at
60
64
  * @property int $sort_order
61
65
  */
@@ -126,7 +130,7 @@ class FileBaseModel extends BaseModel
126
130
  return [
127
131
  'size' => 'integer',
128
132
  'sort_order' => 'integer',
129
- 'status' => \\${modelNamespace}\\FileStatusEnum::class,
133
+ 'status' => \\${enumNamespace}\\FileStatusEnum::class,
130
134
  'expires_at' => 'datetime',
131
135
  ];
132
136
  }
@@ -190,7 +194,7 @@ class FileBaseModel extends BaseModel
190
194
  */
191
195
  public function isPermanent(): bool
192
196
  {
193
- return $this->status === \\${modelNamespace}\\FileStatusEnum::Permanent;
197
+ return $this->status === \\${enumNamespace}\\FileStatusEnum::Permanent;
194
198
  }
195
199
 
196
200
  /**
@@ -198,7 +202,7 @@ class FileBaseModel extends BaseModel
198
202
  */
199
203
  public function isExpired(): bool
200
204
  {
201
- return $this->status === \\${modelNamespace}\\FileStatusEnum::Temporary
205
+ return $this->status === \\${enumNamespace}\\FileStatusEnum::Temporary
202
206
  && $this->expires_at !== null
203
207
  && $this->expires_at->isPast();
204
208
  }
@@ -209,7 +213,7 @@ class FileBaseModel extends BaseModel
209
213
  public function makePermanent(): static
210
214
  {
211
215
  $this->update([
212
- 'status' => \\${modelNamespace}\\FileStatusEnum::Permanent,
216
+ 'status' => \\${enumNamespace}\\FileStatusEnum::Permanent,
213
217
  'expires_at' => null,
214
218
  ]);
215
219
 
@@ -217,11 +221,11 @@ class FileBaseModel extends BaseModel
217
221
  }
218
222
  }
219
223
  `;
220
- return baseFile(`${config.models.basePath}/FileBaseModel.php`, content);
224
+ return baseFile(resolveModularBasePath(config, 'File', 'Models', 'FileBaseModel.php', config.models.basePath), content);
221
225
  }
222
226
  function generateFileUserModel(config) {
223
227
  const modelNamespace = config.models.namespace;
224
- const baseNamespace = config.models.baseNamespace;
228
+ const baseNamespace = resolveModularBaseNamespace(config, 'File', 'Models', config.models.baseNamespace);
225
229
  const factoryNamespace = config.factories.namespace;
226
230
  const content = `<?php
227
231
 
@@ -258,7 +262,7 @@ class File extends FileBaseModel
258
262
  return userFile(`${config.models.path}/File.php`, content);
259
263
  }
260
264
  function generateFileStatusEnum(config) {
261
- const modelNamespace = config.models.namespace;
265
+ const modelNamespace = resolveModularBaseNamespace(config, 'File', 'Enums', config.models.namespace);
262
266
  const content = `<?php
263
267
 
264
268
  namespace ${modelNamespace};
@@ -296,10 +300,10 @@ enum FileStatusEnum: string
296
300
  }
297
301
  }
298
302
  `;
299
- return baseFile(`${config.models.path}/FileStatusEnum.php`, content);
303
+ return baseFile(resolveModularBasePath(config, 'File', 'Enums', 'FileStatusEnum.php', config.models.path), content);
300
304
  }
301
305
  function generateFileLocales(config, locales) {
302
- const baseNamespace = config.models.baseNamespace;
306
+ const baseNamespace = resolveModularBaseNamespace(config, 'File', 'Locales', config.models.baseNamespace + '\\Locales');
303
307
  // Display names for the File model
304
308
  const displayNames = {
305
309
  ja: 'ファイル',
@@ -329,7 +333,7 @@ function generateFileLocales(config, locales) {
329
333
  .join(',\n');
330
334
  const content = `<?php
331
335
 
332
- namespace ${baseNamespace}\\Locales;
336
+ namespace ${baseNamespace};
333
337
 
334
338
  /**
335
339
  * DO NOT EDIT - This file is auto-generated by Omnify.
@@ -346,7 +350,7 @@ ${propertyNamesPhp},
346
350
  ];
347
351
  }
348
352
  `;
349
- return baseFile(`${config.models.basePath}/Locales/FileLocales.php`, content);
353
+ return baseFile(resolveModularBasePath(config, 'File', 'Locales', 'FileLocales.php', config.models.basePath + '/Locales'), content);
350
354
  }
351
355
  function formatPhpArray(obj) {
352
356
  const entries = Object.entries(obj)
@@ -1,14 +1,15 @@
1
1
  /**
2
2
  * Generates the HasFiles trait for models with File-type properties.
3
3
  */
4
- import { baseFile } from './types.js';
4
+ import { baseFile, resolveModularBasePath, resolveModularBaseNamespace } from './types.js';
5
5
  /** Generate the HasFiles trait. */
6
6
  export function generateFileTrait(config) {
7
- const baseNamespace = config.models.baseNamespace;
7
+ const traitsNamespace = resolveModularBaseNamespace(config, 'File', 'Traits', config.models.baseNamespace + '\\Traits');
8
+ const enumNamespace = resolveModularBaseNamespace(config, 'File', 'Enums', config.models.namespace);
8
9
  const modelNamespace = config.models.namespace;
9
10
  const content = `<?php
10
11
 
11
- namespace ${baseNamespace}\\Traits;
12
+ namespace ${traitsNamespace};
12
13
 
13
14
  /**
14
15
  * Trait for models with polymorphic file attachments.
@@ -21,7 +22,7 @@ namespace ${baseNamespace}\\Traits;
21
22
 
22
23
  use Illuminate\\Database\\Eloquent\\Relations\\MorphMany;
23
24
  use ${modelNamespace}\\File;
24
- use ${modelNamespace}\\FileStatusEnum;
25
+ use ${enumNamespace}\\FileStatusEnum;
25
26
 
26
27
  trait HasFiles
27
28
  {
@@ -91,6 +92,6 @@ trait HasFiles
91
92
  }
92
93
  `;
93
94
  return [
94
- baseFile(`${config.models.basePath}/Traits/HasFiles.php`, content),
95
+ baseFile(resolveModularBasePath(config, 'File', 'Traits', 'HasFiles.php', config.models.basePath + '/Traits'), content),
95
96
  ];
96
97
  }
@@ -2,7 +2,7 @@
2
2
  * Port of LocalesGenerator.php — generates Locales classes with display name constants.
3
3
  */
4
4
  import { toPascalCase, toSnakeCase } from './naming-helper.js';
5
- import { baseFile } from './types.js';
5
+ import { baseFile, resolveModularBasePath, resolveModularBaseNamespace } from './types.js';
6
6
  /** Generate Locales classes for all project-owned object schemas. */
7
7
  export function generateLocales(reader, config) {
8
8
  const files = [];
@@ -15,14 +15,14 @@ export function generateLocales(reader, config) {
15
15
  }
16
16
  function generateLocalesClass(name, schema, reader, config) {
17
17
  const modelName = toPascalCase(name);
18
- const baseNamespace = config.models.baseNamespace;
18
+ const baseNamespace = resolveModularBaseNamespace(config, name, 'Locales', config.models.baseNamespace + '\\Locales');
19
19
  const displayNames = buildDisplayNames(schema);
20
20
  const propertyDisplayNames = buildPropertyDisplayNames(schema, reader, name);
21
21
  const displayNamesPhp = formatArrayConstant(displayNames, 8);
22
22
  const propertyDisplayNamesPhp = formatNestedArrayConstant(propertyDisplayNames, 8);
23
23
  const content = `<?php
24
24
 
25
- namespace ${baseNamespace}\\Locales;
25
+ namespace ${baseNamespace};
26
26
 
27
27
  /**
28
28
  * Localized display names for ${modelName}.
@@ -49,7 +49,7 @@ class ${modelName}Locales
49
49
  public const PROPERTY_DISPLAY_NAMES = ${propertyDisplayNamesPhp};
50
50
  }
51
51
  `;
52
- return baseFile(`${config.models.basePath}/Locales/${modelName}Locales.php`, content);
52
+ return baseFile(resolveModularBasePath(config, name, 'Locales', `${modelName}Locales.php`, config.models.basePath + '/Locales'), content);
53
53
  }
54
54
  function buildDisplayNames(schema) {
55
55
  const displayName = schema.displayName;
@@ -4,7 +4,7 @@
4
4
  import { toPascalCase, toSnakeCase, toCamelCase } 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 } from './types.js';
7
+ import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveSharedBaseNamespace } from './types.js';
8
8
  /** Generate base model and user model for all project-owned object schemas,
9
9
  * plus user models for package schemas (extending the package model). */
10
10
  export function generateModels(reader, config) {
@@ -33,7 +33,10 @@ function generateForSchema(name, schema, reader, config) {
33
33
  }
34
34
  function generateBaseModel(name, schema, reader, config) {
35
35
  const modelName = toPascalCase(name);
36
- const baseNamespace = config.models.baseNamespace;
36
+ const baseNamespace = resolveModularBaseNamespace(config, name, 'Models', config.models.baseNamespace);
37
+ const localesNamespace = resolveModularBaseNamespace(config, name, 'Locales', config.models.baseNamespace + '\\Locales');
38
+ const traitsNamespace = resolveSharedBaseNamespace(config, 'Traits', config.models.baseNamespace + '\\Traits');
39
+ const sharedModelsNamespace = resolveSharedBaseNamespace(config, 'Models', config.models.baseNamespace);
37
40
  const modelNamespace = config.models.namespace;
38
41
  const options = schema.options ?? {};
39
42
  const properties = (schema.properties ?? {});
@@ -59,7 +62,7 @@ function generateBaseModel(name, schema, reader, config) {
59
62
  const prop = properties[p];
60
63
  return prop && prop['type'] === 'File';
61
64
  });
62
- const imports = buildImports(baseNamespace, modelName, hasSoftDelete, isAuthenticatable, hasTranslatable, properties, needsUuidTrait, needsUlidTrait, hasNestedSet, config.nestedset.namespace, hasFiles, modelNamespace);
65
+ const imports = buildImports(baseNamespace, modelName, hasSoftDelete, isAuthenticatable, hasTranslatable, properties, needsUuidTrait, needsUlidTrait, hasNestedSet, config.nestedset.namespace, hasFiles, modelNamespace, localesNamespace, traitsNamespace, sharedModelsNamespace);
63
66
  const docProperties = buildDocProperties(properties, expandedProperties, propertyOrder);
64
67
  const baseClass = isAuthenticatable ? 'Authenticatable' : 'BaseModel';
65
68
  const implementsClause = hasTranslatable ? ' implements TranslatableContract' : '';
@@ -171,12 +174,12 @@ ${casts} ];
171
174
  ${relations}${accessors}${fileAccessors}${nestedSetMethod}
172
175
  }
173
176
  `;
174
- return baseFile(`${config.models.basePath}/${modelName}BaseModel.php`, content);
177
+ return baseFile(resolveModularBasePath(config, name, 'Models', `${modelName}BaseModel.php`, config.models.basePath), content);
175
178
  }
176
179
  function generateUserModel(name, config, hasNestedSet = false) {
177
180
  const modelName = toPascalCase(name);
178
181
  const modelNamespace = config.models.namespace;
179
- const baseNamespace = config.models.baseNamespace;
182
+ const baseNamespace = resolveModularBaseNamespace(config, name, 'Models', config.models.baseNamespace);
180
183
  const factoryNamespace = config.factories.namespace;
181
184
  const nestedSetNamespace = config.nestedset.namespace;
182
185
  const imports = [
@@ -223,8 +226,12 @@ ${traits.join('\n')}
223
226
  `;
224
227
  return userFile(`${config.models.path}/${modelName}.php`, content);
225
228
  }
226
- function buildImports(baseNamespace, modelName, hasSoftDelete, isAuthenticatable, hasTranslatable, properties, needsUuidTrait = false, needsUlidTrait = false, hasNestedSet = false, nestedSetNamespace = 'Aimeos\\Nestedset', hasFiles = false, modelNamespace = '') {
229
+ function buildImports(baseNamespace, modelName, hasSoftDelete, isAuthenticatable, hasTranslatable, properties, needsUuidTrait = false, needsUlidTrait = false, hasNestedSet = false, nestedSetNamespace = 'Aimeos\\Nestedset', hasFiles = false, modelNamespace = '', localesNamespace = '', traitsNamespace = '', sharedModelsNamespace = '') {
227
230
  const lines = [];
231
+ // Import BaseModel from shared namespace when in modular mode
232
+ if (sharedModelsNamespace && sharedModelsNamespace !== baseNamespace && !isAuthenticatable) {
233
+ lines.push(`use ${sharedModelsNamespace}\\BaseModel;`);
234
+ }
228
235
  if (isAuthenticatable) {
229
236
  lines.push('use Illuminate\\Foundation\\Auth\\User as Authenticatable;');
230
237
  lines.push('use Illuminate\\Notifications\\Notifiable;');
@@ -241,10 +248,10 @@ function buildImports(baseNamespace, modelName, hasSoftDelete, isAuthenticatable
241
248
  lines.push(`use Illuminate\\Database\\Eloquent\\Relations\\${type};`);
242
249
  }
243
250
  lines.push('use Illuminate\\Database\\Eloquent\\Collection as EloquentCollection;');
244
- lines.push(`use ${baseNamespace}\\Traits\\HasLocalizedDisplayName;`);
245
- lines.push(`use ${baseNamespace}\\Locales\\${modelName}Locales;`);
251
+ lines.push(`use ${traitsNamespace || baseNamespace + '\\Traits'}\\HasLocalizedDisplayName;`);
252
+ lines.push(`use ${localesNamespace || baseNamespace + '\\Locales'}\\${modelName}Locales;`);
246
253
  if (hasFiles) {
247
- lines.push(`use ${baseNamespace}\\Traits\\HasFiles;`);
254
+ lines.push(`use ${traitsNamespace || baseNamespace + '\\Traits'}\\HasFiles;`);
248
255
  if (modelNamespace) {
249
256
  lines.push(`use ${modelNamespace}\\File;`);
250
257
  }
@@ -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 } from './types.js';
8
+ import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace } from './types.js';
9
9
  // ============================================================================
10
10
  // Action mapping: Omnify → Laravel
11
11
  // ============================================================================
@@ -45,7 +45,7 @@ function generateForSchema(name, schema, reader, config) {
45
45
  }
46
46
  function generateBasePolicyClass(name, schema, reader, config) {
47
47
  const modelName = toPascalCase(name);
48
- const baseNamespace = config.policies.baseNamespace;
48
+ const baseNamespace = resolveModularBaseNamespace(config, name, 'Policies', config.policies.baseNamespace);
49
49
  const modelNamespace = config.models.namespace;
50
50
  const policies = schema.policies;
51
51
  const hasSoftDelete = schema.options?.softDelete ?? false;
@@ -89,12 +89,12 @@ class ${modelName}PolicyBase
89
89
  ${methods.join('\n')}${cidrHelper}
90
90
  }
91
91
  `;
92
- return baseFile(`${config.policies.basePath}/${modelName}PolicyBase.php`, content);
92
+ return baseFile(resolveModularBasePath(config, name, 'Policies', `${modelName}PolicyBase.php`, config.policies.basePath), content);
93
93
  }
94
94
  function generateUserPolicyClass(name, config) {
95
95
  const modelName = toPascalCase(name);
96
96
  const policyNamespace = config.policies.namespace;
97
- const baseNamespace = config.policies.baseNamespace;
97
+ const baseNamespace = resolveModularBaseNamespace(config, name, 'Policies', config.policies.baseNamespace);
98
98
  const content = `<?php
99
99
 
100
100
  namespace ${policyNamespace};
@@ -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 } from './types.js';
6
+ import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace } from './types.js';
7
7
  /** Generate Store/Update request classes for all project-owned visible object schemas. */
8
8
  export function generateRequests(reader, config) {
9
9
  const files = [];
@@ -24,8 +24,8 @@ function generateForSchema(name, schema, reader, config) {
24
24
  }
25
25
  function generateBaseRequest(name, schema, reader, config, action) {
26
26
  const modelName = toPascalCase(name);
27
- const baseNamespace = config.requests.baseNamespace;
28
- const modelBaseNamespace = config.models.baseNamespace;
27
+ const baseNamespace = resolveModularBaseNamespace(config, name, 'Requests', config.requests.baseNamespace);
28
+ const modelLocalesNamespace = resolveModularBaseNamespace(config, name, 'Locales', config.models.baseNamespace + '\\Locales');
29
29
  const properties = (schema.properties ?? {});
30
30
  const expandedProperties = reader.getExpandedProperties(name);
31
31
  const propertyOrder = reader.getPropertyOrder(name);
@@ -136,7 +136,7 @@ function generateBaseRequest(name, schema, reader, config, action) {
136
136
  attributeKeys.push(snakeName);
137
137
  }
138
138
  const rulesContent = rulesLines.join('\n');
139
- const localesClass = `${modelBaseNamespace}\\Locales\\${modelName}Locales`;
139
+ const localesClass = `${modelLocalesNamespace}\\${modelName}Locales`;
140
140
  const attributeKeysList = attributeKeys.map(k => ` '${k}',`).join('\n');
141
141
  const ruleImport = needsRuleImport ? '\nuse Illuminate\\Validation\\Rule;' : '';
142
142
  const content = `<?php
@@ -222,12 +222,12 @@ ${attributeKeysList}
222
222
  }
223
223
  }
224
224
  `;
225
- return baseFile(`${config.requests.basePath}/${modelName}${action}RequestBase.php`, content);
225
+ return baseFile(resolveModularBasePath(config, name, 'Requests', `${modelName}${action}RequestBase.php`, config.requests.basePath), content);
226
226
  }
227
227
  function generateUserRequest(name, config, action) {
228
228
  const modelName = toPascalCase(name);
229
229
  const requestNamespace = config.requests.namespace;
230
- const baseNamespace = config.requests.baseNamespace;
230
+ const baseNamespace = resolveModularBaseNamespace(config, name, 'Requests', config.requests.baseNamespace);
231
231
  const content = `<?php
232
232
 
233
233
  /**
@@ -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 } from './types.js';
6
+ import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace } 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 = [];
@@ -17,7 +17,7 @@ export function generateResources(reader, config) {
17
17
  }
18
18
  function generateBaseResource(name, schema, reader, config) {
19
19
  const modelName = toPascalCase(name);
20
- const baseNamespace = config.resources.baseNamespace;
20
+ const baseNamespace = resolveModularBaseNamespace(config, name, 'Resources', config.resources.baseNamespace);
21
21
  const resourceNamespace = config.resources.namespace;
22
22
  const modelNamespace = config.models.namespace;
23
23
  const properties = (schema.properties ?? {});
@@ -114,12 +114,12 @@ ${fieldsContent}
114
114
  }
115
115
  }
116
116
  `;
117
- return baseFile(`${config.resources.basePath}/${modelName}ResourceBase.php`, content);
117
+ return baseFile(resolveModularBasePath(config, name, 'Resources', `${modelName}ResourceBase.php`, config.resources.basePath), content);
118
118
  }
119
119
  function generateUserResource(name, config) {
120
120
  const modelName = toPascalCase(name);
121
121
  const resourceNamespace = config.resources.namespace;
122
- const baseNamespace = config.resources.baseNamespace;
122
+ const baseNamespace = resolveModularBaseNamespace(config, name, 'Resources', config.resources.baseNamespace);
123
123
  const content = `<?php
124
124
 
125
125
  /**
@@ -2,7 +2,7 @@
2
2
  * Generates base + editable service classes for schemas with `options.api`.
3
3
  */
4
4
  import { toPascalCase, toSnakeCase } from './naming-helper.js';
5
- import { baseFile, userFile } from './types.js';
5
+ import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace } from './types.js';
6
6
  /** Generate service classes for all schemas with api config. */
7
7
  export function generateServices(reader, config) {
8
8
  const files = [];
@@ -27,7 +27,7 @@ function generateBaseService(name, schema, reader, config) {
27
27
  const restore = api.restore ?? (schema.options?.softDelete ?? false);
28
28
  const perPage = api.perPage ?? 15;
29
29
  const lookup = api.lookup ?? true;
30
- const baseNs = config.services.baseNamespace;
30
+ const baseNs = resolveModularBaseNamespace(config, name, 'Services', config.services.baseNamespace);
31
31
  const modelNs = config.models.namespace;
32
32
  const properties = schema.properties ?? {};
33
33
  const propertyOrder = reader.getPropertyOrder(name);
@@ -186,7 +186,7 @@ class ${modelName}ServiceBase
186
186
  ${methods.join('\n')}
187
187
  }
188
188
  `;
189
- return baseFile(`${config.services.basePath}/${modelName}ServiceBase.php`, content);
189
+ return baseFile(resolveModularBasePath(config, name, 'Services', `${modelName}ServiceBase.php`, config.services.basePath), content);
190
190
  }
191
191
  // ============================================================================
192
192
  // Search / Filter / Sort block builders
@@ -291,7 +291,7 @@ function buildSortBlock(sortableFields) {
291
291
  function generateUserService(name, schema, config) {
292
292
  const modelName = toPascalCase(name);
293
293
  const group = schema.group ? toPascalCase(schema.group) : null;
294
- const baseNs = config.services.baseNamespace;
294
+ const baseNs = resolveModularBaseNamespace(config, name, 'Services', config.services.baseNamespace);
295
295
  const userNs = group
296
296
  ? `${config.services.namespace}\\${group}`
297
297
  : config.services.namespace;
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Port of TraitGenerator.php — generates HasLocalizedDisplayName trait.
3
3
  */
4
- import { baseFile } from './types.js';
4
+ import { baseFile, resolveSharedBasePath, resolveSharedBaseNamespace } from './types.js';
5
5
  /** Generate the HasLocalizedDisplayName trait. */
6
6
  export function generateTrait(config) {
7
- const baseNamespace = config.models.baseNamespace;
7
+ const ns = resolveSharedBaseNamespace(config, 'Traits', config.models.baseNamespace + '\\Traits');
8
8
  const content = `<?php
9
9
 
10
- namespace ${baseNamespace}\\Traits;
10
+ namespace ${ns};
11
11
 
12
12
  /**
13
13
  * Trait for localized display names.
@@ -99,6 +99,6 @@ trait HasLocalizedDisplayName
99
99
  }
100
100
  `;
101
101
  return [
102
- baseFile(`${config.models.basePath}/Traits/HasLocalizedDisplayName.php`, content),
102
+ baseFile(resolveSharedBasePath(config, 'Traits', 'HasLocalizedDisplayName.php', config.models.basePath + '/Traits'), content),
103
103
  ];
104
104
  }
@@ -8,7 +8,7 @@
8
8
  * - $fillable = [translatable field names]
9
9
  */
10
10
  import { toPascalCase, toSnakeCase } from './naming-helper.js';
11
- import { baseFile, userFile } from './types.js';
11
+ import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace } from './types.js';
12
12
  /** Generate translation model files for all project schemas with translatable fields. */
13
13
  export function generateTranslationModels(reader, config) {
14
14
  const files = [];
@@ -24,7 +24,7 @@ export function generateTranslationModels(reader, config) {
24
24
  function generateTranslationBaseModel(name, translatableFields, config) {
25
25
  const modelName = toPascalCase(name);
26
26
  const modelSnake = toSnakeCase(name);
27
- const baseNamespace = config.models.baseNamespace;
27
+ const baseNamespace = resolveModularBaseNamespace(config, name, 'Models', config.models.baseNamespace);
28
28
  const tableName = `${modelSnake}_translations`;
29
29
  const fillableLines = translatableFields
30
30
  .map(f => ` '${f}',`)
@@ -65,12 +65,12 @@ ${fillableLines}
65
65
  ];
66
66
  }
67
67
  `;
68
- return baseFile(`${config.models.basePath}/${modelName}TranslationBaseModel.php`, content);
68
+ return baseFile(resolveModularBasePath(config, name, 'Models', `${modelName}TranslationBaseModel.php`, config.models.basePath), content);
69
69
  }
70
70
  function generateTranslationUserModel(name, config) {
71
71
  const modelName = toPascalCase(name);
72
72
  const modelNamespace = config.models.namespace;
73
- const baseNamespace = config.models.baseNamespace;
73
+ const baseNamespace = resolveModularBaseNamespace(config, name, 'Models', config.models.baseNamespace);
74
74
  const content = `<?php
75
75
 
76
76
  namespace ${modelNamespace};
@@ -12,6 +12,32 @@ export interface GeneratedFile {
12
12
  export declare function baseFile(path: string, content: string): GeneratedFile;
13
13
  /** Create a user file (created once, skip if exists). */
14
14
  export declare function userFile(path: string, content: string): GeneratedFile;
15
+ /** Category of base file for modular path resolution. */
16
+ export type BaseCategory = 'Models' | 'Controllers' | 'Requests' | 'Resources' | 'Services' | 'Enums' | 'Traits' | 'Locales' | 'Policies';
17
+ /**
18
+ * Resolve base file path for a schema based on structure.
19
+ * Modular: app/Modules/{SchemaName}/{category}/{fileName}
20
+ * Legacy: {legacyPath}/{fileName}
21
+ */
22
+ export declare function resolveModularBasePath(config: PhpConfig, schemaName: string, category: BaseCategory, fileName: string, legacyPath: string): string;
23
+ /**
24
+ * Resolve base namespace for a schema based on structure.
25
+ * Modular: App\Modules\{SchemaName}\{Category}
26
+ * Legacy: {legacyNamespace}
27
+ */
28
+ export declare function resolveModularBaseNamespace(config: PhpConfig, schemaName: string, category: BaseCategory, legacyNamespace: string): string;
29
+ /**
30
+ * Resolve shared base path (for cross-module files like BaseModel, HasLocalizedDisplayName).
31
+ * Modular: app/Modules/Shared/{category}/{fileName}
32
+ * Legacy: {legacyPath}/{fileName}
33
+ */
34
+ export declare function resolveSharedBasePath(config: PhpConfig, category: BaseCategory, fileName: string, legacyPath: string): string;
35
+ /**
36
+ * Resolve shared base namespace.
37
+ * Modular: App\Modules\Shared\{Category}
38
+ * Legacy: {legacyNamespace}
39
+ */
40
+ export declare function resolveSharedBaseNamespace(config: PhpConfig, category: BaseCategory, legacyNamespace: string): string;
15
41
  /** Per-target path and namespace override from codegen.laravel config. */
16
42
  export interface LaravelPathOverride {
17
43
  path?: string;
@@ -23,6 +49,7 @@ export interface NestedSetOverride {
23
49
  }
24
50
  /** Overrides from codegen.laravel YAML config (all optional). */
25
51
  export interface LaravelCodegenOverrides {
52
+ structure?: 'legacy' | 'modular';
26
53
  model?: LaravelPathOverride;
27
54
  request?: LaravelPathOverride;
28
55
  resource?: LaravelPathOverride;
@@ -36,6 +63,8 @@ export interface LaravelCodegenOverrides {
36
63
  }
37
64
  /** PHP codegen configuration (resolved with defaults). */
38
65
  export interface PhpConfig {
66
+ structure: 'legacy' | 'modular';
67
+ modulesPath: string;
39
68
  models: {
40
69
  namespace: string;
41
70
  baseNamespace: string;
package/dist/php/types.js CHANGED
@@ -9,6 +9,50 @@ export function baseFile(path, content) {
9
9
  export function userFile(path, content) {
10
10
  return { path, content, overwrite: false, type: 'user' };
11
11
  }
12
+ /**
13
+ * Resolve base file path for a schema based on structure.
14
+ * Modular: app/Modules/{SchemaName}/{category}/{fileName}
15
+ * Legacy: {legacyPath}/{fileName}
16
+ */
17
+ export function resolveModularBasePath(config, schemaName, category, fileName, legacyPath) {
18
+ if (config.structure === 'modular') {
19
+ return `${config.modulesPath}/${schemaName}/${category}/${fileName}`;
20
+ }
21
+ return `${legacyPath}/${fileName}`;
22
+ }
23
+ /**
24
+ * Resolve base namespace for a schema based on structure.
25
+ * Modular: App\Modules\{SchemaName}\{Category}
26
+ * Legacy: {legacyNamespace}
27
+ */
28
+ export function resolveModularBaseNamespace(config, schemaName, category, legacyNamespace) {
29
+ if (config.structure === 'modular') {
30
+ return pathToNamespace(`${config.modulesPath}/${schemaName}/${category}`);
31
+ }
32
+ return legacyNamespace;
33
+ }
34
+ /**
35
+ * Resolve shared base path (for cross-module files like BaseModel, HasLocalizedDisplayName).
36
+ * Modular: app/Modules/Shared/{category}/{fileName}
37
+ * Legacy: {legacyPath}/{fileName}
38
+ */
39
+ export function resolveSharedBasePath(config, category, fileName, legacyPath) {
40
+ if (config.structure === 'modular') {
41
+ return `${config.modulesPath}/Shared/${category}/${fileName}`;
42
+ }
43
+ return `${legacyPath}/${fileName}`;
44
+ }
45
+ /**
46
+ * Resolve shared base namespace.
47
+ * Modular: App\Modules\Shared\{Category}
48
+ * Legacy: {legacyNamespace}
49
+ */
50
+ export function resolveSharedBaseNamespace(config, category, legacyNamespace) {
51
+ if (config.structure === 'modular') {
52
+ return pathToNamespace(`${config.modulesPath}/Shared/${category}`);
53
+ }
54
+ return legacyNamespace;
55
+ }
12
56
  /** Convert a file path to a PHP namespace (e.g., app/Models/Omnify → App\Models\Omnify). */
13
57
  function pathToNamespace(p) {
14
58
  return p
@@ -49,7 +93,11 @@ export function derivePhpConfig(overrides) {
49
93
  const serviceNs = overrides?.service?.namespace ?? pathToNamespace(servicePath);
50
94
  const routePath = overrides?.route?.path ?? DEFAULT_ROUTE_PATH;
51
95
  const nestedsetNs = overrides?.nestedset?.namespace ?? 'Aimeos\\Nestedset';
96
+ const structure = overrides?.structure ?? 'legacy';
97
+ const modulesPath = 'app/Modules';
52
98
  return {
99
+ structure,
100
+ modulesPath,
53
101
  models: {
54
102
  namespace: modelNs,
55
103
  baseNamespace: `${modelNs}\\Base`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnifyjp/ts",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "TypeScript model type generator from Omnify schemas.json",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",