@sprig-and-prose/sprig-universe 0.2.0 → 0.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/package.json +1 -1
- package/src/ast.js +30 -32
- package/src/cli.js +1 -210
- package/src/graph.js +635 -147
- package/src/ir.js +21 -6
- package/src/parser.js +225 -215
- package/test/fixtures/amaranthine-mini.prose +14 -8
- package/test/fixtures/multi-file-universe-a.prose +9 -4
- package/test/fixtures/multi-file-universe-b.prose +5 -4
- package/test/fixtures/named-duplicate.prose +6 -4
- package/test/fixtures/reference-attachments.prose +19 -0
- package/test/fixtures/reference-commas.prose +15 -0
- package/test/fixtures/reference-inline.prose +14 -0
- package/test/fixtures/reference-raw-url.prose +9 -0
- package/test/fixtures/reference-repo-paths.prose +11 -0
- package/test/fixtures/reference-unknown.prose +7 -0
- package/test/references.test.js +105 -0
- package/test/universe-basic.test.js +21 -166
- package/repositories/sprig-repository-github/index.js +0 -29
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
universe Amaranthine {
|
|
2
|
+
repository AmaranthineRepo {
|
|
3
|
+
url { 'https://example.com/amaranthine' }
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
reference ItemsData in AmaranthineRepo {
|
|
7
|
+
paths { '/data/items.yaml' }
|
|
8
|
+
}
|
|
9
|
+
|
|
2
10
|
series Items {
|
|
3
11
|
describe {
|
|
4
12
|
Items are objects that can exist in the world.
|
|
@@ -6,10 +14,7 @@ universe Amaranthine {
|
|
|
6
14
|
}
|
|
7
15
|
|
|
8
16
|
references {
|
|
9
|
-
|
|
10
|
-
repository { 'amaranthine' }
|
|
11
|
-
paths { '/data/items.yaml' }
|
|
12
|
-
}
|
|
17
|
+
ItemsData
|
|
13
18
|
}
|
|
14
19
|
}
|
|
15
20
|
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
universe Amaranthine {
|
|
2
|
+
reference SkillsData in AmaranthineRepo {
|
|
3
|
+
paths { '/data/skills.yaml' }
|
|
4
|
+
}
|
|
5
|
+
|
|
2
6
|
series Skills {
|
|
3
7
|
describe {
|
|
4
8
|
Skills represent abilities that players can learn.
|
|
@@ -6,10 +10,7 @@ universe Amaranthine {
|
|
|
6
10
|
}
|
|
7
11
|
|
|
8
12
|
references {
|
|
9
|
-
|
|
10
|
-
repository { 'amaranthine' }
|
|
11
|
-
paths { '/data/skills.yaml' }
|
|
12
|
-
}
|
|
13
|
+
SkillsData
|
|
13
14
|
}
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
universe Test {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
repository BackendRepo {
|
|
3
|
+
url { 'https://example.com/backend' }
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
reference ItemRouter in BackendRepo {
|
|
4
7
|
paths { '/src/routers/items.ts' }
|
|
5
8
|
}
|
|
6
9
|
|
|
7
|
-
reference ItemRouter {
|
|
8
|
-
repository { 'amaranthine-backend' }
|
|
10
|
+
reference ItemRouter in BackendRepo {
|
|
9
11
|
paths { '/src/routers/items-v2.ts' }
|
|
10
12
|
}
|
|
11
13
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
universe Test {
|
|
2
|
+
repository Repo {
|
|
3
|
+
url { 'https://example.com/base' }
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
reference Alpha in Repo {
|
|
7
|
+
paths { '/alpha' }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
reference Beta in Repo {
|
|
11
|
+
paths { '/beta' }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
concept Widget {
|
|
15
|
+
references { Alpha Beta }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tests for repositories + references
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { test } from 'node:test';
|
|
6
|
+
import { readFileSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
import { dirname } from 'path';
|
|
10
|
+
import { parseFiles } from '../src/index.js';
|
|
11
|
+
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
const fixturesDir = join(__dirname, 'fixtures');
|
|
15
|
+
|
|
16
|
+
function loadGraph(fixtureName) {
|
|
17
|
+
const file = join(fixturesDir, fixtureName);
|
|
18
|
+
const text = readFileSync(file, 'utf-8');
|
|
19
|
+
return parseFiles([{ file, text }]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test('raw url reference parses with computed urls', () => {
|
|
23
|
+
const graph = loadGraph('reference-raw-url.prose');
|
|
24
|
+
const ref = graph.references['Test:reference:ConfluenceDoc'];
|
|
25
|
+
assert(ref !== undefined, 'Reference should exist');
|
|
26
|
+
assert(ref.urls.length === 1, 'Reference should have one URL');
|
|
27
|
+
assert(
|
|
28
|
+
ref.urls[0] === 'https://example.com/docs/confluence',
|
|
29
|
+
'Reference should preserve raw URL',
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('repository references compute urls with join rules', () => {
|
|
34
|
+
const graph = loadGraph('reference-repo-paths.prose');
|
|
35
|
+
const ref = graph.references['Test:reference:DataFiles'];
|
|
36
|
+
assert(ref !== undefined, 'Reference should exist');
|
|
37
|
+
assert(ref.urls.length === 2, 'Reference should have two URLs');
|
|
38
|
+
assert(
|
|
39
|
+
ref.urls[0] === 'https://example.com/base/data/items',
|
|
40
|
+
'Reference should join base + leading-slash path',
|
|
41
|
+
);
|
|
42
|
+
assert(
|
|
43
|
+
ref.urls[1] === 'https://example.com/base/data/other',
|
|
44
|
+
'Reference should join base + non-leading path',
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('commas are ignored in paths and references lists', () => {
|
|
49
|
+
const graph = loadGraph('reference-commas.prose');
|
|
50
|
+
const ref = graph.references['Test:reference:PathsRef'];
|
|
51
|
+
assert(ref !== undefined, 'Reference should exist');
|
|
52
|
+
assert(ref.paths.length === 3, 'Paths list should contain 3 entries');
|
|
53
|
+
|
|
54
|
+
const seriesNode = graph.nodes['Test:series:Things'];
|
|
55
|
+
assert(seriesNode !== undefined, 'Series node should exist');
|
|
56
|
+
assert(seriesNode.references?.length === 1, 'Series should have one reference attached');
|
|
57
|
+
assert(seriesNode.references[0] === 'Test:reference:PathsRef', 'Reference ID should resolve');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('references list attaches to concepts and resolves', () => {
|
|
61
|
+
const graph = loadGraph('reference-attachments.prose');
|
|
62
|
+
const conceptNode = graph.nodes['Test:concept:Widget'];
|
|
63
|
+
assert(conceptNode !== undefined, 'Concept node should exist');
|
|
64
|
+
assert(conceptNode.references?.length === 2, 'Concept should have two references');
|
|
65
|
+
assert(
|
|
66
|
+
conceptNode.references.includes('Test:reference:Alpha'),
|
|
67
|
+
'Concept should include Alpha reference',
|
|
68
|
+
);
|
|
69
|
+
assert(
|
|
70
|
+
conceptNode.references.includes('Test:reference:Beta'),
|
|
71
|
+
'Concept should include Beta reference',
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('unknown reference names emit a calm error but do not crash', () => {
|
|
76
|
+
const graph = loadGraph('reference-unknown.prose');
|
|
77
|
+
const errors = graph.diagnostics.filter((d) => d.severity === 'error');
|
|
78
|
+
const unknownErrors = errors.filter((e) => e.message.includes('Unknown reference'));
|
|
79
|
+
assert(unknownErrors.length > 0, 'Should have error for unknown reference');
|
|
80
|
+
assert(graph.universes.Test !== undefined, 'Graph should still be constructed');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('inline reference in block attaches with derived name', () => {
|
|
84
|
+
const graph = loadGraph('reference-inline.prose');
|
|
85
|
+
const conceptNode = graph.nodes['Test:concept:Widget'];
|
|
86
|
+
assert(conceptNode !== undefined, 'Concept node should exist');
|
|
87
|
+
assert(conceptNode.references?.length === 1, 'Concept should have one reference');
|
|
88
|
+
const refId = conceptNode.references[0];
|
|
89
|
+
const ref = graph.references[refId];
|
|
90
|
+
assert(ref !== undefined, 'Reference should exist');
|
|
91
|
+
assert(ref.urls.length === 1, 'Reference should have one URL');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Simple assertion helper
|
|
96
|
+
* @param {boolean} condition
|
|
97
|
+
* @param {string} message
|
|
98
|
+
*/
|
|
99
|
+
function assert(condition, message) {
|
|
100
|
+
if (!condition) {
|
|
101
|
+
throw new Error(message);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
@@ -73,15 +73,19 @@ test('parses amaranthine-mini.prose', () => {
|
|
|
73
73
|
'Normalized field should be a string',
|
|
74
74
|
);
|
|
75
75
|
|
|
76
|
-
// Check references
|
|
76
|
+
// Check references list
|
|
77
77
|
assert(playerNode.references !== undefined, 'Player should have references');
|
|
78
78
|
assert(playerNode.references.length > 0, 'Player should have at least one reference');
|
|
79
|
-
const
|
|
80
|
-
assert(
|
|
81
|
-
|
|
82
|
-
assert(ref
|
|
83
|
-
assert(ref.
|
|
84
|
-
assert(ref.
|
|
79
|
+
const refId = playerNode.references[0];
|
|
80
|
+
assert(refId !== undefined, 'Player should have a reference id');
|
|
81
|
+
const ref = graph.references[refId];
|
|
82
|
+
assert(ref !== undefined, 'Reference should exist in graph');
|
|
83
|
+
assert(ref.name === 'PlayerRouter', 'Reference should have correct name');
|
|
84
|
+
assert(ref.urls.length === 1, 'Reference should have one URL');
|
|
85
|
+
assert(
|
|
86
|
+
ref.urls[0] === 'https://example.com/amaranthine/backends/api/src/routers/players.js',
|
|
87
|
+
'Reference should have correct computed URL',
|
|
88
|
+
);
|
|
85
89
|
|
|
86
90
|
// Check source spans
|
|
87
91
|
assert(universeNode.source !== undefined, 'Universe node should have source span');
|
|
@@ -428,6 +432,9 @@ test('merges multiple files with same universe name', () => {
|
|
|
428
432
|
// References should be merged (both files had references)
|
|
429
433
|
assert(universeNode.references !== undefined, 'Universe should have references');
|
|
430
434
|
assert(universeNode.references.length === 2, 'Universe should have 2 references (one from each file)');
|
|
435
|
+
for (const refId of universeNode.references) {
|
|
436
|
+
assert(graph.references[refId] !== undefined, 'Reference id should resolve');
|
|
437
|
+
}
|
|
431
438
|
|
|
432
439
|
// No errors should be present
|
|
433
440
|
const errors = graph.diagnostics.filter((d) => d.severity === 'error');
|
|
@@ -517,8 +524,10 @@ test('preserves source locations for merged content', () => {
|
|
|
517
524
|
assert(universeNode.references !== undefined, 'Universe should have references');
|
|
518
525
|
assert(universeNode.references.length === 2, 'Should have 2 references');
|
|
519
526
|
|
|
520
|
-
// Check that
|
|
521
|
-
for (const
|
|
527
|
+
// Check that reference models have source locations
|
|
528
|
+
for (const refId of universeNode.references) {
|
|
529
|
+
const ref = graph.references[refId];
|
|
530
|
+
assert(ref !== undefined, 'Reference should exist in graph');
|
|
522
531
|
assert(ref.source !== undefined, 'Reference should have source location');
|
|
523
532
|
assert(ref.source.file !== undefined, 'Reference source should have file path');
|
|
524
533
|
}
|
|
@@ -576,41 +585,6 @@ test('deterministic output order', () => {
|
|
|
576
585
|
);
|
|
577
586
|
});
|
|
578
587
|
|
|
579
|
-
test('parses named reference blocks at universe scope', () => {
|
|
580
|
-
const file = join(fixturesDir, 'named-reference.prose');
|
|
581
|
-
const text = readFileSync(file, 'utf-8');
|
|
582
|
-
const graph = parseFiles([{ file, text }]);
|
|
583
|
-
|
|
584
|
-
// Check universe exists
|
|
585
|
-
assert(graph.universes.Test !== undefined, 'Universe Test should exist');
|
|
586
|
-
|
|
587
|
-
const universeNodeId = graph.universes.Test.root;
|
|
588
|
-
const universeNode = graph.nodes[universeNodeId];
|
|
589
|
-
assert(universeNode !== undefined, 'Universe node should exist');
|
|
590
|
-
|
|
591
|
-
// Check named reference exists in registry
|
|
592
|
-
assert(graph.referencesByName !== undefined, 'referencesByName registry should exist');
|
|
593
|
-
assert(graph.referencesByName.Test !== undefined, 'Universe Test should have referencesByName');
|
|
594
|
-
assert(graph.referencesByName.Test.ItemRouter !== undefined, 'ItemRouter named reference should exist');
|
|
595
|
-
|
|
596
|
-
const namedRef = graph.referencesByName.Test.ItemRouter;
|
|
597
|
-
assert(namedRef.name === 'ItemRouter', 'Named reference should have correct name');
|
|
598
|
-
assert(namedRef.repository === 'amaranthine-backend', 'Named reference should have repository');
|
|
599
|
-
assert(namedRef.paths.length === 1, 'Named reference should have paths');
|
|
600
|
-
assert(namedRef.paths[0] === '/src/routers/items.ts', 'Named reference should have correct path');
|
|
601
|
-
assert(namedRef.describe !== undefined, 'Named reference should have describe');
|
|
602
|
-
assert(namedRef.describe.raw.includes('Routes that implement item endpoints'), 'Named reference describe should contain expected text');
|
|
603
|
-
assert(namedRef.source !== undefined, 'Named reference should have source location');
|
|
604
|
-
|
|
605
|
-
// Check that inline reference still works
|
|
606
|
-
const itemsNodeId = `${universeNodeId.split(':')[0]}:series:Items`;
|
|
607
|
-
const itemsNode = graph.nodes[itemsNodeId];
|
|
608
|
-
assert(itemsNode !== undefined, 'Items series should exist');
|
|
609
|
-
assert(itemsNode.references !== undefined, 'Items series should have references');
|
|
610
|
-
assert(itemsNode.references.length === 1, 'Items series should have one inline reference');
|
|
611
|
-
assert(itemsNode.references[0].repository === 'amaranthine-backend', 'Inline reference should have repository');
|
|
612
|
-
});
|
|
613
|
-
|
|
614
588
|
test('parses named document blocks at universe scope', () => {
|
|
615
589
|
const file = join(fixturesDir, 'named-document.prose');
|
|
616
590
|
const text = readFileSync(file, 'utf-8');
|
|
@@ -643,43 +617,24 @@ test('parses named document blocks at universe scope', () => {
|
|
|
643
617
|
assert(itemsNode.documentation[0].kind === 'internal', 'Inline document should have kind');
|
|
644
618
|
});
|
|
645
619
|
|
|
646
|
-
test('detects duplicate named
|
|
620
|
+
test('detects duplicate named document names', () => {
|
|
647
621
|
const file = join(fixturesDir, 'named-duplicate.prose');
|
|
648
622
|
const text = readFileSync(file, 'utf-8');
|
|
649
623
|
const graph = parseFiles([{ file, text }]);
|
|
650
624
|
|
|
651
|
-
// Should have errors for duplicate
|
|
625
|
+
// Should have errors for duplicate named documents (2 errors per duplicate: one for duplicate, one pointing to first)
|
|
652
626
|
const errors = graph.diagnostics.filter((d) => d.severity === 'error');
|
|
653
|
-
const duplicateRefErrors = errors.filter((e) => e.message.includes('Duplicate named reference') || e.message.includes('First declaration of named reference'));
|
|
654
627
|
const duplicateDocErrors = errors.filter((e) => e.message.includes('Duplicate named document') || e.message.includes('First declaration of named document'));
|
|
655
628
|
|
|
656
|
-
assert(duplicateRefErrors.length >= 2, `Should have errors for duplicate named reference (duplicate + first declaration), got ${duplicateRefErrors.length}: ${duplicateRefErrors.map(e => e.message).join('; ')}`);
|
|
657
629
|
assert(duplicateDocErrors.length >= 2, `Should have errors for duplicate named document (duplicate + first declaration), got ${duplicateDocErrors.length}: ${duplicateDocErrors.map(e => e.message).join('; ')}`);
|
|
658
630
|
|
|
659
|
-
// Check that error messages include both locations
|
|
660
|
-
const refError = duplicateRefErrors.find((e) => e.message.includes('ItemRouter') && e.message.includes('Duplicate'));
|
|
661
|
-
assert(refError !== undefined, 'Should have error for duplicate ItemRouter');
|
|
662
|
-
assert(refError.source !== undefined, 'Error should have source location');
|
|
663
|
-
assert(refError.message.includes('First declared at'), 'Error should mention first declaration location');
|
|
664
|
-
|
|
665
631
|
const docError = duplicateDocErrors.find((e) => e.message.includes('ItemsDesignDoc') && e.message.includes('Duplicate'));
|
|
666
632
|
assert(docError !== undefined, 'Should have error for duplicate ItemsDesignDoc');
|
|
667
633
|
assert(docError.source !== undefined, 'Error should have source location');
|
|
668
634
|
assert(docError.message.includes('First declared at'), 'Error should mention first declaration location');
|
|
669
635
|
});
|
|
670
636
|
|
|
671
|
-
test('preserves source locations for named
|
|
672
|
-
const file = join(fixturesDir, 'named-reference.prose');
|
|
673
|
-
const text = readFileSync(file, 'utf-8');
|
|
674
|
-
const graph = parseFiles([{ file, text }]);
|
|
675
|
-
|
|
676
|
-
const namedRef = graph.referencesByName.Test.ItemRouter;
|
|
677
|
-
assert(namedRef.source !== undefined, 'Named reference should have source');
|
|
678
|
-
assert(namedRef.source.file === file, 'Named reference source should reference correct file');
|
|
679
|
-
assert(namedRef.source.start !== undefined, 'Named reference source should have start');
|
|
680
|
-
assert(namedRef.source.end !== undefined, 'Named reference source should have end');
|
|
681
|
-
assert(namedRef.source.start.offset < namedRef.source.end.offset, 'Source offsets should be monotonic');
|
|
682
|
-
|
|
637
|
+
test('preserves source locations for named documents', () => {
|
|
683
638
|
const file2 = join(fixturesDir, 'named-document.prose');
|
|
684
639
|
const text2 = readFileSync(file2, 'utf-8');
|
|
685
640
|
const graph2 = parseFiles([{ file: file2, text: text2 }]);
|
|
@@ -691,106 +646,6 @@ test('preserves source locations for named blocks', () => {
|
|
|
691
646
|
assert(namedDoc.source.end !== undefined, 'Named document source should have end');
|
|
692
647
|
});
|
|
693
648
|
|
|
694
|
-
test('parses using blocks in references', () => {
|
|
695
|
-
const file = join(fixturesDir, 'using-in-references.prose');
|
|
696
|
-
const text = readFileSync(file, 'utf-8');
|
|
697
|
-
const graph = parseFiles([{ file, text }]);
|
|
698
|
-
|
|
699
|
-
// Check universe exists
|
|
700
|
-
assert(graph.universes.Test !== undefined, 'Universe Test should exist');
|
|
701
|
-
|
|
702
|
-
const universeNodeId = graph.universes.Test.root;
|
|
703
|
-
|
|
704
|
-
// Check that named references exist
|
|
705
|
-
assert(graph.referencesByName.Test.ItemRouter !== undefined, 'ItemRouter named reference should exist');
|
|
706
|
-
assert(graph.referencesByName.Test.PlayerRouter !== undefined, 'PlayerRouter named reference should exist');
|
|
707
|
-
|
|
708
|
-
// Check Items series has using block resolved
|
|
709
|
-
const itemsNodeId = `${universeNodeId.split(':')[0]}:series:Items`;
|
|
710
|
-
const itemsNode = graph.nodes[itemsNodeId];
|
|
711
|
-
assert(itemsNode !== undefined, 'Items series should exist');
|
|
712
|
-
assert(itemsNode.references !== undefined, 'Items series should have references');
|
|
713
|
-
assert(itemsNode.references.length === 1, 'Items series should have one reference from using');
|
|
714
|
-
assert(itemsNode.references[0].repository === 'amaranthine-backend', 'Resolved reference should have repository');
|
|
715
|
-
assert(itemsNode.references[0].paths[0] === '/src/routers/items.ts', 'Resolved reference should have correct path');
|
|
716
|
-
assert(itemsNode.references[0].describe !== undefined, 'Resolved reference should have describe');
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
test('parses mixed inline and using blocks in references', () => {
|
|
720
|
-
const file = join(fixturesDir, 'using-in-references.prose');
|
|
721
|
-
const text = readFileSync(file, 'utf-8');
|
|
722
|
-
const graph = parseFiles([{ file, text }]);
|
|
723
|
-
|
|
724
|
-
const universeNodeId = graph.universes.Test.root;
|
|
725
|
-
|
|
726
|
-
// Check Players series has both inline and using references
|
|
727
|
-
const playersNodeId = `${universeNodeId.split(':')[0]}:series:Players`;
|
|
728
|
-
const playersNode = graph.nodes[playersNodeId];
|
|
729
|
-
assert(playersNode !== undefined, 'Players series should exist');
|
|
730
|
-
assert(playersNode.references !== undefined, 'Players series should have references');
|
|
731
|
-
assert(playersNode.references.length === 2, 'Players series should have 2 references (inline + using)');
|
|
732
|
-
|
|
733
|
-
// First should be inline reference
|
|
734
|
-
assert(playersNode.references[0].repository === 'amaranthine-backend', 'First reference should have repository');
|
|
735
|
-
assert(playersNode.references[0].paths[0] === '/src/players/helpers.ts', 'First reference should be inline');
|
|
736
|
-
|
|
737
|
-
// Second should be resolved from using
|
|
738
|
-
assert(playersNode.references[1].repository === 'amaranthine-backend', 'Second reference should have repository');
|
|
739
|
-
assert(playersNode.references[1].paths[0] === '/src/routers/players.ts', 'Second reference should be from using');
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
test('parses multiple names in using block', () => {
|
|
743
|
-
const file = join(fixturesDir, 'using-in-references.prose');
|
|
744
|
-
const text = readFileSync(file, 'utf-8');
|
|
745
|
-
const graph = parseFiles([{ file, text }]);
|
|
746
|
-
|
|
747
|
-
const universeNodeId = graph.universes.Test.root;
|
|
748
|
-
|
|
749
|
-
// Check Mixed series has multiple references from single using block
|
|
750
|
-
const mixedNodeId = `${universeNodeId.split(':')[0]}:series:Mixed`;
|
|
751
|
-
const mixedNode = graph.nodes[mixedNodeId];
|
|
752
|
-
assert(mixedNode !== undefined, 'Mixed series should exist');
|
|
753
|
-
assert(mixedNode.references !== undefined, 'Mixed series should have references');
|
|
754
|
-
assert(mixedNode.references.length === 2, 'Mixed series should have 2 references from using block');
|
|
755
|
-
|
|
756
|
-
// Both should be resolved
|
|
757
|
-
assert(mixedNode.references[0].paths[0] === '/src/routers/items.ts', 'First reference should be ItemRouter');
|
|
758
|
-
assert(mixedNode.references[1].paths[0] === '/src/routers/players.ts', 'Second reference should be PlayerRouter');
|
|
759
|
-
});
|
|
760
|
-
|
|
761
|
-
test('errors on unknown reference name in using block', () => {
|
|
762
|
-
const file = join(fixturesDir, 'using-unknown.prose');
|
|
763
|
-
const text = readFileSync(file, 'utf-8');
|
|
764
|
-
const graph = parseFiles([{ file, text }]);
|
|
765
|
-
|
|
766
|
-
// Should have error for unknown reference
|
|
767
|
-
const errors = graph.diagnostics.filter((d) => d.severity === 'error');
|
|
768
|
-
const unknownRefErrors = errors.filter((e) => e.message.includes('Unknown reference'));
|
|
769
|
-
|
|
770
|
-
assert(unknownRefErrors.length > 0, 'Should have error for unknown reference');
|
|
771
|
-
const error = unknownRefErrors.find((e) => e.message.includes('UnknownRouter'));
|
|
772
|
-
assert(error !== undefined, 'Should have error for UnknownRouter');
|
|
773
|
-
assert(error.source !== undefined, 'Error should have source location');
|
|
774
|
-
assert(error.message.includes("Unknown reference 'UnknownRouter'"), 'Error message should mention UnknownRouter');
|
|
775
|
-
});
|
|
776
|
-
|
|
777
|
-
test('inline references still work unchanged', () => {
|
|
778
|
-
const file = join(fixturesDir, 'amaranthine-mini.prose');
|
|
779
|
-
const text = readFileSync(file, 'utf-8');
|
|
780
|
-
const graph = parseFiles([{ file, text }]);
|
|
781
|
-
|
|
782
|
-
const universeNodeId = graph.universes.Amaranthine.root;
|
|
783
|
-
|
|
784
|
-
// Check Player series has inline references (baseline)
|
|
785
|
-
const playerNodeId = `${universeNodeId.split(':')[0]}:series:Player`;
|
|
786
|
-
const playerNode = graph.nodes[playerNodeId];
|
|
787
|
-
assert(playerNode !== undefined, 'Player series should exist');
|
|
788
|
-
assert(playerNode.references !== undefined, 'Player should have references');
|
|
789
|
-
assert(playerNode.references.length === 1, 'Player should have one inline reference');
|
|
790
|
-
assert(playerNode.references[0].repository === 'amaranthine', 'Inline reference should have repository');
|
|
791
|
-
assert(playerNode.references[0].paths[0] === '/backends/api/src/routers/players.js', 'Inline reference should have path');
|
|
792
|
-
});
|
|
793
|
-
|
|
794
649
|
/**
|
|
795
650
|
* Simple assertion helper
|
|
796
651
|
* @param {boolean} condition
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview GitHub repository handler for Sprig
|
|
3
|
-
*
|
|
4
|
-
* This repository handler provides functionality for working with GitHub repositories.
|
|
5
|
-
* It can be extended to support cloning, fetching, and other GitHub-specific operations.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Repository handler interface
|
|
10
|
-
* @typedef {Object} RepositoryHandler
|
|
11
|
-
* @property {string} kind - Repository kind identifier
|
|
12
|
-
* @property {Function} [clone] - Clone repository function (optional)
|
|
13
|
-
* @property {Function} [fetch] - Fetch repository function (optional)
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* GitHub repository handler
|
|
18
|
-
* @param {Record<string, string | number>} options - Repository options
|
|
19
|
-
* @returns {RepositoryHandler}
|
|
20
|
-
*/
|
|
21
|
-
export function createGitHubRepository(options) {
|
|
22
|
-
return {
|
|
23
|
-
kind: 'sprig-repository-github',
|
|
24
|
-
// Future: Add clone, fetch, and other GitHub-specific operations here
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export default createGitHubRepository;
|
|
29
|
-
|