agileflow 2.91.0 → 2.92.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 +10 -0
- package/README.md +6 -6
- package/lib/README.md +178 -0
- package/lib/codebase-indexer.js +32 -23
- package/lib/colors.js +190 -12
- package/lib/consent.js +232 -0
- package/lib/correlation.js +277 -0
- package/lib/error-codes.js +46 -0
- package/lib/errors.js +48 -6
- package/lib/file-cache.js +182 -0
- package/lib/format-error.js +156 -0
- package/lib/path-resolver.js +155 -7
- package/lib/paths.js +212 -20
- package/lib/placeholder-registry.js +205 -0
- package/lib/registry-di.js +358 -0
- package/lib/result-schema.js +363 -0
- package/lib/result.js +210 -0
- package/lib/session-registry.js +13 -0
- package/lib/session-state-machine.js +465 -0
- package/lib/validate-commands.js +308 -0
- package/lib/validate.js +116 -52
- package/package.json +1 -1
- package/scripts/af +34 -0
- package/scripts/agent-loop.js +63 -9
- package/scripts/agileflow-configure.js +2 -2
- package/scripts/agileflow-welcome.js +491 -23
- package/scripts/archive-completed-stories.sh +57 -11
- package/scripts/claude-tmux.sh +102 -0
- package/scripts/damage-control-bash.js +3 -70
- package/scripts/damage-control-edit.js +3 -20
- package/scripts/damage-control-write.js +3 -20
- package/scripts/dependency-check.js +310 -0
- package/scripts/get-env.js +11 -4
- package/scripts/lib/configure-detect.js +23 -1
- package/scripts/lib/configure-features.js +50 -2
- package/scripts/lib/context-formatter.js +771 -0
- package/scripts/lib/context-loader.js +699 -0
- package/scripts/lib/damage-control-utils.js +107 -0
- package/scripts/lib/json-utils.sh +162 -0
- package/scripts/lib/state-migrator.js +353 -0
- package/scripts/lib/story-state-machine.js +437 -0
- package/scripts/obtain-context.js +80 -1248
- package/scripts/pre-push-check.sh +46 -0
- package/scripts/precompact-context.sh +23 -10
- package/scripts/query-codebase.js +127 -14
- package/scripts/ralph-loop.js +5 -5
- package/scripts/session-manager.js +408 -55
- package/scripts/spawn-parallel.js +666 -0
- package/scripts/tui/blessed/data/watcher.js +20 -15
- package/scripts/tui/blessed/index.js +2 -2
- package/scripts/tui/blessed/panels/output.js +14 -8
- package/scripts/tui/blessed/panels/sessions.js +22 -15
- package/scripts/tui/blessed/panels/trace.js +14 -8
- package/scripts/tui/blessed/ui/help.js +3 -3
- package/scripts/tui/blessed/ui/screen.js +4 -4
- package/scripts/tui/blessed/ui/statusbar.js +5 -9
- package/scripts/tui/blessed/ui/tabbar.js +11 -11
- package/scripts/validators/component-validator.js +41 -14
- package/scripts/validators/json-schema-validator.js +11 -4
- package/scripts/validators/markdown-validator.js +1 -2
- package/scripts/validators/migration-validator.js +17 -5
- package/scripts/validators/security-validator.js +137 -33
- package/scripts/validators/story-format-validator.js +31 -10
- package/scripts/validators/test-result-validator.js +19 -4
- package/scripts/validators/workflow-validator.js +12 -5
- package/src/core/agents/codebase-query.md +24 -0
- package/src/core/commands/adr.md +114 -0
- package/src/core/commands/agent.md +120 -0
- package/src/core/commands/assign.md +145 -0
- package/src/core/commands/babysit.md +32 -5
- package/src/core/commands/changelog.md +118 -0
- package/src/core/commands/configure.md +42 -6
- package/src/core/commands/diagnose.md +114 -0
- package/src/core/commands/epic.md +113 -0
- package/src/core/commands/handoff.md +128 -0
- package/src/core/commands/help.md +75 -0
- package/src/core/commands/pr.md +96 -0
- package/src/core/commands/roadmap/analyze.md +400 -0
- package/src/core/commands/session/new.md +132 -6
- package/src/core/commands/session/spawn.md +197 -0
- package/src/core/commands/sprint.md +22 -0
- package/src/core/commands/status.md +74 -0
- package/src/core/commands/story.md +143 -4
- package/src/core/templates/agileflow-metadata.json +55 -2
- package/src/core/templates/plan-template.md +125 -0
- package/src/core/templates/story-lifecycle.md +213 -0
- package/src/core/templates/story-template.md +4 -0
- package/src/core/templates/tdd-test-template.js +241 -0
- package/tools/cli/commands/setup.js +95 -0
- package/tools/cli/installers/core/installer.js +94 -0
- package/tools/cli/installers/ide/_base-ide.js +20 -11
- package/tools/cli/installers/ide/codex.js +29 -47
- package/tools/cli/installers/ide/windsurf.js +1 -1
- package/tools/cli/lib/config-manager.js +17 -2
- package/tools/cli/lib/content-transformer.js +271 -0
- package/tools/cli/lib/error-handler.js +14 -22
- package/tools/cli/lib/ide-error-factory.js +421 -0
- package/tools/cli/lib/ide-health-monitor.js +364 -0
- package/tools/cli/lib/ide-registry.js +113 -2
- package/tools/cli/lib/ui.js +15 -25
package/lib/paths.js
CHANGED
|
@@ -2,11 +2,43 @@
|
|
|
2
2
|
* AgileFlow CLI - Shared Path Utilities
|
|
3
3
|
*
|
|
4
4
|
* Centralized path resolution functions used across scripts.
|
|
5
|
+
*
|
|
6
|
+
* NOTE: This module delegates to PathResolver for the implementation.
|
|
7
|
+
* Functions accept optional rootDir for backwards compatibility, but
|
|
8
|
+
* if not provided, use PathResolver's manifest-aware auto-detection.
|
|
9
|
+
*
|
|
10
|
+
* For new code, prefer using PathResolver directly:
|
|
11
|
+
* const { PathResolver, getDefaultResolver } = require('./path-resolver');
|
|
12
|
+
* const resolver = getDefaultResolver();
|
|
13
|
+
* const statusPath = resolver.getStatusPath();
|
|
5
14
|
*/
|
|
6
15
|
|
|
7
|
-
const
|
|
16
|
+
const { PathResolver, getDefaultResolver } = require('./path-resolver');
|
|
8
17
|
const path = require('path');
|
|
9
18
|
|
|
19
|
+
// Cache for resolvers by rootDir
|
|
20
|
+
const resolverCache = new Map();
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get a PathResolver for the given rootDir.
|
|
24
|
+
* Uses singleton for default (no rootDir) case, caches others.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} [rootDir] - Project root (auto-detected if not provided)
|
|
27
|
+
* @returns {PathResolver}
|
|
28
|
+
* @private
|
|
29
|
+
*/
|
|
30
|
+
function getResolver(rootDir) {
|
|
31
|
+
if (!rootDir) {
|
|
32
|
+
return getDefaultResolver();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Cache resolvers by rootDir to avoid repeated construction
|
|
36
|
+
if (!resolverCache.has(rootDir)) {
|
|
37
|
+
resolverCache.set(rootDir, new PathResolver(rootDir, { autoDetect: false }));
|
|
38
|
+
}
|
|
39
|
+
return resolverCache.get(rootDir);
|
|
40
|
+
}
|
|
41
|
+
|
|
10
42
|
/**
|
|
11
43
|
* Find the project root by looking for .agileflow directory.
|
|
12
44
|
* Walks up from current directory until .agileflow is found.
|
|
@@ -15,11 +47,8 @@ const path = require('path');
|
|
|
15
47
|
* @returns {string} Project root path, or startDir if not found
|
|
16
48
|
*/
|
|
17
49
|
function getProjectRoot(startDir = process.cwd()) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
dir = path.dirname(dir);
|
|
21
|
-
}
|
|
22
|
-
return dir !== '/' ? dir : startDir;
|
|
50
|
+
const resolver = new PathResolver(startDir, { autoDetect: true });
|
|
51
|
+
return resolver.getProjectRoot();
|
|
23
52
|
}
|
|
24
53
|
|
|
25
54
|
/**
|
|
@@ -29,8 +58,7 @@ function getProjectRoot(startDir = process.cwd()) {
|
|
|
29
58
|
* @returns {string} Path to .agileflow directory
|
|
30
59
|
*/
|
|
31
60
|
function getAgileflowDir(rootDir) {
|
|
32
|
-
|
|
33
|
-
return path.join(root, '.agileflow');
|
|
61
|
+
return getResolver(rootDir).getAgileflowDir();
|
|
34
62
|
}
|
|
35
63
|
|
|
36
64
|
/**
|
|
@@ -40,8 +68,7 @@ function getAgileflowDir(rootDir) {
|
|
|
40
68
|
* @returns {string} Path to .claude directory
|
|
41
69
|
*/
|
|
42
70
|
function getClaudeDir(rootDir) {
|
|
43
|
-
|
|
44
|
-
return path.join(root, '.claude');
|
|
71
|
+
return getResolver(rootDir).getClaudeDir();
|
|
45
72
|
}
|
|
46
73
|
|
|
47
74
|
/**
|
|
@@ -51,8 +78,7 @@ function getClaudeDir(rootDir) {
|
|
|
51
78
|
* @returns {string} Path to docs directory
|
|
52
79
|
*/
|
|
53
80
|
function getDocsDir(rootDir) {
|
|
54
|
-
|
|
55
|
-
return path.join(root, 'docs');
|
|
81
|
+
return getResolver(rootDir).getDocsDir();
|
|
56
82
|
}
|
|
57
83
|
|
|
58
84
|
/**
|
|
@@ -62,8 +88,7 @@ function getDocsDir(rootDir) {
|
|
|
62
88
|
* @returns {string} Path to status.json
|
|
63
89
|
*/
|
|
64
90
|
function getStatusPath(rootDir) {
|
|
65
|
-
|
|
66
|
-
return path.join(root, 'docs', '09-agents', 'status.json');
|
|
91
|
+
return getResolver(rootDir).getStatusPath();
|
|
67
92
|
}
|
|
68
93
|
|
|
69
94
|
/**
|
|
@@ -73,27 +98,194 @@ function getStatusPath(rootDir) {
|
|
|
73
98
|
* @returns {string} Path to session-state.json
|
|
74
99
|
*/
|
|
75
100
|
function getSessionStatePath(rootDir) {
|
|
76
|
-
|
|
77
|
-
|
|
101
|
+
return getResolver(rootDir).getSessionStatePath();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get the agileflow-metadata.json path.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} [rootDir] - Project root (auto-detected if not provided)
|
|
108
|
+
* @returns {string} Path to agileflow-metadata.json
|
|
109
|
+
*/
|
|
110
|
+
function getMetadataPath(rootDir) {
|
|
111
|
+
return getResolver(rootDir).getMetadataPath();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get the message bus log path.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} [rootDir] - Project root (auto-detected if not provided)
|
|
118
|
+
* @returns {string} Path to bus/log.jsonl
|
|
119
|
+
*/
|
|
120
|
+
function getBusLogPath(rootDir) {
|
|
121
|
+
return getResolver(rootDir).getBusLogPath();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get the epics directory path.
|
|
126
|
+
*
|
|
127
|
+
* @param {string} [rootDir] - Project root (auto-detected if not provided)
|
|
128
|
+
* @returns {string} Path to epics directory
|
|
129
|
+
*/
|
|
130
|
+
function getEpicsDir(rootDir) {
|
|
131
|
+
return getResolver(rootDir).getEpicsDir();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get the stories directory path.
|
|
136
|
+
*
|
|
137
|
+
* @param {string} [rootDir] - Project root (auto-detected if not provided)
|
|
138
|
+
* @returns {string} Path to stories directory
|
|
139
|
+
*/
|
|
140
|
+
function getStoriesDir(rootDir) {
|
|
141
|
+
return getResolver(rootDir).getStoriesDir();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get the archive directory path for completed stories.
|
|
146
|
+
*
|
|
147
|
+
* @param {string} [rootDir] - Project root (auto-detected if not provided)
|
|
148
|
+
* @returns {string} Path to archive directory
|
|
149
|
+
*/
|
|
150
|
+
function getArchiveDir(rootDir) {
|
|
151
|
+
return getResolver(rootDir).getArchiveDir();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Get the agents directory path (docs/09-agents).
|
|
156
|
+
*
|
|
157
|
+
* @param {string} [rootDir] - Project root (auto-detected if not provided)
|
|
158
|
+
* @returns {string} Path to agents directory (docs/09-agents)
|
|
159
|
+
*/
|
|
160
|
+
function getAgentsDir(rootDir) {
|
|
161
|
+
return getResolver(rootDir).getDocsAgentsDir();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get the decisions (ADR) directory path.
|
|
166
|
+
*
|
|
167
|
+
* @param {string} [rootDir] - Project root (auto-detected if not provided)
|
|
168
|
+
* @returns {string} Path to decisions directory
|
|
169
|
+
*/
|
|
170
|
+
function getDecisionsDir(rootDir) {
|
|
171
|
+
return getResolver(rootDir).getDecisionsDir();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get the research directory path.
|
|
176
|
+
*
|
|
177
|
+
* @param {string} [rootDir] - Project root (auto-detected if not provided)
|
|
178
|
+
* @returns {string} Path to research directory
|
|
179
|
+
*/
|
|
180
|
+
function getResearchDir(rootDir) {
|
|
181
|
+
return getResolver(rootDir).getResearchDir();
|
|
78
182
|
}
|
|
79
183
|
|
|
80
184
|
/**
|
|
81
185
|
* Check if we're in an AgileFlow project.
|
|
186
|
+
* Walks up from dir to find .agileflow directory.
|
|
82
187
|
*
|
|
83
|
-
* @param {string} [dir=process.cwd()] - Directory to check
|
|
84
|
-
* @returns {boolean} True if .agileflow directory exists
|
|
188
|
+
* @param {string} [dir=process.cwd()] - Directory to check from
|
|
189
|
+
* @returns {boolean} True if .agileflow directory exists in dir or any parent
|
|
85
190
|
*/
|
|
86
191
|
function isAgileflowProject(dir = process.cwd()) {
|
|
87
|
-
|
|
88
|
-
|
|
192
|
+
// Use auto-detection to walk up and find project root
|
|
193
|
+
const resolver = new PathResolver(dir, { autoDetect: true });
|
|
194
|
+
return resolver.isAgileflowProject();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ============================================================================
|
|
198
|
+
// New functions (added in ConfigResolver consolidation)
|
|
199
|
+
// ============================================================================
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get the .claude/settings.json path.
|
|
203
|
+
*
|
|
204
|
+
* @param {string} [rootDir] - Project root (auto-detected if not provided)
|
|
205
|
+
* @returns {string} Path to settings.json
|
|
206
|
+
*/
|
|
207
|
+
function getSettingsPath(rootDir) {
|
|
208
|
+
return getResolver(rootDir).getSettingsPath();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get the .claude/settings.local.json path.
|
|
213
|
+
*
|
|
214
|
+
* @param {string} [rootDir] - Project root (auto-detected if not provided)
|
|
215
|
+
* @returns {string} Path to settings.local.json
|
|
216
|
+
*/
|
|
217
|
+
function getSettingsLocalPath(rootDir) {
|
|
218
|
+
return getResolver(rootDir).getSettingsLocalPath();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Get the skills directory path.
|
|
223
|
+
*
|
|
224
|
+
* @param {string} [rootDir] - Project root (auto-detected if not provided)
|
|
225
|
+
* @returns {string} Path to skills directory
|
|
226
|
+
*/
|
|
227
|
+
function getSkillsDir(rootDir) {
|
|
228
|
+
return getResolver(rootDir).getSkillsDir();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get the scripts directory path.
|
|
233
|
+
*
|
|
234
|
+
* @param {string} [rootDir] - Project root (auto-detected if not provided)
|
|
235
|
+
* @returns {string} Path to scripts directory
|
|
236
|
+
*/
|
|
237
|
+
function getScriptsDir(rootDir) {
|
|
238
|
+
return getResolver(rootDir).getScriptsDir();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Get the commands directory path.
|
|
243
|
+
*
|
|
244
|
+
* @param {string} [rootDir] - Project root (auto-detected if not provided)
|
|
245
|
+
* @returns {string} Path to commands directory
|
|
246
|
+
*/
|
|
247
|
+
function getCommandsDir(rootDir) {
|
|
248
|
+
return getResolver(rootDir).getCommandsDir();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Clear the resolver cache (useful for testing or after config changes).
|
|
253
|
+
*/
|
|
254
|
+
function clearResolverCache() {
|
|
255
|
+
resolverCache.clear();
|
|
89
256
|
}
|
|
90
257
|
|
|
91
258
|
module.exports = {
|
|
259
|
+
// Root and base directories
|
|
92
260
|
getProjectRoot,
|
|
93
261
|
getAgileflowDir,
|
|
94
262
|
getClaudeDir,
|
|
95
263
|
getDocsDir,
|
|
264
|
+
|
|
265
|
+
// Status and session files
|
|
96
266
|
getStatusPath,
|
|
97
267
|
getSessionStatePath,
|
|
268
|
+
getMetadataPath,
|
|
269
|
+
getBusLogPath,
|
|
270
|
+
|
|
271
|
+
// Documentation directories
|
|
272
|
+
getEpicsDir,
|
|
273
|
+
getStoriesDir,
|
|
274
|
+
getArchiveDir,
|
|
275
|
+
getAgentsDir,
|
|
276
|
+
getDecisionsDir,
|
|
277
|
+
getResearchDir,
|
|
278
|
+
|
|
279
|
+
// Configuration files (new in ConfigResolver consolidation)
|
|
280
|
+
getSettingsPath,
|
|
281
|
+
getSettingsLocalPath,
|
|
282
|
+
|
|
283
|
+
// AgileFlow subdirectories (new in ConfigResolver consolidation)
|
|
284
|
+
getSkillsDir,
|
|
285
|
+
getScriptsDir,
|
|
286
|
+
getCommandsDir,
|
|
287
|
+
|
|
288
|
+
// Utilities
|
|
98
289
|
isAgileflowProject,
|
|
290
|
+
clearResolverCache,
|
|
99
291
|
};
|
|
@@ -39,6 +39,29 @@ const {
|
|
|
39
39
|
* @property {string} [source='builtin'] - Source: 'builtin' | 'plugin'
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Schema version for serialization compatibility
|
|
44
|
+
* Increment when making breaking changes to serialized format
|
|
45
|
+
*
|
|
46
|
+
* Version history:
|
|
47
|
+
* - 1: Initial schema with name, type, description, secure, cacheable, source
|
|
48
|
+
* - 2: Added validator serialization support, context metadata
|
|
49
|
+
*/
|
|
50
|
+
const SCHEMA_VERSION = 2;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Schema migrations for upgrading old configurations
|
|
54
|
+
*/
|
|
55
|
+
const SCHEMA_MIGRATIONS = {
|
|
56
|
+
// Migrate from v1 to v2
|
|
57
|
+
1: config => ({
|
|
58
|
+
...config,
|
|
59
|
+
schemaVersion: 2,
|
|
60
|
+
contextMetadata: config.contextMetadata || {},
|
|
61
|
+
validatorName: config.validatorName || null,
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
|
|
42
65
|
/**
|
|
43
66
|
* Default resolver types with their sanitization rules
|
|
44
67
|
*/
|
|
@@ -470,6 +493,186 @@ class PlaceholderRegistry extends EventEmitter {
|
|
|
470
493
|
// Could be extended to include context hash
|
|
471
494
|
return name;
|
|
472
495
|
}
|
|
496
|
+
|
|
497
|
+
// ===========================================================================
|
|
498
|
+
// Schema Versioning and Serialization
|
|
499
|
+
// ===========================================================================
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Get current schema version
|
|
503
|
+
* @returns {number}
|
|
504
|
+
*/
|
|
505
|
+
static getSchemaVersion() {
|
|
506
|
+
return SCHEMA_VERSION;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Serialize registry configuration for persistence
|
|
511
|
+
* @returns {Object} Serializable configuration
|
|
512
|
+
*/
|
|
513
|
+
serialize() {
|
|
514
|
+
const configs = {};
|
|
515
|
+
|
|
516
|
+
for (const [name, config] of this._resolvers) {
|
|
517
|
+
configs[name] = {
|
|
518
|
+
schemaVersion: SCHEMA_VERSION,
|
|
519
|
+
name: config.name,
|
|
520
|
+
description: config.description,
|
|
521
|
+
type: config.type,
|
|
522
|
+
secure: config.secure,
|
|
523
|
+
cacheable: config.cacheable,
|
|
524
|
+
source: config.source,
|
|
525
|
+
contextMetadata: config.contextMetadata || {},
|
|
526
|
+
validatorName: config.validatorName || null,
|
|
527
|
+
// Note: resolver function cannot be serialized - must be re-registered
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
return {
|
|
532
|
+
schemaVersion: SCHEMA_VERSION,
|
|
533
|
+
registryOptions: {
|
|
534
|
+
cacheEnabled: this.cacheEnabled,
|
|
535
|
+
strictMode: this.strictMode,
|
|
536
|
+
secureByDefault: this.secureByDefault,
|
|
537
|
+
},
|
|
538
|
+
placeholders: configs,
|
|
539
|
+
plugins: Array.from(this._plugins.keys()),
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Export configuration as JSON string
|
|
545
|
+
* @param {number} [spaces=2] - JSON indentation
|
|
546
|
+
* @returns {string}
|
|
547
|
+
*/
|
|
548
|
+
toJSON(spaces = 2) {
|
|
549
|
+
return JSON.stringify(this.serialize(), null, spaces);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Migrate configuration to current schema version
|
|
554
|
+
* @param {Object} config - Configuration to migrate
|
|
555
|
+
* @returns {Object} Migrated configuration
|
|
556
|
+
*/
|
|
557
|
+
static migrateConfig(config) {
|
|
558
|
+
let migrated = { ...config };
|
|
559
|
+
let version = config.schemaVersion || 1;
|
|
560
|
+
|
|
561
|
+
// Apply migrations sequentially
|
|
562
|
+
while (version < SCHEMA_VERSION) {
|
|
563
|
+
const migration = SCHEMA_MIGRATIONS[version];
|
|
564
|
+
if (migration) {
|
|
565
|
+
migrated = migration(migrated);
|
|
566
|
+
}
|
|
567
|
+
version++;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
return migrated;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Validate configuration against current schema
|
|
575
|
+
* @param {Object} config - Configuration to validate
|
|
576
|
+
* @returns {{valid: boolean, errors: string[]}}
|
|
577
|
+
*/
|
|
578
|
+
static validateConfig(config) {
|
|
579
|
+
const errors = [];
|
|
580
|
+
|
|
581
|
+
if (!config || typeof config !== 'object') {
|
|
582
|
+
return { valid: false, errors: ['Configuration must be an object'] };
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Check schema version
|
|
586
|
+
if (config.schemaVersion && config.schemaVersion > SCHEMA_VERSION) {
|
|
587
|
+
errors.push(
|
|
588
|
+
`Configuration schema version ${config.schemaVersion} is newer than supported ${SCHEMA_VERSION}`
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Validate required fields for placeholder configs
|
|
593
|
+
if (config.placeholders) {
|
|
594
|
+
for (const [name, placeholder] of Object.entries(config.placeholders)) {
|
|
595
|
+
if (!placeholder.name) {
|
|
596
|
+
errors.push(`Placeholder "${name}" missing required field: name`);
|
|
597
|
+
}
|
|
598
|
+
if (!placeholder.type) {
|
|
599
|
+
errors.push(`Placeholder "${name}" missing required field: type`);
|
|
600
|
+
}
|
|
601
|
+
if (placeholder.type && !RESOLVER_TYPES[placeholder.type]) {
|
|
602
|
+
errors.push(`Placeholder "${name}" has invalid type: ${placeholder.type}`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return { valid: errors.length === 0, errors };
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Load configuration from serialized data
|
|
612
|
+
* Note: Only loads metadata - resolvers must be re-registered
|
|
613
|
+
* @param {Object} data - Serialized configuration
|
|
614
|
+
* @param {Object} [resolvers={}] - Map of resolver functions by name
|
|
615
|
+
* @returns {PlaceholderRegistry} New registry instance
|
|
616
|
+
*/
|
|
617
|
+
static deserialize(data, resolvers = {}) {
|
|
618
|
+
// Validate
|
|
619
|
+
const validation = PlaceholderRegistry.validateConfig(data);
|
|
620
|
+
if (!validation.valid) {
|
|
621
|
+
throw new Error(`Invalid configuration: ${validation.errors.join(', ')}`);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Migrate if needed
|
|
625
|
+
const migrated = PlaceholderRegistry.migrateConfig(data);
|
|
626
|
+
|
|
627
|
+
// Create registry with options
|
|
628
|
+
const options = migrated.registryOptions || {};
|
|
629
|
+
const registry = new PlaceholderRegistry({
|
|
630
|
+
cache: options.cacheEnabled,
|
|
631
|
+
strict: options.strictMode,
|
|
632
|
+
secure: options.secureByDefault,
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
// Register placeholders with provided resolvers
|
|
636
|
+
if (migrated.placeholders) {
|
|
637
|
+
for (const [name, config] of Object.entries(migrated.placeholders)) {
|
|
638
|
+
const resolver = resolvers[name];
|
|
639
|
+
if (resolver) {
|
|
640
|
+
registry.register(name, resolver, {
|
|
641
|
+
description: config.description,
|
|
642
|
+
type: config.type,
|
|
643
|
+
secure: config.secure,
|
|
644
|
+
cacheable: config.cacheable,
|
|
645
|
+
source: config.source,
|
|
646
|
+
contextMetadata: config.contextMetadata,
|
|
647
|
+
validatorName: config.validatorName,
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return registry;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Parse JSON configuration and create registry
|
|
658
|
+
* @param {string} json - JSON string
|
|
659
|
+
* @param {Object} [resolvers={}] - Map of resolver functions by name
|
|
660
|
+
* @returns {PlaceholderRegistry}
|
|
661
|
+
*/
|
|
662
|
+
static fromJSON(json, resolvers = {}) {
|
|
663
|
+
const data = JSON.parse(json);
|
|
664
|
+
return PlaceholderRegistry.deserialize(data, resolvers);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Check if configuration needs migration
|
|
669
|
+
* @param {Object} config - Configuration to check
|
|
670
|
+
* @returns {boolean}
|
|
671
|
+
*/
|
|
672
|
+
static needsMigration(config) {
|
|
673
|
+
const version = config.schemaVersion || 1;
|
|
674
|
+
return version < SCHEMA_VERSION;
|
|
675
|
+
}
|
|
473
676
|
}
|
|
474
677
|
|
|
475
678
|
// =============================================================================
|
|
@@ -608,6 +811,8 @@ function resetRegistry() {
|
|
|
608
811
|
module.exports = {
|
|
609
812
|
PlaceholderRegistry,
|
|
610
813
|
RESOLVER_TYPES,
|
|
814
|
+
SCHEMA_VERSION,
|
|
815
|
+
SCHEMA_MIGRATIONS,
|
|
611
816
|
createCountResolver,
|
|
612
817
|
createListResolver,
|
|
613
818
|
createStaticResolver,
|