@xenonbyte/da-vinci-workflow 0.1.16 → 0.1.17

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.
@@ -0,0 +1,315 @@
1
+ const assert = require("assert/strict");
2
+ const fs = require("fs");
3
+ const os = require("os");
4
+ const path = require("path");
5
+ const { spawnSync } = require("child_process");
6
+
7
+ const repo = path.resolve(__dirname, "..");
8
+ const cli = path.join(repo, "bin", "da-vinci.js");
9
+ const fixture = JSON.parse(
10
+ fs.readFileSync(path.join(__dirname, "fixtures", "complex-sample.pen"), "utf8")
11
+ );
12
+ const changeId = "redesign-001";
13
+
14
+ function runTest(name, fn) {
15
+ try {
16
+ fn();
17
+ console.log(`PASS ${name}`);
18
+ } catch (error) {
19
+ console.error(`FAIL ${name}`);
20
+ throw error;
21
+ }
22
+ }
23
+
24
+ function createHarness() {
25
+ const base = fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-flow-"));
26
+ return {
27
+ base,
28
+ home: path.join(base, "home")
29
+ };
30
+ }
31
+
32
+ function runCli(harness, args) {
33
+ const [command, ...rest] = args;
34
+ const result = spawnSync(process.execPath, [cli, command, "--home", harness.home, ...rest], {
35
+ cwd: repo,
36
+ encoding: "utf8",
37
+ maxBuffer: 8 * 1024 * 1024
38
+ });
39
+
40
+ return {
41
+ code: result.status,
42
+ stdout: (result.stdout || "").trim(),
43
+ stderr: (result.stderr || "").trim()
44
+ };
45
+ }
46
+
47
+ function expectOk(step, result) {
48
+ assert.equal(result.code, 0, `${step} failed:\n${result.stderr || result.stdout}`);
49
+ }
50
+
51
+ function expectFail(step, result, pattern) {
52
+ assert.notEqual(result.code, 0, `${step} unexpectedly succeeded:\n${result.stdout}`);
53
+ assert.match(`${result.stdout}\n${result.stderr}`, pattern, `${step} failed for the wrong reason.`);
54
+ }
55
+
56
+ function writeJson(filePath, payload) {
57
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
58
+ fs.writeFileSync(filePath, JSON.stringify(payload, null, 2));
59
+ }
60
+
61
+ function writeText(filePath, text) {
62
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
63
+ fs.writeFileSync(filePath, text);
64
+ }
65
+
66
+ function createProject(harness, name, options = {}) {
67
+ const root = path.join(harness.base, name);
68
+ const daVinciDir = path.join(root, ".da-vinci");
69
+ const penPath = path.join(daVinciDir, "designs", `${name}.pen`);
70
+ const changeDir = path.join(daVinciDir, "changes", changeId);
71
+ const nodesFile = path.join(root, `${name}-nodes.json`);
72
+ const variablesFile = path.join(root, `${name}-variables.json`);
73
+ const liveNodesFile = path.join(root, `${name}-live-nodes.json`);
74
+
75
+ writeText(path.join(root, "DA-VINCI.md"), "# Test\n");
76
+ writeText(path.join(daVinciDir, "project-inventory.md"), "# Inventory\n");
77
+ writeText(path.join(daVinciDir, "page-map.md"), "# Page Map\n");
78
+ writeText(
79
+ path.join(daVinciDir, "design-registry.md"),
80
+ `# Registry\n- Preferred .pen: .da-vinci/designs/${name}.pen\n`
81
+ );
82
+ writeText(path.join(changeDir, "design-brief.md"), "# Brief\n");
83
+ writeText(path.join(changeDir, "design.md"), "# Design\n");
84
+ writeText(path.join(changeDir, "pencil-design.md"), "# Pencil\n");
85
+ writeText(path.join(changeDir, "pencil-bindings.md"), "# Bindings\n");
86
+
87
+ writeJson(nodesFile, { nodes: fixture.children });
88
+ writeJson(variablesFile, { variables: fixture.variables });
89
+
90
+ const liveNodes = JSON.parse(JSON.stringify(fixture.children));
91
+ if (options.modifiedPayload) {
92
+ liveNodes.push({ id: `${name}-extra`, type: "frame", children: [] });
93
+ }
94
+ writeJson(liveNodesFile, { nodes: liveNodes });
95
+
96
+ if (options.withExistingPen) {
97
+ const seed = runCli(harness, [
98
+ "write-pen",
99
+ "--output",
100
+ penPath,
101
+ "--nodes-file",
102
+ nodesFile,
103
+ "--variables-file",
104
+ variablesFile,
105
+ "--version",
106
+ fixture.version
107
+ ]);
108
+ expectOk(`${name} precreate existing .pen`, seed);
109
+ }
110
+
111
+ return {
112
+ root,
113
+ penPath,
114
+ nodesFile,
115
+ variablesFile,
116
+ liveNodesFile
117
+ };
118
+ }
119
+
120
+ runTest("new project flow passes end-to-end", () => {
121
+ const harness = createHarness();
122
+ const project = createProject(harness, "fresh-project");
123
+
124
+ expectOk(
125
+ "fresh begin",
126
+ runCli(harness, ["pencil-session", "begin", "--project", project.root, "--pen", project.penPath])
127
+ );
128
+ expectOk(
129
+ "fresh persist",
130
+ runCli(harness, [
131
+ "pencil-session",
132
+ "persist",
133
+ "--project",
134
+ project.root,
135
+ "--pen",
136
+ project.penPath,
137
+ "--nodes-file",
138
+ project.nodesFile,
139
+ "--variables-file",
140
+ project.variablesFile,
141
+ "--version",
142
+ fixture.version
143
+ ])
144
+ );
145
+ expectOk(
146
+ "fresh end",
147
+ runCli(harness, [
148
+ "pencil-session",
149
+ "end",
150
+ "--project",
151
+ project.root,
152
+ "--pen",
153
+ project.penPath,
154
+ "--nodes-file",
155
+ project.nodesFile,
156
+ "--variables-file",
157
+ project.variablesFile,
158
+ "--version",
159
+ fixture.version
160
+ ])
161
+ );
162
+ expectOk(
163
+ "fresh completion audit",
164
+ runCli(harness, ["audit", "--mode", "completion", "--change", changeId, "--project", project.root])
165
+ );
166
+ });
167
+
168
+ runTest("existing .pen flow persists modified live payload", () => {
169
+ const harness = createHarness();
170
+ const project = createProject(harness, "existing-project", {
171
+ withExistingPen: true,
172
+ modifiedPayload: true
173
+ });
174
+
175
+ expectOk(
176
+ "existing begin",
177
+ runCli(harness, ["pencil-session", "begin", "--project", project.root, "--pen", project.penPath])
178
+ );
179
+ expectOk(
180
+ "existing persist",
181
+ runCli(harness, [
182
+ "pencil-session",
183
+ "persist",
184
+ "--project",
185
+ project.root,
186
+ "--pen",
187
+ project.penPath,
188
+ "--nodes-file",
189
+ project.liveNodesFile,
190
+ "--variables-file",
191
+ project.variablesFile,
192
+ "--version",
193
+ fixture.version
194
+ ])
195
+ );
196
+ expectOk(
197
+ "existing end",
198
+ runCli(harness, [
199
+ "pencil-session",
200
+ "end",
201
+ "--project",
202
+ project.root,
203
+ "--pen",
204
+ project.penPath,
205
+ "--nodes-file",
206
+ project.liveNodesFile,
207
+ "--variables-file",
208
+ project.variablesFile,
209
+ "--version",
210
+ fixture.version
211
+ ])
212
+ );
213
+ expectOk(
214
+ "existing completion audit",
215
+ runCli(harness, ["audit", "--mode", "completion", "--change", changeId, "--project", project.root])
216
+ );
217
+
218
+ const pen = JSON.parse(fs.readFileSync(project.penPath, "utf8"));
219
+ assert.equal(pen.children.some((node) => node.id === "existing-project-extra"), true);
220
+ });
221
+
222
+ runTest("parallel mixed projects are serialized by the global Pencil lock", () => {
223
+ const harness = createHarness();
224
+ const freshA = createProject(harness, "parallel-fresh-a");
225
+ const freshB = createProject(harness, "parallel-fresh-b");
226
+ const existing = createProject(harness, "parallel-existing", {
227
+ withExistingPen: true
228
+ });
229
+
230
+ expectOk(
231
+ "parallel A begin",
232
+ runCli(harness, ["pencil-session", "begin", "--project", freshA.root, "--pen", freshA.penPath])
233
+ );
234
+ expectFail(
235
+ "parallel B blocked while A is active",
236
+ runCli(harness, ["pencil-session", "begin", "--project", freshB.root, "--pen", freshB.penPath]),
237
+ /lock is already held/i
238
+ );
239
+ expectFail(
240
+ "parallel existing blocked while A is active",
241
+ runCli(harness, ["pencil-session", "begin", "--project", existing.root, "--pen", existing.penPath]),
242
+ /lock is already held/i
243
+ );
244
+ expectOk(
245
+ "parallel A end",
246
+ runCli(harness, ["pencil-session", "end", "--project", freshA.root, "--pen", freshA.penPath])
247
+ );
248
+
249
+ expectOk(
250
+ "parallel existing begin",
251
+ runCli(harness, ["pencil-session", "begin", "--project", existing.root, "--pen", existing.penPath])
252
+ );
253
+ expectFail(
254
+ "parallel B blocked while existing is active",
255
+ runCli(harness, ["pencil-session", "begin", "--project", freshB.root, "--pen", freshB.penPath]),
256
+ /lock is already held/i
257
+ );
258
+ expectOk(
259
+ "parallel existing end",
260
+ runCli(harness, [
261
+ "pencil-session",
262
+ "end",
263
+ "--project",
264
+ existing.root,
265
+ "--pen",
266
+ existing.penPath,
267
+ "--nodes-file",
268
+ existing.nodesFile,
269
+ "--variables-file",
270
+ existing.variablesFile,
271
+ "--version",
272
+ fixture.version
273
+ ])
274
+ );
275
+ expectOk(
276
+ "parallel B begin",
277
+ runCli(harness, ["pencil-session", "begin", "--project", freshB.root, "--pen", freshB.penPath])
278
+ );
279
+ expectOk(
280
+ "parallel B persist",
281
+ runCli(harness, [
282
+ "pencil-session",
283
+ "persist",
284
+ "--project",
285
+ freshB.root,
286
+ "--pen",
287
+ freshB.penPath,
288
+ "--nodes-file",
289
+ freshB.nodesFile,
290
+ "--variables-file",
291
+ freshB.variablesFile,
292
+ "--version",
293
+ fixture.version
294
+ ])
295
+ );
296
+ expectOk(
297
+ "parallel B end",
298
+ runCli(harness, [
299
+ "pencil-session",
300
+ "end",
301
+ "--project",
302
+ freshB.root,
303
+ "--pen",
304
+ freshB.penPath,
305
+ "--nodes-file",
306
+ freshB.nodesFile,
307
+ "--variables-file",
308
+ freshB.variablesFile,
309
+ "--version",
310
+ fixture.version
311
+ ])
312
+ );
313
+ });
314
+
315
+ console.log("All persistence flow integration tests passed.");