@sprig-and-prose/sprig 0.8.0 → 0.9.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 +20 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +58 -18
- package/dist/cli.js.map +1 -1
- package/dist/scene-compiler.d.ts +9 -2
- package/dist/scene-compiler.d.ts.map +1 -1
- package/dist/scene-compiler.js +55 -39
- package/dist/scene-compiler.js.map +1 -1
- package/dist/scene-discovery.d.ts +11 -0
- package/dist/scene-discovery.d.ts.map +1 -0
- package/dist/scene-discovery.js +41 -0
- package/dist/scene-discovery.js.map +1 -0
- package/package.json +1 -1
- package/src/cli.ts +77 -19
- package/src/scene-compiler.ts +62 -39
- package/src/scene-discovery.ts +51 -0
- package/tests/compile-scene-only.test.js +109 -0
- package/tests/fixtures/scene-only-empty/.gitkeep +0 -0
- package/tests/fixtures/scene-only-invalid/.sprig-out/bad.error.txt +2 -0
- package/tests/fixtures/scene-only-invalid/bad.scene.prose +10 -0
- package/tests/fixtures/scene-only-valid/.sprig-emit/AnotherScene.scene.json +15 -0
- package/tests/fixtures/scene-only-valid/.sprig-emit/SimpleScene.scene.json +15 -0
- package/tests/fixtures/scene-only-valid/.sprig-out/AnotherScene.scene.json +15 -0
- package/tests/fixtures/scene-only-valid/.sprig-out/SimpleScene.scene.json +15 -0
- package/tests/fixtures/scene-only-valid/another.scene.prose +3 -0
- package/tests/fixtures/scene-only-valid/simple.scene.prose +3 -0
- package/tests/fixtures/universe-unchanged/.sprig/TestScene.scene.json +15 -0
- package/tests/fixtures/universe-unchanged/.sprig/manifest.json +62 -0
- package/tests/fixtures/universe-unchanged/custom-out/TestScene.scene.json +15 -0
- package/tests/fixtures/universe-unchanged/custom-out/manifest.json +62 -0
- package/tests/fixtures/universe-unchanged/scene.scene.prose +3 -0
- package/tests/fixtures/universe-unchanged/universe.prose +5 -0
- package/tests/fixtures/universe-upstream/subdir/.sprig/OneScene.scene.json +15 -0
- package/tests/fixtures/universe-upstream/subdir/.sprig/manifest.json +62 -0
- package/tests/fixtures/universe-upstream/subdir/one.scene.prose +3 -0
- package/tests/fixtures/universe-upstream/universe.prose +5 -0
package/src/cli.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { resolve, join } from "node:path";
|
|
4
4
|
import { existsSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { discoverUniverseRoot } from "./root.js";
|
|
6
|
+
import { discoverSceneFiles } from "./scene-discovery.js";
|
|
6
7
|
import { loadProseFiles } from "./prose.js";
|
|
7
8
|
import { compileUniverse } from "./compiler.js";
|
|
8
9
|
import { compileScenes } from "./scene-compiler.js";
|
|
@@ -26,13 +27,15 @@ function printHelp() {
|
|
|
26
27
|
console.log(" --legacy Use the legacy parser/graph pipeline");
|
|
27
28
|
console.log("");
|
|
28
29
|
console.log("Root discovery:");
|
|
29
|
-
console.log(" - Walks upward from
|
|
30
|
+
console.log(" - Walks upward from path (or cwd for 'sprig compile')");
|
|
30
31
|
console.log(" - Finds directory containing universe.prose");
|
|
32
|
+
console.log(" - Scene-only mode: requires --out when no universe.prose");
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
function parseArgs(): {
|
|
34
36
|
command: Command | null;
|
|
35
37
|
path: string;
|
|
38
|
+
pathExplicit: boolean;
|
|
36
39
|
port?: number;
|
|
37
40
|
universeName?: string;
|
|
38
41
|
legacy?: boolean;
|
|
@@ -41,7 +44,7 @@ function parseArgs(): {
|
|
|
41
44
|
const args = process.argv.slice(2);
|
|
42
45
|
|
|
43
46
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
44
|
-
return { command: null, path: process.cwd() };
|
|
47
|
+
return { command: null, path: process.cwd(), pathExplicit: false };
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
const command = args[0] as Command;
|
|
@@ -57,11 +60,12 @@ function parseArgs(): {
|
|
|
57
60
|
console.error("Usage: sprig init <UniverseName>");
|
|
58
61
|
process.exit(1);
|
|
59
62
|
}
|
|
60
|
-
return { command, path: process.cwd(), universeName: args[1] };
|
|
63
|
+
return { command, path: process.cwd(), pathExplicit: false, universeName: args[1] };
|
|
61
64
|
}
|
|
62
65
|
|
|
63
66
|
// Parse path and options
|
|
64
67
|
let path = process.cwd();
|
|
68
|
+
let pathExplicit = false;
|
|
65
69
|
let port: number | undefined;
|
|
66
70
|
let legacy = false;
|
|
67
71
|
let out: string | undefined;
|
|
@@ -92,8 +96,9 @@ function parseArgs(): {
|
|
|
92
96
|
} else if (arg === "--legacy") {
|
|
93
97
|
legacy = true;
|
|
94
98
|
} else if (!arg.startsWith("-")) {
|
|
95
|
-
// First non-option argument is the path
|
|
99
|
+
// First non-option argument is the path (in-directory)
|
|
96
100
|
path = resolve(arg);
|
|
101
|
+
pathExplicit = true;
|
|
97
102
|
} else {
|
|
98
103
|
console.error(`Error: Unknown option "${arg}"`);
|
|
99
104
|
console.error("Run 'sprig --help' for usage information");
|
|
@@ -101,22 +106,78 @@ function parseArgs(): {
|
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
108
|
|
|
104
|
-
return { command, path, port, legacy, out };
|
|
109
|
+
return { command, path, pathExplicit, port, legacy, out };
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
async function handleCompile(
|
|
112
|
+
async function handleCompile(
|
|
113
|
+
path: string,
|
|
114
|
+
outDir: string | undefined,
|
|
115
|
+
legacy = false,
|
|
116
|
+
pathExplicit = false,
|
|
117
|
+
): Promise<void> {
|
|
108
118
|
const root = discoverUniverseRoot(path);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
|
|
120
|
+
if (!pathExplicit) {
|
|
121
|
+
// sprig compile (no path): discover from cwd, require universe
|
|
122
|
+
if (!root) {
|
|
123
|
+
console.error("No universe.prose found.");
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
const outputDir = outDir || join(root, ".sprig");
|
|
127
|
+
await runUniverseCompile(root, outputDir, legacy);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// sprig compile <in-directory>: path was explicitly provided
|
|
132
|
+
if (root) {
|
|
133
|
+
// Universe found: output to in-directory/.sprig or --out
|
|
134
|
+
const outputDir = outDir || join(path, ".sprig");
|
|
135
|
+
await runUniverseCompile(root, outputDir, legacy);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Universe not found: scene-only gated by --out
|
|
140
|
+
if (!outDir) {
|
|
141
|
+
console.error(`No universe.prose found for ${path}. Provide --out <out-directory> to compile scenes only.`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const discovery = discoverSceneFiles(path);
|
|
146
|
+
if (!discovery) {
|
|
147
|
+
console.error("No universe.prose found and no *.scene.prose files found in:", path);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const sceneResult = await compileScenes(discovery.searchRoot, outDir, {
|
|
152
|
+
sceneFiles: discovery.sceneFiles,
|
|
153
|
+
emitManifests: true,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const errors = sceneResult.diagnostics.filter((d) => d.severity === "error");
|
|
157
|
+
const warnings = sceneResult.diagnostics.filter((d) => d.severity === "warning");
|
|
158
|
+
|
|
159
|
+
if (!sceneResult.success) {
|
|
160
|
+
if (errors.length > 0) {
|
|
161
|
+
printDiagnostics(errors, "Error");
|
|
162
|
+
}
|
|
113
163
|
process.exit(1);
|
|
114
164
|
}
|
|
115
165
|
|
|
116
|
-
|
|
117
|
-
const
|
|
166
|
+
const sceneCount = sceneResult.scenes.length;
|
|
167
|
+
const scenesText = sceneCount === 1 ? "scene" : "scenes";
|
|
168
|
+
console.log(`✓ Compiled ${sceneCount} ${scenesText}`);
|
|
169
|
+
|
|
170
|
+
if (warnings.length > 0) {
|
|
171
|
+
console.log("");
|
|
172
|
+
printDiagnostics(warnings, "Warning");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
118
175
|
|
|
119
|
-
|
|
176
|
+
async function runUniverseCompile(
|
|
177
|
+
root: string,
|
|
178
|
+
outputDir: string,
|
|
179
|
+
legacy: boolean,
|
|
180
|
+
): Promise<void> {
|
|
120
181
|
const files = await loadProseFiles(root);
|
|
121
182
|
const universeResult = await compileUniverse(root, files, true, { legacy, outputDir });
|
|
122
183
|
|
|
@@ -128,10 +189,8 @@ async function handleCompile(path: string, outDir: string | undefined, legacy =
|
|
|
128
189
|
process.exit(1);
|
|
129
190
|
}
|
|
130
191
|
|
|
131
|
-
// Compile scenes
|
|
132
192
|
const sceneResult = await compileScenes(root, outputDir);
|
|
133
|
-
|
|
134
|
-
// Combine diagnostics
|
|
193
|
+
|
|
135
194
|
const allDiagnostics = [...universeResult.diagnostics, ...sceneResult.diagnostics];
|
|
136
195
|
const errors = allDiagnostics.filter((d) => d.severity === "error");
|
|
137
196
|
const warnings = allDiagnostics.filter((d) => d.severity === "warning");
|
|
@@ -143,7 +202,6 @@ async function handleCompile(path: string, outDir: string | undefined, legacy =
|
|
|
143
202
|
process.exit(1);
|
|
144
203
|
}
|
|
145
204
|
|
|
146
|
-
// Print summary
|
|
147
205
|
const sceneCount = sceneResult.scenes.length;
|
|
148
206
|
const scenesText = sceneCount === 1 ? "scene" : "scenes";
|
|
149
207
|
console.log(`✓ Compiled 1 universe + ${sceneCount} ${scenesText}`);
|
|
@@ -341,7 +399,7 @@ async function handleUI(path: string, port?: number, legacy = false): Promise<vo
|
|
|
341
399
|
}
|
|
342
400
|
|
|
343
401
|
async function main() {
|
|
344
|
-
const { command, path, port, universeName, legacy, out } = parseArgs();
|
|
402
|
+
const { command, path, pathExplicit, port, universeName, legacy, out } = parseArgs();
|
|
345
403
|
|
|
346
404
|
if (!command) {
|
|
347
405
|
printHelp();
|
|
@@ -351,7 +409,7 @@ async function main() {
|
|
|
351
409
|
try {
|
|
352
410
|
switch (command) {
|
|
353
411
|
case "compile":
|
|
354
|
-
await handleCompile(path, out, legacy);
|
|
412
|
+
await handleCompile(path, out, legacy, pathExplicit);
|
|
355
413
|
break;
|
|
356
414
|
case "validate":
|
|
357
415
|
await handleValidate(path, legacy);
|
package/src/scene-compiler.ts
CHANGED
|
@@ -53,26 +53,41 @@ function generateManifestId(): string {
|
|
|
53
53
|
return `time:${new Date().toISOString()}`;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
export interface CompileScenesOptions {
|
|
57
|
+
/** Explicit scene file paths (skips globbing) */
|
|
58
|
+
sceneFiles?: string[];
|
|
59
|
+
/** If false, parse and validate only; do not write manifests or error files */
|
|
60
|
+
emitManifests?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
56
63
|
/**
|
|
57
64
|
* Compile all scene files in the universe
|
|
58
|
-
* @param universeRoot - Universe root directory
|
|
65
|
+
* @param universeRoot - Universe root directory (or search root for scene-only mode)
|
|
59
66
|
* @param outputDir - Output directory for scene manifests
|
|
67
|
+
* @param options - Optional: sceneFiles (explicit list), emitManifests (default true in universe mode)
|
|
60
68
|
* @returns Compile result with success status and list of compiled scenes
|
|
61
69
|
*/
|
|
62
70
|
export async function compileScenes(
|
|
63
71
|
universeRoot: string,
|
|
64
72
|
outputDir: string,
|
|
73
|
+
options?: CompileScenesOptions,
|
|
65
74
|
): Promise<SceneCompileResult> {
|
|
66
75
|
const diagnostics: Array<{ severity: string; message: string; source?: unknown; scene?: string }> = [];
|
|
67
76
|
const compiledScenes: string[] = [];
|
|
77
|
+
const emitManifests = options?.emitManifests !== false;
|
|
68
78
|
|
|
69
79
|
try {
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
// Use explicit scene files or discover via glob
|
|
81
|
+
let sceneFiles: string[];
|
|
82
|
+
if (options?.sceneFiles && options.sceneFiles.length > 0) {
|
|
83
|
+
sceneFiles = options.sceneFiles;
|
|
84
|
+
} else {
|
|
85
|
+
sceneFiles = await fg("**/*.scene.prose", {
|
|
86
|
+
cwd: universeRoot,
|
|
87
|
+
absolute: true,
|
|
88
|
+
ignore: ["node_modules/**", ".git/**"],
|
|
89
|
+
});
|
|
90
|
+
}
|
|
76
91
|
|
|
77
92
|
if (sceneFiles.length === 0) {
|
|
78
93
|
// No scenes to compile is not an error
|
|
@@ -83,15 +98,17 @@ export async function compileScenes(
|
|
|
83
98
|
};
|
|
84
99
|
}
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
if (emitManifests) {
|
|
102
|
+
// Ensure output directory exists
|
|
103
|
+
try {
|
|
104
|
+
mkdirSync(outputDir, { recursive: true });
|
|
105
|
+
} catch (error) {
|
|
106
|
+
// Directory might already exist, ignore
|
|
107
|
+
}
|
|
91
108
|
}
|
|
92
109
|
|
|
93
110
|
const generatedAt = new Date().toISOString();
|
|
94
|
-
const manifestId = generateManifestId();
|
|
111
|
+
const manifestId = emitManifests ? generateManifestId() : "";
|
|
95
112
|
|
|
96
113
|
// Compile each scene
|
|
97
114
|
for (const sceneFile of sceneFiles) {
|
|
@@ -107,35 +124,39 @@ export async function compileScenes(
|
|
|
107
124
|
message,
|
|
108
125
|
source: sceneFile,
|
|
109
126
|
});
|
|
110
|
-
|
|
127
|
+
if (emitManifests) {
|
|
128
|
+
writeSceneErrors(outputDir, sceneFile, [message], sourceText);
|
|
129
|
+
}
|
|
111
130
|
continue;
|
|
112
131
|
}
|
|
113
132
|
|
|
114
|
-
// Inject metadata
|
|
115
|
-
const manifestWithMeta = {
|
|
116
|
-
...manifest,
|
|
117
|
-
meta: {
|
|
118
|
-
generatedAt,
|
|
119
|
-
manifestId,
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
// Write to output directory
|
|
124
|
-
const outputPath = join(outputDir, `${manifest.sceneName}.scene.json`);
|
|
125
|
-
const tempPath = `${outputPath}.tmp`;
|
|
126
|
-
const json = JSON.stringify(manifestWithMeta, null, 2);
|
|
127
|
-
writeFileSync(tempPath, json, "utf-8");
|
|
128
|
-
renameSync(tempPath, outputPath);
|
|
129
|
-
|
|
130
|
-
// Remove any prior .error.txt for this scene (same source file base name)
|
|
131
|
-
const errorPath = join(outputDir, `${sceneFileBaseName(sceneFile)}.error.txt`);
|
|
132
|
-
try {
|
|
133
|
-
unlinkSync(errorPath);
|
|
134
|
-
} catch {
|
|
135
|
-
// ignore if missing
|
|
136
|
-
}
|
|
137
|
-
|
|
138
133
|
compiledScenes.push(manifest.sceneName);
|
|
134
|
+
|
|
135
|
+
if (emitManifests) {
|
|
136
|
+
// Inject metadata
|
|
137
|
+
const manifestWithMeta = {
|
|
138
|
+
...manifest,
|
|
139
|
+
meta: {
|
|
140
|
+
generatedAt,
|
|
141
|
+
manifestId,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Write to output directory
|
|
146
|
+
const outputPath = join(outputDir, `${manifest.sceneName}.scene.json`);
|
|
147
|
+
const tempPath = `${outputPath}.tmp`;
|
|
148
|
+
const json = JSON.stringify(manifestWithMeta, null, 2);
|
|
149
|
+
writeFileSync(tempPath, json, "utf-8");
|
|
150
|
+
renameSync(tempPath, outputPath);
|
|
151
|
+
|
|
152
|
+
// Remove any prior .error.txt for this scene (same source file base name)
|
|
153
|
+
const errorPath = join(outputDir, `${sceneFileBaseName(sceneFile)}.error.txt`);
|
|
154
|
+
try {
|
|
155
|
+
unlinkSync(errorPath);
|
|
156
|
+
} catch {
|
|
157
|
+
// ignore if missing
|
|
158
|
+
}
|
|
159
|
+
}
|
|
139
160
|
} catch (error) {
|
|
140
161
|
const message = error instanceof Error ? error.message : String(error);
|
|
141
162
|
diagnostics.push({
|
|
@@ -143,7 +164,9 @@ export async function compileScenes(
|
|
|
143
164
|
message: `Scene compile failed: ${message}`,
|
|
144
165
|
source: sceneFile,
|
|
145
166
|
});
|
|
146
|
-
|
|
167
|
+
if (emitManifests) {
|
|
168
|
+
writeSceneErrors(outputDir, sceneFile, [message], sourceText);
|
|
169
|
+
}
|
|
147
170
|
}
|
|
148
171
|
}
|
|
149
172
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { statSync, existsSync } from "node:fs";
|
|
2
|
+
import { resolve, dirname, join } from "node:path";
|
|
3
|
+
import fg from "fast-glob";
|
|
4
|
+
|
|
5
|
+
export interface SceneDiscovery {
|
|
6
|
+
sceneFiles: string[];
|
|
7
|
+
searchRoot: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Discover scene files when no universe.prose is found.
|
|
12
|
+
* @param path - Path (file or directory) from user
|
|
13
|
+
* @returns SceneDiscovery with sceneFiles and searchRoot, or null if no scenes found
|
|
14
|
+
*/
|
|
15
|
+
export function discoverSceneFiles(path: string): SceneDiscovery | null {
|
|
16
|
+
const resolved = resolve(path);
|
|
17
|
+
|
|
18
|
+
if (!existsSync(resolved)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const stat = statSync(resolved);
|
|
23
|
+
|
|
24
|
+
if (stat.isFile()) {
|
|
25
|
+
if (/\.scene\.prose$/i.test(resolved)) {
|
|
26
|
+
return {
|
|
27
|
+
sceneFiles: [resolved],
|
|
28
|
+
searchRoot: dirname(resolved),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Other file: search in same directory
|
|
32
|
+
const searchRoot = dirname(resolved);
|
|
33
|
+
const sceneFiles = fg.sync("**/*.scene.prose", {
|
|
34
|
+
cwd: searchRoot,
|
|
35
|
+
absolute: true,
|
|
36
|
+
ignore: ["node_modules/**", ".git/**"],
|
|
37
|
+
});
|
|
38
|
+
return sceneFiles.length > 0 ? { sceneFiles, searchRoot } : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (stat.isDirectory()) {
|
|
42
|
+
const sceneFiles = fg.sync("**/*.scene.prose", {
|
|
43
|
+
cwd: resolved,
|
|
44
|
+
absolute: true,
|
|
45
|
+
ignore: ["node_modules/**", ".git/**"],
|
|
46
|
+
});
|
|
47
|
+
return sceneFiles.length > 0 ? { sceneFiles, searchRoot: resolved } : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import { strict as assert } from "node:assert";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { join, dirname } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const FIXTURES = join(__dirname, "fixtures");
|
|
10
|
+
const CLI_PATH = join(__dirname, "..", "dist", "cli.js");
|
|
11
|
+
|
|
12
|
+
function runCompile(args, cwd) {
|
|
13
|
+
const result = spawnSync("node", [CLI_PATH, "compile", ...args], {
|
|
14
|
+
encoding: "utf-8",
|
|
15
|
+
cwd: cwd ?? join(__dirname, ".."),
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
status: result.status,
|
|
19
|
+
stdout: result.stdout ?? "",
|
|
20
|
+
stderr: result.stderr ?? "",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test("sprig compile with no universe in cwd: errors", () => {
|
|
25
|
+
const cwd = join(FIXTURES, "scene-only-empty");
|
|
26
|
+
const { status, stderr } = runCompile([], cwd);
|
|
27
|
+
|
|
28
|
+
assert.notStrictEqual(status, 0, "Expected non-zero exit");
|
|
29
|
+
assert.ok(stderr.includes("No universe.prose found."), "Should show expected error");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("sprig compile <dir> universe in dir, no --out: writes to dir/.sprig", () => {
|
|
33
|
+
const dir = join(FIXTURES, "universe-unchanged");
|
|
34
|
+
const { status, stderr } = runCompile([dir]);
|
|
35
|
+
|
|
36
|
+
assert.strictEqual(status, 0, `Expected exit 0, got ${status}. stderr: ${stderr}`);
|
|
37
|
+
|
|
38
|
+
const sprigDir = join(dir, ".sprig");
|
|
39
|
+
assert.ok(existsSync(sprigDir), ".sprig should exist");
|
|
40
|
+
assert.ok(existsSync(join(sprigDir, "manifest.json")), "manifest.json should exist");
|
|
41
|
+
assert.ok(existsSync(join(sprigDir, "TestScene.scene.json")), "TestScene.scene.json should exist");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("sprig compile <dir> --out <out> universe found: writes to out", () => {
|
|
45
|
+
const dir = join(FIXTURES, "universe-unchanged");
|
|
46
|
+
const outDir = join(dir, "custom-out");
|
|
47
|
+
const { status, stderr } = runCompile([dir, "--out", outDir]);
|
|
48
|
+
|
|
49
|
+
assert.strictEqual(status, 0, `Expected exit 0, got ${status}. stderr: ${stderr}`);
|
|
50
|
+
|
|
51
|
+
assert.ok(existsSync(join(outDir, "manifest.json")), "manifest.json should exist in --out");
|
|
52
|
+
assert.ok(existsSync(join(outDir, "TestScene.scene.json")), "TestScene.scene.json should exist in --out");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("sprig compile <dir> no universe, no --out: errors with guidance", () => {
|
|
56
|
+
const dir = join(FIXTURES, "scene-only-valid");
|
|
57
|
+
const { status, stderr } = runCompile([dir]);
|
|
58
|
+
|
|
59
|
+
assert.notStrictEqual(status, 0, "Expected non-zero exit");
|
|
60
|
+
assert.ok(
|
|
61
|
+
stderr.includes("No universe.prose found for"),
|
|
62
|
+
"Should mention no universe for dir",
|
|
63
|
+
);
|
|
64
|
+
assert.ok(stderr.includes("Provide --out"), "Should suggest --out");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("sprig compile <dir> --out <out> no universe, scenes present: emits to out", () => {
|
|
68
|
+
const dir = join(FIXTURES, "scene-only-valid");
|
|
69
|
+
const outDir = join(dir, ".sprig-out");
|
|
70
|
+
const { status, stderr } = runCompile([dir, "--out", outDir]);
|
|
71
|
+
|
|
72
|
+
assert.strictEqual(status, 0, `Expected exit 0, got ${status}. stderr: ${stderr}`);
|
|
73
|
+
|
|
74
|
+
assert.ok(existsSync(join(outDir, "SimpleScene.scene.json")), "SimpleScene.scene.json should exist");
|
|
75
|
+
assert.ok(existsSync(join(outDir, "AnotherScene.scene.json")), "AnotherScene.scene.json should exist");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("sprig compile <dir> --out <out> no universe, no scenes: errors", () => {
|
|
79
|
+
const dir = join(FIXTURES, "scene-only-empty");
|
|
80
|
+
const outDir = join(dir, ".sprig-out");
|
|
81
|
+
const { status, stderr } = runCompile([dir, "--out", outDir]);
|
|
82
|
+
|
|
83
|
+
assert.notStrictEqual(status, 0, "Expected non-zero exit");
|
|
84
|
+
assert.ok(
|
|
85
|
+
stderr.includes("No universe.prose found and no *.scene.prose files found in:"),
|
|
86
|
+
"Should show no scenes error",
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("sprig compile <dir> universe in parent, no --out: writes to in-directory/.sprig", () => {
|
|
91
|
+
const subdir = join(FIXTURES, "universe-upstream", "subdir");
|
|
92
|
+
const { status, stderr } = runCompile([subdir]);
|
|
93
|
+
|
|
94
|
+
assert.strictEqual(status, 0, `Expected exit 0, got ${status}. stderr: ${stderr}`);
|
|
95
|
+
|
|
96
|
+
const sprigDir = join(subdir, ".sprig");
|
|
97
|
+
assert.ok(existsSync(sprigDir), "Output should be in subdir/.sprig (in-directory)");
|
|
98
|
+
assert.ok(existsSync(join(sprigDir, "manifest.json")), "manifest.json should exist");
|
|
99
|
+
assert.ok(existsSync(join(sprigDir, "OneScene.scene.json")), "OneScene.scene.json should exist");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("scene-only invalid dir with --out: exits non-zero", () => {
|
|
103
|
+
const dir = join(FIXTURES, "scene-only-invalid");
|
|
104
|
+
const outDir = join(dir, ".sprig-out");
|
|
105
|
+
const { status, stderr } = runCompile([dir, "--out", outDir]);
|
|
106
|
+
|
|
107
|
+
assert.notStrictEqual(status, 0, "Expected non-zero exit on invalid scene");
|
|
108
|
+
assert.ok(stderr.includes("BadScene") || stderr.includes("bad.scene.prose"), "Error should mention scene or file");
|
|
109
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sceneName": "AnotherScene",
|
|
3
|
+
"actors": {
|
|
4
|
+
"NameActor": {
|
|
5
|
+
"type": "string",
|
|
6
|
+
"identity": []
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"portals": {},
|
|
10
|
+
"derived": {},
|
|
11
|
+
"meta": {
|
|
12
|
+
"generatedAt": "2026-02-21T20:00:48.348Z",
|
|
13
|
+
"manifestId": "git:e97c34ae2cb10839cafef399d1ef329411e3bd22"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sceneName": "SimpleScene",
|
|
3
|
+
"actors": {
|
|
4
|
+
"IntActor": {
|
|
5
|
+
"type": "integer",
|
|
6
|
+
"identity": []
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"portals": {},
|
|
10
|
+
"derived": {},
|
|
11
|
+
"meta": {
|
|
12
|
+
"generatedAt": "2026-02-21T20:00:48.348Z",
|
|
13
|
+
"manifestId": "git:e97c34ae2cb10839cafef399d1ef329411e3bd22"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sceneName": "AnotherScene",
|
|
3
|
+
"actors": {
|
|
4
|
+
"NameActor": {
|
|
5
|
+
"type": "string",
|
|
6
|
+
"identity": []
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"portals": {},
|
|
10
|
+
"derived": {},
|
|
11
|
+
"meta": {
|
|
12
|
+
"generatedAt": "2026-02-21T20:23:33.528Z",
|
|
13
|
+
"manifestId": "git:e97c34ae2cb10839cafef399d1ef329411e3bd22"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sceneName": "SimpleScene",
|
|
3
|
+
"actors": {
|
|
4
|
+
"IntActor": {
|
|
5
|
+
"type": "integer",
|
|
6
|
+
"identity": []
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"portals": {},
|
|
10
|
+
"derived": {},
|
|
11
|
+
"meta": {
|
|
12
|
+
"generatedAt": "2026-02-21T20:23:33.528Z",
|
|
13
|
+
"manifestId": "git:e97c34ae2cb10839cafef399d1ef329411e3bd22"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sceneName": "TestScene",
|
|
3
|
+
"actors": {
|
|
4
|
+
"IntActor": {
|
|
5
|
+
"type": "integer",
|
|
6
|
+
"identity": []
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"portals": {},
|
|
10
|
+
"derived": {},
|
|
11
|
+
"meta": {
|
|
12
|
+
"generatedAt": "2026-02-21T20:23:33.122Z",
|
|
13
|
+
"manifestId": "git:e97c34ae2cb10839cafef399d1ef329411e3bd22"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"universes": {
|
|
4
|
+
"TestUniverse": {
|
|
5
|
+
"name": "TestUniverse",
|
|
6
|
+
"root": "TestUniverse:universe:TestUniverse"
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"nodes": {
|
|
10
|
+
"TestUniverse:universe:TestUniverse": {
|
|
11
|
+
"id": "TestUniverse:universe:TestUniverse",
|
|
12
|
+
"kind": "universe",
|
|
13
|
+
"name": "TestUniverse",
|
|
14
|
+
"children": [],
|
|
15
|
+
"source": {
|
|
16
|
+
"file": "/home/grumpy/git/sprig/packages/cli/tests/fixtures/universe-unchanged/universe.prose",
|
|
17
|
+
"start": {
|
|
18
|
+
"line": 1,
|
|
19
|
+
"col": 1,
|
|
20
|
+
"offset": 0
|
|
21
|
+
},
|
|
22
|
+
"end": {
|
|
23
|
+
"line": 5,
|
|
24
|
+
"col": 2,
|
|
25
|
+
"offset": 63
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"describe": {
|
|
29
|
+
"raw": "\n A test universe.\n ",
|
|
30
|
+
"normalized": "\n A test universe.\n ",
|
|
31
|
+
"source": {
|
|
32
|
+
"file": "/home/grumpy/git/sprig/packages/cli/tests/fixtures/universe-unchanged/universe.prose",
|
|
33
|
+
"start": {
|
|
34
|
+
"line": 2,
|
|
35
|
+
"col": 3,
|
|
36
|
+
"offset": 26
|
|
37
|
+
},
|
|
38
|
+
"end": {
|
|
39
|
+
"line": 4,
|
|
40
|
+
"col": 4,
|
|
41
|
+
"offset": 61
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"edges": [],
|
|
48
|
+
"edgesAsserted": [],
|
|
49
|
+
"diagnostics": [],
|
|
50
|
+
"repositories": {},
|
|
51
|
+
"references": {},
|
|
52
|
+
"arcs": {},
|
|
53
|
+
"documentsByName": {},
|
|
54
|
+
"relationshipDecls": {
|
|
55
|
+
"TestUniverse": {}
|
|
56
|
+
},
|
|
57
|
+
"generatedAt": "2026-02-21T20:23:33.116Z",
|
|
58
|
+
"meta": {
|
|
59
|
+
"generatedAt": "2026-02-21T20:23:33.116Z",
|
|
60
|
+
"manifestId": "git:e97c34ae2cb10839cafef399d1ef329411e3bd22"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sceneName": "TestScene",
|
|
3
|
+
"actors": {
|
|
4
|
+
"IntActor": {
|
|
5
|
+
"type": "integer",
|
|
6
|
+
"identity": []
|
|
7
|
+
}
|
|
8
|
+
},
|
|
9
|
+
"portals": {},
|
|
10
|
+
"derived": {},
|
|
11
|
+
"meta": {
|
|
12
|
+
"generatedAt": "2026-02-21T20:23:33.279Z",
|
|
13
|
+
"manifestId": "git:e97c34ae2cb10839cafef399d1ef329411e3bd22"
|
|
14
|
+
}
|
|
15
|
+
}
|