@xenonbyte/da-vinci-workflow 0.1.23 → 0.1.25
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 +26 -0
- package/README.md +14 -1
- package/README.zh-CN.md +14 -1
- package/SKILL.md +3 -2
- package/bin/design-supervisor.js +39 -0
- package/commands/claude/dv/design.md +1 -1
- package/commands/codex/prompts/dv-design.md +1 -1
- package/commands/gemini/dv/design.toml +1 -1
- package/docs/constraint-files.md +5 -1
- package/docs/dv-command-reference.md +6 -0
- package/docs/zh-CN/constraint-files.md +5 -1
- package/docs/zh-CN/dv-command-reference.md +6 -0
- package/lib/audit-parsers.js +216 -18
- package/lib/audit.js +96 -3
- package/lib/cli.js +97 -0
- package/lib/pen-persistence.js +47 -0
- package/lib/supervisor-review.js +680 -0
- package/package.json +6 -3
- package/references/artifact-templates.md +3 -0
- package/scripts/test-audit-design-supervisor.js +343 -0
- package/scripts/test-mode-consistency.js +5 -0
- package/scripts/test-pen-persistence.js +149 -0
- package/scripts/test-supervisor-review-cli.js +619 -0
- package/scripts/test-supervisor-review-integration.js +115 -0
|
@@ -559,6 +559,8 @@ Use this structure:
|
|
|
559
559
|
|
|
560
560
|
## Design-Supervisor Review
|
|
561
561
|
- Configured reviewers
|
|
562
|
+
- Executed reviewers
|
|
563
|
+
- Review source (`skill` / `manual` / `inferred`)
|
|
562
564
|
- Review mode
|
|
563
565
|
- Review inputs
|
|
564
566
|
- Whether reviewers were distinct from `Preferred adapters`
|
|
@@ -567,6 +569,7 @@ Use this structure:
|
|
|
567
569
|
- Revision outcome
|
|
568
570
|
- Whether broad expansion is approved
|
|
569
571
|
- Whether implementation-task handoff is approved
|
|
572
|
+
- Keep one canonical structured supervisor-review section; avoid ad-hoc headings such as `## Design-Supervisor Review (Round X Attempt)`
|
|
570
573
|
|
|
571
574
|
## Anchor Surfaces
|
|
572
575
|
- Which 1-3 anchor screens were designed first
|
|
@@ -5,6 +5,7 @@ const path = require("path");
|
|
|
5
5
|
const { spawnSync } = require("child_process");
|
|
6
6
|
|
|
7
7
|
const { auditProject } = require("../lib/audit");
|
|
8
|
+
const { inspectDesignSupervisorReview } = require("../lib/audit-parsers");
|
|
8
9
|
|
|
9
10
|
const repo = path.resolve(__dirname, "..");
|
|
10
11
|
const cli = path.join(repo, "bin", "da-vinci.js");
|
|
@@ -318,6 +319,8 @@ runTest("completion audit passes when required supervisor review is recorded as
|
|
|
318
319
|
"",
|
|
319
320
|
"## Design-Supervisor Review",
|
|
320
321
|
"- Configured reviewers: frontend-skill",
|
|
322
|
+
"- Executed reviewers: frontend-skill",
|
|
323
|
+
"- Review source: skill",
|
|
321
324
|
"- Review mode: screenshot-and-theme",
|
|
322
325
|
"- Review inputs: screenshots, pencil variables, visual thesis, content plan, interaction thesis",
|
|
323
326
|
"- Status: PASS",
|
|
@@ -345,4 +348,344 @@ runTest("completion audit passes when required supervisor review is recorded as
|
|
|
345
348
|
);
|
|
346
349
|
});
|
|
347
350
|
|
|
351
|
+
runTest("completion audit accepts the latest structured round section when multiple Design-Supervisor Review headings exist", () => {
|
|
352
|
+
const harness = createHarness();
|
|
353
|
+
const project = setupProject(harness, "latest-supervisor-round-wins", {
|
|
354
|
+
requireSupervisorReview: true
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
writeText(
|
|
358
|
+
project.pencilDesignPath,
|
|
359
|
+
[
|
|
360
|
+
"# Pencil Design",
|
|
361
|
+
"",
|
|
362
|
+
"## Design-Supervisor Review",
|
|
363
|
+
"- Configured reviewers: frontend-skill",
|
|
364
|
+
"- Executed reviewers: frontend-skill",
|
|
365
|
+
"- Review source: skill",
|
|
366
|
+
"- Status: BLOCK",
|
|
367
|
+
"- Issue list: command not found",
|
|
368
|
+
"- Revision outcome: blocked",
|
|
369
|
+
"",
|
|
370
|
+
"## Design-Supervisor Review (Round 2 Attempt)",
|
|
371
|
+
"- Configured reviewers: frontend-skill",
|
|
372
|
+
"- Executed reviewers: frontend-skill",
|
|
373
|
+
"- Review source: skill",
|
|
374
|
+
"- Status: PASS",
|
|
375
|
+
"- Issue list: none",
|
|
376
|
+
"- Revision outcome: accepted",
|
|
377
|
+
""
|
|
378
|
+
].join("\n")
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
const result = auditProject(project.root, {
|
|
382
|
+
mode: "completion",
|
|
383
|
+
changeId
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
assert.equal(
|
|
387
|
+
result.status,
|
|
388
|
+
"PASS",
|
|
389
|
+
`expected completion audit to use latest review section:\n${JSON.stringify(result, null, 2)}`
|
|
390
|
+
);
|
|
391
|
+
assert.match(
|
|
392
|
+
result.notes.join("\n"),
|
|
393
|
+
/Detected design-supervisor review status PASS/i
|
|
394
|
+
);
|
|
395
|
+
assert.doesNotMatch(
|
|
396
|
+
result.warnings.join("\n"),
|
|
397
|
+
/legacy\/non-structured format/i
|
|
398
|
+
);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
runTest("completion audit falls back to the latest structured section when newest review entry is legacy-style", () => {
|
|
402
|
+
const harness = createHarness();
|
|
403
|
+
const project = setupProject(harness, "legacy-supervisor-entry-fallback", {
|
|
404
|
+
requireSupervisorReview: true
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
writeText(
|
|
408
|
+
project.pencilDesignPath,
|
|
409
|
+
[
|
|
410
|
+
"# Pencil Design",
|
|
411
|
+
"",
|
|
412
|
+
"## Design-Supervisor Review",
|
|
413
|
+
"- Configured reviewers: frontend-skill",
|
|
414
|
+
"- Executed reviewers: frontend-skill",
|
|
415
|
+
"- Review source: skill",
|
|
416
|
+
"- Status: PASS",
|
|
417
|
+
"- Issue list: none",
|
|
418
|
+
"- Revision outcome: approved",
|
|
419
|
+
"",
|
|
420
|
+
"## Design-Supervisor Review (Round 2 Attempt)",
|
|
421
|
+
"- Result: BLOCK",
|
|
422
|
+
"- Issue list: style quality not approved",
|
|
423
|
+
"- Revision outcome: pending",
|
|
424
|
+
""
|
|
425
|
+
].join("\n")
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
const result = auditProject(project.root, {
|
|
429
|
+
mode: "completion",
|
|
430
|
+
changeId
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
assert.equal(
|
|
434
|
+
result.status,
|
|
435
|
+
"WARN",
|
|
436
|
+
`expected completion audit to warn but not fail when latest entry is legacy:\n${JSON.stringify(result, null, 2)}`
|
|
437
|
+
);
|
|
438
|
+
assert.match(
|
|
439
|
+
result.notes.join("\n"),
|
|
440
|
+
/Detected design-supervisor review status PASS/i
|
|
441
|
+
);
|
|
442
|
+
assert.match(
|
|
443
|
+
result.warnings.join("\n"),
|
|
444
|
+
/legacy\/non-structured format; gate evaluation used ## Design-Supervisor Review/i
|
|
445
|
+
);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
runTest("completion audit fails when only legacy-style Design-Supervisor Review sections exist", () => {
|
|
449
|
+
const harness = createHarness();
|
|
450
|
+
const project = setupProject(harness, "legacy-supervisor-only", {
|
|
451
|
+
requireSupervisorReview: true
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
writeText(
|
|
455
|
+
project.pencilDesignPath,
|
|
456
|
+
[
|
|
457
|
+
"# Pencil Design",
|
|
458
|
+
"",
|
|
459
|
+
"## Design-Supervisor Review (Round 3 Attempt)",
|
|
460
|
+
"- Result: PASS",
|
|
461
|
+
"- Issue list: none",
|
|
462
|
+
"- Revision outcome: accepted",
|
|
463
|
+
""
|
|
464
|
+
].join("\n")
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
const result = auditProject(project.root, {
|
|
468
|
+
mode: "completion",
|
|
469
|
+
changeId
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
assert.equal(result.status, "FAIL");
|
|
473
|
+
assert.match(
|
|
474
|
+
result.failures.join("\n"),
|
|
475
|
+
/missing required field\(s\): Configured reviewers/i
|
|
476
|
+
);
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
runTest("completion audit treats Chinese round-attempt heading as legacy and falls back to latest structured section", () => {
|
|
480
|
+
const harness = createHarness();
|
|
481
|
+
const project = setupProject(harness, "legacy-supervisor-chinese-heading", {
|
|
482
|
+
requireSupervisorReview: true
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
writeText(
|
|
486
|
+
project.pencilDesignPath,
|
|
487
|
+
[
|
|
488
|
+
"# Pencil Design",
|
|
489
|
+
"",
|
|
490
|
+
"## Design-Supervisor Review",
|
|
491
|
+
"- Configured reviewers: frontend-skill",
|
|
492
|
+
"- Executed reviewers: frontend-skill",
|
|
493
|
+
"- Review source: skill",
|
|
494
|
+
"- Status: PASS",
|
|
495
|
+
"- Issue list: none",
|
|
496
|
+
"- Revision outcome: approved",
|
|
497
|
+
"",
|
|
498
|
+
"## Design-Supervisor Review(第2轮尝试)",
|
|
499
|
+
"- Result: BLOCK",
|
|
500
|
+
"- Issue list: 视觉层级仍需调整",
|
|
501
|
+
"- Revision outcome: 待修订",
|
|
502
|
+
""
|
|
503
|
+
].join("\n")
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
const result = auditProject(project.root, {
|
|
507
|
+
mode: "completion",
|
|
508
|
+
changeId
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
assert.equal(result.status, "WARN");
|
|
512
|
+
assert.match(
|
|
513
|
+
result.warnings.join("\n"),
|
|
514
|
+
/legacy\/non-structured format; gate evaluation used ## Design-Supervisor Review/i
|
|
515
|
+
);
|
|
516
|
+
assert.match(
|
|
517
|
+
result.notes.join("\n"),
|
|
518
|
+
/Detected design-supervisor review status PASS/i
|
|
519
|
+
);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
runTest("inspectDesignSupervisorReview marks Chinese round-attempt suffix as legacy", () => {
|
|
523
|
+
const review = inspectDesignSupervisorReview(
|
|
524
|
+
[
|
|
525
|
+
"# Pencil Design",
|
|
526
|
+
"",
|
|
527
|
+
"## Design-Supervisor Review",
|
|
528
|
+
"- Configured reviewers: frontend-skill",
|
|
529
|
+
"- Executed reviewers: frontend-skill",
|
|
530
|
+
"- Review source: skill",
|
|
531
|
+
"- Status: PASS",
|
|
532
|
+
"- Issue list: none",
|
|
533
|
+
"- Revision outcome: approved",
|
|
534
|
+
"",
|
|
535
|
+
"## Design-Supervisor Review(第3轮尝试)",
|
|
536
|
+
"- Result: BLOCK",
|
|
537
|
+
"- Issue list: 继续迭代",
|
|
538
|
+
"- Revision outcome: pending",
|
|
539
|
+
""
|
|
540
|
+
].join("\n")
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
assert.equal(review.found, true);
|
|
544
|
+
assert.equal(review.latestSectionLooksLegacyAttempt, true);
|
|
545
|
+
assert.equal(review.usedStructuredSectionFallback, true);
|
|
546
|
+
assert.equal(review.selectedFromLatest, false);
|
|
547
|
+
assert.equal(review.status, "PASS");
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
runTest("completion audit accepts multiline Issue list and Revision outcome fields", () => {
|
|
551
|
+
const harness = createHarness();
|
|
552
|
+
const project = setupProject(harness, "multiline-supervisor-fields", {
|
|
553
|
+
requireSupervisorReview: true
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
writeText(
|
|
557
|
+
project.pencilDesignPath,
|
|
558
|
+
[
|
|
559
|
+
"# Pencil Design",
|
|
560
|
+
"",
|
|
561
|
+
"## Design-Supervisor Review",
|
|
562
|
+
"- Configured reviewers: frontend-skill",
|
|
563
|
+
"- Executed reviewers: frontend-skill",
|
|
564
|
+
"- Review source: skill",
|
|
565
|
+
"- Status: WARN",
|
|
566
|
+
"- Issue list:",
|
|
567
|
+
" - icon alignment drift on top bar",
|
|
568
|
+
" - hierarchy contrast needs one more pass",
|
|
569
|
+
"- Revision outcome:",
|
|
570
|
+
" - accepted with follow-up in next visual pass",
|
|
571
|
+
""
|
|
572
|
+
].join("\n")
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
const result = auditProject(project.root, {
|
|
576
|
+
mode: "completion",
|
|
577
|
+
changeId
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
assert.equal(
|
|
581
|
+
result.status,
|
|
582
|
+
"PASS",
|
|
583
|
+
`expected accepted WARN with multiline fields to pass:\n${JSON.stringify(result, null, 2)}`
|
|
584
|
+
);
|
|
585
|
+
assert.match(
|
|
586
|
+
result.notes.join("\n"),
|
|
587
|
+
/Detected design-supervisor review status WARN/i
|
|
588
|
+
);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
runTest("completion audit fails when required supervisor review is not skill-backed", () => {
|
|
592
|
+
const harness = createHarness();
|
|
593
|
+
const project = setupProject(harness, "required-supervisor-skill-source", {
|
|
594
|
+
requireSupervisorReview: true
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
writeText(
|
|
598
|
+
project.pencilDesignPath,
|
|
599
|
+
[
|
|
600
|
+
"# Pencil Design",
|
|
601
|
+
"",
|
|
602
|
+
"## Design-Supervisor Review",
|
|
603
|
+
"- Configured reviewers: frontend-skill",
|
|
604
|
+
"- Review source: inferred",
|
|
605
|
+
"- Status: PASS",
|
|
606
|
+
"- Issue list: none",
|
|
607
|
+
"- Revision outcome: approved",
|
|
608
|
+
""
|
|
609
|
+
].join("\n")
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
const result = auditProject(project.root, {
|
|
613
|
+
mode: "completion",
|
|
614
|
+
changeId
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
assert.equal(result.status, "FAIL");
|
|
618
|
+
assert.match(
|
|
619
|
+
result.failures.join("\n"),
|
|
620
|
+
/must be skill-backed.*Require Supervisor Review: true/i
|
|
621
|
+
);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
runTest("completion audit fails when required supervisor review omits executed reviewers", () => {
|
|
625
|
+
const harness = createHarness();
|
|
626
|
+
const project = setupProject(harness, "required-supervisor-executed-missing", {
|
|
627
|
+
requireSupervisorReview: true
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
writeText(
|
|
631
|
+
project.pencilDesignPath,
|
|
632
|
+
[
|
|
633
|
+
"# Pencil Design",
|
|
634
|
+
"",
|
|
635
|
+
"## Design-Supervisor Review",
|
|
636
|
+
"- Configured reviewers: frontend-skill",
|
|
637
|
+
"- Review source: skill",
|
|
638
|
+
"- Status: PASS",
|
|
639
|
+
"- Issue list: none",
|
|
640
|
+
"- Revision outcome: approved",
|
|
641
|
+
""
|
|
642
|
+
].join("\n")
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
const result = auditProject(project.root, {
|
|
646
|
+
mode: "completion",
|
|
647
|
+
changeId
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
assert.equal(result.status, "FAIL");
|
|
651
|
+
assert.match(
|
|
652
|
+
result.failures.join("\n"),
|
|
653
|
+
/missing required field\(s\): Executed reviewers/i
|
|
654
|
+
);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
runTest("completion audit fails when required supervisor review did not execute all configured reviewers", () => {
|
|
658
|
+
const harness = createHarness();
|
|
659
|
+
const project = setupProject(harness, "required-supervisor-executed-incomplete", {
|
|
660
|
+
requireSupervisorReview: true
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
writeText(
|
|
664
|
+
project.pencilDesignPath,
|
|
665
|
+
[
|
|
666
|
+
"# Pencil Design",
|
|
667
|
+
"",
|
|
668
|
+
"## Design-Supervisor Review",
|
|
669
|
+
"- Configured reviewers: frontend-skill",
|
|
670
|
+
"- Executed reviewers: ui-ux-pro-max",
|
|
671
|
+
"- Review source: skill",
|
|
672
|
+
"- Status: PASS",
|
|
673
|
+
"- Issue list: none",
|
|
674
|
+
"- Revision outcome: approved",
|
|
675
|
+
""
|
|
676
|
+
].join("\n")
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
const result = auditProject(project.root, {
|
|
680
|
+
mode: "completion",
|
|
681
|
+
changeId
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
assert.equal(result.status, "FAIL");
|
|
685
|
+
assert.match(
|
|
686
|
+
result.failures.join("\n"),
|
|
687
|
+
/did not execute configured reviewer skill\(s\): frontend-skill/i
|
|
688
|
+
);
|
|
689
|
+
});
|
|
690
|
+
|
|
348
691
|
console.log("All design-supervisor audit tests passed.");
|
|
@@ -283,6 +283,11 @@ runTest("design-supervisor review stays distinct from preferred adapters and is
|
|
|
283
283
|
const content = read(file);
|
|
284
284
|
assert.match(content, /design-supervisor review/, `${file} should instruct design flows to run design-supervisor review when configured`);
|
|
285
285
|
assert.match(content, /Require Supervisor Review: true/, `${file} should explain the hard-gate branch for supervisor review`);
|
|
286
|
+
assert.doesNotMatch(
|
|
287
|
+
content,
|
|
288
|
+
/^\s*##\s*Design-Supervisor Review\s*\(Round [^)]+Attempt\)\s*$/im,
|
|
289
|
+
`${file} should not provide ad-hoc supervisor-review heading templates`
|
|
290
|
+
);
|
|
286
291
|
}
|
|
287
292
|
});
|
|
288
293
|
|
|
@@ -33,6 +33,10 @@ function createTempDir() {
|
|
|
33
33
|
return fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-pen-persistence-"));
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function escapeRegExp(value) {
|
|
37
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
38
|
+
}
|
|
39
|
+
|
|
36
40
|
function writePayloadFiles(tempDir, fixture) {
|
|
37
41
|
const nodesFile = path.join(tempDir, "nodes.json");
|
|
38
42
|
const variablesFile = path.join(tempDir, "variables.json");
|
|
@@ -183,6 +187,151 @@ runTest("comparePenSync fails when live payload is newer than disk", () => {
|
|
|
183
187
|
assert.notEqual(result.liveHash, result.persistedHash);
|
|
184
188
|
});
|
|
185
189
|
|
|
190
|
+
runTest("writePenFromPayloadFiles rejects pen-state metadata passed as nodes payload", () => {
|
|
191
|
+
const tempDir = createTempDir();
|
|
192
|
+
const outputPath = path.join(tempDir, "written.pen");
|
|
193
|
+
const nodesFile = path.join(tempDir, "state-like.json");
|
|
194
|
+
|
|
195
|
+
fs.writeFileSync(
|
|
196
|
+
nodesFile,
|
|
197
|
+
JSON.stringify(
|
|
198
|
+
{
|
|
199
|
+
schema: 1,
|
|
200
|
+
penPath: "/tmp/demo.pen",
|
|
201
|
+
snapshotHash: "abc123",
|
|
202
|
+
topLevelIds: [],
|
|
203
|
+
topLevelCount: 0
|
|
204
|
+
},
|
|
205
|
+
null,
|
|
206
|
+
2
|
|
207
|
+
)
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
assert.throws(
|
|
211
|
+
() =>
|
|
212
|
+
writePenFromPayloadFiles({
|
|
213
|
+
outputPath,
|
|
214
|
+
nodesFile
|
|
215
|
+
}),
|
|
216
|
+
/appears to be pen state metadata/i
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
runTest("comparePenSync rejects pen-state metadata passed as nodes payload", () => {
|
|
221
|
+
const fixture = JSON.parse(fs.readFileSync(fixturePath, "utf8"));
|
|
222
|
+
const tempDir = createTempDir();
|
|
223
|
+
const { nodesFile, variablesFile } = writePayloadFiles(tempDir, fixture);
|
|
224
|
+
const outputPath = path.join(tempDir, "written.pen");
|
|
225
|
+
const invalidNodesFile = path.join(tempDir, "state-like.json");
|
|
226
|
+
|
|
227
|
+
writePenFromPayloadFiles({
|
|
228
|
+
outputPath,
|
|
229
|
+
nodesFile,
|
|
230
|
+
variablesFile,
|
|
231
|
+
version: fixture.version
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
fs.writeFileSync(
|
|
235
|
+
invalidNodesFile,
|
|
236
|
+
JSON.stringify(
|
|
237
|
+
{
|
|
238
|
+
schema: 1,
|
|
239
|
+
penPath: outputPath,
|
|
240
|
+
snapshotHash: "abc123",
|
|
241
|
+
topLevelIds: fixture.children.map((node) => node.id),
|
|
242
|
+
topLevelCount: fixture.children.length
|
|
243
|
+
},
|
|
244
|
+
null,
|
|
245
|
+
2
|
|
246
|
+
)
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
assert.throws(
|
|
250
|
+
() =>
|
|
251
|
+
comparePenSync({
|
|
252
|
+
penPath: outputPath,
|
|
253
|
+
nodesFile: invalidNodesFile,
|
|
254
|
+
version: fixture.version
|
|
255
|
+
}),
|
|
256
|
+
/appears to be pen state metadata/i
|
|
257
|
+
);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
runTest("writePenFromPayloadFiles accepts nodes payload even when metadata keys are present", () => {
|
|
261
|
+
const fixture = JSON.parse(fs.readFileSync(fixturePath, "utf8"));
|
|
262
|
+
const tempDir = createTempDir();
|
|
263
|
+
const nodesFile = path.join(tempDir, "nodes-with-metadata.json");
|
|
264
|
+
const variablesFile = path.join(tempDir, "variables.json");
|
|
265
|
+
const outputPath = path.join(tempDir, "written.pen");
|
|
266
|
+
|
|
267
|
+
fs.writeFileSync(
|
|
268
|
+
nodesFile,
|
|
269
|
+
JSON.stringify(
|
|
270
|
+
{
|
|
271
|
+
nodes: fixture.children,
|
|
272
|
+
penPath: "/tmp/demo.pen",
|
|
273
|
+
snapshotHash: "abc123",
|
|
274
|
+
topLevelIds: fixture.children.map((node) => node.id),
|
|
275
|
+
topLevelCount: fixture.children.length
|
|
276
|
+
},
|
|
277
|
+
null,
|
|
278
|
+
2
|
|
279
|
+
)
|
|
280
|
+
);
|
|
281
|
+
fs.writeFileSync(variablesFile, JSON.stringify({ variables: fixture.variables }, null, 2));
|
|
282
|
+
|
|
283
|
+
writePenFromPayloadFiles({
|
|
284
|
+
outputPath,
|
|
285
|
+
nodesFile,
|
|
286
|
+
variablesFile,
|
|
287
|
+
version: fixture.version
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
assert.deepEqual(JSON.parse(fs.readFileSync(outputPath, "utf8")), fixture);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
runTest("writePenFromPayloadFiles shows relative nodes path in metadata-payload error when file is under cwd", () => {
|
|
294
|
+
const originalCwd = process.cwd();
|
|
295
|
+
const localRoot = fs.mkdtempSync(path.join(originalCwd, ".tmp-da-vinci-pen-path-"));
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
process.chdir(localRoot);
|
|
299
|
+
const outputPath = path.join(localRoot, "written.pen");
|
|
300
|
+
const nodesFile = path.join(localRoot, "state-like.json");
|
|
301
|
+
|
|
302
|
+
fs.writeFileSync(
|
|
303
|
+
nodesFile,
|
|
304
|
+
JSON.stringify(
|
|
305
|
+
{
|
|
306
|
+
schema: 1,
|
|
307
|
+
penPath: "/tmp/demo.pen",
|
|
308
|
+
snapshotHash: "abc123",
|
|
309
|
+
topLevelIds: [],
|
|
310
|
+
topLevelCount: 0
|
|
311
|
+
},
|
|
312
|
+
null,
|
|
313
|
+
2
|
|
314
|
+
)
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
assert.throws(
|
|
318
|
+
() =>
|
|
319
|
+
writePenFromPayloadFiles({
|
|
320
|
+
outputPath,
|
|
321
|
+
nodesFile
|
|
322
|
+
}),
|
|
323
|
+
(error) => {
|
|
324
|
+
assert.match(error.message, /\(state-like\.json\)/i);
|
|
325
|
+
assert.doesNotMatch(error.message, new RegExp(escapeRegExp(localRoot)));
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
} finally {
|
|
330
|
+
process.chdir(originalCwd);
|
|
331
|
+
fs.rmSync(localRoot, { recursive: true, force: true });
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
|
|
186
335
|
runTest("global Pencil lock serializes projects", () => {
|
|
187
336
|
const tempDir = createTempDir();
|
|
188
337
|
const homeDir = path.join(tempDir, "home");
|