@1agh/maude 0.49.2 → 0.51.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/apps/studio/api.ts +751 -52
- package/apps/studio/canvas-artifacts.ts +153 -0
- package/apps/studio/canvas-create.ts +23 -0
- package/apps/studio/canvas-slug.ts +30 -0
- package/apps/studio/client/app.jsx +547 -90
- package/apps/studio/client/panels/CloudBar.jsx +388 -0
- package/apps/studio/client/styles/3-shell-maude.css +28 -0
- package/apps/studio/client/tree-row-menu.jsx +132 -0
- package/apps/studio/client/use-tree-drag.js +120 -0
- package/apps/studio/cloud/endpoints.ts +284 -0
- package/apps/studio/collab/registry.ts +23 -0
- package/apps/studio/debug-bundle.ts +144 -0
- package/apps/studio/dist/client.bundle.js +1235 -1228
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/http.ts +205 -1
- package/apps/studio/inspect.ts +49 -0
- package/apps/studio/server.ts +15 -0
- package/apps/studio/sync/workspace-signin.ts +15 -0
- package/apps/studio/test/canvas-artifacts.test.ts +125 -0
- package/apps/studio/test/canvas-create-api.test.ts +159 -1
- package/apps/studio/test/canvas-move-api.test.ts +556 -0
- package/apps/studio/test/canvas-origin-gate.test.ts +6 -0
- package/apps/studio/test/cloud-endpoints.test.ts +130 -0
- package/apps/studio/test/debug-bundle.test.ts +105 -0
- package/apps/studio/test/fs-mkdir-api.test.ts +248 -0
- package/apps/studio/test/workspace-signin.test.ts +19 -0
- package/apps/studio/whats-new.json +27 -0
- package/cli/bin/maude.mjs +1 -0
- package/cli/commands/hub-workspace.mjs +294 -7
- package/cli/commands/init.mjs +33 -2
- package/cli/commands/kg.mjs +129 -4
- package/cli/commands/share.mjs +184 -0
- package/cli/lib/ddr-to-kgai.mjs +310 -82
- package/cli/lib/ddr-to-kgai.test.mjs +20 -0
- package/cli/lib/share-plan.mjs +96 -0
- package/cli/lib/share-plan.test.mjs +57 -0
- package/cli/lib/workspace-plan.mjs +130 -12
- package/cli/lib/workspace-plan.test.mjs +160 -0
- package/package.json +9 -8
|
@@ -7,7 +7,14 @@
|
|
|
7
7
|
// covering happy path, the rejection matrix, group allowlist, duplicate.
|
|
8
8
|
|
|
9
9
|
import { describe, expect, test } from 'bun:test';
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
existsSync,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
readdirSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
symlinkSync,
|
|
16
|
+
writeFileSync,
|
|
17
|
+
} from 'node:fs';
|
|
11
18
|
import { join } from 'node:path';
|
|
12
19
|
|
|
13
20
|
import { componentNameFrom, renderBriefBoard, validateCanvasName } from '../canvas-create.ts';
|
|
@@ -455,6 +462,36 @@ describe('/_api/canvas — DELETE (soft-delete)', () => {
|
|
|
455
462
|
}
|
|
456
463
|
});
|
|
457
464
|
|
|
465
|
+
// Task 2 bug fix: deleteCanvas now consumes canvas-artifacts.ts's inventory,
|
|
466
|
+
// which closes two gaps the old hand-rolled sidecar list missed.
|
|
467
|
+
test('also trashes the .ydoc.bin cache and the footage EDL (bug fix — canvas-artifacts.ts inventory)', async () => {
|
|
468
|
+
const { root, designRoot } = makeSandbox();
|
|
469
|
+
const port = nextPort();
|
|
470
|
+
const proc = await bootServer(root, port);
|
|
471
|
+
try {
|
|
472
|
+
const created = await createBoard(port, 'Full Sweep');
|
|
473
|
+
mkdirSync(join(designRoot, '_state'), { recursive: true });
|
|
474
|
+
const ydocAbs = join(designRoot, '_state', `${created.slug}.ydoc.bin`);
|
|
475
|
+
writeFileSync(ydocAbs, Buffer.from([1, 2, 3]));
|
|
476
|
+
const edlAbs = join(designRoot, `${created.slug}.edl.json`);
|
|
477
|
+
writeFileSync(edlAbs, JSON.stringify({ beats: [] }));
|
|
478
|
+
|
|
479
|
+
const r = await del(port, created.file);
|
|
480
|
+
expect(r.status).toBe(200);
|
|
481
|
+
const j = (await r.json()) as { ok: boolean; trashed: string[] };
|
|
482
|
+
expect(j.trashed.some((t) => t.endsWith('.ydoc.bin'))).toBe(true);
|
|
483
|
+
expect(j.trashed.some((t) => t.endsWith('.edl.json'))).toBe(true);
|
|
484
|
+
expect(existsSync(ydocAbs)).toBe(false);
|
|
485
|
+
expect(existsSync(edlAbs)).toBe(false);
|
|
486
|
+
const trashDirs = readdirSync(join(designRoot, '_trash'));
|
|
487
|
+
const td = join(designRoot, '_trash', trashDirs[0] as string);
|
|
488
|
+
expect(existsSync(join(td, `_state__${created.slug}.ydoc.bin`))).toBe(true);
|
|
489
|
+
expect(existsSync(join(td, `${created.slug}.edl.json`))).toBe(true);
|
|
490
|
+
} finally {
|
|
491
|
+
await killProc(proc);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
|
|
458
495
|
test('refuses to delete a design-system file (400, untouched)', async () => {
|
|
459
496
|
const { root, designRoot } = makeSandbox();
|
|
460
497
|
const port = nextPort();
|
|
@@ -507,3 +544,124 @@ describe('/_api/canvas — DELETE (soft-delete)', () => {
|
|
|
507
544
|
}
|
|
508
545
|
});
|
|
509
546
|
});
|
|
547
|
+
|
|
548
|
+
// feature-file-tree-drag-drop-folders (dogfood follow-up) — DELETE now
|
|
549
|
+
// auto-detects a non-.tsx target as a folder delete.
|
|
550
|
+
describe('/_api/canvas — DELETE (folder delete, dogfood follow-up)', () => {
|
|
551
|
+
async function createBoard(port: number, name: string) {
|
|
552
|
+
const r = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
553
|
+
method: 'POST',
|
|
554
|
+
headers: { 'Content-Type': 'application/json' },
|
|
555
|
+
body: JSON.stringify({ name }),
|
|
556
|
+
});
|
|
557
|
+
return (await r.json()) as { file: string; slug: string };
|
|
558
|
+
}
|
|
559
|
+
const del = (port: number, file: string) =>
|
|
560
|
+
fetch(`http://localhost:${port}/_api/canvas?file=${encodeURIComponent(file)}`, {
|
|
561
|
+
method: 'DELETE',
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
test('deletes a folder: trashes every nested canvas, folder itself is gone', async () => {
|
|
565
|
+
const { root, designRoot } = makeSandbox();
|
|
566
|
+
const port = nextPort();
|
|
567
|
+
const proc = await bootServer(root, port);
|
|
568
|
+
try {
|
|
569
|
+
const created = await createBoard(port, 'Inside');
|
|
570
|
+
const moved = await fetch(`http://localhost:${port}/_api/fs-move`, {
|
|
571
|
+
method: 'POST',
|
|
572
|
+
headers: { 'Content-Type': 'application/json' },
|
|
573
|
+
body: JSON.stringify({ file: created.file, toDir: 'ui/Box' }),
|
|
574
|
+
});
|
|
575
|
+
expect(moved.status).toBe(200);
|
|
576
|
+
expect(existsSync(join(designRoot, 'ui', 'Box', 'Inside.tsx'))).toBe(true);
|
|
577
|
+
|
|
578
|
+
const r = await del(port, 'ui/Box');
|
|
579
|
+
expect(r.status).toBe(200);
|
|
580
|
+
const j = (await r.json()) as { ok: boolean; trashed: string[] };
|
|
581
|
+
expect(j.ok).toBe(true);
|
|
582
|
+
expect(j.trashed.some((t) => t.endsWith('Inside.tsx'))).toBe(true);
|
|
583
|
+
expect(existsSync(join(designRoot, 'ui', 'Box'))).toBe(false);
|
|
584
|
+
const trashDirs = readdirSync(join(designRoot, '_trash'));
|
|
585
|
+
expect(trashDirs.length).toBe(1);
|
|
586
|
+
} finally {
|
|
587
|
+
await killProc(proc);
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
test('deletes an EMPTY folder (mkdir + delete, no canvases inside)', async () => {
|
|
592
|
+
const { root, designRoot } = makeSandbox();
|
|
593
|
+
const port = nextPort();
|
|
594
|
+
const proc = await bootServer(root, port);
|
|
595
|
+
try {
|
|
596
|
+
await fetch(`http://localhost:${port}/_api/fs-mkdir`, {
|
|
597
|
+
method: 'POST',
|
|
598
|
+
headers: { 'Content-Type': 'application/json' },
|
|
599
|
+
body: JSON.stringify({ parent: 'ui', name: 'Empty' }),
|
|
600
|
+
});
|
|
601
|
+
expect(existsSync(join(designRoot, 'ui', 'Empty'))).toBe(true);
|
|
602
|
+
const r = await del(port, 'ui/Empty');
|
|
603
|
+
expect(r.status).toBe(200);
|
|
604
|
+
expect(existsSync(join(designRoot, 'ui', 'Empty'))).toBe(false);
|
|
605
|
+
} finally {
|
|
606
|
+
await killProc(proc);
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
test('refuses deleting a canvas GROUP ROOT (400, untouched)', async () => {
|
|
611
|
+
const { root, designRoot } = makeSandbox();
|
|
612
|
+
const port = nextPort();
|
|
613
|
+
const proc = await bootServer(root, port);
|
|
614
|
+
try {
|
|
615
|
+
const r = await del(port, 'ui');
|
|
616
|
+
expect(r.status).toBe(400);
|
|
617
|
+
expect(existsSync(join(designRoot, 'ui'))).toBe(true);
|
|
618
|
+
} finally {
|
|
619
|
+
await killProc(proc);
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
test('refuses deleting a design-system folder (400, untouched)', async () => {
|
|
624
|
+
const { root, designRoot } = makeSandbox();
|
|
625
|
+
const port = nextPort();
|
|
626
|
+
const proc = await bootServer(root, port);
|
|
627
|
+
try {
|
|
628
|
+
mkdirSync(join(designRoot, 'system', 'sub'), { recursive: true });
|
|
629
|
+
const r = await del(port, 'system/sub');
|
|
630
|
+
expect(r.status).toBe(400);
|
|
631
|
+
expect(existsSync(join(designRoot, 'system', 'sub'))).toBe(true);
|
|
632
|
+
} finally {
|
|
633
|
+
await killProc(proc);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
test('a missing folder returns 404', async () => {
|
|
638
|
+
const { root } = makeSandbox();
|
|
639
|
+
const port = nextPort();
|
|
640
|
+
const proc = await bootServer(root, port);
|
|
641
|
+
try {
|
|
642
|
+
const r = await del(port, 'ui/Nope');
|
|
643
|
+
expect(r.status).toBe(404);
|
|
644
|
+
} finally {
|
|
645
|
+
await killProc(proc);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// Security review finding — see the matching tests in canvas-move-api.test.ts
|
|
650
|
+
// and fs-mkdir-api.test.ts.
|
|
651
|
+
test('refuses deleting a symlinked "folder" (would rm -rf outside designRoot)', async () => {
|
|
652
|
+
const { root, designRoot } = makeSandbox();
|
|
653
|
+
const outside = join(root, 'OUTSIDE');
|
|
654
|
+
mkdirSync(outside, { recursive: true });
|
|
655
|
+
writeFileSync(join(outside, 'Secret.txt'), 'do not delete me');
|
|
656
|
+
const port = nextPort();
|
|
657
|
+
const proc = await bootServer(root, port);
|
|
658
|
+
try {
|
|
659
|
+
symlinkSync(outside, join(designRoot, 'ui', 'evil'));
|
|
660
|
+
const r = await del(port, 'ui/evil');
|
|
661
|
+
expect(r.status).toBe(400);
|
|
662
|
+
expect(existsSync(join(outside, 'Secret.txt'))).toBe(true);
|
|
663
|
+
} finally {
|
|
664
|
+
await killProc(proc);
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
});
|