@omnifyjp/omnify 3.3.0 → 3.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnifyjp/omnify",
3
- "version": "3.3.0",
3
+ "version": "3.5.0",
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": "3.3.0",
40
- "@omnifyjp/omnify-darwin-x64": "3.3.0",
41
- "@omnifyjp/omnify-linux-x64": "3.3.0",
42
- "@omnifyjp/omnify-linux-arm64": "3.3.0",
43
- "@omnifyjp/omnify-win32-x64": "3.3.0"
39
+ "@omnifyjp/omnify-darwin-arm64": "3.5.0",
40
+ "@omnifyjp/omnify-darwin-x64": "3.5.0",
41
+ "@omnifyjp/omnify-linux-x64": "3.5.0",
42
+ "@omnifyjp/omnify-linux-arm64": "3.5.0",
43
+ "@omnifyjp/omnify-win32-x64": "3.5.0"
44
44
  }
45
45
  }
@@ -5,7 +5,7 @@
5
5
  * - FileStatusEnum.php (base, always overwritten)
6
6
  * - FileLocales.php (base, always overwritten)
7
7
  */
8
- import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveSharedBaseNamespace } from './types.js';
8
+ import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveSharedBaseNamespace, resolveGlobalEnumPath, resolveGlobalEnumNamespace, resolveGlobalTraitNamespace, } from './types.js';
9
9
  /** Generate all File model infrastructure files. */
10
10
  export function generateFileModels(reader, config) {
11
11
  if (!reader.hasFileProperties())
@@ -96,9 +96,10 @@ class FileResource extends FileResourceBase
96
96
  function generateFileBaseModel(config, defaultDisk) {
97
97
  const baseNamespace = resolveModularBaseNamespace(config, 'File', 'Models', config.models.baseNamespace);
98
98
  const localesNamespace = resolveModularBaseNamespace(config, 'File', 'Locales', config.models.baseNamespace + '\\Locales');
99
- const traitsNamespace = resolveSharedBaseNamespace(config, 'Traits', config.models.baseNamespace + '\\Traits');
99
+ // Issue #33: HasLocalizedDisplayName is a global Omnify trait — see trait-generator.ts.
100
+ const traitsNamespace = resolveGlobalTraitNamespace(config, config.models.baseNamespace + '\\Traits');
100
101
  const sharedModelsNamespace = resolveSharedBaseNamespace(config, 'Models', config.models.baseNamespace);
101
- const enumNamespace = resolveModularBaseNamespace(config, 'File', 'Enums', config.models.namespace);
102
+ const enumNamespace = resolveGlobalEnumNamespace(config, config.models.namespace);
102
103
  const modelNamespace = config.models.namespace;
103
104
  const content = `<?php
104
105
 
@@ -334,7 +335,10 @@ class File extends FileBaseModel
334
335
  return userFile(`${config.models.path}/File.php`, content);
335
336
  }
336
337
  function generateFileStatusEnum(config) {
337
- const modelNamespace = resolveModularBaseNamespace(config, 'File', 'Enums', config.models.namespace);
338
+ // Issue #33: FileStatusEnum belongs to the Omnify file-attachment subsystem,
339
+ // not to a user-defined "File" schema, so it lives in the global enums
340
+ // folder rather than under `Modules/File/Enums/`.
341
+ const modelNamespace = resolveGlobalEnumNamespace(config, config.models.namespace);
338
342
  const content = `<?php
339
343
 
340
344
  namespace ${modelNamespace};
@@ -372,7 +376,7 @@ enum FileStatusEnum: string
372
376
  }
373
377
  }
374
378
  `;
375
- return baseFile(resolveModularBasePath(config, 'File', 'Enums', 'FileStatusEnum.php', config.models.path), content);
379
+ return baseFile(resolveGlobalEnumPath(config, 'FileStatusEnum.php', config.models.path), content);
376
380
  }
377
381
  function generateFileLocales(config, locales) {
378
382
  const baseNamespace = resolveModularBaseNamespace(config, 'File', 'Locales', config.models.baseNamespace + '\\Locales');
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * Generates the HasFiles trait for models with File-type properties.
3
3
  */
4
- import { baseFile, resolveModularBasePath, resolveModularBaseNamespace } from './types.js';
4
+ import { baseFile, resolveGlobalTraitPath, resolveGlobalTraitNamespace, resolveGlobalEnumNamespace, } from './types.js';
5
5
  /** Generate the HasFiles trait. */
6
6
  export function generateFileTrait(config) {
7
- const traitsNamespace = resolveModularBaseNamespace(config, 'File', 'Traits', config.models.baseNamespace + '\\Traits');
8
- const enumNamespace = resolveModularBaseNamespace(config, 'File', 'Enums', config.models.namespace);
7
+ // Issue #33: HasFiles is a global Omnify trait — not scoped to any user
8
+ // schema so it lives in the dedicated global traits folder.
9
+ const traitsNamespace = resolveGlobalTraitNamespace(config, config.models.baseNamespace + '\\Traits');
10
+ const enumNamespace = resolveGlobalEnumNamespace(config, config.models.namespace);
9
11
  const modelNamespace = config.models.namespace;
10
12
  const content = `<?php
11
13
 
@@ -92,6 +94,6 @@ trait HasFiles
92
94
  }
93
95
  `;
94
96
  return [
95
- baseFile(resolveModularBasePath(config, 'File', 'Traits', 'HasFiles.php', config.models.basePath + '/Traits'), content),
97
+ baseFile(resolveGlobalTraitPath(config, 'HasFiles.php', config.models.basePath + '/Traits'), content),
96
98
  ];
97
99
  }
@@ -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, resolveModularBasePath, resolveModularBaseNamespace, resolveSharedBaseNamespace } from './types.js';
7
+ import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveSharedBaseNamespace, resolveGlobalTraitNamespace, } 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) {
@@ -35,7 +35,8 @@ function generateBaseModel(name, schema, reader, config) {
35
35
  const modelName = toPascalCase(name);
36
36
  const baseNamespace = resolveModularBaseNamespace(config, name, 'Models', config.models.baseNamespace);
37
37
  const localesNamespace = resolveModularBaseNamespace(config, name, 'Locales', config.models.baseNamespace + '\\Locales');
38
- const traitsNamespace = resolveSharedBaseNamespace(config, 'Traits', config.models.baseNamespace + '\\Traits');
38
+ // Issue #33: HasLocalizedDisplayName is a global Omnify trait, not a per-shared module file.
39
+ const traitsNamespace = resolveGlobalTraitNamespace(config, config.models.baseNamespace + '\\Traits');
39
40
  const sharedModelsNamespace = resolveSharedBaseNamespace(config, 'Models', config.models.baseNamespace);
40
41
  const modelNamespace = config.models.namespace;
41
42
  const options = schema.options ?? {};
@@ -1,10 +1,13 @@
1
1
  /**
2
2
  * Port of TraitGenerator.php — generates HasLocalizedDisplayName trait.
3
3
  */
4
- import { baseFile, resolveSharedBasePath, resolveSharedBaseNamespace } from './types.js';
4
+ import { baseFile, resolveGlobalTraitPath, resolveGlobalTraitNamespace } from './types.js';
5
5
  /** Generate the HasLocalizedDisplayName trait. */
6
6
  export function generateTrait(config) {
7
- const ns = resolveSharedBaseNamespace(config, 'Traits', config.models.baseNamespace + '\\Traits');
7
+ // Issue #33: HasLocalizedDisplayName is a global Omnify trait shared across
8
+ // all schemas, so it belongs in the global traits folder, not under any
9
+ // single schema's `Modules/{Schema}/Traits/` subfolder.
10
+ const ns = resolveGlobalTraitNamespace(config, config.models.baseNamespace + '\\Traits');
8
11
  const content = `<?php
9
12
 
10
13
  namespace ${ns};
@@ -99,6 +102,6 @@ trait HasLocalizedDisplayName
99
102
  }
100
103
  `;
101
104
  return [
102
- baseFile(resolveSharedBasePath(config, 'Traits', 'HasLocalizedDisplayName.php', config.models.basePath + '/Traits'), content),
105
+ baseFile(resolveGlobalTraitPath(config, 'HasLocalizedDisplayName.php', config.models.basePath + '/Traits'), content),
103
106
  ];
104
107
  }
@@ -16,28 +16,42 @@ export declare function userFile(path: string, content: string): GeneratedFile;
16
16
  export type BaseCategory = 'Models' | 'Controllers' | 'Requests' | 'Resources' | 'Services' | 'Enums' | 'Traits' | 'Locales' | 'Policies';
17
17
  /**
18
18
  * Resolve base file path for a schema based on structure.
19
- * Modular: app/Modules/{SchemaName}/{category}/{fileName}
19
+ * Modular: {modules.path}/{SchemaName}/{category}/{fileName}
20
20
  * Legacy: {legacyPath}/{fileName}
21
21
  */
22
22
  export declare function resolveModularBasePath(config: PhpConfig, schemaName: string, category: BaseCategory, fileName: string, legacyPath: string): string;
23
23
  /**
24
24
  * Resolve base namespace for a schema based on structure.
25
- * Modular: App\Modules\{SchemaName}\{Category}
25
+ * Modular: {modules.namespace}\{SchemaName}\{Category}
26
26
  * Legacy: {legacyNamespace}
27
27
  */
28
28
  export declare function resolveModularBaseNamespace(config: PhpConfig, schemaName: string, category: BaseCategory, legacyNamespace: string): string;
29
29
  /**
30
- * Resolve shared base path (for cross-module files like BaseModel, HasLocalizedDisplayName).
31
- * Modular: app/Modules/Shared/{category}/{fileName}
30
+ * Resolve shared base path (for cross-module files like BaseModel).
31
+ * Modular: {shared.path}/{category}/{fileName}
32
32
  * Legacy: {legacyPath}/{fileName}
33
33
  */
34
34
  export declare function resolveSharedBasePath(config: PhpConfig, category: BaseCategory, fileName: string, legacyPath: string): string;
35
35
  /**
36
36
  * Resolve shared base namespace.
37
- * Modular: App\Modules\Shared\{Category}
37
+ * Modular: {shared.namespace}\{Category}
38
38
  * Legacy: {legacyNamespace}
39
39
  */
40
40
  export declare function resolveSharedBaseNamespace(config: PhpConfig, category: BaseCategory, legacyNamespace: string): string;
41
+ /**
42
+ * Resolve global Omnify enum file path. Used for system enums that aren't tied
43
+ * to a single user-defined schema (e.g. FileStatusEnum belongs to the Omnify
44
+ * file-attachment subsystem). In modular mode these live under their own
45
+ * top-level folder so they're easy to find. In legacy mode the original
46
+ * `legacyPath` is preserved for backward compat.
47
+ */
48
+ export declare function resolveGlobalEnumPath(config: PhpConfig, fileName: string, legacyPath: string): string;
49
+ /** Resolve global Omnify enum namespace. Modular: {globalEnums.namespace}. Legacy: passthrough. */
50
+ export declare function resolveGlobalEnumNamespace(config: PhpConfig, legacyNamespace: string): string;
51
+ /** Resolve global Omnify trait file path (HasFiles, HasLocalizedDisplayName, ...). */
52
+ export declare function resolveGlobalTraitPath(config: PhpConfig, fileName: string, legacyPath: string): string;
53
+ /** Resolve global Omnify trait namespace. */
54
+ export declare function resolveGlobalTraitNamespace(config: PhpConfig, legacyNamespace: string): string;
41
55
  /** Per-target path and namespace override from codegen.laravel config. */
42
56
  export interface LaravelPathOverride {
43
57
  path?: string;
@@ -65,6 +79,26 @@ export interface LaravelCodegenOverrides {
65
79
  policy?: LaravelPathOverride;
66
80
  controller?: LaravelPathOverride;
67
81
  service?: LaravelPathOverride;
82
+ /**
83
+ * Per-schema base-class root for `structure: modular`. Default
84
+ * `App\Omnify\Modules` (path `app/Omnify/Modules`) so generated base classes
85
+ * never collide with a team's hand-written `app/Modules/` (Modular Monolith
86
+ * pattern is common in Laravel).
87
+ */
88
+ modules?: LaravelPathOverride;
89
+ /** Cross-module shared classes (e.g. `BaseModel`). Default `App\Omnify\Shared`. */
90
+ shared?: LaravelPathOverride;
91
+ /**
92
+ * Global Omnify enums that aren't tied to a single user-defined schema
93
+ * (e.g. `FileStatusEnum` for the file-attachment subsystem).
94
+ * Default `App\Omnify\Enums`.
95
+ */
96
+ enums?: LaravelPathOverride;
97
+ /**
98
+ * Global Omnify traits (e.g. `HasFiles`, `HasLocalizedDisplayName`).
99
+ * Default `App\Omnify\Traits`.
100
+ */
101
+ traits?: LaravelPathOverride;
68
102
  route?: LaravelPathOverride;
69
103
  config?: LaravelPathOverride;
70
104
  nestedset?: NestedSetOverride;
@@ -74,6 +108,35 @@ export interface PhpConfig {
74
108
  /** Filesystem prefix applied to all generated paths. Empty when not set. */
75
109
  rootPath: string;
76
110
  structure: 'legacy' | 'modular';
111
+ /**
112
+ * Modular base-class root: `{path}/{Schema}/{Layer}/...` and
113
+ * `{namespace}\{Schema}\{Layer}`. Only consulted in `structure: modular`.
114
+ */
115
+ modules: {
116
+ namespace: string;
117
+ path: string;
118
+ };
119
+ /** Cross-module shared root (e.g. BaseModel lives at `{shared.path}/Models/`). */
120
+ shared: {
121
+ namespace: string;
122
+ path: string;
123
+ };
124
+ /** Global Omnify enums (e.g. FileStatusEnum). */
125
+ globalEnums: {
126
+ namespace: string;
127
+ path: string;
128
+ };
129
+ /** Global Omnify traits (e.g. HasFiles, HasLocalizedDisplayName). */
130
+ globalTraits: {
131
+ namespace: string;
132
+ path: string;
133
+ };
134
+ /**
135
+ * Backward-compat alias for `modules.path`. Kept so external callers and
136
+ * tests that read `config.modulesPath` keep working; new code should use
137
+ * `config.modules.path` instead.
138
+ * @deprecated use `modules.path`
139
+ */
77
140
  modulesPath: string;
78
141
  models: {
79
142
  namespace: string;
@@ -11,57 +11,81 @@ export function userFile(path, content) {
11
11
  }
12
12
  /**
13
13
  * Resolve base file path for a schema based on structure.
14
- * Modular: app/Modules/{SchemaName}/{category}/{fileName}
14
+ * Modular: {modules.path}/{SchemaName}/{category}/{fileName}
15
15
  * Legacy: {legacyPath}/{fileName}
16
16
  */
17
17
  export function resolveModularBasePath(config, schemaName, category, fileName, legacyPath) {
18
18
  if (config.structure === 'modular') {
19
- return `${config.modulesPath}/${schemaName}/${category}/${fileName}`;
19
+ return `${config.modules.path}/${schemaName}/${category}/${fileName}`;
20
20
  }
21
21
  return `${legacyPath}/${fileName}`;
22
22
  }
23
23
  /**
24
24
  * Resolve base namespace for a schema based on structure.
25
- * Modular: App\Modules\{SchemaName}\{Category}
25
+ * Modular: {modules.namespace}\{SchemaName}\{Category}
26
26
  * Legacy: {legacyNamespace}
27
27
  */
28
28
  export function resolveModularBaseNamespace(config, schemaName, category, legacyNamespace) {
29
29
  if (config.structure === 'modular') {
30
- return pathToNamespace(`${stripRootPath(config, config.modulesPath)}/${schemaName}/${category}`);
30
+ return `${config.modules.namespace}\\${schemaName}\\${category}`;
31
31
  }
32
32
  return legacyNamespace;
33
33
  }
34
34
  /**
35
- * Resolve shared base path (for cross-module files like BaseModel, HasLocalizedDisplayName).
36
- * Modular: app/Modules/Shared/{category}/{fileName}
35
+ * Resolve shared base path (for cross-module files like BaseModel).
36
+ * Modular: {shared.path}/{category}/{fileName}
37
37
  * Legacy: {legacyPath}/{fileName}
38
38
  */
39
39
  export function resolveSharedBasePath(config, category, fileName, legacyPath) {
40
40
  if (config.structure === 'modular') {
41
- return `${config.modulesPath}/Shared/${category}/${fileName}`;
41
+ return `${config.shared.path}/${category}/${fileName}`;
42
42
  }
43
43
  return `${legacyPath}/${fileName}`;
44
44
  }
45
45
  /**
46
46
  * Resolve shared base namespace.
47
- * Modular: App\Modules\Shared\{Category}
47
+ * Modular: {shared.namespace}\{Category}
48
48
  * Legacy: {legacyNamespace}
49
49
  */
50
50
  export function resolveSharedBaseNamespace(config, category, legacyNamespace) {
51
51
  if (config.structure === 'modular') {
52
- return pathToNamespace(`${stripRootPath(config, config.modulesPath)}/Shared/${category}`);
52
+ return `${config.shared.namespace}\\${category}`;
53
53
  }
54
54
  return legacyNamespace;
55
55
  }
56
- /** Strip the configured rootPath prefix from a filesystem path. */
57
- function stripRootPath(config, p) {
58
- if (!config.rootPath)
59
- return p;
60
- if (p === config.rootPath)
61
- return '';
62
- if (p.startsWith(`${config.rootPath}/`))
63
- return p.slice(config.rootPath.length + 1);
64
- return p;
56
+ /**
57
+ * Resolve global Omnify enum file path. Used for system enums that aren't tied
58
+ * to a single user-defined schema (e.g. FileStatusEnum belongs to the Omnify
59
+ * file-attachment subsystem). In modular mode these live under their own
60
+ * top-level folder so they're easy to find. In legacy mode the original
61
+ * `legacyPath` is preserved for backward compat.
62
+ */
63
+ export function resolveGlobalEnumPath(config, fileName, legacyPath) {
64
+ if (config.structure === 'modular') {
65
+ return `${config.globalEnums.path}/${fileName}`;
66
+ }
67
+ return `${legacyPath}/${fileName}`;
68
+ }
69
+ /** Resolve global Omnify enum namespace. Modular: {globalEnums.namespace}. Legacy: passthrough. */
70
+ export function resolveGlobalEnumNamespace(config, legacyNamespace) {
71
+ if (config.structure === 'modular') {
72
+ return config.globalEnums.namespace;
73
+ }
74
+ return legacyNamespace;
75
+ }
76
+ /** Resolve global Omnify trait file path (HasFiles, HasLocalizedDisplayName, ...). */
77
+ export function resolveGlobalTraitPath(config, fileName, legacyPath) {
78
+ if (config.structure === 'modular') {
79
+ return `${config.globalTraits.path}/${fileName}`;
80
+ }
81
+ return `${legacyPath}/${fileName}`;
82
+ }
83
+ /** Resolve global Omnify trait namespace. */
84
+ export function resolveGlobalTraitNamespace(config, legacyNamespace) {
85
+ if (config.structure === 'modular') {
86
+ return config.globalTraits.namespace;
87
+ }
88
+ return legacyNamespace;
65
89
  }
66
90
  /** Convert a file path to a PHP namespace (e.g., app/Models/Omnify → App\Models\Omnify). */
67
91
  function pathToNamespace(p) {
@@ -70,18 +94,67 @@ function pathToNamespace(p) {
70
94
  .map(s => s.charAt(0).toUpperCase() + s.slice(1))
71
95
  .join('\\');
72
96
  }
73
- // Default paths
74
- const DEFAULT_MODEL_PATH = 'app/Models/Omnify';
75
- const DEFAULT_REQUEST_PATH = 'app/Http/Requests';
76
- const DEFAULT_RESOURCE_PATH = 'app/Http/Resources';
97
+ /**
98
+ * Convert a PHP namespace to a Laravel filesystem path
99
+ * (e.g., App\Models → app/Models, App\Models\Omnify → app/Models/Omnify).
100
+ *
101
+ * Lowercases the leading "App" segment to match Laravel's autoload convention.
102
+ */
103
+ function namespaceToPath(ns) {
104
+ const parts = ns.split('\\').filter(Boolean);
105
+ if (parts.length === 0)
106
+ return '';
107
+ if (parts[0] === 'App')
108
+ parts[0] = 'app';
109
+ return parts.join('/');
110
+ }
111
+ /**
112
+ * Resolve the path/namespace pair for a target (model, policy, etc.) so they
113
+ * always stay in sync. Either side can be overridden independently:
114
+ * - both unset → use defaults
115
+ * - only path set → derive namespace from path
116
+ * - only namespace set → derive path from namespace (prevents autoload
117
+ * mismatches like generating App\Foo into app/Foo/Bar/)
118
+ * - both set → use as-is (caller is responsible for keeping them aligned)
119
+ */
120
+ function resolvePathAndNamespace(rootPath, override, defaultPath, nsFromPath) {
121
+ const hasPath = override?.path !== undefined;
122
+ const hasNs = override?.namespace !== undefined;
123
+ if (hasNs && !hasPath) {
124
+ const namespace = override.namespace;
125
+ const path = withRoot(rootPath, namespaceToPath(namespace));
126
+ return { path, namespace };
127
+ }
128
+ const path = withRoot(rootPath, override?.path ?? defaultPath);
129
+ const namespace = hasNs ? override.namespace : nsFromPath(path);
130
+ return { path, namespace };
131
+ }
132
+ // Default paths.
133
+ //
134
+ // Hybrid projects (manual team code + Omnify codegen) need Omnify-generated
135
+ // files to live in their own `Omnify/` subfolder so they don't collide with
136
+ // hand-written controllers/services/requests/resources of the same name.
137
+ // See issue #32. Models keep the standard `app/Models` location because
138
+ // editable models are first-class entities the team owns directly; the
139
+ // regenerated base classes already live under `app/Modules/{Schema}/Models/`
140
+ // in modular structure.
141
+ const DEFAULT_MODEL_PATH = 'app/Models';
142
+ const DEFAULT_REQUEST_PATH = 'app/Http/Requests/Omnify';
143
+ const DEFAULT_RESOURCE_PATH = 'app/Http/Resources/Omnify';
77
144
  const DEFAULT_FACTORY_PATH = 'database/factories';
78
145
  const DEFAULT_PROVIDER_PATH = 'app/Providers';
79
146
  const DEFAULT_POLICY_PATH = 'app/Policies/Omnify';
80
- const DEFAULT_CONTROLLER_PATH = 'app/Http/Controllers';
81
- const DEFAULT_SERVICE_PATH = 'app/Services';
147
+ const DEFAULT_CONTROLLER_PATH = 'app/Http/Controllers/Omnify';
148
+ const DEFAULT_SERVICE_PATH = 'app/Services/Omnify';
82
149
  const DEFAULT_ROUTE_PATH = 'routes/api/omnify';
83
150
  const DEFAULT_CONFIG_FILE_PATH = 'config/omnify-schemas.php';
84
- const DEFAULT_MODULES_PATH = 'app/Modules';
151
+ // Modular base-class roots — issue #33. All Omnify auto-generated code lives
152
+ // under `app/Omnify/` so it's clearly separated from team's manual `app/Modules/`
153
+ // (Modular Monolith pattern), and easy to gitignore / nuke as a single tree.
154
+ const DEFAULT_MODULES_PATH = 'app/Omnify/Modules';
155
+ const DEFAULT_SHARED_PATH = 'app/Omnify/Shared';
156
+ const DEFAULT_GLOBAL_ENUMS_PATH = 'app/Omnify/Enums';
157
+ const DEFAULT_GLOBAL_TRAITS_PATH = 'app/Omnify/Traits';
85
158
  /**
86
159
  * Apply a rootPath prefix to a path. Absolute paths and paths that already
87
160
  * begin with `${rootPath}/` are returned as-is, so users can mix and match
@@ -115,30 +188,29 @@ export function derivePhpConfig(overrides) {
115
188
  return p;
116
189
  };
117
190
  const nsFromPath = (p) => pathToNamespace(stripRoot(p));
118
- const modelPath = withRoot(rootPath, overrides?.model?.path ?? DEFAULT_MODEL_PATH);
119
- const modelNs = overrides?.model?.namespace ?? nsFromPath(modelPath);
120
- const requestPath = withRoot(rootPath, overrides?.request?.path ?? DEFAULT_REQUEST_PATH);
121
- const requestNs = overrides?.request?.namespace ?? nsFromPath(requestPath);
122
- const resourcePath = withRoot(rootPath, overrides?.resource?.path ?? DEFAULT_RESOURCE_PATH);
123
- const resourceNs = overrides?.resource?.namespace ?? nsFromPath(resourcePath);
124
- const factoryPath = withRoot(rootPath, overrides?.factory?.path ?? DEFAULT_FACTORY_PATH);
125
- const factoryNs = overrides?.factory?.namespace ?? nsFromPath(factoryPath);
126
- const providerPath = withRoot(rootPath, overrides?.provider?.path ?? DEFAULT_PROVIDER_PATH);
127
- const providerNs = overrides?.provider?.namespace ?? nsFromPath(providerPath);
128
- const policyPath = withRoot(rootPath, overrides?.policy?.path ?? DEFAULT_POLICY_PATH);
129
- const policyNs = overrides?.policy?.namespace ?? nsFromPath(policyPath);
130
- const controllerPath = withRoot(rootPath, overrides?.controller?.path ?? DEFAULT_CONTROLLER_PATH);
131
- const controllerNs = overrides?.controller?.namespace ?? nsFromPath(controllerPath);
132
- const servicePath = withRoot(rootPath, overrides?.service?.path ?? DEFAULT_SERVICE_PATH);
133
- const serviceNs = overrides?.service?.namespace ?? nsFromPath(servicePath);
191
+ const { path: modelPath, namespace: modelNs } = resolvePathAndNamespace(rootPath, overrides?.model, DEFAULT_MODEL_PATH, nsFromPath);
192
+ const { path: requestPath, namespace: requestNs } = resolvePathAndNamespace(rootPath, overrides?.request, DEFAULT_REQUEST_PATH, nsFromPath);
193
+ const { path: resourcePath, namespace: resourceNs } = resolvePathAndNamespace(rootPath, overrides?.resource, DEFAULT_RESOURCE_PATH, nsFromPath);
194
+ const { path: factoryPath, namespace: factoryNs } = resolvePathAndNamespace(rootPath, overrides?.factory, DEFAULT_FACTORY_PATH, nsFromPath);
195
+ const { path: providerPath, namespace: providerNs } = resolvePathAndNamespace(rootPath, overrides?.provider, DEFAULT_PROVIDER_PATH, nsFromPath);
196
+ const { path: policyPath, namespace: policyNs } = resolvePathAndNamespace(rootPath, overrides?.policy, DEFAULT_POLICY_PATH, nsFromPath);
197
+ const { path: controllerPath, namespace: controllerNs } = resolvePathAndNamespace(rootPath, overrides?.controller, DEFAULT_CONTROLLER_PATH, nsFromPath);
198
+ const { path: servicePath, namespace: serviceNs } = resolvePathAndNamespace(rootPath, overrides?.service, DEFAULT_SERVICE_PATH, nsFromPath);
199
+ const { path: modulesPath, namespace: modulesNs } = resolvePathAndNamespace(rootPath, overrides?.modules, DEFAULT_MODULES_PATH, nsFromPath);
200
+ const { path: sharedPath, namespace: sharedNs } = resolvePathAndNamespace(rootPath, overrides?.shared, DEFAULT_SHARED_PATH, nsFromPath);
201
+ const { path: globalEnumsPath, namespace: globalEnumsNs } = resolvePathAndNamespace(rootPath, overrides?.enums, DEFAULT_GLOBAL_ENUMS_PATH, nsFromPath);
202
+ const { path: globalTraitsPath, namespace: globalTraitsNs } = resolvePathAndNamespace(rootPath, overrides?.traits, DEFAULT_GLOBAL_TRAITS_PATH, nsFromPath);
134
203
  const routePath = withRoot(rootPath, overrides?.route?.path ?? DEFAULT_ROUTE_PATH);
135
204
  const configFilePath = withRoot(rootPath, overrides?.config?.path ?? DEFAULT_CONFIG_FILE_PATH);
136
205
  const nestedsetNs = overrides?.nestedset?.namespace ?? 'Aimeos\\Nestedset';
137
206
  const structure = overrides?.structure ?? 'legacy';
138
- const modulesPath = withRoot(rootPath, DEFAULT_MODULES_PATH);
139
207
  return {
140
208
  rootPath,
141
209
  structure,
210
+ modules: { namespace: modulesNs, path: modulesPath },
211
+ shared: { namespace: sharedNs, path: sharedPath },
212
+ globalEnums: { namespace: globalEnumsNs, path: globalEnumsPath },
213
+ globalTraits: { namespace: globalTraitsNs, path: globalTraitsPath },
142
214
  modulesPath,
143
215
  models: {
144
216
  namespace: modelNs,