@pattern-stack/codegen 0.6.0 → 0.6.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/CHANGELOG.md +12 -0
- package/package.json +6 -1
- package/src/config/case-converters.mjs +181 -0
- package/src/config/config-loader.mjs +34 -0
- package/src/config/locations.mjs +298 -0
- package/src/config/naming-config.mjs +173 -0
- package/src/config/paths.mjs +690 -0
- package/src/patterns/library/activity.pattern.ts +32 -0
- package/src/patterns/library/base.pattern.ts +28 -0
- package/src/patterns/library/index.ts +30 -0
- package/src/patterns/library/knowledge.pattern.ts +31 -0
- package/src/patterns/library/metadata.pattern.ts +31 -0
- package/src/patterns/library/synced.pattern.ts +34 -0
- package/src/patterns/pattern-definition.ts +280 -0
- package/src/patterns/registry.ts +365 -0
- package/src/schema/naming-config.schema.mjs +119 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.6.1] — 2026-04-26
|
|
8
|
+
|
|
9
|
+
Critical hotfix for #266. `0.6.0` shipped with a broken `entity new` command for every npm consumer: `templates/entity/new/prompt.js` and `templates/entity/new/clean-lite-ps/prompt-extension.js` import runtime helpers from `../../../src/config/*.mjs` and `../../../src/patterns/registry.js`, but the `package.json:files` manifest excluded `src/`. Every published-tarball invocation died with `ResolveMessage: Cannot find module '../../../src/config/paths.mjs'`. The repo's smoke + baseline tests didn't catch this because they run from the source checkout, where the relative paths resolve directly.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **`fix(release)` #266** — extend `package.json:files` with the narrow set of `src/` paths that templates reach for at runtime: `src/config/*.mjs`, `src/schema/naming-config.schema.mjs`, `src/patterns/registry.ts`, `src/patterns/pattern-definition.ts`, `src/patterns/library/*.ts`. Narrow paths (not a broad `src/` entry) so test files and CLI source stay out of the tarball. Verified by `npm pack && npm install ./pack.tgz && entity new` against a real fixture entity — generation now succeeds end-to-end.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **Prevention test** at `src/__tests__/templates/files-manifest-coverage.test.ts`: statically scans every `templates/**/*.{js,mjs,cjs}` for relative imports that escape the `templates/` tree, resolves each against the source layout (with `.js → .ts` rewrite for Bun TS-aware ESM), and asserts the resolved path matches at least one entry in `package.json:files`. Fails CI when a future template adds a cross-package import without updating the manifest. Closes the hole identified in #190 (post-publish smoke proposal).
|
|
18
|
+
|
|
7
19
|
## [0.6.0] — 2026-04-26
|
|
8
20
|
|
|
9
21
|
Sync subsystem Phase 2: configurable change sources land. Detection mode is now declarative — entity YAML carries a `detection:` block parsed into a typed `DetectionConfig`, and codegen emits a per-entity Map factory module that wires consumer-supplied adapter callbacks to the right primitive. Plus audit-tier event classification (epic #242 phases 1–4).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pattern-stack/codegen",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Entity-driven code generation for full-stack TypeScript applications",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,6 +37,11 @@
|
|
|
37
37
|
"dist",
|
|
38
38
|
"runtime",
|
|
39
39
|
"templates",
|
|
40
|
+
"src/config/*.mjs",
|
|
41
|
+
"src/schema/naming-config.schema.mjs",
|
|
42
|
+
"src/patterns/registry.ts",
|
|
43
|
+
"src/patterns/pattern-definition.ts",
|
|
44
|
+
"src/patterns/library/*.ts",
|
|
40
45
|
"CHANGELOG.md",
|
|
41
46
|
"README.md",
|
|
42
47
|
"LICENSE"
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Case Conversion Utilities
|
|
3
|
+
*
|
|
4
|
+
* Converts strings between different case styles for file naming.
|
|
5
|
+
* Handles various input formats (snake_case, camelCase, PascalCase, kebab-case).
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import { toKebabCase, toCamelCase, toPascalCase, toSnakeCase } from './case-converters.mjs';
|
|
9
|
+
*
|
|
10
|
+
* toKebabCase('deal_state'); // 'deal-state'
|
|
11
|
+
* toPascalCase('deal_state'); // 'DealState'
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Input Normalization
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Split a string into words, handling various input formats
|
|
20
|
+
*
|
|
21
|
+
* Handles:
|
|
22
|
+
* - snake_case: deal_state → ['deal', 'state']
|
|
23
|
+
* - kebab-case: deal-state → ['deal', 'state']
|
|
24
|
+
* - camelCase: dealState → ['deal', 'State']
|
|
25
|
+
* - PascalCase: DealState → ['Deal', 'State']
|
|
26
|
+
*
|
|
27
|
+
* @param {string} str - Input string in any case format
|
|
28
|
+
* @returns {string[]} Array of lowercased words
|
|
29
|
+
*/
|
|
30
|
+
function splitWords(str) {
|
|
31
|
+
if (!str) return [];
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
str
|
|
35
|
+
// Insert space before capitals in camelCase/PascalCase
|
|
36
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
37
|
+
// Replace underscores and hyphens with spaces
|
|
38
|
+
.replace(/[_-]/g, ' ')
|
|
39
|
+
// Split on spaces
|
|
40
|
+
.split(/\s+/)
|
|
41
|
+
// Filter empty strings and lowercase all words
|
|
42
|
+
.filter((word) => word.length > 0)
|
|
43
|
+
.map((word) => word.toLowerCase())
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Case Converters
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Convert to kebab-case
|
|
53
|
+
*
|
|
54
|
+
* @param {string} str - Input string in any case format
|
|
55
|
+
* @returns {string} kebab-case string
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* toKebabCase('deal_state') // 'deal-state'
|
|
59
|
+
* toKebabCase('DealState') // 'deal-state'
|
|
60
|
+
* toKebabCase('dealState') // 'deal-state'
|
|
61
|
+
*/
|
|
62
|
+
export function toKebabCase(str) {
|
|
63
|
+
return splitWords(str).join('-');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Convert to snake_case
|
|
68
|
+
*
|
|
69
|
+
* @param {string} str - Input string in any case format
|
|
70
|
+
* @returns {string} snake_case string
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* toSnakeCase('deal-state') // 'deal_state'
|
|
74
|
+
* toSnakeCase('DealState') // 'deal_state'
|
|
75
|
+
* toSnakeCase('dealState') // 'deal_state'
|
|
76
|
+
*/
|
|
77
|
+
export function toSnakeCase(str) {
|
|
78
|
+
return splitWords(str).join('_');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Convert to camelCase
|
|
83
|
+
*
|
|
84
|
+
* @param {string} str - Input string in any case format
|
|
85
|
+
* @returns {string} camelCase string
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* toCamelCase('deal_state') // 'dealState'
|
|
89
|
+
* toCamelCase('deal-state') // 'dealState'
|
|
90
|
+
* toCamelCase('DealState') // 'dealState'
|
|
91
|
+
*/
|
|
92
|
+
export function toCamelCase(str) {
|
|
93
|
+
const words = splitWords(str);
|
|
94
|
+
if (words.length === 0) return '';
|
|
95
|
+
|
|
96
|
+
return words
|
|
97
|
+
.map((word, index) =>
|
|
98
|
+
index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)
|
|
99
|
+
)
|
|
100
|
+
.join('');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Convert to PascalCase
|
|
105
|
+
*
|
|
106
|
+
* @param {string} str - Input string in any case format
|
|
107
|
+
* @returns {string} PascalCase string
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* toPascalCase('deal_state') // 'DealState'
|
|
111
|
+
* toPascalCase('deal-state') // 'DealState'
|
|
112
|
+
* toPascalCase('dealState') // 'DealState'
|
|
113
|
+
*/
|
|
114
|
+
export function toPascalCase(str) {
|
|
115
|
+
const words = splitWords(str);
|
|
116
|
+
return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join('');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ============================================================================
|
|
120
|
+
// Case Application
|
|
121
|
+
// ============================================================================
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Apply a case style to a string
|
|
125
|
+
*
|
|
126
|
+
* @param {string} str - Input string in any case format
|
|
127
|
+
* @param {'kebab-case' | 'snake_case' | 'camelCase' | 'PascalCase'} caseStyle - Target case style
|
|
128
|
+
* @returns {string} String in target case style
|
|
129
|
+
*/
|
|
130
|
+
export function applyCase(str, caseStyle) {
|
|
131
|
+
switch (caseStyle) {
|
|
132
|
+
case 'kebab-case':
|
|
133
|
+
return toKebabCase(str);
|
|
134
|
+
case 'snake_case':
|
|
135
|
+
return toSnakeCase(str);
|
|
136
|
+
case 'camelCase':
|
|
137
|
+
return toCamelCase(str);
|
|
138
|
+
case 'PascalCase':
|
|
139
|
+
return toPascalCase(str);
|
|
140
|
+
default:
|
|
141
|
+
// Default to kebab-case for safety
|
|
142
|
+
return toKebabCase(str);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get the separator for a case style
|
|
148
|
+
*
|
|
149
|
+
* @param {'kebab-case' | 'snake_case' | 'camelCase' | 'PascalCase'} caseStyle
|
|
150
|
+
* @returns {string} Separator character (or empty string for camel/Pascal)
|
|
151
|
+
*/
|
|
152
|
+
export function getCaseSeparator(caseStyle) {
|
|
153
|
+
switch (caseStyle) {
|
|
154
|
+
case 'kebab-case':
|
|
155
|
+
return '-';
|
|
156
|
+
case 'snake_case':
|
|
157
|
+
return '_';
|
|
158
|
+
case 'camelCase':
|
|
159
|
+
case 'PascalCase':
|
|
160
|
+
return ''; // No separator, uses capitalization
|
|
161
|
+
default:
|
|
162
|
+
return '-';
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ============================================================================
|
|
167
|
+
// Exports
|
|
168
|
+
// ============================================================================
|
|
169
|
+
|
|
170
|
+
// Named export for splitWords (used internally, exported for testing)
|
|
171
|
+
export { splitWords };
|
|
172
|
+
|
|
173
|
+
export default {
|
|
174
|
+
toKebabCase,
|
|
175
|
+
toSnakeCase,
|
|
176
|
+
toCamelCase,
|
|
177
|
+
toPascalCase,
|
|
178
|
+
applyCase,
|
|
179
|
+
getCaseSeparator,
|
|
180
|
+
splitWords,
|
|
181
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared configuration loader for codegen
|
|
3
|
+
*
|
|
4
|
+
* Loads and caches codegen.config.yaml once, shared by all config modules.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import yaml from 'yaml';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load project-specific codegen configuration from codegen.config.yaml
|
|
13
|
+
* Returns null if config file doesn't exist (falls back to defaults)
|
|
14
|
+
*/
|
|
15
|
+
function loadProjectConfig(cwd = process.cwd()) {
|
|
16
|
+
const configPath = path.resolve(cwd, 'codegen.config.yaml');
|
|
17
|
+
|
|
18
|
+
if (!fs.existsSync(configPath)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
24
|
+
return yaml.parse(content);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.warn(`Warning: Failed to load codegen.config.yaml: ${error.message}`);
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Load project config once at module initialization
|
|
32
|
+
export const projectConfig = loadProjectConfig();
|
|
33
|
+
|
|
34
|
+
export default projectConfig;
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized location configuration for codegen
|
|
3
|
+
*
|
|
4
|
+
* Each location defines both:
|
|
5
|
+
* - path: where files are written (filesystem path from project root)
|
|
6
|
+
* - import: how to import from that location (TypeScript import alias)
|
|
7
|
+
*
|
|
8
|
+
* This ensures a single source of truth for all path references.
|
|
9
|
+
*
|
|
10
|
+
* Usage in templates:
|
|
11
|
+
* to: <%= locations.dbEntities.path %>/<%= name %>.ts
|
|
12
|
+
* import { schema } from '<%= locations.dbEntities.import %>/<%= name %>';
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { projectConfig } from './config-loader.mjs';
|
|
16
|
+
|
|
17
|
+
const backendSrcPath = projectConfig?.paths?.backend_src ?? 'app/backend/src';
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Default Locations
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default location definitions
|
|
25
|
+
* Each location has a `path` (filesystem) and `import` (TypeScript alias)
|
|
26
|
+
*
|
|
27
|
+
* These can be overridden in codegen.config.yaml under the `locations:` key
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULT_LOCATIONS = {
|
|
30
|
+
// ===========================================================================
|
|
31
|
+
// Shared packages (consumed by both frontend and backend)
|
|
32
|
+
// ===========================================================================
|
|
33
|
+
|
|
34
|
+
/** Shared Zod entity schemas */
|
|
35
|
+
dbEntities: {
|
|
36
|
+
path: 'packages/db/src/entities',
|
|
37
|
+
import: '@repo/db/entities',
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
/** Monolithic server schema (Drizzle tables, relations, types) */
|
|
41
|
+
dbSchemaServer: {
|
|
42
|
+
path: 'packages/db/src/server/schema.ts',
|
|
43
|
+
import: '@repo/db/server/schema',
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
/** Monolithic client schema exports */
|
|
47
|
+
dbSchemaClient: {
|
|
48
|
+
path: 'packages/db/src/client/schema.ts',
|
|
49
|
+
import: '@repo/db/client/schema',
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
/** Database migrations directory */
|
|
53
|
+
dbMigrations: {
|
|
54
|
+
path: 'packages/db/src/server/migrations',
|
|
55
|
+
import: null, // Migrations are not imported
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
/** Context engine (polymorphic relationships, facts) */
|
|
59
|
+
dbContextEngine: {
|
|
60
|
+
path: 'packages/db/src/context-engine',
|
|
61
|
+
import: '@repo/db/context-engine',
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/** tRPC client */
|
|
65
|
+
trpcClient: {
|
|
66
|
+
path: 'packages/trpc/src/client',
|
|
67
|
+
import: '@repo/trpc/client',
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// ===========================================================================
|
|
71
|
+
// Frontend locations
|
|
72
|
+
// ===========================================================================
|
|
73
|
+
|
|
74
|
+
/** Frontend source root */
|
|
75
|
+
frontendSrc: {
|
|
76
|
+
path: 'apps/frontend/src',
|
|
77
|
+
import: '@',
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/** Electric SQL collections (directory containing collections.ts) */
|
|
81
|
+
frontendCollections: {
|
|
82
|
+
path: 'apps/frontend/src/lib',
|
|
83
|
+
import: '@/lib',
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/** Frontend store (TanStack DB) */
|
|
87
|
+
frontendStore: {
|
|
88
|
+
path: 'apps/frontend/src/lib/store',
|
|
89
|
+
import: '@/lib/store',
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
/** Per-entity store hooks */
|
|
93
|
+
frontendStoreEntities: {
|
|
94
|
+
path: 'apps/frontend/src/lib/store/entities',
|
|
95
|
+
import: '@/lib/store/entities',
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/** Unified entity definitions */
|
|
99
|
+
frontendEntities: {
|
|
100
|
+
path: 'apps/frontend/src/lib/entities',
|
|
101
|
+
import: '@/lib/entities',
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
/** Generated entity files (metadata, collections, types) */
|
|
105
|
+
frontendGenerated: {
|
|
106
|
+
path: 'apps/frontend/src/generated',
|
|
107
|
+
import: '@/generated',
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
/** Entity metadata generated files */
|
|
111
|
+
frontendEntityMetadata: {
|
|
112
|
+
path: 'apps/frontend/src/generated/entity-metadata',
|
|
113
|
+
import: '@/generated/entity-metadata',
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
/** Field meta type definitions */
|
|
117
|
+
frontendFieldMetaTypes: {
|
|
118
|
+
path: 'apps/frontend/src/lib/types',
|
|
119
|
+
import: '@/lib/types',
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/** Auth helpers (for collections) */
|
|
123
|
+
frontendCollectionsAuth: {
|
|
124
|
+
path: 'apps/frontend/src/lib/collections/auth',
|
|
125
|
+
import: '@/lib/collections/auth',
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
// ===========================================================================
|
|
129
|
+
// Backend locations
|
|
130
|
+
// ===========================================================================
|
|
131
|
+
|
|
132
|
+
/** Backend source root */
|
|
133
|
+
backendSrc: {
|
|
134
|
+
path: backendSrcPath,
|
|
135
|
+
import: '@backend',
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
/** Domain layer */
|
|
139
|
+
backendDomain: {
|
|
140
|
+
path: `${backendSrcPath}/domain`,
|
|
141
|
+
import: '@backend/domain',
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/** Application layer - commands */
|
|
145
|
+
backendCommands: {
|
|
146
|
+
path: `${backendSrcPath}/application/commands`,
|
|
147
|
+
import: '@backend/application/commands',
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/** Application layer - queries */
|
|
151
|
+
backendQueries: {
|
|
152
|
+
path: `${backendSrcPath}/application/queries`,
|
|
153
|
+
import: '@backend/application/queries',
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/** Application layer - schemas/DTOs */
|
|
157
|
+
backendSchemas: {
|
|
158
|
+
path: `${backendSrcPath}/application/schemas`,
|
|
159
|
+
import: '@backend/application/schemas',
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
/** Infrastructure - Drizzle schemas */
|
|
163
|
+
backendDrizzle: {
|
|
164
|
+
path: `${backendSrcPath}/infrastructure/persistence/drizzle`,
|
|
165
|
+
import: '@backend/infrastructure/persistence/drizzle',
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
/** Infrastructure - Repositories */
|
|
169
|
+
backendRepositories: {
|
|
170
|
+
path: `${backendSrcPath}/infrastructure/persistence/repositories`,
|
|
171
|
+
import: '@backend/infrastructure/persistence/repositories',
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
/** Infrastructure - Database module (for dependency injection) */
|
|
175
|
+
backendDatabaseModule: {
|
|
176
|
+
path: `${backendSrcPath}/infrastructure/database`,
|
|
177
|
+
import: '@backend/infrastructure/database',
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
/** Presentation - REST controllers */
|
|
181
|
+
backendControllers: {
|
|
182
|
+
path: `${backendSrcPath}/presentation/rest`,
|
|
183
|
+
import: '@backend/presentation/rest',
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
/** NestJS modules */
|
|
187
|
+
backendModules: {
|
|
188
|
+
path: `${backendSrcPath}/infrastructure/modules`,
|
|
189
|
+
import: '@backend/infrastructure/modules',
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
/** Constants (tokens, etc) */
|
|
193
|
+
backendConstants: {
|
|
194
|
+
path: `${backendSrcPath}/constants`,
|
|
195
|
+
import: '@backend/constants',
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
/** Core guards (AuthGuard) */
|
|
199
|
+
backendAuthGuard: {
|
|
200
|
+
path: `${backendSrcPath}/core/guards`,
|
|
201
|
+
import: '@backend/core/guards',
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
/** Core decorators (CurrentUser) */
|
|
205
|
+
backendCurrentUserDecorator: {
|
|
206
|
+
path: `${backendSrcPath}/core/decorators`,
|
|
207
|
+
import: '@backend/core/decorators',
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
/** Core services (Electric) */
|
|
211
|
+
backendElectricService: {
|
|
212
|
+
path: `${backendSrcPath}/core/services`,
|
|
213
|
+
import: '@backend/core/services',
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
/** Electric module */
|
|
217
|
+
backendElectricModule: {
|
|
218
|
+
path: `${backendSrcPath}/infrastructure/electric`,
|
|
219
|
+
import: '@backend/infrastructure/electric',
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// ============================================================================
|
|
224
|
+
// Merge with project config
|
|
225
|
+
// ============================================================================
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Merge default locations with project-specific overrides
|
|
229
|
+
*/
|
|
230
|
+
function buildLocations(projectConfig) {
|
|
231
|
+
const overrides = projectConfig?.locations || {};
|
|
232
|
+
const locations = { ...DEFAULT_LOCATIONS };
|
|
233
|
+
|
|
234
|
+
// Deep merge each location
|
|
235
|
+
for (const [key, override] of Object.entries(overrides)) {
|
|
236
|
+
if (locations[key]) {
|
|
237
|
+
// Merge with existing defaults
|
|
238
|
+
locations[key] = {
|
|
239
|
+
...locations[key],
|
|
240
|
+
...override,
|
|
241
|
+
};
|
|
242
|
+
} else {
|
|
243
|
+
// New location defined in config
|
|
244
|
+
locations[key] = override;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return locations;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export const LOCATIONS = buildLocations(projectConfig);
|
|
252
|
+
|
|
253
|
+
// ============================================================================
|
|
254
|
+
// Helper functions
|
|
255
|
+
// ============================================================================
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get a location by key, with optional subpath
|
|
259
|
+
* @param {string} key - Location key (e.g., 'dbEntities')
|
|
260
|
+
* @param {string} [subpath] - Optional subpath to append
|
|
261
|
+
* @returns {{ path: string, import: string }}
|
|
262
|
+
*/
|
|
263
|
+
export function getLocation(key, subpath = '') {
|
|
264
|
+
const location = LOCATIONS[key];
|
|
265
|
+
if (!location) {
|
|
266
|
+
throw new Error(`Unknown location: ${key}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!subpath) {
|
|
270
|
+
return location;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return {
|
|
274
|
+
path: `${location.path}/${subpath}`,
|
|
275
|
+
import: `${location.import}/${subpath}`,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Get just the filesystem path for a location
|
|
281
|
+
*/
|
|
282
|
+
export function getLocationPath(key, subpath = '') {
|
|
283
|
+
return getLocation(key, subpath).path;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get just the import alias for a location
|
|
288
|
+
*/
|
|
289
|
+
export function getLocationImport(key, subpath = '') {
|
|
290
|
+
return getLocation(key, subpath).import;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export default {
|
|
294
|
+
LOCATIONS,
|
|
295
|
+
getLocation,
|
|
296
|
+
getLocationPath,
|
|
297
|
+
getLocationImport,
|
|
298
|
+
};
|