@pennyfarthing/shared 10.2.0 → 10.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/repos-topology.d.ts +49 -0
- package/dist/repos-topology.d.ts.map +1 -0
- package/dist/repos-topology.js +101 -0
- package/dist/repos-topology.js.map +1 -0
- package/dist/repos-topology.test.d.ts +8 -0
- package/dist/repos-topology.test.d.ts.map +1 -0
- package/dist/repos-topology.test.js +250 -0
- package/dist/repos-topology.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repos-topology.ts — Schema validation for repos.yaml topology fields
|
|
3
|
+
*
|
|
4
|
+
* Story 87-1: Extend repos.yaml schema with ownership and boundaries.
|
|
5
|
+
* Validates the spatial awareness fields that agents use to know which
|
|
6
|
+
* repo owns which paths, what's off-limits, and rendering context.
|
|
7
|
+
*/
|
|
8
|
+
/** Valid values for the ui_layer field */
|
|
9
|
+
export type UiLayer = 'react' | 'cli' | 'none';
|
|
10
|
+
/** Topology-extended repo entry */
|
|
11
|
+
export interface RepoTopology {
|
|
12
|
+
path: string;
|
|
13
|
+
type: string;
|
|
14
|
+
description: string;
|
|
15
|
+
language?: string;
|
|
16
|
+
build_command?: string;
|
|
17
|
+
test_command?: string;
|
|
18
|
+
lint_command?: string;
|
|
19
|
+
notes?: string;
|
|
20
|
+
owns?: string[];
|
|
21
|
+
never_edit?: string[];
|
|
22
|
+
symlinks?: Record<string, string>;
|
|
23
|
+
ui_layer?: UiLayer;
|
|
24
|
+
components_path?: string;
|
|
25
|
+
}
|
|
26
|
+
/** Top-level repos.yaml structure */
|
|
27
|
+
export interface ReposConfig {
|
|
28
|
+
repos: Record<string, RepoTopology>;
|
|
29
|
+
}
|
|
30
|
+
/** Validation result */
|
|
31
|
+
export interface ValidationResult {
|
|
32
|
+
valid: boolean;
|
|
33
|
+
errors: string[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Validate a parsed repos.yaml config for topology schema compliance.
|
|
37
|
+
* Returns { valid, errors } — never throws.
|
|
38
|
+
*/
|
|
39
|
+
export declare function validateReposTopology(config: unknown): ValidationResult;
|
|
40
|
+
/**
|
|
41
|
+
* Load and validate repos.yaml from a file path.
|
|
42
|
+
* Returns the parsed config if valid, or errors if not.
|
|
43
|
+
*/
|
|
44
|
+
export declare function loadReposConfig(filePath: string): Promise<{
|
|
45
|
+
success: boolean;
|
|
46
|
+
data?: ReposConfig;
|
|
47
|
+
errors?: string[];
|
|
48
|
+
}>;
|
|
49
|
+
//# sourceMappingURL=repos-topology.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repos-topology.d.ts","sourceRoot":"","sources":["../src/repos-topology.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,0CAA0C;AAC1C,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAE/C,mCAAmC;AACnC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,qCAAqC;AACrC,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACrC;AAED,wBAAwB;AACxB,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAmED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,gBAAgB,CAgBvE;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,WAAW,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAc5H"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repos-topology.ts — Schema validation for repos.yaml topology fields
|
|
3
|
+
*
|
|
4
|
+
* Story 87-1: Extend repos.yaml schema with ownership and boundaries.
|
|
5
|
+
* Validates the spatial awareness fields that agents use to know which
|
|
6
|
+
* repo owns which paths, what's off-limits, and rendering context.
|
|
7
|
+
*/
|
|
8
|
+
const VALID_UI_LAYERS = ['react', 'cli', 'none'];
|
|
9
|
+
function isRecord(v) {
|
|
10
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
11
|
+
}
|
|
12
|
+
function validateStringArray(value, repoName, field, errors) {
|
|
13
|
+
if (!Array.isArray(value)) {
|
|
14
|
+
errors.push(`repos.${repoName}.${field} must be an array`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
for (let i = 0; i < value.length; i++) {
|
|
18
|
+
if (typeof value[i] !== 'string') {
|
|
19
|
+
errors.push(`repos.${repoName}.${field}[${i}] must be a string`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function validateRepo(repoName, repo, errors) {
|
|
24
|
+
if (!isRecord(repo)) {
|
|
25
|
+
errors.push(`repos.${repoName} must be an object`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Required fields
|
|
29
|
+
for (const field of ['path', 'type', 'description']) {
|
|
30
|
+
if (typeof repo[field] !== 'string') {
|
|
31
|
+
errors.push(`repos.${repoName}.${field} is required and must be a string`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Optional topology fields — validate only when present
|
|
35
|
+
if ('owns' in repo && repo.owns !== undefined) {
|
|
36
|
+
validateStringArray(repo.owns, repoName, 'owns', errors);
|
|
37
|
+
}
|
|
38
|
+
if ('never_edit' in repo && repo.never_edit !== undefined) {
|
|
39
|
+
validateStringArray(repo.never_edit, repoName, 'never_edit', errors);
|
|
40
|
+
}
|
|
41
|
+
if ('symlinks' in repo && repo.symlinks !== undefined) {
|
|
42
|
+
if (!isRecord(repo.symlinks)) {
|
|
43
|
+
errors.push(`repos.${repoName}.symlinks must be an object`);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
for (const [key, val] of Object.entries(repo.symlinks)) {
|
|
47
|
+
if (typeof val !== 'string') {
|
|
48
|
+
errors.push(`repos.${repoName}.symlinks["${key}"] must be a string`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if ('ui_layer' in repo && repo.ui_layer !== undefined) {
|
|
54
|
+
if (typeof repo.ui_layer !== 'string' || !VALID_UI_LAYERS.includes(repo.ui_layer)) {
|
|
55
|
+
errors.push(`repos.${repoName}.ui_layer must be one of: ${VALID_UI_LAYERS.join(', ')}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if ('components_path' in repo && repo.components_path !== undefined) {
|
|
59
|
+
if (typeof repo.components_path !== 'string') {
|
|
60
|
+
errors.push(`repos.${repoName}.components_path must be a string`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Validate a parsed repos.yaml config for topology schema compliance.
|
|
66
|
+
* Returns { valid, errors } — never throws.
|
|
67
|
+
*/
|
|
68
|
+
export function validateReposTopology(config) {
|
|
69
|
+
const errors = [];
|
|
70
|
+
if (!isRecord(config)) {
|
|
71
|
+
return { valid: false, errors: ['config must be an object'] };
|
|
72
|
+
}
|
|
73
|
+
if (!isRecord(config.repos)) {
|
|
74
|
+
return { valid: false, errors: ['config.repos must be an object'] };
|
|
75
|
+
}
|
|
76
|
+
for (const [name, repo] of Object.entries(config.repos)) {
|
|
77
|
+
validateRepo(name, repo, errors);
|
|
78
|
+
}
|
|
79
|
+
return { valid: errors.length === 0, errors };
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Load and validate repos.yaml from a file path.
|
|
83
|
+
* Returns the parsed config if valid, or errors if not.
|
|
84
|
+
*/
|
|
85
|
+
export async function loadReposConfig(filePath) {
|
|
86
|
+
try {
|
|
87
|
+
const { readFileSync } = await import('node:fs');
|
|
88
|
+
const { parse } = await import('yaml');
|
|
89
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
90
|
+
const parsed = parse(content);
|
|
91
|
+
const result = validateReposTopology(parsed);
|
|
92
|
+
if (!result.valid) {
|
|
93
|
+
return { success: false, errors: result.errors };
|
|
94
|
+
}
|
|
95
|
+
return { success: true, data: parsed };
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
return { success: false, errors: [err.message] };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=repos-topology.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repos-topology.js","sourceRoot":"","sources":["../src/repos-topology.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repos-topology.test.ts — Tests for repos.yaml topology schema validation
|
|
3
|
+
*
|
|
4
|
+
* Story 87-1: Extend repos.yaml schema with ownership and boundaries
|
|
5
|
+
* RED STATE: These tests should FAIL until Dev implements validateReposTopology()
|
|
6
|
+
*/
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=repos-topology.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repos-topology.test.d.ts","sourceRoot":"","sources":["../src/repos-topology.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* repos-topology.test.ts — Tests for repos.yaml topology schema validation
|
|
3
|
+
*
|
|
4
|
+
* Story 87-1: Extend repos.yaml schema with ownership and boundaries
|
|
5
|
+
* RED STATE: These tests should FAIL until Dev implements validateReposTopology()
|
|
6
|
+
*/
|
|
7
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
8
|
+
import { describe, it } from 'node:test';
|
|
9
|
+
import assert from 'node:assert';
|
|
10
|
+
import { validateReposTopology } from './repos-topology.js';
|
|
11
|
+
// ── Fixtures ──────────────────────────────────────────────────────────
|
|
12
|
+
/** Minimal valid config with all topology fields */
|
|
13
|
+
const VALID_CONFIG = {
|
|
14
|
+
repos: {
|
|
15
|
+
orchestrator: {
|
|
16
|
+
path: '.',
|
|
17
|
+
type: 'orchestrator',
|
|
18
|
+
description: 'Sprint management and coordination',
|
|
19
|
+
language: 'javascript',
|
|
20
|
+
build_command: 'npm install',
|
|
21
|
+
owns: ['sprint/**', 'docs/**', '.session/**'],
|
|
22
|
+
never_edit: ['node_modules/**', '.pennyfarthing/**'],
|
|
23
|
+
symlinks: {
|
|
24
|
+
'.pennyfarthing/agents': 'pennyfarthing/pennyfarthing-dist/agents',
|
|
25
|
+
'.pennyfarthing/scripts': 'pennyfarthing/pennyfarthing-dist/scripts',
|
|
26
|
+
},
|
|
27
|
+
ui_layer: 'none',
|
|
28
|
+
},
|
|
29
|
+
pennyfarthing: {
|
|
30
|
+
path: 'pennyfarthing',
|
|
31
|
+
type: 'framework',
|
|
32
|
+
description: 'Pennyfarthing framework source',
|
|
33
|
+
language: 'typescript',
|
|
34
|
+
test_command: 'pnpm test',
|
|
35
|
+
build_command: 'pnpm build',
|
|
36
|
+
owns: ['pennyfarthing-dist/**', 'packages/**'],
|
|
37
|
+
never_edit: ['node_modules/**', 'dist/**'],
|
|
38
|
+
symlinks: {},
|
|
39
|
+
ui_layer: 'react',
|
|
40
|
+
components_path: 'packages/cyclist/src/components',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
/** Config with only legacy fields (no topology) — backwards compatible */
|
|
45
|
+
const LEGACY_CONFIG = {
|
|
46
|
+
repos: {
|
|
47
|
+
orchestrator: {
|
|
48
|
+
path: '.',
|
|
49
|
+
type: 'orchestrator',
|
|
50
|
+
description: 'Sprint management',
|
|
51
|
+
language: 'javascript',
|
|
52
|
+
build_command: 'npm install',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
// ── AC1: owns field ───────────────────────────────────────────────────
|
|
57
|
+
describe('AC1: owns field — glob patterns for directory ownership', () => {
|
|
58
|
+
it('accepts valid owns array with glob patterns', () => {
|
|
59
|
+
const result = validateReposTopology(VALID_CONFIG);
|
|
60
|
+
assert.strictEqual(result.valid, true, `Expected valid but got errors: ${result.errors.join(', ')}`);
|
|
61
|
+
});
|
|
62
|
+
it('rejects owns when not an array', () => {
|
|
63
|
+
const config = structuredClone(VALID_CONFIG);
|
|
64
|
+
config.repos.orchestrator.owns = 'sprint/**';
|
|
65
|
+
const result = validateReposTopology(config);
|
|
66
|
+
assert.strictEqual(result.valid, false);
|
|
67
|
+
assert.ok(result.errors.some(e => e.includes('owns')), 'Should mention owns field');
|
|
68
|
+
});
|
|
69
|
+
it('rejects owns with non-string entries', () => {
|
|
70
|
+
const config = structuredClone(VALID_CONFIG);
|
|
71
|
+
config.repos.orchestrator.owns = ['sprint/**', 42, null];
|
|
72
|
+
const result = validateReposTopology(config);
|
|
73
|
+
assert.strictEqual(result.valid, false);
|
|
74
|
+
assert.ok(result.errors.some(e => e.includes('owns')), 'Should mention owns field');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
// ── AC2: never_edit field ─────────────────────────────────────────────
|
|
78
|
+
describe('AC2: never_edit field — off-limits paths', () => {
|
|
79
|
+
it('accepts valid never_edit array', () => {
|
|
80
|
+
const result = validateReposTopology(VALID_CONFIG);
|
|
81
|
+
assert.strictEqual(result.valid, true, `Expected valid but got errors: ${result.errors.join(', ')}`);
|
|
82
|
+
});
|
|
83
|
+
it('rejects never_edit when not an array', () => {
|
|
84
|
+
const config = structuredClone(VALID_CONFIG);
|
|
85
|
+
config.repos.orchestrator.never_edit = 'node_modules/**';
|
|
86
|
+
const result = validateReposTopology(config);
|
|
87
|
+
assert.strictEqual(result.valid, false);
|
|
88
|
+
assert.ok(result.errors.some(e => e.includes('never_edit')), 'Should mention never_edit field');
|
|
89
|
+
});
|
|
90
|
+
it('rejects never_edit with non-string entries', () => {
|
|
91
|
+
const config = structuredClone(VALID_CONFIG);
|
|
92
|
+
config.repos.pennyfarthing.never_edit = [123];
|
|
93
|
+
const result = validateReposTopology(config);
|
|
94
|
+
assert.strictEqual(result.valid, false);
|
|
95
|
+
assert.ok(result.errors.some(e => e.includes('never_edit')), 'Should mention never_edit field');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
// ── AC3: symlinks mapping ─────────────────────────────────────────────
|
|
99
|
+
describe('AC3: symlinks field — mapping of symlink paths to sources', () => {
|
|
100
|
+
it('accepts valid symlinks object', () => {
|
|
101
|
+
const result = validateReposTopology(VALID_CONFIG);
|
|
102
|
+
assert.strictEqual(result.valid, true, `Expected valid but got errors: ${result.errors.join(', ')}`);
|
|
103
|
+
});
|
|
104
|
+
it('accepts empty symlinks object', () => {
|
|
105
|
+
const config = structuredClone(VALID_CONFIG);
|
|
106
|
+
config.repos.orchestrator.symlinks = {};
|
|
107
|
+
const result = validateReposTopology(config);
|
|
108
|
+
assert.strictEqual(result.valid, true, `Expected valid but got errors: ${result.errors.join(', ')}`);
|
|
109
|
+
});
|
|
110
|
+
it('rejects symlinks when not an object', () => {
|
|
111
|
+
const config = structuredClone(VALID_CONFIG);
|
|
112
|
+
config.repos.orchestrator.symlinks = ['.pennyfarthing/agents'];
|
|
113
|
+
const result = validateReposTopology(config);
|
|
114
|
+
assert.strictEqual(result.valid, false);
|
|
115
|
+
assert.ok(result.errors.some(e => e.includes('symlinks')), 'Should mention symlinks field');
|
|
116
|
+
});
|
|
117
|
+
it('rejects symlinks with non-string values', () => {
|
|
118
|
+
const config = structuredClone(VALID_CONFIG);
|
|
119
|
+
config.repos.orchestrator.symlinks = { '.pennyfarthing/agents': 42 };
|
|
120
|
+
const result = validateReposTopology(config);
|
|
121
|
+
assert.strictEqual(result.valid, false);
|
|
122
|
+
assert.ok(result.errors.some(e => e.includes('symlinks')), 'Should mention symlinks field');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
// ── AC4: ui_layer field ───────────────────────────────────────────────
|
|
126
|
+
describe('AC4: ui_layer field — rendering context', () => {
|
|
127
|
+
it('accepts react as ui_layer', () => {
|
|
128
|
+
const config = structuredClone(VALID_CONFIG);
|
|
129
|
+
config.repos.pennyfarthing.ui_layer = 'react';
|
|
130
|
+
const result = validateReposTopology(config);
|
|
131
|
+
assert.strictEqual(result.valid, true, `Expected valid but got errors: ${result.errors.join(', ')}`);
|
|
132
|
+
});
|
|
133
|
+
it('accepts cli as ui_layer', () => {
|
|
134
|
+
const config = structuredClone(VALID_CONFIG);
|
|
135
|
+
config.repos.pennyfarthing.ui_layer = 'cli';
|
|
136
|
+
const result = validateReposTopology(config);
|
|
137
|
+
assert.strictEqual(result.valid, true, `Expected valid but got errors: ${result.errors.join(', ')}`);
|
|
138
|
+
});
|
|
139
|
+
it('accepts none as ui_layer', () => {
|
|
140
|
+
const config = structuredClone(VALID_CONFIG);
|
|
141
|
+
config.repos.orchestrator.ui_layer = 'none';
|
|
142
|
+
const result = validateReposTopology(config);
|
|
143
|
+
assert.strictEqual(result.valid, true, `Expected valid but got errors: ${result.errors.join(', ')}`);
|
|
144
|
+
});
|
|
145
|
+
it('rejects invalid ui_layer value', () => {
|
|
146
|
+
const config = structuredClone(VALID_CONFIG);
|
|
147
|
+
config.repos.orchestrator.ui_layer = 'angular';
|
|
148
|
+
const result = validateReposTopology(config);
|
|
149
|
+
assert.strictEqual(result.valid, false);
|
|
150
|
+
assert.ok(result.errors.some(e => e.includes('ui_layer')), 'Should mention ui_layer field');
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
// ── AC5: components_path field (optional) ─────────────────────────────
|
|
154
|
+
describe('AC5: components_path field — optional UI component location', () => {
|
|
155
|
+
it('accepts config with components_path set', () => {
|
|
156
|
+
const result = validateReposTopology(VALID_CONFIG);
|
|
157
|
+
assert.strictEqual(result.valid, true, `Expected valid but got errors: ${result.errors.join(', ')}`);
|
|
158
|
+
assert.strictEqual(VALID_CONFIG.repos.pennyfarthing.components_path, 'packages/cyclist/src/components');
|
|
159
|
+
});
|
|
160
|
+
it('accepts config without components_path (optional field)', () => {
|
|
161
|
+
const config = structuredClone(VALID_CONFIG);
|
|
162
|
+
delete config.repos.orchestrator.components_path;
|
|
163
|
+
const result = validateReposTopology(config);
|
|
164
|
+
assert.strictEqual(result.valid, true, `Expected valid but got errors: ${result.errors.join(', ')}`);
|
|
165
|
+
});
|
|
166
|
+
it('rejects components_path when not a string', () => {
|
|
167
|
+
const config = structuredClone(VALID_CONFIG);
|
|
168
|
+
config.repos.pennyfarthing.components_path = ['src/components'];
|
|
169
|
+
const result = validateReposTopology(config);
|
|
170
|
+
assert.strictEqual(result.valid, false);
|
|
171
|
+
assert.ok(result.errors.some(e => e.includes('components_path')), 'Should mention components_path field');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
// ── AC6: Schema validation ────────────────────────────────────────────
|
|
175
|
+
describe('AC6: Schema validation — structural correctness', () => {
|
|
176
|
+
it('rejects null input', () => {
|
|
177
|
+
const result = validateReposTopology(null);
|
|
178
|
+
assert.strictEqual(result.valid, false);
|
|
179
|
+
});
|
|
180
|
+
it('rejects input without repos key', () => {
|
|
181
|
+
const result = validateReposTopology({ notRepos: {} });
|
|
182
|
+
assert.strictEqual(result.valid, false);
|
|
183
|
+
assert.ok(result.errors.some(e => e.includes('repos')));
|
|
184
|
+
});
|
|
185
|
+
it('rejects repos that is not an object', () => {
|
|
186
|
+
const result = validateReposTopology({ repos: 'bad' });
|
|
187
|
+
assert.strictEqual(result.valid, false);
|
|
188
|
+
});
|
|
189
|
+
it('rejects repo entry without required path field', () => {
|
|
190
|
+
const config = {
|
|
191
|
+
repos: {
|
|
192
|
+
myrepo: { type: 'app', description: 'test' },
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
const result = validateReposTopology(config);
|
|
196
|
+
assert.strictEqual(result.valid, false);
|
|
197
|
+
assert.ok(result.errors.some(e => e.includes('path')));
|
|
198
|
+
});
|
|
199
|
+
it('rejects repo entry without required type field', () => {
|
|
200
|
+
const config = {
|
|
201
|
+
repos: {
|
|
202
|
+
myrepo: { path: '.', description: 'test' },
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
const result = validateReposTopology(config);
|
|
206
|
+
assert.strictEqual(result.valid, false);
|
|
207
|
+
assert.ok(result.errors.some(e => e.includes('type')));
|
|
208
|
+
});
|
|
209
|
+
it('rejects repo entry without required description field', () => {
|
|
210
|
+
const config = {
|
|
211
|
+
repos: {
|
|
212
|
+
myrepo: { path: '.', type: 'app' },
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
const result = validateReposTopology(config);
|
|
216
|
+
assert.strictEqual(result.valid, false);
|
|
217
|
+
assert.ok(result.errors.some(e => e.includes('description')));
|
|
218
|
+
});
|
|
219
|
+
it('validates a fully populated config', () => {
|
|
220
|
+
const result = validateReposTopology(VALID_CONFIG);
|
|
221
|
+
assert.strictEqual(result.valid, true, `Expected valid but got errors: ${result.errors.join(', ')}`);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
// ── AC7: Backwards compatibility ──────────────────────────────────────
|
|
225
|
+
describe('AC7: Backwards compatibility — legacy configs still valid', () => {
|
|
226
|
+
it('accepts legacy config without any topology fields', () => {
|
|
227
|
+
const result = validateReposTopology(LEGACY_CONFIG);
|
|
228
|
+
assert.strictEqual(result.valid, true, `Legacy config should be valid. Errors: ${result.errors.join(', ')}`);
|
|
229
|
+
});
|
|
230
|
+
it('accepts config with only some topology fields set', () => {
|
|
231
|
+
const config = structuredClone(LEGACY_CONFIG);
|
|
232
|
+
config.repos.orchestrator.owns = ['sprint/**'];
|
|
233
|
+
// No never_edit, symlinks, ui_layer, or components_path
|
|
234
|
+
const result = validateReposTopology(config);
|
|
235
|
+
assert.strictEqual(result.valid, true, `Partial topology should be valid. Errors: ${result.errors.join(', ')}`);
|
|
236
|
+
});
|
|
237
|
+
it('preserves existing fields alongside new topology fields', () => {
|
|
238
|
+
const config = structuredClone(VALID_CONFIG);
|
|
239
|
+
// Verify base fields still present
|
|
240
|
+
assert.strictEqual(config.repos.orchestrator.path, '.');
|
|
241
|
+
assert.strictEqual(config.repos.orchestrator.type, 'orchestrator');
|
|
242
|
+
assert.strictEqual(config.repos.orchestrator.description, 'Sprint management and coordination');
|
|
243
|
+
assert.strictEqual(config.repos.orchestrator.language, 'javascript');
|
|
244
|
+
assert.strictEqual(config.repos.orchestrator.build_command, 'npm install');
|
|
245
|
+
// And topology fields coexist
|
|
246
|
+
assert.ok(Array.isArray(config.repos.orchestrator.owns));
|
|
247
|
+
assert.ok(Array.isArray(config.repos.orchestrator.never_edit));
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
//# sourceMappingURL=repos-topology.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repos-topology.test.js","sourceRoot":"","sources":["../src/repos-topology.test.ts"],"names":[],"mappings":"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"}
|