@sprig-and-prose/sprig 0.9.0 → 0.10.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/CHANGELOG.md +33 -0
- package/README.md +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +22 -129
- package/dist/cli.js.map +1 -1
- package/dist/compiler.d.ts +1 -3
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +30 -63
- package/dist/compiler.js.map +1 -1
- package/dist/diagnostics.d.ts +7 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +69 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/prose.d.ts +1 -1
- package/dist/prose.d.ts.map +1 -1
- package/dist/prose.js +2 -5
- package/dist/prose.js.map +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +11 -12
- package/dist/ui.js.map +1 -1
- package/package.json +4 -7
- package/src/cli.ts +21 -168
- package/src/compiler.ts +40 -76
- package/src/diagnostics.ts +100 -0
- package/src/prose.ts +2 -5
- package/src/ui.ts +9 -12
- package/tests/compile.test.js +44 -17
- package/tests/compile.test.ts +65 -14
- package/tests/init.test.ts +8 -0
- package/tests/root.test.js +1 -1
- package/scripts/switch-deps.js +0 -44
- package/src/scene-compiler.ts +0 -192
- package/src/scene-discovery.ts +0 -51
- package/tests/compile-scene-only.test.js +0 -109
- package/tests/fixtures/scene-only-invalid/.sprig-out/bad.error.txt +0 -2
- package/tests/fixtures/scene-only-invalid/bad.scene.prose +0 -10
- package/tests/fixtures/scene-only-valid/.sprig-emit/AnotherScene.scene.json +0 -15
- package/tests/fixtures/scene-only-valid/.sprig-emit/SimpleScene.scene.json +0 -15
- package/tests/fixtures/scene-only-valid/.sprig-out/AnotherScene.scene.json +0 -15
- package/tests/fixtures/scene-only-valid/.sprig-out/SimpleScene.scene.json +0 -15
- package/tests/fixtures/scene-only-valid/another.scene.prose +0 -3
- package/tests/fixtures/scene-only-valid/simple.scene.prose +0 -3
- package/tests/fixtures/universe-unchanged/.sprig/TestScene.scene.json +0 -15
- package/tests/fixtures/universe-unchanged/custom-out/TestScene.scene.json +0 -15
- package/tests/fixtures/universe-unchanged/scene.scene.prose +0 -3
- package/tests/fixtures/universe-upstream/subdir/.sprig/OneScene.scene.json +0 -15
- package/tests/fixtures/universe-upstream/subdir/one.scene.prose +0 -3
package/src/compiler.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync, mkdirSync, renameSync } from "node:fs";
|
|
2
2
|
import { join, dirname } from "node:path";
|
|
3
3
|
import { execSync } from "node:child_process";
|
|
4
|
-
// @ts-expect-error -
|
|
5
|
-
import {
|
|
4
|
+
// @ts-expect-error - prose-parser package does not ship TS types
|
|
5
|
+
import { compileFiles as compileProseFiles } from "@sprig-and-prose/prose-parser";
|
|
6
6
|
|
|
7
7
|
export interface CompileResult {
|
|
8
8
|
success: boolean;
|
|
9
|
-
manifest?: Record<string, unknown> & {
|
|
9
|
+
manifest?: Record<string, unknown> & {
|
|
10
|
+
generatedAt: string;
|
|
11
|
+
meta?: { generatedAt: string; manifestId: string };
|
|
12
|
+
};
|
|
10
13
|
diagnostics: Array<{ severity: string; message: string; source?: unknown }>;
|
|
11
14
|
}
|
|
12
15
|
|
|
@@ -25,64 +28,22 @@ function generateManifestId(): string {
|
|
|
25
28
|
return `time:${new Date().toISOString()}`;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
/**
|
|
29
|
-
* Validates that exactly one universe declaration exists in the parsed graph
|
|
30
|
-
*/
|
|
31
|
-
function validateUniverseCount(graph: Record<string, unknown>): {
|
|
32
|
-
valid: boolean;
|
|
33
|
-
universeName?: string;
|
|
34
|
-
error?: string;
|
|
35
|
-
} {
|
|
36
|
-
const universes = graph.universes as Record<string, unknown> | undefined;
|
|
37
|
-
const universeNames = Object.keys(universes || {});
|
|
38
|
-
|
|
39
|
-
if (universeNames.length === 0) {
|
|
40
|
-
return {
|
|
41
|
-
valid: false,
|
|
42
|
-
error: "No universe declaration found. At least one universe declaration is required.",
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (universeNames.length > 1) {
|
|
47
|
-
const nodes = graph.nodes as Record<string, { source?: { file?: string } }> | undefined;
|
|
48
|
-
const fileList = Array.from(universeNames)
|
|
49
|
-
.map((name) => {
|
|
50
|
-
const universe = universes?.[name] as { root?: string } | undefined;
|
|
51
|
-
const rootNode = universe?.root && nodes ? nodes[universe.root] : null;
|
|
52
|
-
return rootNode?.source?.file || "unknown";
|
|
53
|
-
})
|
|
54
|
-
.filter((file, index, arr) => arr.indexOf(file) === index) // unique files
|
|
55
|
-
.sort()
|
|
56
|
-
.join(", ");
|
|
57
|
-
return {
|
|
58
|
-
valid: false,
|
|
59
|
-
error: `Multiple distinct universes found: ${universeNames.join(", ")}. Files: ${fileList}. Exactly one universe declaration is required.`,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
valid: true,
|
|
65
|
-
universeName: universeNames[0],
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
31
|
/**
|
|
70
32
|
* Compiles prose files into a manifest
|
|
71
33
|
* @param universeRoot - Universe root directory
|
|
72
34
|
* @param files - Array of prose file paths
|
|
73
35
|
* @param writeManifest - If true, write manifest to disk
|
|
74
|
-
* @param options - Compilation options (
|
|
36
|
+
* @param options - Compilation options (outputDir)
|
|
75
37
|
* @returns Compile result with success status, manifest, and diagnostics
|
|
76
38
|
*/
|
|
77
39
|
export async function compileUniverse(
|
|
78
40
|
universeRoot: string,
|
|
79
41
|
files: string[],
|
|
80
42
|
writeManifest = true,
|
|
81
|
-
options?: {
|
|
43
|
+
options?: { outputDir?: string },
|
|
82
44
|
): Promise<CompileResult> {
|
|
83
45
|
const outputDir = options?.outputDir || join(universeRoot, ".sprig");
|
|
84
46
|
const manifestPath = join(outputDir, "manifest.json");
|
|
85
|
-
const useLegacy = options?.legacy ?? false;
|
|
86
47
|
|
|
87
48
|
// Read and parse files
|
|
88
49
|
const fileContents = files.map((file) => ({
|
|
@@ -91,43 +52,46 @@ export async function compileUniverse(
|
|
|
91
52
|
}));
|
|
92
53
|
|
|
93
54
|
try {
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
// Validate exactly one universe
|
|
97
|
-
const validation = validateUniverseCount(graph);
|
|
98
|
-
if (!validation.valid) {
|
|
99
|
-
return {
|
|
100
|
-
success: false,
|
|
101
|
-
diagnostics: [
|
|
102
|
-
{
|
|
103
|
-
severity: "error",
|
|
104
|
-
message: validation.error || "Unknown validation error",
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
};
|
|
108
|
-
}
|
|
55
|
+
const result = compileProseFiles(fileContents);
|
|
56
|
+
const baseDiagnostics = result.diagnostics || [];
|
|
109
57
|
|
|
110
|
-
// Add
|
|
58
|
+
// Add metadata to manifest
|
|
111
59
|
const generatedAt = new Date().toISOString();
|
|
112
60
|
const manifestId = generateManifestId();
|
|
113
|
-
|
|
114
|
-
const manifest =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
61
|
+
|
|
62
|
+
const manifest = result.manifest
|
|
63
|
+
? {
|
|
64
|
+
...result.manifest,
|
|
65
|
+
generatedAt,
|
|
66
|
+
meta: {
|
|
67
|
+
generatedAt,
|
|
68
|
+
manifestId,
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
: undefined;
|
|
123
72
|
|
|
124
73
|
// Check for parsing errors
|
|
125
|
-
const hasErrors =
|
|
74
|
+
const hasErrors = baseDiagnostics.some(
|
|
75
|
+
(d: { severity: string }) => d.severity === "error",
|
|
76
|
+
);
|
|
126
77
|
if (hasErrors) {
|
|
127
78
|
return {
|
|
128
79
|
success: false,
|
|
129
80
|
manifest,
|
|
130
|
-
diagnostics:
|
|
81
|
+
diagnostics: baseDiagnostics,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!manifest) {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
diagnostics: [
|
|
89
|
+
...baseDiagnostics,
|
|
90
|
+
{
|
|
91
|
+
severity: "error",
|
|
92
|
+
message: "Compilation produced no manifest output.",
|
|
93
|
+
},
|
|
94
|
+
],
|
|
131
95
|
};
|
|
132
96
|
}
|
|
133
97
|
|
|
@@ -151,7 +115,7 @@ export async function compileUniverse(
|
|
|
151
115
|
return {
|
|
152
116
|
success: true,
|
|
153
117
|
manifest,
|
|
154
|
-
diagnostics:
|
|
118
|
+
diagnostics: baseDiagnostics,
|
|
155
119
|
};
|
|
156
120
|
} catch (error) {
|
|
157
121
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { basename } from "node:path";
|
|
2
|
+
|
|
3
|
+
export type Diagnostic = { severity: string; message: string; source?: unknown };
|
|
4
|
+
|
|
5
|
+
export function printDiagnostics(diagnostics: Diagnostic[], label: "Error" | "Warning"): void {
|
|
6
|
+
const grouped = new Map<
|
|
7
|
+
string,
|
|
8
|
+
{ count: number; diagnostic: Diagnostic; diagnosticWithSource: Diagnostic | null }
|
|
9
|
+
>();
|
|
10
|
+
|
|
11
|
+
for (const diag of diagnostics) {
|
|
12
|
+
const key = diag.message;
|
|
13
|
+
const entry = grouped.get(key) || { count: 0, diagnostic: diag, diagnosticWithSource: null };
|
|
14
|
+
entry.count += 1;
|
|
15
|
+
if (hasSource(diag.source) && !entry.diagnosticWithSource) {
|
|
16
|
+
entry.diagnosticWithSource = diag;
|
|
17
|
+
}
|
|
18
|
+
grouped.set(key, entry);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const entries = Array.from(grouped.values());
|
|
22
|
+
entries.forEach(({ count, diagnostic, diagnosticWithSource }, index) => {
|
|
23
|
+
const source = formatSource(diagnosticWithSource?.source || diagnostic.source);
|
|
24
|
+
const { text, code } = extractMessageAndCode(diagnostic.message);
|
|
25
|
+
const countText = count > 1 ? ` (and ${count - 1} more)` : "";
|
|
26
|
+
const codeLabel = label === "Warning" ? "Warning Code" : "Error Code";
|
|
27
|
+
console.error(`${text}${countText}`);
|
|
28
|
+
console.error("");
|
|
29
|
+
console.error(source);
|
|
30
|
+
console.error(`${codeLabel}: ${code}`);
|
|
31
|
+
if (index < entries.length - 1) {
|
|
32
|
+
console.error("");
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function hasSource(source: unknown): boolean {
|
|
38
|
+
if (!source) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (typeof source === "string") {
|
|
43
|
+
return source.trim().length > 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof source !== "object") {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const candidate = source as {
|
|
51
|
+
file?: string;
|
|
52
|
+
source?: { file?: string };
|
|
53
|
+
location?: { file?: string };
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const span = candidate.source || candidate.location || candidate;
|
|
57
|
+
return typeof span.file === "string" && span.file.length > 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function extractMessageAndCode(message: string): { text: string; code: string } {
|
|
61
|
+
const match = message.match(/^\[(SP\d{3})\]\s*(.*)$/);
|
|
62
|
+
if (!match) {
|
|
63
|
+
return { text: message, code: "UNKNOWN" };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
code: match[1],
|
|
68
|
+
text: match[2] || "Unknown error.",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function formatSource(source: unknown): string {
|
|
73
|
+
if (!source) {
|
|
74
|
+
return "<unknown>:0:0";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (typeof source === "string") {
|
|
78
|
+
return `${basename(source)}:0:0`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (typeof source !== "object") {
|
|
82
|
+
return "<unknown>:0:0";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const candidate = source as {
|
|
86
|
+
file?: string;
|
|
87
|
+
line?: number;
|
|
88
|
+
col?: number;
|
|
89
|
+
start?: { line?: number; col?: number };
|
|
90
|
+
source?: { file?: string; start?: { line?: number; col?: number } };
|
|
91
|
+
location?: { file?: string; start?: { line?: number; col?: number } };
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const span = candidate.source || candidate.location || candidate;
|
|
95
|
+
const file = span.file ? basename(span.file) : "<unknown>";
|
|
96
|
+
const line = span.start?.line ?? candidate.line ?? 0;
|
|
97
|
+
const col = span.start?.col ?? candidate.col ?? 0;
|
|
98
|
+
|
|
99
|
+
return `${file}:${line}:${col}`;
|
|
100
|
+
}
|
package/src/prose.ts
CHANGED
|
@@ -4,7 +4,7 @@ import fastGlob from "fast-glob";
|
|
|
4
4
|
const DEFAULT_EXCLUDES = [".sprig/**", "dist/**", "node_modules/**", ".git/**"];
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Loads all .prose files under root
|
|
7
|
+
* Loads all .prose files under root with default excludes
|
|
8
8
|
* @param root - Universe root directory
|
|
9
9
|
* @returns Array of prose file paths, sorted deterministically
|
|
10
10
|
*/
|
|
@@ -12,10 +12,7 @@ export async function loadProseFiles(root: string): Promise<string[]> {
|
|
|
12
12
|
const pattern = join(root, "**/*.prose");
|
|
13
13
|
const allFiles = await fastGlob(pattern, {
|
|
14
14
|
absolute: true,
|
|
15
|
-
ignore: [
|
|
16
|
-
...DEFAULT_EXCLUDES.map((exclude) => join(root, exclude)),
|
|
17
|
-
join(root, "**/*.scene.prose"), // Exclude scene files
|
|
18
|
-
],
|
|
15
|
+
ignore: [...DEFAULT_EXCLUDES.map((exclude) => join(root, exclude))],
|
|
19
16
|
});
|
|
20
17
|
|
|
21
18
|
// Sort for deterministic ordering
|
package/src/ui.ts
CHANGED
|
@@ -7,6 +7,7 @@ import chokidar from "chokidar";
|
|
|
7
7
|
import { discoverUniverseRoot } from "./root.js";
|
|
8
8
|
import { loadProseFiles } from "./prose.js";
|
|
9
9
|
import { compileUniverse } from "./compiler.js";
|
|
10
|
+
import { printDiagnostics } from "./diagnostics.js";
|
|
10
11
|
|
|
11
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
13
|
const __dirname = dirname(__filename);
|
|
@@ -29,14 +30,14 @@ const mimeTypes: Record<string, string> = {
|
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
|
-
* Resolves the dist directory path for sprig-ui
|
|
33
|
+
* Resolves the dist directory path for sprig-ui
|
|
33
34
|
* Works for both local and global installations
|
|
34
35
|
*/
|
|
35
36
|
function resolveUIDist(): string {
|
|
36
37
|
try {
|
|
37
38
|
// Use Node's module resolution to find the package location
|
|
38
39
|
// This works for both local (node_modules) and global installations
|
|
39
|
-
const packageJsonPath = require.resolve("@sprig-and-prose/sprig-ui
|
|
40
|
+
const packageJsonPath = require.resolve("@sprig-and-prose/sprig-ui/package.json");
|
|
40
41
|
const packageDir = dirname(packageJsonPath);
|
|
41
42
|
const distPath = join(packageDir, "dist");
|
|
42
43
|
|
|
@@ -47,13 +48,13 @@ function resolveUIDist(): string {
|
|
|
47
48
|
throw new Error(`Found package at ${packageDir} but dist directory does not exist`);
|
|
48
49
|
} catch (error) {
|
|
49
50
|
// Fallback: try relative to current package (for development)
|
|
50
|
-
const fallbackPath = resolve(__dirname, "../../sprig-ui
|
|
51
|
+
const fallbackPath = resolve(__dirname, "../../sprig-ui/dist");
|
|
51
52
|
if (existsSync(fallbackPath)) {
|
|
52
53
|
return fallbackPath;
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
56
|
-
throw new Error(`Could not locate sprig-ui
|
|
57
|
+
throw new Error(`Could not locate sprig-ui dist directory: ${errorMessage}`);
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -196,11 +197,10 @@ function createServerHandler(
|
|
|
196
197
|
async function rebuildManifest(
|
|
197
198
|
universeRoot: string,
|
|
198
199
|
manifestPath: string,
|
|
199
|
-
legacy: boolean,
|
|
200
200
|
): Promise<boolean> {
|
|
201
201
|
try {
|
|
202
202
|
const files = await loadProseFiles(universeRoot);
|
|
203
|
-
const result = await compileUniverse(universeRoot, files, true
|
|
203
|
+
const result = await compileUniverse(universeRoot, files, true);
|
|
204
204
|
|
|
205
205
|
// Only broadcast if compilation succeeded (manifest was atomically written)
|
|
206
206
|
if (result.success) {
|
|
@@ -219,7 +219,6 @@ async function rebuildManifest(
|
|
|
219
219
|
export async function startUIServer(
|
|
220
220
|
universeRoot: string,
|
|
221
221
|
port: number = 6336,
|
|
222
|
-
legacy = false,
|
|
223
222
|
): Promise<void> {
|
|
224
223
|
const uiDistDir = resolveUIDist();
|
|
225
224
|
const manifestPath = join(universeRoot, ".sprig", "manifest.json");
|
|
@@ -227,14 +226,12 @@ export async function startUIServer(
|
|
|
227
226
|
// Compile once on startup
|
|
228
227
|
console.log("Compiling universe...");
|
|
229
228
|
const files = await loadProseFiles(universeRoot);
|
|
230
|
-
const result = await compileUniverse(universeRoot, files, true
|
|
229
|
+
const result = await compileUniverse(universeRoot, files, true);
|
|
231
230
|
|
|
232
231
|
if (!result.success) {
|
|
233
232
|
const errors = result.diagnostics.filter((d) => d.severity === "error");
|
|
234
233
|
if (errors.length > 0) {
|
|
235
|
-
|
|
236
|
-
console.error(`Error: ${error.message}`);
|
|
237
|
-
}
|
|
234
|
+
printDiagnostics(errors, "Error");
|
|
238
235
|
}
|
|
239
236
|
process.exit(1);
|
|
240
237
|
}
|
|
@@ -292,7 +289,7 @@ export async function startUIServer(
|
|
|
292
289
|
}
|
|
293
290
|
|
|
294
291
|
rebuildTimeout = setTimeout(async () => {
|
|
295
|
-
const success = await rebuildManifest(universeRoot, manifestPath
|
|
292
|
+
const success = await rebuildManifest(universeRoot, manifestPath);
|
|
296
293
|
if (success) {
|
|
297
294
|
// One-line status per rebuild (calm output)
|
|
298
295
|
process.stdout.write("\r✓ Rebuilt\n");
|
package/tests/compile.test.js
CHANGED
|
@@ -1,34 +1,33 @@
|
|
|
1
1
|
import { test } from "node:test";
|
|
2
2
|
import { strict as assert } from "node:assert";
|
|
3
3
|
import { mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from "node:fs";
|
|
4
|
-
import { join
|
|
5
|
-
import {
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { compileUniverse } from "../dist/compiler.js";
|
|
6
7
|
test("compileUniverse writes .sprig/manifest.json", async () => {
|
|
7
8
|
const testDir = join(tmpdir(), `sprig-test-${Date.now()}`);
|
|
8
9
|
mkdirSync(testDir, { recursive: true });
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
const universeFile = join(testDir, "universe.prose");
|
|
11
|
+
const proseFile = join(testDir, "flora.prose");
|
|
12
|
+
writeFileSync(universeFile, `universe TestUniverse {
|
|
11
13
|
describe {
|
|
12
14
|
A test universe.
|
|
13
15
|
}
|
|
14
16
|
}`);
|
|
15
|
-
|
|
16
|
-
const proseFile = join(testDir, "test.prose");
|
|
17
|
-
writeFileSync(proseFile, `series TestSeries {
|
|
17
|
+
writeFileSync(proseFile, `concept Rose in TestUniverse {
|
|
18
18
|
describe {
|
|
19
|
-
A
|
|
19
|
+
A flower.
|
|
20
20
|
}
|
|
21
21
|
}`);
|
|
22
22
|
try {
|
|
23
|
-
const result = await compileUniverse(testDir, [proseFile], true);
|
|
23
|
+
const result = await compileUniverse(testDir, [universeFile, proseFile], true);
|
|
24
24
|
assert.strictEqual(result.success, true);
|
|
25
25
|
assert.ok(result.manifest);
|
|
26
|
-
// Check that manifest.json was written
|
|
27
26
|
const manifestPath = join(testDir, ".sprig", "manifest.json");
|
|
28
27
|
assert.ok(existsSync(manifestPath), "manifest.json should exist");
|
|
29
|
-
// Check manifest content
|
|
30
28
|
const manifestContent = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
31
|
-
assert.strictEqual(manifestContent.
|
|
29
|
+
assert.strictEqual(manifestContent.universe?.name, "TestUniverse");
|
|
30
|
+
assert.ok(manifestContent.concepts?.["TestUniverse.Rose"]);
|
|
32
31
|
assert.ok(manifestContent.generatedAt);
|
|
33
32
|
}
|
|
34
33
|
finally {
|
|
@@ -38,22 +37,22 @@ test("compileUniverse writes .sprig/manifest.json", async () => {
|
|
|
38
37
|
test("compileUniverse does not write manifest when writeManifest is false", async () => {
|
|
39
38
|
const testDir = join(tmpdir(), `sprig-test-${Date.now()}`);
|
|
40
39
|
mkdirSync(testDir, { recursive: true });
|
|
41
|
-
|
|
40
|
+
const universeFile = join(testDir, "universe.prose");
|
|
41
|
+
writeFileSync(universeFile, `universe TestUniverse {
|
|
42
42
|
describe {
|
|
43
43
|
A test universe.
|
|
44
44
|
}
|
|
45
45
|
}`);
|
|
46
46
|
const proseFile = join(testDir, "test.prose");
|
|
47
|
-
writeFileSync(proseFile, `
|
|
47
|
+
writeFileSync(proseFile, `concept Lily in TestUniverse {
|
|
48
48
|
describe {
|
|
49
|
-
A test
|
|
49
|
+
A test concept.
|
|
50
50
|
}
|
|
51
51
|
}`);
|
|
52
52
|
try {
|
|
53
|
-
const result = await compileUniverse(testDir, [proseFile], false);
|
|
53
|
+
const result = await compileUniverse(testDir, [universeFile, proseFile], false);
|
|
54
54
|
assert.strictEqual(result.success, true);
|
|
55
55
|
assert.ok(result.manifest);
|
|
56
|
-
// Check that manifest.json was NOT written
|
|
57
56
|
const manifestPath = join(testDir, ".sprig", "manifest.json");
|
|
58
57
|
assert.ok(!existsSync(manifestPath), "manifest.json should not exist when writeManifest is false");
|
|
59
58
|
}
|
|
@@ -61,4 +60,32 @@ test("compileUniverse does not write manifest when writeManifest is false", asyn
|
|
|
61
60
|
rmSync(testDir, { recursive: true, force: true });
|
|
62
61
|
}
|
|
63
62
|
});
|
|
63
|
+
test("compileUniverse supports declarations split across files", async () => {
|
|
64
|
+
const testDir = join(tmpdir(), `sprig-test-${Date.now()}`);
|
|
65
|
+
mkdirSync(testDir, { recursive: true });
|
|
66
|
+
const universeFile = join(testDir, "universe.prose");
|
|
67
|
+
const relationshipsFile = join(testDir, "relationships.prose");
|
|
68
|
+
const conceptsFile = join(testDir, "concepts.prose");
|
|
69
|
+
writeFileSync(universeFile, `universe TestUniverse {
|
|
70
|
+
concept Fox {}
|
|
71
|
+
concept Chicken {}
|
|
72
|
+
}`);
|
|
73
|
+
writeFileSync(relationshipsFile, `relationship hunts and isHuntedBy in TestUniverse {}`);
|
|
74
|
+
writeFileSync(conceptsFile, `concept Raid in TestUniverse {
|
|
75
|
+
relationships {
|
|
76
|
+
hunts {
|
|
77
|
+
TestUniverse.Fox
|
|
78
|
+
TestUniverse.Chicken
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}`);
|
|
82
|
+
try {
|
|
83
|
+
const result = await compileUniverse(testDir, [universeFile, relationshipsFile, conceptsFile], false);
|
|
84
|
+
assert.strictEqual(result.success, true);
|
|
85
|
+
assert.deepEqual(result.manifest?.concepts?.["TestUniverse.Raid"]?.relationships?.map((reference) => reference.to), ["TestUniverse.Fox", "TestUniverse.Chicken"]);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
64
91
|
//# sourceMappingURL=compile.test.js.map
|
package/tests/compile.test.ts
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
import { test } from "node:test";
|
|
2
2
|
import { strict as assert } from "node:assert";
|
|
3
3
|
import { mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from "node:fs";
|
|
4
|
-
import { join
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
5
6
|
import { compileUniverse } from "../src/compiler.js";
|
|
6
7
|
|
|
7
8
|
test("compileUniverse writes .sprig/manifest.json", async () => {
|
|
8
9
|
const testDir = join(tmpdir(), `sprig-test-${Date.now()}`);
|
|
9
10
|
mkdirSync(testDir, { recursive: true });
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
const universeFile = join(testDir, "universe.prose");
|
|
13
|
+
const proseFile = join(testDir, "flora.prose");
|
|
14
|
+
|
|
12
15
|
writeFileSync(
|
|
13
|
-
|
|
16
|
+
universeFile,
|
|
14
17
|
`universe TestUniverse {
|
|
15
18
|
describe {
|
|
16
19
|
A test universe.
|
|
17
20
|
}
|
|
18
21
|
}`,
|
|
19
22
|
);
|
|
20
|
-
|
|
21
|
-
// Create a simple prose file
|
|
22
|
-
const proseFile = join(testDir, "test.prose");
|
|
23
23
|
writeFileSync(
|
|
24
24
|
proseFile,
|
|
25
|
-
`
|
|
25
|
+
`concept Rose in TestUniverse {
|
|
26
26
|
describe {
|
|
27
|
-
A
|
|
27
|
+
A flower.
|
|
28
28
|
}
|
|
29
29
|
}`,
|
|
30
30
|
);
|
|
31
31
|
|
|
32
32
|
try {
|
|
33
|
-
const result = await compileUniverse(testDir, [proseFile], true);
|
|
33
|
+
const result = await compileUniverse(testDir, [universeFile, proseFile], true);
|
|
34
34
|
|
|
35
35
|
assert.strictEqual(result.success, true);
|
|
36
36
|
assert.ok(result.manifest);
|
|
@@ -41,7 +41,8 @@ test("compileUniverse writes .sprig/manifest.json", async () => {
|
|
|
41
41
|
|
|
42
42
|
// Check manifest content
|
|
43
43
|
const manifestContent = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
44
|
-
assert.strictEqual(manifestContent.
|
|
44
|
+
assert.strictEqual(manifestContent.universe?.name, "TestUniverse");
|
|
45
|
+
assert.ok(manifestContent.concepts?.["TestUniverse.Rose"]);
|
|
45
46
|
assert.ok(manifestContent.generatedAt);
|
|
46
47
|
} finally {
|
|
47
48
|
rmSync(testDir, { recursive: true, force: true });
|
|
@@ -52,8 +53,9 @@ test("compileUniverse does not write manifest when writeManifest is false", asyn
|
|
|
52
53
|
const testDir = join(tmpdir(), `sprig-test-${Date.now()}`);
|
|
53
54
|
mkdirSync(testDir, { recursive: true });
|
|
54
55
|
|
|
56
|
+
const universeFile = join(testDir, "universe.prose");
|
|
55
57
|
writeFileSync(
|
|
56
|
-
|
|
58
|
+
universeFile,
|
|
57
59
|
`universe TestUniverse {
|
|
58
60
|
describe {
|
|
59
61
|
A test universe.
|
|
@@ -64,15 +66,15 @@ test("compileUniverse does not write manifest when writeManifest is false", asyn
|
|
|
64
66
|
const proseFile = join(testDir, "test.prose");
|
|
65
67
|
writeFileSync(
|
|
66
68
|
proseFile,
|
|
67
|
-
`
|
|
69
|
+
`concept Lily in TestUniverse {
|
|
68
70
|
describe {
|
|
69
|
-
A test
|
|
71
|
+
A test concept.
|
|
70
72
|
}
|
|
71
73
|
}`,
|
|
72
74
|
);
|
|
73
75
|
|
|
74
76
|
try {
|
|
75
|
-
const result = await compileUniverse(testDir, [proseFile], false);
|
|
77
|
+
const result = await compileUniverse(testDir, [universeFile, proseFile], false);
|
|
76
78
|
|
|
77
79
|
assert.strictEqual(result.success, true);
|
|
78
80
|
assert.ok(result.manifest);
|
|
@@ -85,3 +87,52 @@ test("compileUniverse does not write manifest when writeManifest is false", asyn
|
|
|
85
87
|
}
|
|
86
88
|
});
|
|
87
89
|
|
|
90
|
+
test("compileUniverse supports declarations split across files", async () => {
|
|
91
|
+
const testDir = join(tmpdir(), `sprig-test-${Date.now()}`);
|
|
92
|
+
mkdirSync(testDir, { recursive: true });
|
|
93
|
+
|
|
94
|
+
const universeFile = join(testDir, "universe.prose");
|
|
95
|
+
const relationshipsFile = join(testDir, "relationships.prose");
|
|
96
|
+
const conceptsFile = join(testDir, "concepts.prose");
|
|
97
|
+
|
|
98
|
+
writeFileSync(
|
|
99
|
+
universeFile,
|
|
100
|
+
`universe TestUniverse {
|
|
101
|
+
concept Fox {}
|
|
102
|
+
concept Chicken {}
|
|
103
|
+
}`,
|
|
104
|
+
);
|
|
105
|
+
writeFileSync(
|
|
106
|
+
relationshipsFile,
|
|
107
|
+
`relationship hunts and isHuntedBy in TestUniverse {}`,
|
|
108
|
+
);
|
|
109
|
+
writeFileSync(
|
|
110
|
+
conceptsFile,
|
|
111
|
+
`concept Raid in TestUniverse {
|
|
112
|
+
relationships {
|
|
113
|
+
hunts {
|
|
114
|
+
TestUniverse.Fox
|
|
115
|
+
TestUniverse.Chicken
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}`,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const result = await compileUniverse(
|
|
123
|
+
testDir,
|
|
124
|
+
[universeFile, relationshipsFile, conceptsFile],
|
|
125
|
+
false,
|
|
126
|
+
);
|
|
127
|
+
assert.strictEqual(result.success, true);
|
|
128
|
+
assert.deepEqual(
|
|
129
|
+
result.manifest?.concepts?.["TestUniverse.Raid"]?.relationships?.map(
|
|
130
|
+
(reference: { to: string }) => reference.to,
|
|
131
|
+
),
|
|
132
|
+
["TestUniverse.Fox", "TestUniverse.Chicken"],
|
|
133
|
+
);
|
|
134
|
+
} finally {
|
|
135
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
package/tests/init.test.ts
CHANGED
|
@@ -20,6 +20,10 @@ test("init creates both files when directory is empty", async () => {
|
|
|
20
20
|
|
|
21
21
|
const universeProseContent = readFileSync(universeProsePath, "utf-8");
|
|
22
22
|
assert.ok(universeProseContent.includes("universe Primer"), "universe.prose should contain universe name");
|
|
23
|
+
assert.ok(
|
|
24
|
+
universeProseContent.includes("describe {"),
|
|
25
|
+
"universe.prose should use describe block syntax",
|
|
26
|
+
);
|
|
23
27
|
|
|
24
28
|
const readmeContent = readFileSync(readmePath, "utf-8");
|
|
25
29
|
assert.ok(readmeContent.includes("# Primer"), "readme.md should contain universe name");
|
|
@@ -102,6 +106,10 @@ test("init handles reverse partial file existence correctly", async () => {
|
|
|
102
106
|
assert.ok(existsSync(universeProsePath), "universe.prose should be created");
|
|
103
107
|
const universeProseContent = readFileSync(universeProsePath, "utf-8");
|
|
104
108
|
assert.ok(universeProseContent.includes("universe ReversePartialTest"), "universe.prose should contain new universe name");
|
|
109
|
+
assert.ok(
|
|
110
|
+
universeProseContent.includes("describe {"),
|
|
111
|
+
"universe.prose should use describe block syntax",
|
|
112
|
+
);
|
|
105
113
|
} finally {
|
|
106
114
|
rmSync(testDir, { recursive: true, force: true });
|
|
107
115
|
}
|
package/tests/root.test.js
CHANGED
|
@@ -3,7 +3,7 @@ import { strict as assert } from "node:assert";
|
|
|
3
3
|
import { mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
|
-
import { discoverUniverseRoot } from "../
|
|
6
|
+
import { discoverUniverseRoot } from "../dist/root.js";
|
|
7
7
|
test("discoverUniverseRoot finds universe.prose in current directory", () => {
|
|
8
8
|
const testDir = join(tmpdir(), `sprig-test-${Date.now()}`);
|
|
9
9
|
mkdirSync(testDir, { recursive: true });
|