@xpack/xpm-lib 3.1.2 → 4.0.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/README.md +16 -212
- package/dist/classes/actions.d.ts +58 -0
- package/dist/classes/actions.d.ts.map +1 -0
- package/dist/classes/actions.js +250 -0
- package/dist/classes/actions.js.map +1 -0
- package/dist/classes/build-configurations.d.ts +78 -0
- package/dist/classes/build-configurations.d.ts.map +1 -0
- package/dist/classes/build-configurations.js +489 -0
- package/dist/classes/build-configurations.js.map +1 -0
- package/dist/classes/combinations-generator.d.ts +19 -0
- package/dist/classes/combinations-generator.d.ts.map +1 -0
- package/dist/classes/combinations-generator.js +48 -0
- package/dist/classes/combinations-generator.js.map +1 -0
- package/dist/classes/data-model.d.ts +21 -0
- package/dist/classes/data-model.d.ts.map +1 -0
- package/dist/classes/data-model.js +47 -0
- package/dist/classes/data-model.js.map +1 -0
- package/dist/classes/errors.d.ts +13 -0
- package/dist/classes/errors.d.ts.map +1 -0
- package/dist/classes/errors.js +13 -0
- package/dist/classes/errors.js.map +1 -0
- package/dist/classes/init-template-base.d.ts +47 -0
- package/dist/classes/init-template-base.d.ts.map +1 -0
- package/dist/classes/init-template-base.js +358 -0
- package/dist/classes/init-template-base.js.map +1 -0
- package/dist/classes/liquid-drop.d.ts +28 -0
- package/dist/classes/liquid-drop.d.ts.map +1 -0
- package/dist/classes/liquid-drop.js +70 -0
- package/dist/classes/liquid-drop.js.map +1 -0
- package/dist/classes/liquid-engine.d.ts +7 -0
- package/dist/classes/liquid-engine.d.ts.map +1 -0
- package/dist/classes/liquid-engine.js +72 -0
- package/dist/classes/liquid-engine.js.map +1 -0
- package/dist/classes/package.d.ts +31 -0
- package/dist/classes/package.d.ts.map +1 -0
- package/dist/classes/package.js +268 -0
- package/dist/classes/package.js.map +1 -0
- package/dist/classes/platform-detector.d.ts +14 -0
- package/dist/classes/platform-detector.d.ts.map +1 -0
- package/dist/classes/platform-detector.js +26 -0
- package/dist/classes/platform-detector.js.map +1 -0
- package/dist/classes/policies.d.ts +14 -0
- package/dist/classes/policies.d.ts.map +1 -0
- package/dist/classes/policies.js +20 -0
- package/dist/classes/policies.js.map +1 -0
- package/dist/classes/template-expander.d.ts +29 -0
- package/dist/classes/template-expander.d.ts.map +1 -0
- package/dist/classes/template-expander.js +62 -0
- package/dist/classes/template-expander.js.map +1 -0
- package/dist/data/substitutions-variables.d.ts +43 -0
- package/dist/data/substitutions-variables.d.ts.map +1 -0
- package/dist/{lib → data}/substitutions-variables.js +1 -16
- package/dist/data/substitutions-variables.js.map +1 -0
- package/dist/functions/chmod-recursively.d.ts +9 -0
- package/dist/functions/chmod-recursively.d.ts.map +1 -0
- package/dist/functions/chmod-recursively.js +66 -0
- package/dist/functions/chmod-recursively.js.map +1 -0
- package/dist/functions/filter-paths.d.ts +5 -0
- package/dist/functions/filter-paths.d.ts.map +1 -0
- package/dist/functions/filter-paths.js +16 -0
- package/dist/functions/filter-paths.js.map +1 -0
- package/dist/functions/is-something.d.ts +9 -0
- package/dist/functions/is-something.d.ts.map +1 -0
- package/dist/functions/is-something.js +25 -0
- package/dist/functions/is-something.js.map +1 -0
- package/dist/functions/matrix-expander.d.ts +17 -0
- package/dist/functions/matrix-expander.d.ts.map +1 -0
- package/dist/functions/matrix-expander.js +52 -0
- package/dist/functions/matrix-expander.js.map +1 -0
- package/dist/functions/perform-substitutions.d.ts +12 -0
- package/dist/functions/perform-substitutions.d.ts.map +1 -0
- package/dist/functions/perform-substitutions.js +76 -0
- package/dist/functions/perform-substitutions.js.map +1 -0
- package/dist/functions/utils.d.ts +8 -0
- package/dist/functions/utils.d.ts.map +1 -0
- package/dist/functions/utils.js +16 -0
- package/dist/functions/utils.js.map +1 -0
- package/dist/index.d.ts +22 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -29
- package/dist/index.js.map +1 -1
- package/dist/{lib/types.d.ts → types/json.d.ts} +31 -22
- package/dist/types/json.d.ts.map +1 -0
- package/dist/types/json.js +2 -0
- package/dist/types/json.js.map +1 -0
- package/dist/types/xpm-init-template.d.ts +21 -0
- package/dist/types/xpm-init-template.d.ts.map +1 -0
- package/dist/types/xpm-init-template.js +2 -0
- package/dist/types/xpm-init-template.js.map +1 -0
- package/dist/types/xpm.d.ts +16 -0
- package/dist/types/xpm.d.ts.map +1 -0
- package/dist/types/xpm.js +2 -0
- package/dist/types/xpm.js.map +1 -0
- package/package.json +53 -44
- package/src/CODE-REVIEW.md +2167 -0
- package/src/README.md +393 -6
- package/src/classes/actions.ts +1157 -0
- package/src/classes/build-configurations.ts +2127 -0
- package/src/classes/combinations-generator.ts +331 -0
- package/src/classes/data-model.ts +337 -0
- package/src/classes/errors.ts +105 -0
- package/src/classes/init-template-base.ts +1028 -0
- package/src/classes/liquid-drop.ts +376 -0
- package/src/classes/liquid-engine.ts +249 -0
- package/src/classes/package.ts +765 -0
- package/src/classes/platform-detector.ts +237 -0
- package/src/classes/policies.ts +200 -0
- package/src/classes/template-expander.ts +330 -0
- package/src/data/substitutions-variables.ts +390 -0
- package/src/functions/chmod-recursively.ts +195 -0
- package/src/functions/filter-paths.ts +126 -0
- package/src/functions/is-something.ts +223 -0
- package/src/functions/matrix-expander.ts +172 -0
- package/src/functions/perform-substitutions.ts +253 -0
- package/src/functions/utils.ts +151 -0
- package/src/index.ts +72 -19
- package/src/types/json.ts +519 -0
- package/src/types/xpm-init-template.ts +282 -0
- package/src/types/xpm.ts +162 -0
- package/dist/lib/chmod-recursive.d.ts +0 -7
- package/dist/lib/chmod-recursive.d.ts.map +0 -1
- package/dist/lib/chmod-recursive.js +0 -81
- package/dist/lib/chmod-recursive.js.map +0 -1
- package/dist/lib/errors.d.ts +0 -11
- package/dist/lib/errors.d.ts.map +0 -1
- package/dist/lib/errors.js +0 -26
- package/dist/lib/errors.js.map +0 -1
- package/dist/lib/functions/chmod-recursive.d.ts +0 -7
- package/dist/lib/functions/chmod-recursive.d.ts.map +0 -1
- package/dist/lib/functions/chmod-recursive.js +0 -81
- package/dist/lib/functions/chmod-recursive.js.map +0 -1
- package/dist/lib/functions/perform-substitutions.d.ts +0 -20
- package/dist/lib/functions/perform-substitutions.d.ts.map +0 -1
- package/dist/lib/functions/perform-substitutions.js +0 -85
- package/dist/lib/functions/perform-substitutions.js.map +0 -1
- package/dist/lib/functions/utils.d.ts +0 -30
- package/dist/lib/functions/utils.d.ts.map +0 -1
- package/dist/lib/functions/utils.js +0 -70
- package/dist/lib/functions/utils.js.map +0 -1
- package/dist/lib/init-template-base.d.ts +0 -46
- package/dist/lib/init-template-base.d.ts.map +0 -1
- package/dist/lib/init-template-base.js +0 -281
- package/dist/lib/init-template-base.js.map +0 -1
- package/dist/lib/liquid-actions.d.ts +0 -37
- package/dist/lib/liquid-actions.d.ts.map +0 -1
- package/dist/lib/liquid-actions.js +0 -148
- package/dist/lib/liquid-actions.js.map +0 -1
- package/dist/lib/liquid-build-configurations.d.ts +0 -47
- package/dist/lib/liquid-build-configurations.d.ts.map +0 -1
- package/dist/lib/liquid-build-configurations.js +0 -282
- package/dist/lib/liquid-build-configurations.js.map +0 -1
- package/dist/lib/liquid-drop.d.ts +0 -13
- package/dist/lib/liquid-drop.d.ts.map +0 -1
- package/dist/lib/liquid-drop.js +0 -56
- package/dist/lib/liquid-drop.js.map +0 -1
- package/dist/lib/liquid-engine.d.ts +0 -5
- package/dist/lib/liquid-engine.d.ts.map +0 -1
- package/dist/lib/liquid-engine.js +0 -85
- package/dist/lib/liquid-engine.js.map +0 -1
- package/dist/lib/liquid-package.d.ts +0 -17
- package/dist/lib/liquid-package.d.ts.map +0 -1
- package/dist/lib/liquid-package.js +0 -70
- package/dist/lib/liquid-package.js.map +0 -1
- package/dist/lib/package.d.ts +0 -66
- package/dist/lib/package.d.ts.map +0 -1
- package/dist/lib/package.js +0 -700
- package/dist/lib/package.js.map +0 -1
- package/dist/lib/perform-substitutions.d.ts +0 -20
- package/dist/lib/perform-substitutions.d.ts.map +0 -1
- package/dist/lib/perform-substitutions.js +0 -85
- package/dist/lib/perform-substitutions.js.map +0 -1
- package/dist/lib/policies.d.ts +0 -14
- package/dist/lib/policies.d.ts.map +0 -1
- package/dist/lib/policies.js +0 -33
- package/dist/lib/policies.js.map +0 -1
- package/dist/lib/substitutions-variables.d.ts +0 -117
- package/dist/lib/substitutions-variables.d.ts.map +0 -1
- package/dist/lib/substitutions-variables.js.map +0 -1
- package/dist/lib/types.d.ts.map +0 -1
- package/dist/lib/types.js +0 -13
- package/dist/lib/types.js.map +0 -1
- package/dist/lib/utils.d.ts +0 -30
- package/dist/lib/utils.d.ts.map +0 -1
- package/dist/lib/utils.js +0 -70
- package/dist/lib/utils.js.map +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/src/lib/errors.ts +0 -29
- package/src/lib/functions/chmod-recursive.ts +0 -103
- package/src/lib/functions/perform-substitutions.ts +0 -116
- package/src/lib/functions/utils.ts +0 -88
- package/src/lib/init-template-base.ts +0 -408
- package/src/lib/liquid-actions.ts +0 -223
- package/src/lib/liquid-build-configurations.ts +0 -433
- package/src/lib/liquid-drop.ts +0 -99
- package/src/lib/liquid-engine.ts +0 -135
- package/src/lib/liquid-package.ts +0 -108
- package/src/lib/package.ts +0 -947
- package/src/lib/policies.ts +0 -51
- package/src/lib/substitutions-variables.ts +0 -177
- package/src/lib/types.ts +0 -109
- package/src/package.json +0 -3
- package/src/tsconfig.json +0 -10
|
@@ -0,0 +1,2167 @@
|
|
|
1
|
+
# Source Code Review
|
|
2
|
+
|
|
3
|
+
**Date:** 17 February 2026
|
|
4
|
+
**Codebase Size:** ~9,826 lines across 15 TypeScript source files
|
|
5
|
+
**Focus:** Architecture, code organisation, patterns, and maintainability
|
|
6
|
+
|
|
7
|
+
## Executive Summary
|
|
8
|
+
|
|
9
|
+
The xpm-lib source code demonstrates a well-architected, type-safe TypeScript library with sophisticated lazy evaluation patterns and comprehensive error handling. The codebase exhibits professional software engineering practices with strong separation of concerns and extensive use of TypeScript's type system.
|
|
10
|
+
|
|
11
|
+
**Key Strengths:**
|
|
12
|
+
|
|
13
|
+
- ✅ Sophisticated lazy evaluation architecture
|
|
14
|
+
- ✅ Strong type safety with comprehensive TypeScript usage
|
|
15
|
+
- ✅ Clear separation of concerns (classes, functions, types)
|
|
16
|
+
- ✅ Well-defined error hierarchy
|
|
17
|
+
- ✅ Consistent coding patterns and conventions
|
|
18
|
+
- ✅ Two-step initialisation pattern for complex objects
|
|
19
|
+
|
|
20
|
+
**Priority Areas for Improvement:**
|
|
21
|
+
|
|
22
|
+
1. 🔧 Split oversized files (build-configurations.ts: 2,155 lines)
|
|
23
|
+
2. 🔧 Extract common initialisation boilerplate
|
|
24
|
+
3. 🔧 Reduce cyclomatic complexity in large methods
|
|
25
|
+
4. 🔧 Consider builder pattern for complex constructor parameters
|
|
26
|
+
5. 🔧 Improve consistency in naming conventions
|
|
27
|
+
|
|
28
|
+
**Overall Assessment:** High-quality codebase (8.5/10) with excellent architecture that would benefit from refactoring large files and extracting common patterns.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 1. File Size Analysis
|
|
33
|
+
|
|
34
|
+
### 1.1 Critical Size Issues
|
|
35
|
+
|
|
36
|
+
**build-configurations.ts (2,155 lines) - URGENT**
|
|
37
|
+
|
|
38
|
+
- **Issue:** Single file contains two major classes (`BuildConfigurations` and `BuildConfiguration`)
|
|
39
|
+
- **Complexity:** 300+ lines of inheritance resolution, property merging, dependency substitution
|
|
40
|
+
- **Impact:** Difficult to navigate, high cognitive load, reduced maintainability
|
|
41
|
+
|
|
42
|
+
**Recommendation:** Split into separate files:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
src/classes/build-configurations/
|
|
46
|
+
├── index.ts # Re-exports
|
|
47
|
+
├── build-configurations.ts # Collection class (~900 lines)
|
|
48
|
+
├── build-configuration.ts # Individual class (~800 lines)
|
|
49
|
+
├── inheritance-resolver.ts # Extract inheritance logic (~300 lines)
|
|
50
|
+
└── types.ts # Shared interfaces
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Expected Impact:** Improved navigability, easier testing, reduced merge conflicts
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
**actions.ts (1,160 lines) - HIGH PRIORITY**
|
|
58
|
+
|
|
59
|
+
- **Issue:** Contains both `Actions` collection and `Action` classes
|
|
60
|
+
- **Structure:** Similar pattern to build-configurations but smaller
|
|
61
|
+
|
|
62
|
+
**Recommendation:** Split into:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
src/classes/actions/
|
|
66
|
+
├── index.ts # Re-exports
|
|
67
|
+
├── actions.ts # Collection class (~500 lines)
|
|
68
|
+
├── action.ts # Individual class (~400 lines)
|
|
69
|
+
└── types.ts # Shared interfaces
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Expected Impact:** Better organisation, clearer responsibilities
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
**init-template-base.ts (1,027 lines) - MODERATE PRIORITY**
|
|
77
|
+
|
|
78
|
+
- **Issue:** Large abstract base class with extensive property validation logic
|
|
79
|
+
- **Structure:** Combines validation, prompting, substitution, and generation
|
|
80
|
+
|
|
81
|
+
**Recommendation:** Extract helper modules:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
src/classes/init-template/
|
|
85
|
+
├── index.ts
|
|
86
|
+
├── init-template-base.ts # Core class (~400 lines)
|
|
87
|
+
├── property-validator.ts # Validation logic (~200 lines)
|
|
88
|
+
├── property-prompter.ts # Interactive prompting (~200 lines)
|
|
89
|
+
└── substitution-processor.ts # Variable processing (~200 lines)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Expected Impact:** Improved testability, clearer responsibilities
|
|
93
|
+
|
|
94
|
+
### 1.2 Well-Sized Files
|
|
95
|
+
|
|
96
|
+
**Good Examples:**
|
|
97
|
+
|
|
98
|
+
- `package.ts` (765 lines) - Comprehensive but manageable
|
|
99
|
+
- `json.ts` (519 lines) - Type definitions appropriately grouped
|
|
100
|
+
- `liquid-engine.ts` (249 lines) - Focused on single responsibility
|
|
101
|
+
- `platform-detector.ts` (237 lines) - Well-contained logic
|
|
102
|
+
- `template-expander.ts` (330 lines) - Generic utility class
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 2. Code Duplication Patterns
|
|
107
|
+
|
|
108
|
+
### 2.1 Initialisation Boilerplate
|
|
109
|
+
|
|
110
|
+
**Issue:** Repetitive initialisation pattern across multiple classes
|
|
111
|
+
|
|
112
|
+
**Pattern Found in 8 Locations:**
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
// Found in: BuildConfigurations, BuildConfiguration, Actions, Action
|
|
116
|
+
protected _isInitialised = false
|
|
117
|
+
|
|
118
|
+
async initialise(): Promise<boolean> {
|
|
119
|
+
if (this._isInitialised) {
|
|
120
|
+
return true
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ... initialisation logic ...
|
|
124
|
+
|
|
125
|
+
this._isInitialised = true
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Recommendation:** Create mixin or base class:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
// src/classes/mixins/initialisable.ts
|
|
134
|
+
export abstract class Initialisable {
|
|
135
|
+
protected _isInitialised = false
|
|
136
|
+
|
|
137
|
+
async initialise(): Promise<boolean> {
|
|
138
|
+
if (this._isInitialised) {
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
await this.performInitialisation()
|
|
143
|
+
this._isInitialised = true
|
|
144
|
+
return true
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
protected abstract performInitialisation(): Promise<void>
|
|
148
|
+
|
|
149
|
+
get isInitialised(): boolean {
|
|
150
|
+
return this._isInitialised
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
protected assertInitialised(context: string): void {
|
|
154
|
+
assert(
|
|
155
|
+
this._isInitialised,
|
|
156
|
+
`${this.constructor.name} must be initialised before ${context}`
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Usage:**
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
export class BuildConfigurations extends Initialisable {
|
|
166
|
+
protected async performInitialisation(): Promise<void> {
|
|
167
|
+
// Actual initialisation logic here
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
get names(): string[] {
|
|
171
|
+
this.assertInitialised('accessing names')
|
|
172
|
+
return this._buildConfigurationsNames
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Expected Impact:**
|
|
178
|
+
|
|
179
|
+
- ~200 lines of boilerplate eliminated
|
|
180
|
+
- Consistent initialisation behaviour
|
|
181
|
+
- Easier to add initialisation lifecycle hooks
|
|
182
|
+
|
|
183
|
+
### 2.2 Constructor Parameter Validation
|
|
184
|
+
|
|
185
|
+
**Issue:** Repetitive assertion patterns in every constructor
|
|
186
|
+
|
|
187
|
+
**Pattern Found in 15+ Constructors:**
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
constructor({ engine, substitutionsVariables, log, /* ... */ }) {
|
|
191
|
+
assert(log, 'log is required')
|
|
192
|
+
assert(engine, 'engine is required')
|
|
193
|
+
assert(substitutionsVariables, 'substitutionsVariables is required')
|
|
194
|
+
// ...
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Recommendation:** Use parameter decorator or validation helper:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
// src/functions/validation-helpers.ts
|
|
202
|
+
export function validateRequired<T extends Record<string, unknown>>(
|
|
203
|
+
params: T,
|
|
204
|
+
requiredKeys: Array<keyof T>,
|
|
205
|
+
className: string
|
|
206
|
+
): void {
|
|
207
|
+
for (const key of requiredKeys) {
|
|
208
|
+
assert(params[key], `${String(key)} is required for ${className}`)
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**Usage:**
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
constructor(params: BuildConfigurationsConstructorParameters) {
|
|
217
|
+
validateRequired(
|
|
218
|
+
params,
|
|
219
|
+
['log', 'engine', 'substitutionsVariables'],
|
|
220
|
+
BuildConfigurations.name
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
this.log = params.log
|
|
224
|
+
this.engine = params.engine
|
|
225
|
+
// ...
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Alternative:** Use a validation library like `zod` for runtime type checking:
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import { z } from 'zod'
|
|
233
|
+
|
|
234
|
+
const BuildConfigurationsParamsSchema = z.object({
|
|
235
|
+
engine: z.instanceof(LiquidEngine),
|
|
236
|
+
substitutionsVariables: z.record(z.unknown()),
|
|
237
|
+
jsonBuildConfigurations: z.record(z.unknown()).optional(),
|
|
238
|
+
log: z.instanceof(Logger),
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
constructor(params: BuildConfigurationsConstructorParameters) {
|
|
242
|
+
const validated = BuildConfigurationsParamsSchema.parse(params)
|
|
243
|
+
// TypeScript now knows params are validated
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
**Expected Impact:**
|
|
248
|
+
|
|
249
|
+
- Eliminates ~100 lines of assertion boilerplate
|
|
250
|
+
- More consistent error messages
|
|
251
|
+
- Option for more sophisticated validation
|
|
252
|
+
|
|
253
|
+
### 2.3 Error Message Formatting
|
|
254
|
+
|
|
255
|
+
**Issue:** Similar error construction patterns throughout
|
|
256
|
+
|
|
257
|
+
**Pattern Found in 50+ Locations:**
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
throw new ConfigurationError(`build configuration "${name}" does not exist`)
|
|
261
|
+
|
|
262
|
+
throw new ConfigurationError(`action "${actionName}" does not exist`)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Recommendation:** Create error factory functions:
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// src/classes/errors.ts
|
|
269
|
+
export class ErrorFactory {
|
|
270
|
+
static notFound(
|
|
271
|
+
type: 'action' | 'buildConfiguration' | 'property',
|
|
272
|
+
name: string
|
|
273
|
+
): ConfigurationError {
|
|
274
|
+
return new ConfigurationError(`${type} "${name}" does not exist`)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
static invalidType(
|
|
278
|
+
field: string,
|
|
279
|
+
expectedType: string,
|
|
280
|
+
actualValue: unknown
|
|
281
|
+
): ConfigurationError {
|
|
282
|
+
return new ConfigurationError(
|
|
283
|
+
`${field} must be ${expectedType}, got ${typeof actualValue}`
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
static circularReference(
|
|
288
|
+
type: 'configuration' | 'template',
|
|
289
|
+
chain: string[]
|
|
290
|
+
): ConfigurationError {
|
|
291
|
+
return new ConfigurationError(
|
|
292
|
+
`Circular ${type} reference detected: ${chain.join(' → ')}`
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Usage:**
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
if (!this._buildConfigurationsMap.has(name)) {
|
|
302
|
+
throw ErrorFactory.notFound('buildConfiguration', name)
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Expected Impact:**
|
|
307
|
+
|
|
308
|
+
- More consistent error messages
|
|
309
|
+
- Easier to standardise error formatting
|
|
310
|
+
- Centralised error message maintenance
|
|
311
|
+
|
|
312
|
+
### 2.4 Liquid Template Processing
|
|
313
|
+
|
|
314
|
+
**Issue:** Similar template substitution patterns
|
|
315
|
+
|
|
316
|
+
**Pattern Found in 10+ Locations:**
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
try {
|
|
320
|
+
const result = await performSubstitutions({
|
|
321
|
+
engine: this.engine,
|
|
322
|
+
variables: this._substitutionsVariables,
|
|
323
|
+
template: templateString,
|
|
324
|
+
context: 'some description',
|
|
325
|
+
log: this.log,
|
|
326
|
+
})
|
|
327
|
+
return result
|
|
328
|
+
} catch (error) {
|
|
329
|
+
throw new TemplateError(getErrorMessage(error))
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
**Recommendation:** Create template processor helper:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
// src/functions/template-processor.ts
|
|
337
|
+
export class TemplateProcessor {
|
|
338
|
+
constructor(
|
|
339
|
+
private readonly engine: LiquidEngine,
|
|
340
|
+
private readonly log: Logger
|
|
341
|
+
) {}
|
|
342
|
+
|
|
343
|
+
async process(
|
|
344
|
+
template: string,
|
|
345
|
+
variables: LiquidSubstitutionsVariables,
|
|
346
|
+
context: string
|
|
347
|
+
): Promise<string> {
|
|
348
|
+
try {
|
|
349
|
+
return await performSubstitutions({
|
|
350
|
+
engine: this.engine,
|
|
351
|
+
variables,
|
|
352
|
+
template,
|
|
353
|
+
context,
|
|
354
|
+
log: this.log,
|
|
355
|
+
})
|
|
356
|
+
} catch (error) {
|
|
357
|
+
throw new TemplateError(
|
|
358
|
+
`Failed to process template in ${context}: ${getErrorMessage(error)}`
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async processObject<T>(
|
|
364
|
+
obj: T,
|
|
365
|
+
variables: LiquidSubstitutionsVariables,
|
|
366
|
+
context: string
|
|
367
|
+
): Promise<T> {
|
|
368
|
+
// Recursively process all string values in object
|
|
369
|
+
// Implementation details...
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**Expected Impact:**
|
|
375
|
+
|
|
376
|
+
- ~50-100 lines eliminated
|
|
377
|
+
- More consistent error handling
|
|
378
|
+
- Easier to add template processing features
|
|
379
|
+
|
|
380
|
+
### 2.5 Map/Set Duplicate Detection Pattern
|
|
381
|
+
|
|
382
|
+
**Issue:** Similar duplicate name checking logic
|
|
383
|
+
|
|
384
|
+
**Pattern Found in 6+ Locations:**
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
protected readonly _namesSet: Set<string> = new Set<string>()
|
|
388
|
+
|
|
389
|
+
// Later in code:
|
|
390
|
+
if (this._namesSet.has(name)) {
|
|
391
|
+
throw new ConfigurationError(`duplicate name: "${name}"`)
|
|
392
|
+
}
|
|
393
|
+
this._namesSet.add(name)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**Recommendation:** Create duplicate detector utility:
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
// src/functions/duplicate-detector.ts
|
|
400
|
+
export class DuplicateDetector<T extends string | number = string> {
|
|
401
|
+
private readonly seen = new Set<T>()
|
|
402
|
+
|
|
403
|
+
constructor(private readonly entityType: string) {}
|
|
404
|
+
|
|
405
|
+
check(value: T): void {
|
|
406
|
+
if (this.seen.has(value)) {
|
|
407
|
+
throw new ConfigurationError(`Duplicate ${this.entityType}: "${value}"`)
|
|
408
|
+
}
|
|
409
|
+
this.seen.add(value)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
checkAll(values: T[]): void {
|
|
413
|
+
for (const value of values) {
|
|
414
|
+
this.check(value)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
has(value: T): boolean {
|
|
419
|
+
return this.seen.has(value)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
clear(): void {
|
|
423
|
+
this.seen.clear()
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
get size(): number {
|
|
427
|
+
return this.seen.size
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Usage:**
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
protected readonly _nameChecker = new DuplicateDetector('configuration name')
|
|
436
|
+
|
|
437
|
+
// Later:
|
|
438
|
+
this._nameChecker.check(configurationName)
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**Expected Impact:**
|
|
442
|
+
|
|
443
|
+
- Eliminates ~30-50 lines
|
|
444
|
+
- More consistent duplicate detection
|
|
445
|
+
- Better error messages
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## 3. Architectural Patterns
|
|
450
|
+
|
|
451
|
+
### 3.1 Lazy Evaluation Pattern - EXCELLENT ✅
|
|
452
|
+
|
|
453
|
+
**What's Working Well:**
|
|
454
|
+
|
|
455
|
+
The two-step lazy evaluation pattern is a standout architectural decision:
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
// Step 1: Collection initialisation (only names)
|
|
459
|
+
await buildConfigurations.initialise()
|
|
460
|
+
// -> Expands template names, doesn't process content
|
|
461
|
+
|
|
462
|
+
// Step 2: Item retrieval + initialisation (on-demand)
|
|
463
|
+
const config = buildConfigurations.get('release-x64')
|
|
464
|
+
await config.initialise()
|
|
465
|
+
// -> Now processes inheritance, properties, dependencies
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**Benefits Achieved:**
|
|
469
|
+
|
|
470
|
+
- Performance: Only used configurations are fully processed
|
|
471
|
+
- Memory: Deferred object creation until needed
|
|
472
|
+
- Flexibility: Templates expanded without evaluating content
|
|
473
|
+
|
|
474
|
+
**Recommendation:** Document this pattern prominently and consider extracting it as a reusable abstraction.
|
|
475
|
+
|
|
476
|
+
### 3.2 Inheritance Resolution Pattern
|
|
477
|
+
|
|
478
|
+
**Current Implementation:** Complex recursive inheritance resolution in `BuildConfiguration`
|
|
479
|
+
|
|
480
|
+
**Strengths:**
|
|
481
|
+
|
|
482
|
+
- Circular reference detection
|
|
483
|
+
- Proper merge semantics (later overrides earlier)
|
|
484
|
+
- Supports multiple inheritance
|
|
485
|
+
|
|
486
|
+
**Weaknesses:**
|
|
487
|
+
|
|
488
|
+
- 200+ lines embedded in BuildConfiguration class
|
|
489
|
+
- Complex nested loops and conditionals
|
|
490
|
+
- Limited reusability
|
|
491
|
+
|
|
492
|
+
**Recommendation:** Extract to separate inheritance resolver:
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
// src/classes/inheritance-resolver.ts
|
|
496
|
+
export class InheritanceResolver<T> {
|
|
497
|
+
constructor(
|
|
498
|
+
private readonly getEntity: (name: string) => T,
|
|
499
|
+
private readonly getInheritsNames: (entity: T) => string[]
|
|
500
|
+
) {}
|
|
501
|
+
|
|
502
|
+
async resolve(
|
|
503
|
+
entity: T,
|
|
504
|
+
entityName: string,
|
|
505
|
+
visited: Set<string> = new Set()
|
|
506
|
+
): Promise<{
|
|
507
|
+
properties: Record<string, unknown>
|
|
508
|
+
dependencies: Record<string, unknown>
|
|
509
|
+
devDependencies: Record<string, unknown>
|
|
510
|
+
}> {
|
|
511
|
+
// Circular reference detection
|
|
512
|
+
if (visited.has(entityName)) {
|
|
513
|
+
throw ErrorFactory.circularReference('configuration', [
|
|
514
|
+
...visited,
|
|
515
|
+
entityName,
|
|
516
|
+
])
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
visited.add(entityName)
|
|
520
|
+
|
|
521
|
+
const result = {
|
|
522
|
+
properties: {},
|
|
523
|
+
dependencies: {},
|
|
524
|
+
devDependencies: {},
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Process inheritance chain
|
|
528
|
+
const inheritsNames = this.getInheritsNames(entity)
|
|
529
|
+
for (const inheritedName of inheritsNames) {
|
|
530
|
+
const inherited = this.getEntity(inheritedName)
|
|
531
|
+
const inheritedResult = await this.resolve(
|
|
532
|
+
inherited,
|
|
533
|
+
inheritedName,
|
|
534
|
+
new Set(visited)
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
// Merge logic
|
|
538
|
+
Object.assign(result.properties, inheritedResult.properties)
|
|
539
|
+
Object.assign(result.dependencies, inheritedResult.dependencies)
|
|
540
|
+
Object.assign(result.devDependencies, inheritedResult.devDependencies)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return result
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
**Expected Impact:**
|
|
549
|
+
|
|
550
|
+
- Reusable across Actions and BuildConfigurations
|
|
551
|
+
- Easier to test inheritance logic in isolation
|
|
552
|
+
- ~150 lines extracted from BuildConfiguration
|
|
553
|
+
|
|
554
|
+
### 3.3 Template Expansion Pattern - GOOD ✅
|
|
555
|
+
|
|
556
|
+
**Observation:** The `TemplateExpander` class successfully eliminates duplication
|
|
557
|
+
|
|
558
|
+
**What's Working:**
|
|
559
|
+
|
|
560
|
+
- Generic design supports both Actions and BuildConfigurations
|
|
561
|
+
- Clean separation of matrix processing, combination generation, and instance creation
|
|
562
|
+
- Factory callback pattern provides flexibility
|
|
563
|
+
|
|
564
|
+
**Minor Improvement:**
|
|
565
|
+
|
|
566
|
+
The callback signature is somewhat complex:
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
export type InstanceFactoryCallback<TTemplate, TInstance> = (
|
|
570
|
+
expandedName: string,
|
|
571
|
+
combination: Record<string, string>,
|
|
572
|
+
templateContent: TTemplate,
|
|
573
|
+
originalTemplateName: string
|
|
574
|
+
) => TInstance
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
**Consider:** Builder pattern for factory parameters:
|
|
578
|
+
|
|
579
|
+
```typescript
|
|
580
|
+
export interface InstanceFactoryParams<TTemplate> {
|
|
581
|
+
expandedName: string
|
|
582
|
+
combination: Record<string, string>
|
|
583
|
+
templateContent: TTemplate
|
|
584
|
+
originalTemplateName: string
|
|
585
|
+
context?: unknown // For additional context if needed
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export type InstanceFactoryCallback<TTemplate, TInstance> = (
|
|
589
|
+
params: InstanceFactoryParams<TTemplate>
|
|
590
|
+
) => TInstance
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
**Benefits:**
|
|
594
|
+
|
|
595
|
+
- Easier to add optional parameters
|
|
596
|
+
- More readable at call sites
|
|
597
|
+
- Self-documenting parameter names
|
|
598
|
+
|
|
599
|
+
---
|
|
600
|
+
|
|
601
|
+
## 4. Type Safety Analysis
|
|
602
|
+
|
|
603
|
+
### 4.1 Strengths
|
|
604
|
+
|
|
605
|
+
**Excellent Use of TypeScript:**
|
|
606
|
+
|
|
607
|
+
- Comprehensive interface definitions
|
|
608
|
+
- Strong typing throughout
|
|
609
|
+
- Good use of generics (TemplateExpander, InstanceFactoryCallback)
|
|
610
|
+
- Type guards (`isString`, `isNumber`, `isJsonObject`)
|
|
611
|
+
|
|
612
|
+
### 4.2 Type Definition Organisation
|
|
613
|
+
|
|
614
|
+
**Current Structure:**
|
|
615
|
+
|
|
616
|
+
```
|
|
617
|
+
src/types/
|
|
618
|
+
├── json.ts (519 lines) # All JSON-related types
|
|
619
|
+
├── xpm.ts (162 lines) # XPM-specific types
|
|
620
|
+
└── xpm-init-template.ts # Init template types
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
**Issue:** `json.ts` is large and contains many distinct type groups
|
|
624
|
+
|
|
625
|
+
**Recommendation:** Split json.ts:
|
|
626
|
+
|
|
627
|
+
```
|
|
628
|
+
src/types/json/
|
|
629
|
+
├── index.ts # Re-exports
|
|
630
|
+
├── package.ts # JsonPackage, JsonXpmPackage (~150 lines)
|
|
631
|
+
├── actions.ts # JsonActions, JsonActionTemplate (~100 lines)
|
|
632
|
+
├── build-configurations.ts # JsonBuildConfigurations (~150 lines)
|
|
633
|
+
├── dependencies.ts # JsonDependencies (~50 lines)
|
|
634
|
+
└── common.ts # Shared types (~69 lines)
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
**Expected Impact:**
|
|
638
|
+
|
|
639
|
+
- Easier to find related types
|
|
640
|
+
- Reduced merge conflicts
|
|
641
|
+
- Clearer type ownership
|
|
642
|
+
|
|
643
|
+
### 4.3 Type Narrowing Improvements
|
|
644
|
+
|
|
645
|
+
**Current Pattern:**
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
if (isJsonObject(value)) {
|
|
649
|
+
// TypeScript doesn't automatically narrow here
|
|
650
|
+
const obj = value as Record<string, unknown>
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
**Recommendation:** Make type guards return type predicates:
|
|
655
|
+
|
|
656
|
+
```typescript
|
|
657
|
+
// Current:
|
|
658
|
+
export function isJsonObject(value: unknown): boolean {
|
|
659
|
+
return isObject(value) && value !== null && value !== undefined
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Improved:
|
|
663
|
+
export function isJsonObject(value: unknown): value is Record<string, unknown> {
|
|
664
|
+
return isObject(value) && value !== null && value !== undefined
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
**Expected Impact:**
|
|
669
|
+
|
|
670
|
+
- Eliminates many `as` type assertions
|
|
671
|
+
- Better type inference
|
|
672
|
+
- Safer code
|
|
673
|
+
|
|
674
|
+
### 4.4 Missing Type Guards
|
|
675
|
+
|
|
676
|
+
**Observation:** Some runtime checks lack corresponding type guards
|
|
677
|
+
|
|
678
|
+
**Recommendation:** Add type guards for:
|
|
679
|
+
|
|
680
|
+
```typescript
|
|
681
|
+
// src/functions/is-something.ts
|
|
682
|
+
|
|
683
|
+
export function isJsonArray(value: unknown): value is unknown[] {
|
|
684
|
+
return Array.isArray(value)
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
export function isNonEmptyString(value: unknown): value is string {
|
|
688
|
+
return isString(value) && value.length > 0
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
export function isJsonPrimitive(
|
|
692
|
+
value: unknown
|
|
693
|
+
): value is string | number | boolean | null {
|
|
694
|
+
return isPrimitive(value)
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
export function hasProperty<K extends string>(
|
|
698
|
+
obj: unknown,
|
|
699
|
+
key: K
|
|
700
|
+
): obj is Record<K, unknown> {
|
|
701
|
+
return isJsonObject(obj) && key in obj
|
|
702
|
+
}
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
**Expected Impact:**
|
|
706
|
+
|
|
707
|
+
- Stronger type safety
|
|
708
|
+
- Fewer type assertions
|
|
709
|
+
- More expressive validation code
|
|
710
|
+
|
|
711
|
+
---
|
|
712
|
+
|
|
713
|
+
## 5. Error Handling
|
|
714
|
+
|
|
715
|
+
### 5.1 Error Hierarchy - EXCELLENT ✅
|
|
716
|
+
|
|
717
|
+
**Strengths:**
|
|
718
|
+
|
|
719
|
+
- Clear, semantic error types
|
|
720
|
+
- Proper inheritance from base Error
|
|
721
|
+
- Good separation of concerns:
|
|
722
|
+
- `JsonSyntaxError` - Parsing issues
|
|
723
|
+
- `ConfigurationError` - Configuration problems
|
|
724
|
+
- `InputError` - User input issues
|
|
725
|
+
- `OutputError` - File system/output errors
|
|
726
|
+
- `TemplateError` - Template evaluation issues
|
|
727
|
+
- `PrerequisitesError` - Missing dependencies
|
|
728
|
+
|
|
729
|
+
### 5.2 Error Context
|
|
730
|
+
|
|
731
|
+
**Current Pattern:**
|
|
732
|
+
|
|
733
|
+
```typescript
|
|
734
|
+
throw new ConfigurationError(`action "${actionName}" does not exist`)
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
**Enhancement:** Add structured error context:
|
|
738
|
+
|
|
739
|
+
```typescript
|
|
740
|
+
// src/classes/errors.ts
|
|
741
|
+
export interface ErrorContext {
|
|
742
|
+
fileName?: string
|
|
743
|
+
lineNumber?: number
|
|
744
|
+
configurationName?: string
|
|
745
|
+
actionName?: string
|
|
746
|
+
propertyName?: string
|
|
747
|
+
[key: string]: unknown
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
export class ContextualConfigurationError extends ConfigurationError {
|
|
751
|
+
constructor(
|
|
752
|
+
message: string,
|
|
753
|
+
public readonly context: ErrorContext
|
|
754
|
+
) {
|
|
755
|
+
super(message)
|
|
756
|
+
this.name = 'ContextualConfigurationError'
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
toString(): string {
|
|
760
|
+
const contextStr = Object.entries(this.context)
|
|
761
|
+
.filter(([, value]) => value !== undefined)
|
|
762
|
+
.map(([key, value]) => `${key}: ${value}`)
|
|
763
|
+
.join(', ')
|
|
764
|
+
|
|
765
|
+
return `${this.message} (${contextStr})`
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
**Usage:**
|
|
771
|
+
|
|
772
|
+
```typescript
|
|
773
|
+
throw new ContextualConfigurationError(`Action does not exist`, {
|
|
774
|
+
actionName,
|
|
775
|
+
configurationName: this.buildConfigurationName,
|
|
776
|
+
availableActions: this.actions.names,
|
|
777
|
+
})
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
**Benefits:**
|
|
781
|
+
|
|
782
|
+
- Richer error information for debugging
|
|
783
|
+
- Structured error reporting
|
|
784
|
+
- Better error logging
|
|
785
|
+
|
|
786
|
+
### 5.3 Error Recovery
|
|
787
|
+
|
|
788
|
+
**Observation:** Most errors are fatal (thrown immediately)
|
|
789
|
+
|
|
790
|
+
**Consideration:** For some scenarios, accumulate errors:
|
|
791
|
+
|
|
792
|
+
```typescript
|
|
793
|
+
// src/functions/validation.ts
|
|
794
|
+
export class ValidationResult<T> {
|
|
795
|
+
private _errors: Error[] = []
|
|
796
|
+
|
|
797
|
+
constructor(private readonly value: T | null = null) {}
|
|
798
|
+
|
|
799
|
+
addError(error: Error): void {
|
|
800
|
+
this._errors.push(error)
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
get isValid(): boolean {
|
|
804
|
+
return this._errors.length === 0
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
get errors(): readonly Error[] {
|
|
808
|
+
return this._errors
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
getValue(): T {
|
|
812
|
+
if (!this.isValid) {
|
|
813
|
+
throw new Error(`Cannot get value from invalid result`)
|
|
814
|
+
}
|
|
815
|
+
return this.value!
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
**Use Case:** Validate entire configuration before failing:
|
|
821
|
+
|
|
822
|
+
```typescript
|
|
823
|
+
// Instead of throwing on first error, collect all:
|
|
824
|
+
const result = new ValidationResult<BuildConfiguration>(config)
|
|
825
|
+
|
|
826
|
+
if (!config.properties) {
|
|
827
|
+
result.addError(new ConfigurationError('Missing properties'))
|
|
828
|
+
}
|
|
829
|
+
if (!config.dependencies) {
|
|
830
|
+
result.addError(new ConfigurationError('Missing dependencies'))
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (!result.isValid) {
|
|
834
|
+
throw new ConfigurationError(
|
|
835
|
+
`Configuration validation failed:\n${result.errors
|
|
836
|
+
.map((e) => ` - ${e.message}`)
|
|
837
|
+
.join('\n')}`
|
|
838
|
+
)
|
|
839
|
+
}
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
**Benefits:**
|
|
843
|
+
|
|
844
|
+
- Better user experience (show all errors at once)
|
|
845
|
+
- Reduced iteration cycles during configuration
|
|
846
|
+
- Optional (use for validation, keep throwing for logic errors)
|
|
847
|
+
|
|
848
|
+
---
|
|
849
|
+
|
|
850
|
+
## 6. Code Complexity
|
|
851
|
+
|
|
852
|
+
### 6.1 Cyclomatic Complexity
|
|
853
|
+
|
|
854
|
+
**High-Complexity Methods Identified:**
|
|
855
|
+
|
|
856
|
+
**BuildConfiguration.initialise()** (~150 lines, complexity ~15)
|
|
857
|
+
|
|
858
|
+
- Handles inheritance resolution
|
|
859
|
+
- Property merging
|
|
860
|
+
- Dependency substitution
|
|
861
|
+
- Actions initialisation
|
|
862
|
+
- Build folder computation
|
|
863
|
+
|
|
864
|
+
**Recommendation:** Extract helper methods:
|
|
865
|
+
|
|
866
|
+
```typescript
|
|
867
|
+
async initialise(): Promise<boolean> {
|
|
868
|
+
if (this._isInitialised) {
|
|
869
|
+
return true
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
await this._processTemplateSubstitution()
|
|
873
|
+
await this._resolveInheritance()
|
|
874
|
+
await this._mergeProperties()
|
|
875
|
+
await this._computeBuildFolder()
|
|
876
|
+
await this._initialiseActions()
|
|
877
|
+
|
|
878
|
+
this._isInitialised = true
|
|
879
|
+
return true
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
private async _processTemplateSubstitution(): Promise<void> {
|
|
883
|
+
if (this.isTemplate) {
|
|
884
|
+
await this._substituteTemplate()
|
|
885
|
+
} else {
|
|
886
|
+
await this._substituteInherits()
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
private async _resolveInheritance(): Promise<void> {
|
|
891
|
+
// Extract from current initialise()
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
private async _mergeProperties(): Promise<void> {
|
|
895
|
+
// Extract property merging logic
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
private async _computeBuildFolder(): Promise<void> {
|
|
899
|
+
// Extract build folder computation
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
private async _initialiseActions(): Promise<void> {
|
|
903
|
+
// Extract actions initialisation
|
|
904
|
+
}
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
**Expected Impact:**
|
|
908
|
+
|
|
909
|
+
- Reduced complexity per method
|
|
910
|
+
- Easier to test individual steps
|
|
911
|
+
- Clearer control flow
|
|
912
|
+
|
|
913
|
+
### 6.2 Nesting Depth
|
|
914
|
+
|
|
915
|
+
**Issue:** Some methods have deep nesting (4-5 levels)
|
|
916
|
+
|
|
917
|
+
**Example Pattern:**
|
|
918
|
+
|
|
919
|
+
```typescript
|
|
920
|
+
async processMatrix(): Promise<void> {
|
|
921
|
+
if (matrix) {
|
|
922
|
+
for (const key in matrix) {
|
|
923
|
+
if (matrix.hasOwnProperty(key)) {
|
|
924
|
+
const values = matrix[key]
|
|
925
|
+
if (Array.isArray(values)) {
|
|
926
|
+
for (const value of values) {
|
|
927
|
+
// Processing logic
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
**Recommendation:** Early returns and extraction:
|
|
937
|
+
|
|
938
|
+
```typescript
|
|
939
|
+
async processMatrix(): Promise<void> {
|
|
940
|
+
if (!matrix) {
|
|
941
|
+
return
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
for (const [key, values] of Object.entries(matrix)) {
|
|
945
|
+
await this._processMatrixEntry(key, values)
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
private async _processMatrixEntry(
|
|
950
|
+
key: string,
|
|
951
|
+
values: unknown
|
|
952
|
+
): Promise<void> {
|
|
953
|
+
if (!Array.isArray(values)) {
|
|
954
|
+
throw new ConfigurationError(
|
|
955
|
+
`Matrix value for "${key}" must be an array`
|
|
956
|
+
)
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
for (const value of values) {
|
|
960
|
+
await this._processMatrixValue(key, value)
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
private async _processMatrixValue(
|
|
965
|
+
key: string,
|
|
966
|
+
value: unknown
|
|
967
|
+
): Promise<void> {
|
|
968
|
+
// Processing logic
|
|
969
|
+
}
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
**Benefits:**
|
|
973
|
+
|
|
974
|
+
- Reduced nesting depth
|
|
975
|
+
- More testable units
|
|
976
|
+
- Clearer error handling
|
|
977
|
+
|
|
978
|
+
### 6.3 Long Parameter Lists
|
|
979
|
+
|
|
980
|
+
**Issue:** Some functions have 5+ parameters
|
|
981
|
+
|
|
982
|
+
**Example:**
|
|
983
|
+
|
|
984
|
+
```typescript
|
|
985
|
+
async expandTemplate({
|
|
986
|
+
templateName,
|
|
987
|
+
matrix,
|
|
988
|
+
templateContent,
|
|
989
|
+
templateType,
|
|
990
|
+
instanceFactory,
|
|
991
|
+
}: {
|
|
992
|
+
templateName: string
|
|
993
|
+
matrix: JsonTemplateMatrix
|
|
994
|
+
templateContent: TTemplate
|
|
995
|
+
templateType: string
|
|
996
|
+
instanceFactory: InstanceFactoryCallback<TTemplate, TInstance>
|
|
997
|
+
}): Promise<Map<string, TInstance>>
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
**Observation:** Already using parameter object pattern - GOOD ✅
|
|
1001
|
+
|
|
1002
|
+
**Recommendation Elsewhere:** For complex parameters, consider configuration objects:
|
|
1003
|
+
|
|
1004
|
+
```typescript
|
|
1005
|
+
// Instead of:
|
|
1006
|
+
function processConfiguration(
|
|
1007
|
+
name: string,
|
|
1008
|
+
engine: LiquidEngine,
|
|
1009
|
+
variables: Variables,
|
|
1010
|
+
log: Logger,
|
|
1011
|
+
options?: { strict?: boolean; cache?: boolean }
|
|
1012
|
+
): Result
|
|
1013
|
+
|
|
1014
|
+
// Use:
|
|
1015
|
+
interface ProcessConfigurationParams {
|
|
1016
|
+
name: string
|
|
1017
|
+
engine: LiquidEngine
|
|
1018
|
+
variables: Variables
|
|
1019
|
+
log: Logger
|
|
1020
|
+
options?: ConfigurationProcessingOptions
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function processConfiguration(params: ProcessConfigurationParams): Result
|
|
1024
|
+
```
|
|
1025
|
+
|
|
1026
|
+
**Already following this pattern in most places - maintain consistency.**
|
|
1027
|
+
|
|
1028
|
+
---
|
|
1029
|
+
|
|
1030
|
+
## 7. Naming Conventions
|
|
1031
|
+
|
|
1032
|
+
### 7.1 Inconsistencies
|
|
1033
|
+
|
|
1034
|
+
**Mixed Naming Styles:**
|
|
1035
|
+
|
|
1036
|
+
```typescript
|
|
1037
|
+
// Some use full names:
|
|
1038
|
+
buildFolderRelativePath
|
|
1039
|
+
|
|
1040
|
+
// Others use abbreviations:
|
|
1041
|
+
jsonBuildConfig // vs jsonBuildConfiguration
|
|
1042
|
+
|
|
1043
|
+
// Inconsistent prefixes:
|
|
1044
|
+
_isInitialised // protected with _
|
|
1045
|
+
isHidden // public without _
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
**Recommendation:** Standardise naming:
|
|
1049
|
+
|
|
1050
|
+
1. **Protected/Private Members:** Always prefix with `_`
|
|
1051
|
+
|
|
1052
|
+
```typescript
|
|
1053
|
+
protected _isInitialised = false
|
|
1054
|
+
protected _actions: Actions | undefined
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
2. **Public Properties:** No prefix
|
|
1058
|
+
|
|
1059
|
+
```typescript
|
|
1060
|
+
readonly buildConfigurationName: string
|
|
1061
|
+
readonly isHidden: boolean
|
|
1062
|
+
```
|
|
1063
|
+
|
|
1064
|
+
3. **Avoid Abbreviations:** Use full words
|
|
1065
|
+
|
|
1066
|
+
```typescript
|
|
1067
|
+
// Instead of:
|
|
1068
|
+
jsonBuildConfig
|
|
1069
|
+
|
|
1070
|
+
// Use:
|
|
1071
|
+
jsonBuildConfiguration
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
4. **Boolean Names:** Use consistent patterns
|
|
1075
|
+
|
|
1076
|
+
```typescript
|
|
1077
|
+
// Good patterns:
|
|
1078
|
+
;(isHidden, isInitialised, hasActions)
|
|
1079
|
+
|
|
1080
|
+
// Avoid:
|
|
1081
|
+
;(hidden, initialised)
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
### 7.2 Magic Strings
|
|
1085
|
+
|
|
1086
|
+
**Issue:** Some string literals used multiple times
|
|
1087
|
+
|
|
1088
|
+
**Example:**
|
|
1089
|
+
|
|
1090
|
+
```typescript
|
|
1091
|
+
// Found in multiple places:
|
|
1092
|
+
'buildFolderRelativePath'
|
|
1093
|
+
'inherits'
|
|
1094
|
+
'inherit' // deprecated
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1097
|
+
**Recommendation:** Export as constants:
|
|
1098
|
+
|
|
1099
|
+
```typescript
|
|
1100
|
+
// src/classes/constants.ts
|
|
1101
|
+
export const PropertyNames = {
|
|
1102
|
+
BUILD_FOLDER_RELATIVE_PATH: 'buildFolderRelativePath',
|
|
1103
|
+
INHERITS: 'inherits',
|
|
1104
|
+
INHERIT_DEPRECATED: 'inherit',
|
|
1105
|
+
} as const
|
|
1106
|
+
|
|
1107
|
+
export const NamespaceNames = {
|
|
1108
|
+
MATRIX: 'matrix',
|
|
1109
|
+
PROPERTIES: 'properties',
|
|
1110
|
+
CONFIGURATION: 'configuration',
|
|
1111
|
+
ENV: 'env',
|
|
1112
|
+
OS: 'os',
|
|
1113
|
+
} as const
|
|
1114
|
+
```
|
|
1115
|
+
|
|
1116
|
+
**Usage:**
|
|
1117
|
+
|
|
1118
|
+
```typescript
|
|
1119
|
+
import { PropertyNames } from './constants.js'
|
|
1120
|
+
|
|
1121
|
+
const path = properties[PropertyNames.BUILD_FOLDER_RELATIVE_PATH]
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
**Benefits:**
|
|
1125
|
+
|
|
1126
|
+
- Centralized string management
|
|
1127
|
+
- Easier refactoring
|
|
1128
|
+
- Reduced typos
|
|
1129
|
+
- Better IDE autocomplete
|
|
1130
|
+
|
|
1131
|
+
### 7.3 Typo Found
|
|
1132
|
+
|
|
1133
|
+
**Location:** `build-configurations.ts:280`
|
|
1134
|
+
|
|
1135
|
+
```typescript
|
|
1136
|
+
protected readonly _buildComfigurationsNamesSet: Set<string> =
|
|
1137
|
+
new Set<string>()
|
|
1138
|
+
```
|
|
1139
|
+
|
|
1140
|
+
**Issue:** `Comfigurations` should be `Configurations`
|
|
1141
|
+
|
|
1142
|
+
**Recommendation:** Rename to `_buildConfigurationsNamesSet`
|
|
1143
|
+
|
|
1144
|
+
---
|
|
1145
|
+
|
|
1146
|
+
## 8. Performance Considerations
|
|
1147
|
+
|
|
1148
|
+
### 8.1 Strengths
|
|
1149
|
+
|
|
1150
|
+
**Excellent Lazy Loading:**
|
|
1151
|
+
|
|
1152
|
+
- ✅ Template name expansion without content evaluation
|
|
1153
|
+
- ✅ Configuration instances created on demand
|
|
1154
|
+
- ✅ Actions initialised only when retrieved
|
|
1155
|
+
|
|
1156
|
+
**Effective Caching:**
|
|
1157
|
+
|
|
1158
|
+
- ✅ Cached configuration names array
|
|
1159
|
+
- ✅ Map-based lookups (O(1))
|
|
1160
|
+
- ✅ Sealed substitution variables
|
|
1161
|
+
|
|
1162
|
+
### 8.2 Potential Improvements
|
|
1163
|
+
|
|
1164
|
+
**String Operations:**
|
|
1165
|
+
|
|
1166
|
+
Issue: Multiple string operations in hot paths:
|
|
1167
|
+
|
|
1168
|
+
```typescript
|
|
1169
|
+
// Current:
|
|
1170
|
+
const key = `${platform}-${arch}` // String concatenation
|
|
1171
|
+
if (name.includes('{{')) { ... } // Pattern checking
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
**Recommendation:** Pre-compute or cache where appropriate:
|
|
1175
|
+
|
|
1176
|
+
```typescript
|
|
1177
|
+
// Cache regex patterns:
|
|
1178
|
+
private static readonly LIQUID_PATTERN = /\{\{|\{%/
|
|
1179
|
+
|
|
1180
|
+
hasLiquidSyntax(value: string): boolean {
|
|
1181
|
+
return TemplateExpander.LIQUID_PATTERN.test(value)
|
|
1182
|
+
}
|
|
1183
|
+
```
|
|
1184
|
+
|
|
1185
|
+
**Object Cloning:**
|
|
1186
|
+
|
|
1187
|
+
Current pattern for variable spreading:
|
|
1188
|
+
|
|
1189
|
+
```typescript
|
|
1190
|
+
this._substitutionsVariables = {
|
|
1191
|
+
...this.parentBuildConfigurations.substitutionsVariables,
|
|
1192
|
+
}
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
**Observation:** Shallow spread is appropriate for this use case (variables are sealed and not deeply modified). ✅
|
|
1196
|
+
|
|
1197
|
+
**If deep cloning becomes needed, consider:**
|
|
1198
|
+
|
|
1199
|
+
```typescript
|
|
1200
|
+
import { cloneDeep } from 'lodash-es' // Or custom implementation
|
|
1201
|
+
```
|
|
1202
|
+
|
|
1203
|
+
**Map vs Object for Dynamic Collections:**
|
|
1204
|
+
|
|
1205
|
+
**Current:** Using Maps for runtime collections - GOOD ✅
|
|
1206
|
+
|
|
1207
|
+
```typescript
|
|
1208
|
+
protected readonly _buildConfigurationsMap: Map<string, BuildConfiguration>
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
**Benefit:** O(1) lookup, better for dynamic keys, proper iteration
|
|
1212
|
+
|
|
1213
|
+
**Alternatively considered:** Plain objects are slower for frequent additions/deletions. Current approach is correct.
|
|
1214
|
+
|
|
1215
|
+
### 8.3 Memory Usage
|
|
1216
|
+
|
|
1217
|
+
**Large File Reading:**
|
|
1218
|
+
|
|
1219
|
+
In `Package.readPackageDotJson()`:
|
|
1220
|
+
|
|
1221
|
+
```typescript
|
|
1222
|
+
const jsonContent = await fs.readFile(packageDotJsonPath, 'utf-8')
|
|
1223
|
+
const jsonPackage = JSON.parse(jsonContent)
|
|
1224
|
+
```
|
|
1225
|
+
|
|
1226
|
+
**Observation:** Appropriate for package.json files (typically <100KB). ✅
|
|
1227
|
+
|
|
1228
|
+
**If handling larger files becomes necessary, consider streaming.**
|
|
1229
|
+
|
|
1230
|
+
**Template String Storage:**
|
|
1231
|
+
|
|
1232
|
+
Multiple storage of template strings:
|
|
1233
|
+
|
|
1234
|
+
```typescript
|
|
1235
|
+
readonly templateBuildConfigurationName?: string
|
|
1236
|
+
readonly buildConfigurationName: string
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
**Observation:** Necessary for tracing and debugging. Memory cost is minimal. ✅
|
|
1240
|
+
|
|
1241
|
+
---
|
|
1242
|
+
|
|
1243
|
+
## 9. Testing Considerations
|
|
1244
|
+
|
|
1245
|
+
### 9.1 Testability
|
|
1246
|
+
|
|
1247
|
+
**Strengths:**
|
|
1248
|
+
|
|
1249
|
+
- Dependency injection throughout (Logger, LiquidEngine)
|
|
1250
|
+
- Interfaces for constructor parameters
|
|
1251
|
+
- Clear separation of concerns
|
|
1252
|
+
|
|
1253
|
+
**Areas for Improvement:**
|
|
1254
|
+
|
|
1255
|
+
**Circular Dependencies:**
|
|
1256
|
+
|
|
1257
|
+
Some classes have bidirectional references:
|
|
1258
|
+
|
|
1259
|
+
```typescript
|
|
1260
|
+
// BuildConfiguration has reference to parent collection
|
|
1261
|
+
readonly parentBuildConfigurations: BuildConfigurations
|
|
1262
|
+
|
|
1263
|
+
// Which creates the configuration
|
|
1264
|
+
const config = new BuildConfiguration({ ..., parentBuildConfigurations: this })
|
|
1265
|
+
```
|
|
1266
|
+
|
|
1267
|
+
**Recommendation:** Consider breaking dependency:
|
|
1268
|
+
|
|
1269
|
+
```typescript
|
|
1270
|
+
// Option 1: Pass only what's needed
|
|
1271
|
+
interface BuildConfigurationContext {
|
|
1272
|
+
engine: LiquidEngine
|
|
1273
|
+
variables: LiquidSubstitutionsVariables
|
|
1274
|
+
log: Logger
|
|
1275
|
+
getConfiguration: (name: string) => BuildConfiguration
|
|
1276
|
+
getJsonConfiguration: (name: string) => JsonBuildConfiguration
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// Option 2: Use builder pattern for testing
|
|
1280
|
+
class BuildConfigurationTestBuilder {
|
|
1281
|
+
private params: Partial<BuildConfigurationConstructorParameters> = {}
|
|
1282
|
+
|
|
1283
|
+
withMockParent(): this {
|
|
1284
|
+
this.params.parentBuildConfigurations = createMockCollection()
|
|
1285
|
+
return this
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
build(): BuildConfiguration {
|
|
1289
|
+
return new BuildConfiguration(this.params as any)
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
```
|
|
1293
|
+
|
|
1294
|
+
**Expected Impact:**
|
|
1295
|
+
|
|
1296
|
+
- Easier unit testing without full object graph
|
|
1297
|
+
- Faster test execution
|
|
1298
|
+
- Better test isolation
|
|
1299
|
+
|
|
1300
|
+
### 9.2 Test Helpers Needed
|
|
1301
|
+
|
|
1302
|
+
**Current Observation:** Main codebase lacks test factory utilities
|
|
1303
|
+
|
|
1304
|
+
**Recommendation:** Create test helper module:
|
|
1305
|
+
|
|
1306
|
+
```typescript
|
|
1307
|
+
// src/testing/factories.ts (or tests/helpers/)
|
|
1308
|
+
export class TestFactories {
|
|
1309
|
+
static createMockLogger(level: string = 'silent'): Logger {
|
|
1310
|
+
return new Logger({ level })
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
static createMockEngine(): LiquidEngine {
|
|
1314
|
+
return new LiquidEngine()
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
static createMinimalSubstitutionVariables(): LiquidSubstitutionsVariables {
|
|
1318
|
+
return {
|
|
1319
|
+
env: process.env,
|
|
1320
|
+
os: { platform: 'linux', arch: 'x64' },
|
|
1321
|
+
path: { sep: '/', delimiter: ':' },
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
static createTestBuildConfiguration(
|
|
1326
|
+
overrides?: Partial<BuildConfigurationConstructorParameters>
|
|
1327
|
+
): BuildConfiguration {
|
|
1328
|
+
return new BuildConfiguration({
|
|
1329
|
+
buildConfigurationName: 'test-config',
|
|
1330
|
+
jsonBuildConfiguration: { hidden: false },
|
|
1331
|
+
parentBuildConfigurations: this.createMockBuildConfigurations(),
|
|
1332
|
+
...overrides,
|
|
1333
|
+
})
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
```
|
|
1337
|
+
|
|
1338
|
+
**Benefits:**
|
|
1339
|
+
|
|
1340
|
+
- Consistent test setup
|
|
1341
|
+
- Reduced test boilerplate
|
|
1342
|
+
- Easier to maintain tests
|
|
1343
|
+
|
|
1344
|
+
---
|
|
1345
|
+
|
|
1346
|
+
## 10. Documentation Quality
|
|
1347
|
+
|
|
1348
|
+
**Note:** Per request, skipping detailed documentation review.
|
|
1349
|
+
|
|
1350
|
+
**High-Level Observation:**
|
|
1351
|
+
|
|
1352
|
+
- ✅ Comprehensive TSDoc comments throughout
|
|
1353
|
+
- ✅ Clear explanations of complex patterns
|
|
1354
|
+
- ✅ Good use of `@remarks` sections
|
|
1355
|
+
- ✅ Proper parameter and return type documentation
|
|
1356
|
+
|
|
1357
|
+
**One Suggestion:** Consider adding architectural documentation:
|
|
1358
|
+
|
|
1359
|
+
```typescript
|
|
1360
|
+
// src/README.md or src/ARCHITECTURE.md
|
|
1361
|
+
/**
|
|
1362
|
+
* Architecture Overview
|
|
1363
|
+
*
|
|
1364
|
+
* ## Lazy Evaluation Pattern
|
|
1365
|
+
*
|
|
1366
|
+
* The library uses a two-phase lazy evaluation pattern:
|
|
1367
|
+
*
|
|
1368
|
+
* 1. Collection Initialisation: Template names expanded without content
|
|
1369
|
+
* 2. Item Retrieval: Full initialisation only when accessed
|
|
1370
|
+
*
|
|
1371
|
+
* ## Inheritance Resolution
|
|
1372
|
+
*
|
|
1373
|
+
* Build configurations support multiple inheritance with:
|
|
1374
|
+
* - Circular reference detection
|
|
1375
|
+
* - Property merging (last wins)
|
|
1376
|
+
* - Action inheritance and overriding
|
|
1377
|
+
*/
|
|
1378
|
+
```
|
|
1379
|
+
|
|
1380
|
+
---
|
|
1381
|
+
|
|
1382
|
+
## 11. Dependencies and Imports
|
|
1383
|
+
|
|
1384
|
+
### 11.1 Import Structure
|
|
1385
|
+
|
|
1386
|
+
**Current Pattern:**
|
|
1387
|
+
|
|
1388
|
+
```typescript
|
|
1389
|
+
import assert from 'node:assert'
|
|
1390
|
+
import * as path from 'node:path'
|
|
1391
|
+
import * as os from 'node:os'
|
|
1392
|
+
|
|
1393
|
+
import { Logger } from '@xpack/logger'
|
|
1394
|
+
|
|
1395
|
+
import { LiquidEngine } from './liquid-engine.js'
|
|
1396
|
+
import { ConfigurationError } from './errors.js'
|
|
1397
|
+
```
|
|
1398
|
+
|
|
1399
|
+
**Observation:** Good grouping and separation. ✅
|
|
1400
|
+
|
|
1401
|
+
**Recommendation:** Document import ordering convention:
|
|
1402
|
+
|
|
1403
|
+
```typescript
|
|
1404
|
+
// Convention (add to .eslintrc or documentation):
|
|
1405
|
+
// 1. Node.js built-ins
|
|
1406
|
+
// 2. External dependencies
|
|
1407
|
+
// 3. Internal absolute imports
|
|
1408
|
+
// 4. Internal relative imports
|
|
1409
|
+
```
|
|
1410
|
+
|
|
1411
|
+
### 11.2 Circular Import Risk
|
|
1412
|
+
|
|
1413
|
+
**Observation:** Some files have mutual dependencies:
|
|
1414
|
+
|
|
1415
|
+
```
|
|
1416
|
+
actions.ts → build-configurations.ts
|
|
1417
|
+
build-configurations.ts → actions.ts
|
|
1418
|
+
```
|
|
1419
|
+
|
|
1420
|
+
**Current Mitigation:** Type-only imports where possible
|
|
1421
|
+
|
|
1422
|
+
**Recommendation:** Monitor for circular runtime dependencies. Consider:
|
|
1423
|
+
|
|
1424
|
+
```typescript
|
|
1425
|
+
// Use type-only imports when possible
|
|
1426
|
+
import type { BuildConfiguration } from './build-configurations.js'
|
|
1427
|
+
|
|
1428
|
+
// Or extract shared types to separate file
|
|
1429
|
+
import type { BuildConfigurationInterface } from './types.js'
|
|
1430
|
+
```
|
|
1431
|
+
|
|
1432
|
+
---
|
|
1433
|
+
|
|
1434
|
+
## 12. Function Organization
|
|
1435
|
+
|
|
1436
|
+
### 12.1 Utility Functions
|
|
1437
|
+
|
|
1438
|
+
**Current Structure:**
|
|
1439
|
+
|
|
1440
|
+
```
|
|
1441
|
+
src/functions/
|
|
1442
|
+
├── chmod-recursively.ts
|
|
1443
|
+
├── filter-paths.ts
|
|
1444
|
+
├── is-something.ts
|
|
1445
|
+
├── matrix-expander.ts
|
|
1446
|
+
├── perform-substitutions.ts
|
|
1447
|
+
└── utils.ts
|
|
1448
|
+
```
|
|
1449
|
+
|
|
1450
|
+
**Strengths:**
|
|
1451
|
+
|
|
1452
|
+
- ✅ Good separation by concern
|
|
1453
|
+
- ✅ Pure functions when possible
|
|
1454
|
+
- ✅ Clear naming
|
|
1455
|
+
|
|
1456
|
+
**Recommendation:** Split `utils.ts` into more specific files:
|
|
1457
|
+
|
|
1458
|
+
```
|
|
1459
|
+
src/functions/
|
|
1460
|
+
├── error-handling.ts # getErrorMessage
|
|
1461
|
+
├── platform-detection.ts # getPlatformKey
|
|
1462
|
+
├── template-detection.ts # hasLiquidSyntax
|
|
1463
|
+
└── string-utils.ts # Any string manipulation (if needed)
|
|
1464
|
+
```
|
|
1465
|
+
|
|
1466
|
+
**Expected Impact:**
|
|
1467
|
+
|
|
1468
|
+
- Clearer module boundaries
|
|
1469
|
+
- Easier to find related functions
|
|
1470
|
+
- Better tree-shaking potential
|
|
1471
|
+
|
|
1472
|
+
### 12.2 Missing Utility Functions
|
|
1473
|
+
|
|
1474
|
+
**Identified Pattern:** Need for path manipulation helpers
|
|
1475
|
+
|
|
1476
|
+
**Recommendation:** Add path utilities:
|
|
1477
|
+
|
|
1478
|
+
```typescript
|
|
1479
|
+
// src/functions/path-utils.ts
|
|
1480
|
+
export function normalizePath(p: string): string {
|
|
1481
|
+
return p.replace(/\\/g, '/')
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
export function sanitizePathComponent(component: string): string {
|
|
1485
|
+
return component.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
export function joinPaths(...parts: string[]): string {
|
|
1489
|
+
return path.join(...parts).replace(/\\/g, '/')
|
|
1490
|
+
}
|
|
1491
|
+
```
|
|
1492
|
+
|
|
1493
|
+
**Use Cases:**
|
|
1494
|
+
|
|
1495
|
+
- Build folder path generation
|
|
1496
|
+
- Cross-platform path handling
|
|
1497
|
+
- Template path substitution
|
|
1498
|
+
|
|
1499
|
+
---
|
|
1500
|
+
|
|
1501
|
+
## 13. Specific Code Improvements
|
|
1502
|
+
|
|
1503
|
+
### 13.1 build-configurations.ts
|
|
1504
|
+
|
|
1505
|
+
**Line ~280 - Typo:**
|
|
1506
|
+
|
|
1507
|
+
```typescript
|
|
1508
|
+
// Current:
|
|
1509
|
+
protected readonly _buildComfigurationsNamesSet: Set<string>
|
|
1510
|
+
|
|
1511
|
+
// Fix:
|
|
1512
|
+
protected readonly _buildConfigurationsNamesSet: Set<string>
|
|
1513
|
+
```
|
|
1514
|
+
|
|
1515
|
+
**Lines 404-650 - initialise() method:**
|
|
1516
|
+
|
|
1517
|
+
Extract into smaller methods as shown in section 6.1.
|
|
1518
|
+
|
|
1519
|
+
**Lines 1520-1850 - BuildConfiguration.initialise():**
|
|
1520
|
+
|
|
1521
|
+
Extract inheritance resolution into separate class as shown in section 3.2.
|
|
1522
|
+
|
|
1523
|
+
### 13.2 actions.ts
|
|
1524
|
+
|
|
1525
|
+
**Similar Structure to build-configurations.ts:**
|
|
1526
|
+
|
|
1527
|
+
Apply same refactoring recommendations:
|
|
1528
|
+
|
|
1529
|
+
- Split into separate files
|
|
1530
|
+
- Extract common initialization pattern
|
|
1531
|
+
- Create action-specific inheritance resolver
|
|
1532
|
+
|
|
1533
|
+
### 13.3 init-template-base.ts
|
|
1534
|
+
|
|
1535
|
+
**Lines 400-600 - Property validation:**
|
|
1536
|
+
|
|
1537
|
+
Extract validation logic:
|
|
1538
|
+
|
|
1539
|
+
```typescript
|
|
1540
|
+
// src/classes/init-template/property-validator.ts
|
|
1541
|
+
export class PropertyValidator {
|
|
1542
|
+
validate(
|
|
1543
|
+
properties: Record<string, unknown>,
|
|
1544
|
+
definitions: InitTemplatePropertiesDefinitions
|
|
1545
|
+
): ValidationResult {
|
|
1546
|
+
// Extract current validation logic
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
private validateRequired(
|
|
1550
|
+
value: unknown,
|
|
1551
|
+
key: string,
|
|
1552
|
+
definition: PropertyDefinition
|
|
1553
|
+
): Error | null {
|
|
1554
|
+
// Extract validation logic
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
private validateType(
|
|
1558
|
+
value: unknown,
|
|
1559
|
+
key: string,
|
|
1560
|
+
definition: PropertyDefinition
|
|
1561
|
+
): Error | null {
|
|
1562
|
+
// Extract type validation
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
```
|
|
1566
|
+
|
|
1567
|
+
### 13.4 combinations-generator.ts
|
|
1568
|
+
|
|
1569
|
+
**Line 247 - Single use of reduce:**
|
|
1570
|
+
|
|
1571
|
+
Current code is clear and appropriate. ✅
|
|
1572
|
+
|
|
1573
|
+
Consider adding comment for clarity:
|
|
1574
|
+
|
|
1575
|
+
```typescript
|
|
1576
|
+
// Calculate total combinations as product of all matrix value counts
|
|
1577
|
+
const totalCombinations = this._matrixValues.reduce(
|
|
1578
|
+
(product, values) => product * values.length,
|
|
1579
|
+
1
|
|
1580
|
+
)
|
|
1581
|
+
```
|
|
1582
|
+
|
|
1583
|
+
---
|
|
1584
|
+
|
|
1585
|
+
## 14. Modern JavaScript/TypeScript Features
|
|
1586
|
+
|
|
1587
|
+
### 14.1 Optional Chaining
|
|
1588
|
+
|
|
1589
|
+
**Opportunity:** Use optional chaining more extensively
|
|
1590
|
+
|
|
1591
|
+
**Current Pattern:**
|
|
1592
|
+
|
|
1593
|
+
```typescript
|
|
1594
|
+
if (config.actions && config.actions.names) {
|
|
1595
|
+
// ...
|
|
1596
|
+
}
|
|
1597
|
+
```
|
|
1598
|
+
|
|
1599
|
+
**Modern Approach:**
|
|
1600
|
+
|
|
1601
|
+
```typescript
|
|
1602
|
+
if (config.actions?.names) {
|
|
1603
|
+
// ...
|
|
1604
|
+
}
|
|
1605
|
+
```
|
|
1606
|
+
|
|
1607
|
+
**Observation:** Some files already use this, ensure consistency.
|
|
1608
|
+
|
|
1609
|
+
### 14.2 Nullish Coalescing
|
|
1610
|
+
|
|
1611
|
+
**Current Pattern:**
|
|
1612
|
+
|
|
1613
|
+
```typescript
|
|
1614
|
+
const value = someValue !== undefined ? someValue : defaultValue
|
|
1615
|
+
```
|
|
1616
|
+
|
|
1617
|
+
**Modern Approach:**
|
|
1618
|
+
|
|
1619
|
+
```typescript
|
|
1620
|
+
const value = someValue ?? defaultValue
|
|
1621
|
+
```
|
|
1622
|
+
|
|
1623
|
+
**Already used in some places:**
|
|
1624
|
+
|
|
1625
|
+
```typescript
|
|
1626
|
+
this.isHidden = this.jsonBuildConfiguration.hidden ?? false
|
|
1627
|
+
```
|
|
1628
|
+
|
|
1629
|
+
**Recommendation:** Use consistently throughout. ✅
|
|
1630
|
+
|
|
1631
|
+
### 14.3 Private Fields
|
|
1632
|
+
|
|
1633
|
+
**Current Pattern:**
|
|
1634
|
+
|
|
1635
|
+
```typescript
|
|
1636
|
+
protected _isInitialised = false
|
|
1637
|
+
```
|
|
1638
|
+
|
|
1639
|
+
**Modern Alternative:**
|
|
1640
|
+
|
|
1641
|
+
```typescript
|
|
1642
|
+
#isInitialised = false // True private field
|
|
1643
|
+
```
|
|
1644
|
+
|
|
1645
|
+
**Consideration:**
|
|
1646
|
+
|
|
1647
|
+
- `#` fields are truly private (not visible in subclasses)
|
|
1648
|
+
- `protected` is still needed for inheritance
|
|
1649
|
+
- Current pattern is appropriate for this codebase ✅
|
|
1650
|
+
|
|
1651
|
+
### 14.4 Top-Level Await
|
|
1652
|
+
|
|
1653
|
+
**Not applicable** - Library code should not use top-level await. ✅
|
|
1654
|
+
|
|
1655
|
+
### 14.5 Async Iterators
|
|
1656
|
+
|
|
1657
|
+
**Potential Use Case:** Template expansion over large matrices
|
|
1658
|
+
|
|
1659
|
+
**Current:**
|
|
1660
|
+
|
|
1661
|
+
```typescript
|
|
1662
|
+
for (const combination of combinationsGenerator) {
|
|
1663
|
+
await expandName(combination)
|
|
1664
|
+
}
|
|
1665
|
+
```
|
|
1666
|
+
|
|
1667
|
+
**Consider:**
|
|
1668
|
+
|
|
1669
|
+
```typescript
|
|
1670
|
+
for await (const combination of asyncCombinationsGenerator) {
|
|
1671
|
+
// Process async expansion
|
|
1672
|
+
}
|
|
1673
|
+
```
|
|
1674
|
+
|
|
1675
|
+
**Assessment:** Current sync iteration is fine for typical matrix sizes. Only consider if performance issues arise.
|
|
1676
|
+
|
|
1677
|
+
---
|
|
1678
|
+
|
|
1679
|
+
## 15. Security Considerations
|
|
1680
|
+
|
|
1681
|
+
### 15.1 Template Injection
|
|
1682
|
+
|
|
1683
|
+
**Risk:** User-provided templates could contain malicious Liquid code
|
|
1684
|
+
|
|
1685
|
+
**Current Mitigation:**
|
|
1686
|
+
|
|
1687
|
+
- Templates parsed from package.json (user's own code)
|
|
1688
|
+
- Liquid engine sandboxed (no filesystem access by default)
|
|
1689
|
+
|
|
1690
|
+
**Additional Consideration:**
|
|
1691
|
+
|
|
1692
|
+
Add template size limits:
|
|
1693
|
+
|
|
1694
|
+
```typescript
|
|
1695
|
+
// src/classes/liquid-engine.ts
|
|
1696
|
+
const MAX_TEMPLATE_SIZE = 1024 * 1024 // 1MB
|
|
1697
|
+
|
|
1698
|
+
export class LiquidEngine extends liquidjs.Liquid {
|
|
1699
|
+
async parseAndRender(
|
|
1700
|
+
template: string,
|
|
1701
|
+
variables: LiquidSubstitutionsVariables
|
|
1702
|
+
): Promise<string> {
|
|
1703
|
+
if (template.length > MAX_TEMPLATE_SIZE) {
|
|
1704
|
+
throw new TemplateError(
|
|
1705
|
+
`Template exceeds maximum size of ${MAX_TEMPLATE_SIZE} bytes`
|
|
1706
|
+
)
|
|
1707
|
+
}
|
|
1708
|
+
return super.parseAndRender(template, variables)
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
```
|
|
1712
|
+
|
|
1713
|
+
### 15.2 Path Traversal
|
|
1714
|
+
|
|
1715
|
+
**Risk:** User-provided paths could access unintended files
|
|
1716
|
+
|
|
1717
|
+
**Current Mitigation:**
|
|
1718
|
+
|
|
1719
|
+
- Paths resolved relative to package folder
|
|
1720
|
+
- No direct file system access from templates
|
|
1721
|
+
|
|
1722
|
+
**Recommendation:** Add path validation:
|
|
1723
|
+
|
|
1724
|
+
```typescript
|
|
1725
|
+
// src/functions/path-utils.ts
|
|
1726
|
+
export function validateSafePath(
|
|
1727
|
+
targetPath: string,
|
|
1728
|
+
basePath: string
|
|
1729
|
+
): boolean {
|
|
1730
|
+
const resolved = path.resolve(basePath, targetPath)
|
|
1731
|
+
return resolved.startsWith(path.resolve(basePath))
|
|
1732
|
+
}
|
|
1733
|
+
```
|
|
1734
|
+
|
|
1735
|
+
**Usage:**
|
|
1736
|
+
|
|
1737
|
+
```typescript
|
|
1738
|
+
const safePath = path.join(packagePath, userProvidedPath)
|
|
1739
|
+
if (!validateSafePath(safePath, packagePath)) {
|
|
1740
|
+
throw new InputError('Path traversal detected')
|
|
1741
|
+
}
|
|
1742
|
+
```
|
|
1743
|
+
|
|
1744
|
+
### 15.3 Command Injection
|
|
1745
|
+
|
|
1746
|
+
**Risk:** Action commands could contain injection attacks
|
|
1747
|
+
|
|
1748
|
+
**Current Status:** Commands are user-defined in package.json (own code) ✅
|
|
1749
|
+
|
|
1750
|
+
**If commands came from external sources, would need sanitization.**
|
|
1751
|
+
|
|
1752
|
+
### 15.4 Dependency Security
|
|
1753
|
+
|
|
1754
|
+
**Observation:** Limited external dependencies - GOOD ✅
|
|
1755
|
+
|
|
1756
|
+
**Current Dependencies:**
|
|
1757
|
+
|
|
1758
|
+
- `liquidjs` - Template engine
|
|
1759
|
+
- `@xpack/logger` - Internal package
|
|
1760
|
+
- `semver` - Version parsing
|
|
1761
|
+
|
|
1762
|
+
**Recommendation:**
|
|
1763
|
+
|
|
1764
|
+
- Regular `npm audit`
|
|
1765
|
+
- Pin dependency versions in package.json
|
|
1766
|
+
- Use renovate or dependabot for updates
|
|
1767
|
+
|
|
1768
|
+
---
|
|
1769
|
+
|
|
1770
|
+
## 16. Maintainability Score
|
|
1771
|
+
|
|
1772
|
+
### Current Score: 8.5/10
|
|
1773
|
+
|
|
1774
|
+
**Scoring Breakdown:**
|
|
1775
|
+
|
|
1776
|
+
| Category | Score | Comments |
|
|
1777
|
+
| ------------------- | ----- | ----------------------------------------------- |
|
|
1778
|
+
| Code Organisation | 7/10 | Good separation, but large files need splitting |
|
|
1779
|
+
| Type Safety | 9/10 | Excellent TypeScript usage |
|
|
1780
|
+
| Error Handling | 9/10 | Well-designed error hierarchy |
|
|
1781
|
+
| Pattern Consistency | 8/10 | Mostly consistent, some duplication |
|
|
1782
|
+
| Documentation | 10/10 | Comprehensive TSDoc |
|
|
1783
|
+
| Testing Support | 8/10 | Good DI, but some circular deps |
|
|
1784
|
+
| Code Complexity | 7/10 | Some high-complexity methods |
|
|
1785
|
+
| Naming Conventions | 8/10 | Generally good, minor inconsistencies |
|
|
1786
|
+
| Performance | 9/10 | Excellent lazy loading |
|
|
1787
|
+
| Modularity | 7/10 | Good functions, large class files |
|
|
1788
|
+
|
|
1789
|
+
**Improvement Potential:** 9.5/10 with recommended changes
|
|
1790
|
+
|
|
1791
|
+
---
|
|
1792
|
+
|
|
1793
|
+
## 17. Action Plan
|
|
1794
|
+
|
|
1795
|
+
### Phase 1: Critical Refactorings (High Priority)
|
|
1796
|
+
|
|
1797
|
+
**1.1 Split build-configurations.ts (3-4 hours)**
|
|
1798
|
+
|
|
1799
|
+
- Extract `BuildConfiguration` to separate file
|
|
1800
|
+
- Extract inheritance resolver
|
|
1801
|
+
- Update imports across codebase
|
|
1802
|
+
- **Impact:** High - Improves navigability significantly
|
|
1803
|
+
|
|
1804
|
+
**1.2 Split actions.ts (2-3 hours)**
|
|
1805
|
+
|
|
1806
|
+
- Extract `Action` to separate file
|
|
1807
|
+
- Apply similar pattern to build-configurations
|
|
1808
|
+
- **Impact:** Medium-High - Maintains consistency
|
|
1809
|
+
|
|
1810
|
+
**1.3 Fix Typo (5 minutes)**
|
|
1811
|
+
|
|
1812
|
+
- Rename `_buildComfigurationsNamesSet`
|
|
1813
|
+
- Search/replace all occurrences
|
|
1814
|
+
- **Impact:** Low - But easy to fix
|
|
1815
|
+
|
|
1816
|
+
### Phase 2: Pattern Extraction (Medium Priority)
|
|
1817
|
+
|
|
1818
|
+
**2.1 Create Initialisable Base/Mixin (2-3 hours)**
|
|
1819
|
+
|
|
1820
|
+
- Design mixin or abstract base class
|
|
1821
|
+
- Migrate 4 classes to use it
|
|
1822
|
+
- Test thoroughly
|
|
1823
|
+
- **Impact:** High - Eliminates ~200 lines, improves consistency
|
|
1824
|
+
|
|
1825
|
+
**2.2 Extract Validation Helpers (2 hours)**
|
|
1826
|
+
|
|
1827
|
+
- Create validation utility functions
|
|
1828
|
+
- Replace assertion boilerplate
|
|
1829
|
+
- **Impact:** Medium - ~100 lines eliminated
|
|
1830
|
+
|
|
1831
|
+
**2.3 Create Error Factory (1 hour)**
|
|
1832
|
+
|
|
1833
|
+
- Centralize error message creation
|
|
1834
|
+
- Update error construction sites
|
|
1835
|
+
- **Impact:** Medium - Better error consistency
|
|
1836
|
+
|
|
1837
|
+
### Phase 3: Type System Improvements (Lower Priority)
|
|
1838
|
+
|
|
1839
|
+
**3.1 Split json.ts (1-2 hours)**
|
|
1840
|
+
|
|
1841
|
+
- Organize into logical modules
|
|
1842
|
+
- Update imports
|
|
1843
|
+
- **Impact:** Medium - Better organization
|
|
1844
|
+
|
|
1845
|
+
**3.2 Add Type Guard Returns (1 hour)**
|
|
1846
|
+
|
|
1847
|
+
- Update `isJsonObject` and similar
|
|
1848
|
+
- Remove unnecessary `as` assertions
|
|
1849
|
+
- **Impact:** Medium - Better type safety
|
|
1850
|
+
|
|
1851
|
+
**3.3 Add Missing Type Guards (1 hour)**
|
|
1852
|
+
|
|
1853
|
+
- Implement recommended guards
|
|
1854
|
+
- Use throughout codebase
|
|
1855
|
+
- **Impact:** Low-Medium - Incremental improvement
|
|
1856
|
+
|
|
1857
|
+
### Phase 4: Code Complexity Reduction (Lower Priority)
|
|
1858
|
+
|
|
1859
|
+
**4.1 Extract BuildConfiguration.initialise() Helpers (2 hours)**
|
|
1860
|
+
|
|
1861
|
+
- Break into smaller methods
|
|
1862
|
+
- Improve readability
|
|
1863
|
+
- **Impact:** Medium - Easier maintenance
|
|
1864
|
+
|
|
1865
|
+
**4.2 Reduce Nesting Depth (2 hours)**
|
|
1866
|
+
|
|
1867
|
+
- Apply early returns
|
|
1868
|
+
- Extract nested loops to methods
|
|
1869
|
+
- **Impact:** Low-Medium - Incremental improvement
|
|
1870
|
+
|
|
1871
|
+
**4.3 Extract Long Methods (2-3 hours)**
|
|
1872
|
+
|
|
1873
|
+
- Identify methods >100 lines
|
|
1874
|
+
- Break into logical units
|
|
1875
|
+
- **Impact:** Medium - Better testability
|
|
1876
|
+
|
|
1877
|
+
### Phase 5: Documentation and Standards (Optional)
|
|
1878
|
+
|
|
1879
|
+
**5.1 Create Architecture Documentation (2 hours)**
|
|
1880
|
+
|
|
1881
|
+
- Document lazy evaluation pattern
|
|
1882
|
+
- Document inheritance system
|
|
1883
|
+
- Create README.md in src/
|
|
1884
|
+
- **Impact:** High for onboarding - Better developer experience
|
|
1885
|
+
|
|
1886
|
+
**5.2 Standardise Naming (1-2 hours)**
|
|
1887
|
+
|
|
1888
|
+
- Update naming guide
|
|
1889
|
+
- Lint rule for consistency
|
|
1890
|
+
- **Impact:** Low - Long-term benefit
|
|
1891
|
+
|
|
1892
|
+
**5.3 Add Constants Module (1 hour)**
|
|
1893
|
+
|
|
1894
|
+
- Extract magic strings
|
|
1895
|
+
- Create const assertions
|
|
1896
|
+
- **Impact:** Low-Medium - Reduces typos
|
|
1897
|
+
|
|
1898
|
+
---
|
|
1899
|
+
|
|
1900
|
+
## 18. Estimated Impact Summary
|
|
1901
|
+
|
|
1902
|
+
| Improvement | Effort | Lines Reduced | Maintainability Gain | Priority |
|
|
1903
|
+
| -------------------- | --------------- | ------------------ | -------------------- | -------- |
|
|
1904
|
+
| Split large files | 6-8 hours | 0 (reorganisation) | Very High | URGENT |
|
|
1905
|
+
| Initialisable mixin | 2-3 hours | ~200 | High | High |
|
|
1906
|
+
| Validation helpers | 2 hours | ~100 | Medium | High |
|
|
1907
|
+
| Error factory | 1 hour | ~50 | Medium | Medium |
|
|
1908
|
+
| Type improvements | 3-4 hours | 0 (quality) | Medium | Medium |
|
|
1909
|
+
| Complexity reduction | 6-9 hours | ~100 | Medium | Medium |
|
|
1910
|
+
| **Total** | **20-27 hours** | **~450 lines** | **Very High** | - |
|
|
1911
|
+
|
|
1912
|
+
**Cumulative Benefits:**
|
|
1913
|
+
|
|
1914
|
+
- **Code Reduction:** ~450 lines of boilerplate (4.6% of codebase)
|
|
1915
|
+
- **File Count:** +8-10 files (better organisation)
|
|
1916
|
+
- **Maintainability Score:** 8.5 → 9.3 (+0.8)
|
|
1917
|
+
- **Average File Size:** Reduced from 655 lines to ~450 lines
|
|
1918
|
+
- **Onboarding Time:** Estimated 40% reduction
|
|
1919
|
+
|
|
1920
|
+
---
|
|
1921
|
+
|
|
1922
|
+
## 19. Specific File Recommendations
|
|
1923
|
+
|
|
1924
|
+
### Large Files (Urgent Action Needed)
|
|
1925
|
+
|
|
1926
|
+
#### build-configurations.ts (2,155 lines) - SPLIT IMMEDIATELY
|
|
1927
|
+
|
|
1928
|
+
**Target Structure:**
|
|
1929
|
+
|
|
1930
|
+
```
|
|
1931
|
+
src/classes/build-configurations/
|
|
1932
|
+
├── index.ts # Re-exports (~10 lines)
|
|
1933
|
+
├── build-configurations.ts # Collection (~850 lines)
|
|
1934
|
+
├── build-configuration.ts # Single config (~750 lines)
|
|
1935
|
+
├── inheritance-resolver.ts # Inheritance logic (~300 lines)
|
|
1936
|
+
├── property-merger.ts # Property merging (~150 lines)
|
|
1937
|
+
└── types.ts # Shared interfaces (~95 lines)
|
|
1938
|
+
```
|
|
1939
|
+
|
|
1940
|
+
#### actions.ts (1,160 lines) - SPLIT NEXT
|
|
1941
|
+
|
|
1942
|
+
**Target Structure:**
|
|
1943
|
+
|
|
1944
|
+
```
|
|
1945
|
+
src/classes/actions/
|
|
1946
|
+
├── index.ts # Re-exports (~10 lines)
|
|
1947
|
+
├── actions.ts # Collection (~500 lines)
|
|
1948
|
+
├── action.ts # Single action (~450 lines)
|
|
1949
|
+
├── types.ts # Shared interfaces (~200 lines)
|
|
1950
|
+
```
|
|
1951
|
+
|
|
1952
|
+
#### init-template-base.ts (1,027 lines) - CONSIDER SPLITTING
|
|
1953
|
+
|
|
1954
|
+
**Target Structure:**
|
|
1955
|
+
|
|
1956
|
+
```
|
|
1957
|
+
src/classes/init-template/
|
|
1958
|
+
├── index.ts # Re-exports (~10 lines)
|
|
1959
|
+
├── init-template-base.ts # Core class (~350 lines)
|
|
1960
|
+
├── property-validator.ts # Validation (~250 lines)
|
|
1961
|
+
├── property-prompter.ts # User interaction (~200 lines)
|
|
1962
|
+
├── substitution-processor.ts # Variable processing (~150 lines)
|
|
1963
|
+
└── types.ts # Shared interfaces (~77 lines)
|
|
1964
|
+
```
|
|
1965
|
+
|
|
1966
|
+
### Well-Sized Files (Maintain Current Structure)
|
|
1967
|
+
|
|
1968
|
+
- ✅ **package.ts** (765 lines) - Good cohesion
|
|
1969
|
+
- ✅ **liquid-engine.ts** (249 lines) - Focused responsibility
|
|
1970
|
+
- ✅ **platform-detector.ts** (237 lines) - Well-contained
|
|
1971
|
+
- ✅ **template-expander.ts** (330 lines) - Clean abstraction
|
|
1972
|
+
- ✅ **data-model.ts** (337 lines) - Appropriate size
|
|
1973
|
+
|
|
1974
|
+
---
|
|
1975
|
+
|
|
1976
|
+
## 20. Long-Term Architecture Considerations
|
|
1977
|
+
|
|
1978
|
+
### 20.1 Plugin System
|
|
1979
|
+
|
|
1980
|
+
**Future Enhancement:** Support for custom Liquid filters
|
|
1981
|
+
|
|
1982
|
+
**Potential API:**
|
|
1983
|
+
|
|
1984
|
+
```typescript
|
|
1985
|
+
export interface LiquidFilterPlugin {
|
|
1986
|
+
name: string
|
|
1987
|
+
implementation: (value: any, ...args: any[]) => any
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
export class LiquidEngine extends liquidjs.Liquid {
|
|
1991
|
+
registerPlugin(plugin: LiquidFilterPlugin): void {
|
|
1992
|
+
this.registerFilter(plugin.name, plugin.implementation)
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
```
|
|
1996
|
+
|
|
1997
|
+
### 20.2 Configuration Validation
|
|
1998
|
+
|
|
1999
|
+
**Future Enhancement:** JSON Schema validation for package.json
|
|
2000
|
+
|
|
2001
|
+
**Approach:**
|
|
2002
|
+
|
|
2003
|
+
```typescript
|
|
2004
|
+
import Ajv from 'ajv'
|
|
2005
|
+
|
|
2006
|
+
export class PackageValidator {
|
|
2007
|
+
private readonly ajv = new Ajv()
|
|
2008
|
+
|
|
2009
|
+
validate(jsonPackage: unknown): ValidationResult {
|
|
2010
|
+
const valid = this.ajv.validate(packageSchema, jsonPackage)
|
|
2011
|
+
// Return structured validation results
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
```
|
|
2015
|
+
|
|
2016
|
+
### 20.3 Caching Layer
|
|
2017
|
+
|
|
2018
|
+
**Future Enhancement:** Persistent cache for template expansions
|
|
2019
|
+
|
|
2020
|
+
**Approach:**
|
|
2021
|
+
|
|
2022
|
+
```typescript
|
|
2023
|
+
export class TemplateCache {
|
|
2024
|
+
async get(key: string): Promise<string | undefined> {
|
|
2025
|
+
// Check cache
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
async set(key: string, value: string): Promise<void> {
|
|
2029
|
+
// Store in cache
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
```
|
|
2033
|
+
|
|
2034
|
+
### 20.4 Event System
|
|
2035
|
+
|
|
2036
|
+
**Future Enhancement:** Lifecycle hooks and events
|
|
2037
|
+
|
|
2038
|
+
**Approach:**
|
|
2039
|
+
|
|
2040
|
+
```typescript
|
|
2041
|
+
export interface LifecycleEvents {
|
|
2042
|
+
'configuration:before-init': (config: BuildConfiguration) => void
|
|
2043
|
+
'configuration:after-init': (config: BuildConfiguration) => void
|
|
2044
|
+
'template:before-expand': (template: string) => void
|
|
2045
|
+
'template:after-expand': (result: string) => void
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
export class EventEmitter {
|
|
2049
|
+
on<K extends keyof LifecycleEvents>(
|
|
2050
|
+
event: K,
|
|
2051
|
+
handler: LifecycleEvents[K]
|
|
2052
|
+
): void
|
|
2053
|
+
}
|
|
2054
|
+
```
|
|
2055
|
+
|
|
2056
|
+
---
|
|
2057
|
+
|
|
2058
|
+
## 21. Testing Strategy Recommendations
|
|
2059
|
+
|
|
2060
|
+
### 21.1 Unit Test Coverage Targets
|
|
2061
|
+
|
|
2062
|
+
**Priority Classes:**
|
|
2063
|
+
|
|
2064
|
+
- BuildConfiguration inheritance resolution (high complexity)
|
|
2065
|
+
- TemplateExpander matrix expansion (critical path)
|
|
2066
|
+
- CombinationsGenerator cartesian product (algorithm)
|
|
2067
|
+
- Actions command substitution (user-facing)
|
|
2068
|
+
|
|
2069
|
+
**Recommended Coverage:**
|
|
2070
|
+
|
|
2071
|
+
- Statements: >90%
|
|
2072
|
+
- Branches: >85%
|
|
2073
|
+
- Functions: >95%
|
|
2074
|
+
- Lines: >90%
|
|
2075
|
+
|
|
2076
|
+
### 21.2 Integration Test Scenarios
|
|
2077
|
+
|
|
2078
|
+
**Recommended Test Suites:**
|
|
2079
|
+
|
|
2080
|
+
1. **End-to-End Template Expansion:**
|
|
2081
|
+
- Complex multi-level inheritance
|
|
2082
|
+
- Large matrix expansions
|
|
2083
|
+
- Circular reference detection
|
|
2084
|
+
|
|
2085
|
+
2. **Error Handling Paths:**
|
|
2086
|
+
- Invalid configuration structures
|
|
2087
|
+
- Missing required properties
|
|
2088
|
+
- Template evaluation failures
|
|
2089
|
+
|
|
2090
|
+
3. **Performance Benchmarks:**
|
|
2091
|
+
- Large package with 50+ configurations
|
|
2092
|
+
- Deep inheritance chains (5+ levels)
|
|
2093
|
+
- Complex matrix (5x5x5)
|
|
2094
|
+
|
|
2095
|
+
### 21.3 Property-Based Testing
|
|
2096
|
+
|
|
2097
|
+
**Consider `fast-check` for:**
|
|
2098
|
+
|
|
2099
|
+
```typescript
|
|
2100
|
+
import fc from 'fast-check'
|
|
2101
|
+
|
|
2102
|
+
test('CombinationsGenerator produces expected count', () => {
|
|
2103
|
+
fc.assert(
|
|
2104
|
+
fc.property(fc.array(fc.array(fc.string(), 1, 5), 1, 5), (matrixValues) => {
|
|
2105
|
+
const generator = new CombinationsGenerator({
|
|
2106
|
+
matrixKeys: matrixValues.map((_, i) => `key${i}`),
|
|
2107
|
+
matrixValues,
|
|
2108
|
+
log: mockLogger,
|
|
2109
|
+
})
|
|
2110
|
+
|
|
2111
|
+
const expectedCount = matrixValues.reduce(
|
|
2112
|
+
(product, values) => product * values.length,
|
|
2113
|
+
1
|
|
2114
|
+
)
|
|
2115
|
+
|
|
2116
|
+
const combinations = Array.from(generator)
|
|
2117
|
+
expect(combinations.length).toBe(expectedCount)
|
|
2118
|
+
})
|
|
2119
|
+
)
|
|
2120
|
+
})
|
|
2121
|
+
```
|
|
2122
|
+
|
|
2123
|
+
---
|
|
2124
|
+
|
|
2125
|
+
## 22. Conclusion
|
|
2126
|
+
|
|
2127
|
+
The xpm-lib source code represents a mature, well-designed TypeScript library with sophisticated architectural patterns. The codebase demonstrates strong engineering practices with comprehensive error handling, effective lazy evaluation, and good type safety.
|
|
2128
|
+
|
|
2129
|
+
### Key Achievements ✅
|
|
2130
|
+
|
|
2131
|
+
1. **Excellent Architecture:** Lazy evaluation pattern is well-implemented
|
|
2132
|
+
2. **Strong Type Safety:** Comprehensive TypeScript usage throughout
|
|
2133
|
+
3. **Clean Abstractions:** Good separation of concerns and modularity
|
|
2134
|
+
4. **Robust Error Handling:** Well-designed error hierarchy
|
|
2135
|
+
5. **Code Quality:** No TODO/FIXME comments, clean code
|
|
2136
|
+
|
|
2137
|
+
### Priority Actions 🔧
|
|
2138
|
+
|
|
2139
|
+
1. **URGENT:** Split `build-configurations.ts` (2,155 lines → 5 files)
|
|
2140
|
+
2. **High:** Extract initialization pattern (eliminates ~200 lines)
|
|
2141
|
+
3. **High:** Split `actions.ts` (1,160 lines → 3 files)
|
|
2142
|
+
4. **Medium:** Create validation helpers and error factories
|
|
2143
|
+
5. **Medium:** Improve type guards and reduce `as` assertions
|
|
2144
|
+
|
|
2145
|
+
### Expected Outcomes 📊
|
|
2146
|
+
|
|
2147
|
+
**After Phase 1-2 (Priority Refactorings):**
|
|
2148
|
+
|
|
2149
|
+
- Maintainability Score: 8.5 → 9.3 (+0.8)
|
|
2150
|
+
- Average File Size: 655 → ~450 lines (-31%)
|
|
2151
|
+
- Boilerplate Code: -300 lines (-3.1%)
|
|
2152
|
+
- Onboarding Time: -40% (estimated)
|
|
2153
|
+
|
|
2154
|
+
**Investment:** 12-15 hours for highest-priority improvements
|
|
2155
|
+
|
|
2156
|
+
### Recommended Next Steps
|
|
2157
|
+
|
|
2158
|
+
1. **Week 1:** Split large files (build-configurations, actions)
|
|
2159
|
+
2. **Week 2:** Extract initialization pattern and validation helpers
|
|
2160
|
+
3. **Week 3:** Type system improvements and error factories
|
|
2161
|
+
4. **Week 4:** Code complexity reduction and testing improvements
|
|
2162
|
+
|
|
2163
|
+
### Final Assessment
|
|
2164
|
+
|
|
2165
|
+
**Overall Rating: 8.5/10** - Professional, maintainable codebase that would benefit significantly from file organization improvements. The architecture is sound and the code quality is high. Primary improvement area is reducing file sizes to improve navigability and maintainability.
|
|
2166
|
+
|
|
2167
|
+
**Recommendation:** Proceed with Phase 1 refactorings (file splitting) as the highest priority. These changes will have the most immediate positive impact on developer productivity and code maintainability whilst maintaining the excellent architectural patterns already in place.
|