@1agh/maude 1.0.9 → 1.2.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/acp/bridge.ts +18 -13
- package/apps/studio/acp/probe.ts +268 -16
- package/apps/studio/acp/transcript-io.ts +394 -0
- package/apps/studio/acp/transcript.ts +103 -38
- package/apps/studio/api.ts +139 -26
- package/apps/studio/bin/_import-figma.mjs +57 -11
- package/apps/studio/bin/_seed-fixture.mjs +163 -0
- package/apps/studio/canvas-imports.ts +89 -0
- package/apps/studio/client/app.jsx +257 -7
- package/apps/studio/client/export-center.jsx +15 -7
- package/apps/studio/client/panels/ReadinessList.jsx +7 -1
- package/apps/studio/client/panels/SyncPanel.jsx +125 -4
- package/apps/studio/client/report-bug.jsx +19 -1
- package/apps/studio/client/styles/3-shell-maude.css +34 -0
- package/apps/studio/client/styles/4-components.css +26 -0
- package/apps/studio/collab/persistence.ts +132 -16
- package/apps/studio/collab/registry.ts +10 -18
- package/apps/studio/debug-bundle.ts +13 -0
- package/apps/studio/dist/client.bundle.js +1128 -1128
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/export-dialog.tsx +54 -0
- package/apps/studio/exporters/degraded.ts +19 -3
- package/apps/studio/exporters/ghostscript.ts +194 -0
- package/apps/studio/exporters/pdf-fonts.ts +261 -0
- package/apps/studio/exporters/pdf.ts +152 -3
- package/apps/studio/exporters/remote.ts +36 -1
- package/apps/studio/fs-watch.ts +45 -1
- package/apps/studio/http.ts +7 -1
- package/apps/studio/mem.ts +45 -9
- package/apps/studio/readiness.ts +42 -7
- package/apps/studio/server.ts +11 -4
- package/apps/studio/sync/agent.ts +136 -29
- package/apps/studio/sync/asset-push.ts +1 -47
- package/apps/studio/sync/codec.ts +122 -31
- package/apps/studio/sync/cold-start-apply.ts +7 -0
- package/apps/studio/sync/cold-start.ts +118 -22
- package/apps/studio/sync/comment-identity.ts +68 -0
- package/apps/studio/sync/connection-state.ts +15 -1
- package/apps/studio/sync/file-ledger.ts +117 -0
- package/apps/studio/sync/file-plane.ts +623 -27
- package/apps/studio/sync/index.ts +711 -20
- package/apps/studio/sync/journal-client.ts +13 -1
- package/apps/studio/sync/migrate-seed.ts +40 -3
- package/apps/studio/sync/presentation.ts +26 -3
- package/apps/studio/sync/retry-after.ts +169 -0
- package/apps/studio/sync/seed-progress.ts +218 -0
- package/apps/studio/sync/status.ts +122 -5
- package/apps/studio/sync/transport-error.ts +111 -0
- package/apps/studio/whats-new.json +91 -0
- package/cli/bin/maude.mjs +2 -0
- package/cli/commands/codex.mjs +191 -0
- package/cli/commands/harness.mjs +1051 -0
- package/cli/commands/help.mjs +13 -0
- package/cli/lib/design-link.mjs +190 -2
- package/cli/lib/harness/capabilities.mjs +302 -0
- package/cli/lib/harness/codex-runtime.mjs +889 -0
- package/cli/lib/harness/compatibility.mjs +27 -0
- package/cli/lib/harness/discover-claude.mjs +1393 -0
- package/cli/lib/harness/managed-state.mjs +1296 -0
- package/cli/lib/harness/model.mjs +86 -0
- package/cli/lib/harness/secrets.mjs +390 -0
- package/cli/lib/harness/targets/codex.mjs +1141 -0
- package/cli/lib/harness/targets/opencode.mjs +807 -0
- package/cli/lib/harness/transaction.mjs +1099 -0
- package/cli/lib/hubs-config.mjs +35 -8
- package/cli/templates/harness/opencode/maude-projector.ts +58 -0
- package/docs/harness-capability-matrix.md +125 -0
- package/docs/harness-environment-projection.md +95 -0
- package/package.json +32 -9
- package/plugins/flow/.claude-plugin/config.schema.json +8 -0
- package/apps/studio/.ai/cache/_stats.json +0 -7
- package/apps/studio/bin/_agent-browser-safe.test.mjs +0 -165
- package/apps/studio/bin/_curl-local.test.mjs +0 -280
- package/apps/studio/bin/_pw-launch.test.mjs +0 -90
- package/apps/studio/bin/_smart-frames.test.mjs +0 -195
- package/apps/studio/bin/_transcribe.test.mjs +0 -80
- package/apps/studio/bun.lock +0 -928
- package/apps/studio/draw/test/animate.test.ts +0 -172
- package/apps/studio/draw/test/brush.test.ts +0 -213
- package/apps/studio/draw/test/composition.test.ts +0 -141
- package/apps/studio/draw/test/geometry.test.ts +0 -199
- package/apps/studio/draw/test/gradient.test.ts +0 -167
- package/apps/studio/draw/test/layout.test.ts +0 -120
- package/apps/studio/draw/test/morph.test.ts +0 -107
- package/apps/studio/draw/test/optimize.test.ts +0 -40
- package/apps/studio/draw/test/palette.test.ts +0 -123
- package/apps/studio/draw/test/primitives.test.ts +0 -129
- package/apps/studio/draw/test/serialize-animate.test.ts +0 -142
- package/apps/studio/draw/test/serialize.test.ts +0 -105
- package/apps/studio/figma/assets.test.ts +0 -464
- package/apps/studio/figma/client.test.ts +0 -395
- package/apps/studio/figma/codegen-client.test.ts +0 -276
- package/apps/studio/figma/codegen-fonts.test.ts +0 -103
- package/apps/studio/figma/codegen-values.test.ts +0 -179
- package/apps/studio/figma/comments-to-strokes.test.ts +0 -194
- package/apps/studio/figma/fig-decode.test.ts +0 -788
- package/apps/studio/figma/fig-differential.test.ts +0 -182
- package/apps/studio/figma/fig-translator.test.ts +0 -192
- package/apps/studio/figma/fig-vector.test.ts +0 -113
- package/apps/studio/figma/from-codegen.test.ts +0 -408
- package/apps/studio/figma/sanitize.test.ts +0 -325
- package/apps/studio/figma/tailwind-map.test.ts +0 -142
- package/apps/studio/figma/to-artboard.test.ts +0 -808
- package/apps/studio/figma/to-render.test.ts +0 -180
- package/apps/studio/figma/to-strokes-roundtrip.test.ts +0 -152
- package/apps/studio/figma/to-strokes.test.ts +0 -705
- package/apps/studio/figma/to-tokens.test.ts +0 -321
- package/apps/studio/figma/url.test.ts +0 -167
- package/apps/studio/footage/schema.test.ts +0 -287
- package/apps/studio/generation/adapters/groq.test.ts +0 -80
- package/apps/studio/generation/audio-library.test.ts +0 -95
- package/apps/studio/generation/captions.test.ts +0 -78
- package/apps/studio/generation/elevenlabs.test.ts +0 -223
- package/apps/studio/generation/gemini.test.ts +0 -350
- package/apps/studio/generation/jobs.test.ts +0 -128
- package/apps/studio/generation/keys.test.ts +0 -75
- package/apps/studio/generation/prefs.test.ts +0 -95
- package/apps/studio/generation/types.test.ts +0 -123
- package/apps/studio/generation/whisper-models.test.ts +0 -179
- package/apps/studio/test/_helpers.ts +0 -108
- package/apps/studio/test/acp-activity-endpoint.test.ts +0 -183
- package/apps/studio/test/acp-activity.test.ts +0 -154
- package/apps/studio/test/acp-ai-activity.test.ts +0 -182
- package/apps/studio/test/acp-attachment-serve.test.ts +0 -74
- package/apps/studio/test/acp-bootstrap-brief.test.ts +0 -178
- package/apps/studio/test/acp-branch-guard.test.ts +0 -123
- package/apps/studio/test/acp-bridge-lifetime.test.ts +0 -475
- package/apps/studio/test/acp-bridge.test.ts +0 -301
- package/apps/studio/test/acp-capabilities.test.ts +0 -123
- package/apps/studio/test/acp-caps-bridge.test.ts +0 -279
- package/apps/studio/test/acp-commands.test.ts +0 -119
- package/apps/studio/test/acp-elicitation-bridge.test.ts +0 -480
- package/apps/studio/test/acp-elicitation.test.ts +0 -251
- package/apps/studio/test/acp-env.test.ts +0 -95
- package/apps/studio/test/acp-mode-banner.test.ts +0 -45
- package/apps/studio/test/acp-origin-gate.test.ts +0 -160
- package/apps/studio/test/acp-permission-prompt.test.ts +0 -251
- package/apps/studio/test/acp-permission.test.ts +0 -278
- package/apps/studio/test/acp-plugin-bootstrap.test.ts +0 -89
- package/apps/studio/test/acp-session-allowed-tools.test.ts +0 -235
- package/apps/studio/test/acp-session-plugins.test.ts +0 -209
- package/apps/studio/test/acp-toolgroup.test.ts +0 -76
- package/apps/studio/test/acp-transcript-view.test.ts +0 -71
- package/apps/studio/test/acp-transcript.test.ts +0 -238
- package/apps/studio/test/acp-usage-bridge.test.ts +0 -141
- package/apps/studio/test/acp-usage.test.ts +0 -143
- package/apps/studio/test/acp-write-gate.test.ts +0 -323
- package/apps/studio/test/acp-write-scope.test.ts +0 -533
- package/apps/studio/test/active-state.test.ts +0 -186
- package/apps/studio/test/activity.test.ts +0 -189
- package/apps/studio/test/ai-activity.test.ts +0 -113
- package/apps/studio/test/annotate-write.test.ts +0 -1074
- package/apps/studio/test/annotation-strokes-command.test.ts +0 -166
- package/apps/studio/test/annotations-align.test.ts +0 -88
- package/apps/studio/test/annotations-api.test.ts +0 -145
- package/apps/studio/test/annotations-bindings.test.ts +0 -262
- package/apps/studio/test/annotations-draw-modifiers.test.ts +0 -206
- package/apps/studio/test/annotations-groups.test.ts +0 -231
- package/apps/studio/test/annotations-layer.test.ts +0 -1011
- package/apps/studio/test/annotations-roundtrip.test.ts +0 -663
- package/apps/studio/test/annotations-snap.test.ts +0 -135
- package/apps/studio/test/annotations-text-format.test.ts +0 -396
- package/apps/studio/test/artboard-activity-overlay.test.tsx +0 -56
- package/apps/studio/test/artboard-guides-overlay.test.tsx +0 -152
- package/apps/studio/test/artboard-hug-commit.test.ts +0 -36
- package/apps/studio/test/artboard-kinds.test.tsx +0 -83
- package/apps/studio/test/artboard-selection-attrs.test.ts +0 -69
- package/apps/studio/test/asset-api.test.ts +0 -149
- package/apps/studio/test/assets-s3.test.ts +0 -249
- package/apps/studio/test/binary-smoke.test.ts +0 -47
- package/apps/studio/test/boot-self-heal.test.ts +0 -87
- package/apps/studio/test/browse-posture.test.tsx +0 -107
- package/apps/studio/test/bundle-smoke.test.ts +0 -46
- package/apps/studio/test/camera-reveal.test.tsx +0 -173
- package/apps/studio/test/canvas-artifacts.test.ts +0 -125
- package/apps/studio/test/canvas-build-sandbox.test.ts +0 -69
- package/apps/studio/test/canvas-build.test.ts +0 -141
- package/apps/studio/test/canvas-create-api.test.ts +0 -676
- package/apps/studio/test/canvas-cursors.test.ts +0 -164
- package/apps/studio/test/canvas-edit.test.ts +0 -470
- package/apps/studio/test/canvas-freshness-deps.test.ts +0 -64
- package/apps/studio/test/canvas-header.test.ts +0 -127
- package/apps/studio/test/canvas-hide-chrome.test.ts +0 -88
- package/apps/studio/test/canvas-hmr-runtime.test.tsx +0 -114
- package/apps/studio/test/canvas-lib-inline.test.ts +0 -146
- package/apps/studio/test/canvas-lib-motion.test.ts +0 -131
- package/apps/studio/test/canvas-lib-resolver.test.ts +0 -111
- package/apps/studio/test/canvas-list-watch.test.ts +0 -388
- package/apps/studio/test/canvas-media-drop.test.ts +0 -187
- package/apps/studio/test/canvas-meta-api.test.ts +0 -707
- package/apps/studio/test/canvas-move-api.test.ts +0 -587
- package/apps/studio/test/canvas-origin-gate.test.ts +0 -417
- package/apps/studio/test/canvas-pipeline.test.ts +0 -180
- package/apps/studio/test/canvas-rects.test.ts +0 -198
- package/apps/studio/test/canvas-reorder.test.ts +0 -211
- package/apps/studio/test/canvas-route.test.ts +0 -179
- package/apps/studio/test/canvas-shell-base.test.ts +0 -107
- package/apps/studio/test/canvas-shell-build-error.test.ts +0 -52
- package/apps/studio/test/canvas-url.test.ts +0 -138
- package/apps/studio/test/canvas-wheel-delta.test.ts +0 -42
- package/apps/studio/test/canvas-zoom-floor.test.ts +0 -73
- package/apps/studio/test/capture-core.test.ts +0 -32
- package/apps/studio/test/capture-determinism-shape.test.ts +0 -135
- package/apps/studio/test/chat-attachments.test.ts +0 -117
- package/apps/studio/test/chat-context.test.ts +0 -129
- package/apps/studio/test/chat-markdown.test.tsx +0 -58
- package/apps/studio/test/clip-addressing.test.ts +0 -738
- package/apps/studio/test/clip-ops.test.ts +0 -922
- package/apps/studio/test/cloud-build.test.ts +0 -95
- package/apps/studio/test/cloud-connect-note.test.ts +0 -135
- package/apps/studio/test/cloud-endpoints.test.ts +0 -590
- package/apps/studio/test/cloud-history-hardening.test.ts +0 -165
- package/apps/studio/test/cloud-history-posture.test.ts +0 -230
- package/apps/studio/test/cloud-managed-save-surfaces.test.ts +0 -90
- package/apps/studio/test/cloud-renew.test.ts +0 -205
- package/apps/studio/test/cloud-session-role.test.ts +0 -161
- package/apps/studio/test/cloud-shell-surfaces.test.ts +0 -170
- package/apps/studio/test/cold-start-apply.test.ts +0 -303
- package/apps/studio/test/collab-annotations-bridge.test.ts +0 -55
- package/apps/studio/test/collab-awareness-bridge.test.ts +0 -223
- package/apps/studio/test/collab-bridge.test.ts +0 -79
- package/apps/studio/test/collab-loopback.test.ts +0 -63
- package/apps/studio/test/collab-origin-gate.test.ts +0 -323
- package/apps/studio/test/collab-protocol.test.ts +0 -146
- package/apps/studio/test/collab-readonly-gate.test.ts +0 -194
- package/apps/studio/test/collab-reseed-guard.test.ts +0 -78
- package/apps/studio/test/collab-room.test.ts +0 -188
- package/apps/studio/test/collab-session-survival.test.tsx +0 -176
- package/apps/studio/test/collab-stress.test.ts +0 -129
- package/apps/studio/test/comment-mount.test.ts +0 -97
- package/apps/studio/test/comment-relay-origin-gate.test.ts +0 -117
- package/apps/studio/test/comments-api.test.ts +0 -277
- package/apps/studio/test/comments-fs-rebroadcast.test.ts +0 -155
- package/apps/studio/test/comments-overlay.test.ts +0 -117
- package/apps/studio/test/compile-entry.test.ts +0 -133
- package/apps/studio/test/component-map.test.ts +0 -48
- package/apps/studio/test/config-projection.test.ts +0 -104
- package/apps/studio/test/config-reload.test.ts +0 -230
- package/apps/studio/test/config-version.test.ts +0 -88
- package/apps/studio/test/context-resolve-tokens.test.ts +0 -97
- package/apps/studio/test/convert-to-absolute.test.ts +0 -333
- package/apps/studio/test/credential-grammar.test.ts +0 -107
- package/apps/studio/test/csp-canvas-shell.test.ts +0 -62
- package/apps/studio/test/csrf-write-guard.test.ts +0 -141
- package/apps/studio/test/debug-bundle.test.ts +0 -105
- package/apps/studio/test/design-setup-readiness.test.ts +0 -155
- package/apps/studio/test/detach-component.test.ts +0 -94
- package/apps/studio/test/dns-rebinding-guard.test.ts +0 -101
- package/apps/studio/test/dom-selection.test.ts +0 -198
- package/apps/studio/test/dynamic-text-edit.test.ts +0 -162
- package/apps/studio/test/edit-css-occurrence.test.ts +0 -81
- package/apps/studio/test/edit-persistence.test.ts +0 -91
- package/apps/studio/test/edit-scope-api.test.ts +0 -119
- package/apps/studio/test/edit-source-command.test.ts +0 -142
- package/apps/studio/test/edit-suppress.test.ts +0 -170
- package/apps/studio/test/editing-presence.test.ts +0 -103
- package/apps/studio/test/element-resize.test.ts +0 -136
- package/apps/studio/test/element-structural-api.test.ts +0 -434
- package/apps/studio/test/element-structural-edit.test.ts +0 -750
- package/apps/studio/test/ensure-browser.test.ts +0 -92
- package/apps/studio/test/equal-spacing-detector.test.ts +0 -257
- package/apps/studio/test/export-assemble.test.ts +0 -53
- package/apps/studio/test/export-browser-lane.test.ts +0 -126
- package/apps/studio/test/export-capture-fidelity.test.ts +0 -146
- package/apps/studio/test/export-capture-hygiene.test.ts +0 -675
- package/apps/studio/test/export-center.test.tsx +0 -287
- package/apps/studio/test/export-e2e-lanes.test.ts +0 -857
- package/apps/studio/test/export-format-scope-coherence.test.ts +0 -89
- package/apps/studio/test/export-lane.test.ts +0 -245
- package/apps/studio/test/export-shim-multi-capture.test.ts +0 -83
- package/apps/studio/test/exporters/canva.test.ts +0 -63
- package/apps/studio/test/exporters/degraded-propagation.test.ts +0 -123
- package/apps/studio/test/exporters/endpoint.test.ts +0 -121
- package/apps/studio/test/exporters/history.test.ts +0 -107
- package/apps/studio/test/exporters/html.test.ts +0 -26
- package/apps/studio/test/exporters/jobs.test.ts +0 -342
- package/apps/studio/test/exporters/pdf.test.ts +0 -85
- package/apps/studio/test/exporters/png.test.ts +0 -103
- package/apps/studio/test/exporters/pptx-deck.test.ts +0 -112
- package/apps/studio/test/exporters/pptx.test.ts +0 -31
- package/apps/studio/test/exporters/pw-launch.test.ts +0 -49
- package/apps/studio/test/exporters/runtime.test.ts +0 -59
- package/apps/studio/test/exporters/scope.test.ts +0 -292
- package/apps/studio/test/exporters/svg.test.ts +0 -29
- package/apps/studio/test/exporters/unsupported-media.test.ts +0 -123
- package/apps/studio/test/exporters/zip.test.ts +0 -104
- package/apps/studio/test/fetch-asset-gate.test.ts +0 -189
- package/apps/studio/test/figjam-v3-model.test.ts +0 -342
- package/apps/studio/test/figma-explode.test.ts +0 -438
- package/apps/studio/test/figma-provenance.test.ts +0 -108
- package/apps/studio/test/figma-routes.test.ts +0 -294
- package/apps/studio/test/file-lock.test.ts +0 -84
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -13
- package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +0 -1
- package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +0 -158
- package/apps/studio/test/fixtures/mock-acp-agent-commands.mjs +0 -44
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +0 -46
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +0 -42
- package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +0 -63
- package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +0 -51
- package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +0 -50
- package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +0 -45
- package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +0 -56
- package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +0 -37
- package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +0 -118
- package/apps/studio/test/fixtures/mock-acp-agent.mjs +0 -69
- package/apps/studio/test/fixtures/perf-canvas.mjs +0 -201
- package/apps/studio/test/fixtures/phase-20-annotations.svg +0 -1
- package/apps/studio/test/fixtures/phase-21-annotations.svg +0 -1
- package/apps/studio/test/fixtures/tiny-subset.woff2 +0 -0
- package/apps/studio/test/fixtures/video-comp-fixture.tsx +0 -82
- package/apps/studio/test/footage-store.test.ts +0 -100
- package/apps/studio/test/fs-mkdir-api.test.ts +0 -248
- package/apps/studio/test/fs-watch.test.ts +0 -41
- package/apps/studio/test/gemma-models.test.ts +0 -245
- package/apps/studio/test/generate-route.test.ts +0 -106
- package/apps/studio/test/git-api.test.ts +0 -0
- package/apps/studio/test/git-branches.test.ts +0 -341
- package/apps/studio/test/git-cloud-posture.test.ts +0 -59
- package/apps/studio/test/git-grouping.test.ts +0 -106
- package/apps/studio/test/git-lifecycle.test.ts +0 -100
- package/apps/studio/test/git-log-format.test.ts +0 -95
- package/apps/studio/test/git-safe-rel.test.ts +0 -132
- package/apps/studio/test/git-system-engine.test.ts +0 -135
- package/apps/studio/test/git-watch.test.ts +0 -97
- package/apps/studio/test/github-api.test.ts +0 -577
- package/apps/studio/test/grid-track-handles.test.ts +0 -160
- package/apps/studio/test/handoff-static-frames.test.ts +0 -215
- package/apps/studio/test/handoff.test.ts +0 -329
- package/apps/studio/test/history-rollback.test.ts +0 -88
- package/apps/studio/test/hmr-broadcast.test.ts +0 -236
- package/apps/studio/test/hmr-classify.test.ts +0 -87
- package/apps/studio/test/hub-link.test.ts +0 -80
- package/apps/studio/test/identity-cache.test.ts +0 -83
- package/apps/studio/test/import-asset-browser.test.ts +0 -64
- package/apps/studio/test/import-asset-route.test.ts +0 -71
- package/apps/studio/test/import-asset.test.ts +0 -427
- package/apps/studio/test/import-brand-browser.test.ts +0 -88
- package/apps/studio/test/import-brand-route.test.ts +0 -120
- package/apps/studio/test/import-brand.test.ts +0 -206
- package/apps/studio/test/import-figma.test.ts +0 -667
- package/apps/studio/test/import-tokens.test.ts +0 -722
- package/apps/studio/test/index-data-canvas-ds.test.ts +0 -97
- package/apps/studio/test/input-router.test.ts +0 -757
- package/apps/studio/test/inspect-script-syntax.test.ts +0 -53
- package/apps/studio/test/inspect-selections.test.ts +0 -219
- package/apps/studio/test/knob-props-authored.test.ts +0 -87
- package/apps/studio/test/layers-synthetic-groups.test.ts +0 -96
- package/apps/studio/test/locator.test.ts +0 -214
- package/apps/studio/test/marquee-overlay.test.ts +0 -94
- package/apps/studio/test/media-commit-chain.test.ts +0 -210
- package/apps/studio/test/mode-toggle.test.tsx +0 -226
- package/apps/studio/test/motion-sample.test.ts +0 -35
- package/apps/studio/test/move-artboards-command.test.ts +0 -108
- package/apps/studio/test/participants-chrome.test.ts +0 -65
- package/apps/studio/test/paths.test.ts +0 -88
- package/apps/studio/test/pdf-print-boxes.test.ts +0 -359
- package/apps/studio/test/peer-selection-follows-camera.test.tsx +0 -131
- package/apps/studio/test/perf-harness.ts +0 -193
- package/apps/studio/test/phase-3.6-smoke.test.ts +0 -79
- package/apps/studio/test/photo-bg-remove-validation.test.ts +0 -71
- package/apps/studio/test/photo-canvas-bundle.test.ts +0 -79
- package/apps/studio/test/photo-edit-api.test.ts +0 -201
- package/apps/studio/test/photo-filters.test.ts +0 -149
- package/apps/studio/test/photo-pipeline.test.ts +0 -106
- package/apps/studio/test/photo-taxonomy.test.ts +0 -96
- package/apps/studio/test/print-marks.test.ts +0 -113
- package/apps/studio/test/print-units.test.ts +0 -173
- package/apps/studio/test/read-annotations.test.ts +0 -727
- package/apps/studio/test/read-only-gate.test.ts +0 -188
- package/apps/studio/test/read-only-role.test.ts +0 -95
- package/apps/studio/test/readiness.test.ts +0 -226
- package/apps/studio/test/remote-ahead-behind-cache.test.ts +0 -89
- package/apps/studio/test/reorder-api.test.ts +0 -210
- package/apps/studio/test/repo-concurrency.test.ts +0 -146
- package/apps/studio/test/repo-lock.test.ts +0 -350
- package/apps/studio/test/report-proxy.test.ts +0 -125
- package/apps/studio/test/ripple.test.ts +0 -197
- package/apps/studio/test/runtime-bundle-error-mapping.test.ts +0 -43
- package/apps/studio/test/runtime-bundle.test.ts +0 -69
- package/apps/studio/test/sanitize-annotation-svg.test.ts +0 -238
- package/apps/studio/test/scaffold-design.test.ts +0 -122
- package/apps/studio/test/server-lifecycle.test.ts +0 -38
- package/apps/studio/test/session-runtime-state.test.ts +0 -226
- package/apps/studio/test/session-scope.test.ts +0 -100
- package/apps/studio/test/shared-doc-cell-pairing.test.ts +0 -642
- package/apps/studio/test/shared-doc-convergence.test.ts +0 -265
- package/apps/studio/test/shared-doc-foundation.test.ts +0 -63
- package/apps/studio/test/shared-doc-migrate.test.ts +0 -369
- package/apps/studio/test/shared-doc-projection.test.ts +0 -238
- package/apps/studio/test/shell-importmap.test.ts +0 -56
- package/apps/studio/test/sizing-mode.test.ts +0 -102
- package/apps/studio/test/slash-commands.test.ts +0 -117
- package/apps/studio/test/snap-distance-pill.test.ts +0 -68
- package/apps/studio/test/spacing-handles.test.ts +0 -138
- package/apps/studio/test/specimen-select.test.ts +0 -88
- package/apps/studio/test/sync-agent.test.ts +0 -660
- package/apps/studio/test/sync-annotations-cold-start.test.ts +0 -302
- package/apps/studio/test/sync-asset-push.test.ts +0 -931
- package/apps/studio/test/sync-atomic-write.test.ts +0 -62
- package/apps/studio/test/sync-attach-incremental.test.ts +0 -527
- package/apps/studio/test/sync-autocommit.test.ts +0 -537
- package/apps/studio/test/sync-canvas-path.test.ts +0 -200
- package/apps/studio/test/sync-cell-write-nudge.test.ts +0 -346
- package/apps/studio/test/sync-codec.test.ts +0 -230
- package/apps/studio/test/sync-cold-seed-dedup.test.ts +0 -187
- package/apps/studio/test/sync-cold-start.test.ts +0 -304
- package/apps/studio/test/sync-connect-honesty.test.ts +0 -114
- package/apps/studio/test/sync-connection-state.test.ts +0 -337
- package/apps/studio/test/sync-ctl-channel.test.ts +0 -508
- package/apps/studio/test/sync-decide-file.test.ts +0 -420
- package/apps/studio/test/sync-doc-name.test.ts +0 -281
- package/apps/studio/test/sync-echo-guard.test.ts +0 -96
- package/apps/studio/test/sync-file-ledger.test.ts +0 -334
- package/apps/studio/test/sync-file-membership.test.ts +0 -347
- package/apps/studio/test/sync-file-plane.test.ts +0 -976
- package/apps/studio/test/sync-file-pull.test.ts +0 -333
- package/apps/studio/test/sync-fresh-link-parity.test.ts +0 -267
- package/apps/studio/test/sync-fs-mirror.test.ts +0 -181
- package/apps/studio/test/sync-hardening.test.ts +0 -586
- package/apps/studio/test/sync-hub-listing.test.ts +0 -46
- package/apps/studio/test/sync-hubs-config.test.ts +0 -133
- package/apps/studio/test/sync-incident-replay.test.ts +0 -211
- package/apps/studio/test/sync-journal.test.ts +0 -176
- package/apps/studio/test/sync-meta-codec.test.ts +0 -199
- package/apps/studio/test/sync-migrate-flat-fallback.test.ts +0 -98
- package/apps/studio/test/sync-move-retirement.test.ts +0 -231
- package/apps/studio/test/sync-panel-surface.test.ts +0 -153
- package/apps/studio/test/sync-path-pull.test.ts +0 -531
- package/apps/studio/test/sync-presentation.test.ts +0 -262
- package/apps/studio/test/sync-pull-budget.test.ts +0 -169
- package/apps/studio/test/sync-remote-docs.test.ts +0 -333
- package/apps/studio/test/sync-resync-routes.test.ts +0 -168
- package/apps/studio/test/sync-runtime.test.ts +0 -1675
- package/apps/studio/test/sync-seed-defers-to-hub.test.ts +0 -83
- package/apps/studio/test/sync-settings-routes.test.ts +0 -195
- package/apps/studio/test/sync-settings.test.ts +0 -151
- package/apps/studio/test/sync-status.test.ts +0 -261
- package/apps/studio/test/sync-supervisor.test.ts +0 -262
- package/apps/studio/test/sync-tombstone-apply.test.ts +0 -111
- package/apps/studio/test/sync-trash.test.ts +0 -132
- package/apps/studio/test/sync-two-peer-discovery.test.ts +0 -343
- package/apps/studio/test/sync-untrusted.test.ts +0 -103
- package/apps/studio/test/system-endpoint.test.ts +0 -144
- package/apps/studio/test/text-editability-stamp.test.ts +0 -131
- package/apps/studio/test/timeline-comp-target.test.ts +0 -139
- package/apps/studio/test/timeline-media-cache.test.ts +0 -41
- package/apps/studio/test/timeline-parse.test.ts +0 -184
- package/apps/studio/test/timeline-scale.test.ts +0 -65
- package/apps/studio/test/timeline-snap.test.ts +0 -85
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +0 -86
- package/apps/studio/test/tool-palette.test.tsx +0 -55
- package/apps/studio/test/tour-overlay.test.tsx +0 -90
- package/apps/studio/test/trusted-request-host.test.ts +0 -66
- package/apps/studio/test/undo-sequence-byte-compare.test.ts +0 -211
- package/apps/studio/test/undo-stack.test.ts +0 -244
- package/apps/studio/test/use-agent-presence.test.tsx +0 -117
- package/apps/studio/test/use-annotation-resize.test.ts +0 -402
- package/apps/studio/test/use-annotation-selection.test.tsx +0 -77
- package/apps/studio/test/use-artboard-drag.test.ts +0 -324
- package/apps/studio/test/use-canvas-activity.test.tsx +0 -206
- package/apps/studio/test/use-collab.test.ts +0 -0
- package/apps/studio/test/use-cursor-modifiers.test.ts +0 -59
- package/apps/studio/test/use-keyboard-discipline.test.ts +0 -27
- package/apps/studio/test/use-selection-set.test.tsx +0 -166
- package/apps/studio/test/use-snap-guides.test.ts +0 -271
- package/apps/studio/test/use-tool-mode.test.tsx +0 -186
- package/apps/studio/test/use-undo-stack.test.tsx +0 -223
- package/apps/studio/test/video-asset.test.ts +0 -163
- package/apps/studio/test/video-comp-fixture.test.ts +0 -50
- package/apps/studio/test/video-comp.test.ts +0 -270
- package/apps/studio/test/video-encode-lib.test.ts +0 -63
- package/apps/studio/test/video-render-bridge.test.ts +0 -171
- package/apps/studio/test/whats-new.test.ts +0 -113
- package/apps/studio/test/whisper-setup.test.ts +0 -97
- package/apps/studio/test/workspace-containment.test.ts +0 -382
- package/apps/studio/test/workspace-signin.test.ts +0 -292
- package/apps/studio/test/ws-handshake.test.ts +0 -33
- package/cli/commands/cache.test.mjs +0 -166
- package/cli/commands/design-link.test.mjs +0 -419
- package/cli/commands/design.test.mjs +0 -240
- package/cli/commands/doctor.test.mjs +0 -237
- package/cli/commands/hub-workspace.test.mjs +0 -275
- package/cli/commands/hub.test.mjs +0 -225
- package/cli/commands/kg.test.mjs +0 -142
- package/cli/commands/preflight.test.mjs +0 -46
- package/cli/lib/argv.test.mjs +0 -46
- package/cli/lib/cache.test.mjs +0 -303
- package/cli/lib/cell-plan.test.mjs +0 -225
- package/cli/lib/config-lint.test.mjs +0 -117
- package/cli/lib/ddr-to-kgai.test.mjs +0 -118
- package/cli/lib/design-ownership.test.mjs +0 -329
- package/cli/lib/fetch-asset.test.mjs +0 -213
- package/cli/lib/figma-codegen-reachability.test.mjs +0 -104
- package/cli/lib/figma-import-controls.test.mjs +0 -70
- package/cli/lib/flow-design-integration.test.mjs +0 -165
- package/cli/lib/gitignore-block.test.mjs +0 -136
- package/cli/lib/gitignore-drift.test.mjs +0 -156
- package/cli/lib/hubs-config.test.mjs +0 -165
- package/cli/lib/pkg-root.test.mjs +0 -111
- package/cli/lib/plugin-cli-reachability.test.mjs +0 -57
- package/cli/lib/plugin-name-namespace.test.mjs +0 -71
- package/cli/lib/reconstruct-toolset.test.mjs +0 -194
- package/cli/lib/stack-detect.test.mjs +0 -121
- package/cli/lib/update-check.test.mjs +0 -32
- package/cli/lib/workspace-plan.test.mjs +0 -699
|
@@ -1,676 +0,0 @@
|
|
|
1
|
-
// /_api/canvas — Phase 22 UI add-on. Create a blank brief board from the browser.
|
|
2
|
-
//
|
|
3
|
-
// Two layers:
|
|
4
|
-
// (1) the pure name-validation boundary (the ONE security gate — path +
|
|
5
|
-
// JSX + JSON injection), tested directly;
|
|
6
|
-
// (2) the POST endpoint round-trip (boots a real server against a sandbox),
|
|
7
|
-
// covering happy path, the rejection matrix, group allowlist, duplicate.
|
|
8
|
-
|
|
9
|
-
import { describe, expect, test } from 'bun:test';
|
|
10
|
-
import {
|
|
11
|
-
existsSync,
|
|
12
|
-
mkdirSync,
|
|
13
|
-
readdirSync,
|
|
14
|
-
readFileSync,
|
|
15
|
-
symlinkSync,
|
|
16
|
-
writeFileSync,
|
|
17
|
-
} from 'node:fs';
|
|
18
|
-
import { join } from 'node:path';
|
|
19
|
-
|
|
20
|
-
import { componentNameFrom, renderBriefBoard, validateCanvasName } from '../canvas-create.ts';
|
|
21
|
-
import { bootServer, killProc, makeSandbox, nextPort } from './_helpers.ts';
|
|
22
|
-
|
|
23
|
-
describe('canvas-create / validateCanvasName (the security boundary)', () => {
|
|
24
|
-
test('accepts ordinary names (incl. spaces + accents)', () => {
|
|
25
|
-
for (const n of ['Onboarding Brief', 'checkout-flow', 'Krok_1', 'Přihlášení', 'A1']) {
|
|
26
|
-
const v = validateCanvasName(n);
|
|
27
|
-
expect(v.ok).toBe(true);
|
|
28
|
-
expect(v.name).toBe(n);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('trims surrounding whitespace', () => {
|
|
33
|
-
const v = validateCanvasName(' Onboarding ');
|
|
34
|
-
expect(v.ok).toBe(true);
|
|
35
|
-
expect(v.name).toBe('Onboarding');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
// The rejection matrix — path traversal, separators, and template/JSON
|
|
39
|
-
// injection vectors must ALL be refused by the one allowlist regex.
|
|
40
|
-
const REJECT: Array<[string, unknown]> = [
|
|
41
|
-
['empty', ''],
|
|
42
|
-
['whitespace only', ' '],
|
|
43
|
-
['non-string', 123],
|
|
44
|
-
['null', null],
|
|
45
|
-
['parent traversal', '../evil'],
|
|
46
|
-
['nested traversal', 'a/../../etc/passwd'],
|
|
47
|
-
['forward slash', 'ui/Sneaky'],
|
|
48
|
-
['back slash', 'ui\\Sneaky'],
|
|
49
|
-
['leading dot', '.hidden'],
|
|
50
|
-
['dot-dot', '..'],
|
|
51
|
-
['JSX break-out', 'X</div><script>alert(1)</script>'],
|
|
52
|
-
['brace injection', 'A{2+2}B'],
|
|
53
|
-
['angle bracket', 'a<b'],
|
|
54
|
-
['double quote (JSON break-out)', 'a"b'],
|
|
55
|
-
['backslash (JSON break-out)', 'a\\b'],
|
|
56
|
-
['leading hyphen', '-dash'],
|
|
57
|
-
['too long (61)', 'x'.repeat(61)],
|
|
58
|
-
['tab', 'a\tb'],
|
|
59
|
-
['newline', 'a\nb'],
|
|
60
|
-
['null byte', 'a\u0000b'],
|
|
61
|
-
];
|
|
62
|
-
for (const [label, value] of REJECT) {
|
|
63
|
-
test(`rejects ${label}`, () => {
|
|
64
|
-
expect(validateCanvasName(value).ok).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
test('componentNameFrom yields a valid identifier', () => {
|
|
69
|
-
expect(componentNameFrom('Onboarding Brief')).toBe('OnboardingBrief');
|
|
70
|
-
expect(componentNameFrom('checkout-flow v2')).toBe('CheckoutFlowV2');
|
|
71
|
-
expect(componentNameFrom('123')).toMatch(/^Board/); // digit-leading → prefixed
|
|
72
|
-
// Unicode letters survive (review #1) — not ASCII-mangled to "PIhlEn".
|
|
73
|
-
expect(componentNameFrom('Přihlášení')).toBe('Přihlášení');
|
|
74
|
-
expect(componentNameFrom('123abc')).toBe('Board123abc'); // digit-leading → prefixed, body kept
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('collapses internal double-spaces so the slug methods agree (review #4)', () => {
|
|
78
|
-
const v = validateCanvasName('Two Spaces');
|
|
79
|
-
expect(v.ok).toBe(true);
|
|
80
|
-
expect(v.name).toBe('Two Spaces'); // " +" → " " ; a tab would still be rejected
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test('renderBriefBoard substitutes every placeholder (no {{…}} left)', () => {
|
|
84
|
-
const out = renderBriefBoard({
|
|
85
|
-
name: 'Onboarding Brief',
|
|
86
|
-
componentName: 'OnboardingBrief',
|
|
87
|
-
dsName: 'project',
|
|
88
|
-
platform: 'desktop',
|
|
89
|
-
seedHint: 'seed',
|
|
90
|
-
historyDir: '.design/_history/ui-onboarding_brief',
|
|
91
|
-
});
|
|
92
|
-
expect(out).not.toMatch(/\{\{[A-Z_]+\}\}/);
|
|
93
|
-
expect(out).toContain('export default function OnboardingBrief()');
|
|
94
|
-
expect(out).toContain('@kind brief-board');
|
|
95
|
-
expect(out).toContain('Onboarding Brief');
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe('/_api/canvas — POST round-trip', () => {
|
|
100
|
-
test('creates a brief board (.tsx + .meta.json), returns 201 + the tree path', async () => {
|
|
101
|
-
const { root, designRoot } = makeSandbox();
|
|
102
|
-
const port = nextPort();
|
|
103
|
-
const proc = await bootServer(root, port);
|
|
104
|
-
try {
|
|
105
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
106
|
-
method: 'POST',
|
|
107
|
-
headers: { 'Content-Type': 'application/json' },
|
|
108
|
-
body: JSON.stringify({ name: 'Onboarding Brief', kind: 'brief-board' }),
|
|
109
|
-
});
|
|
110
|
-
expect(r.status).toBe(201);
|
|
111
|
-
const j = (await r.json()) as { ok: boolean; file: string; rel: string; slug: string };
|
|
112
|
-
expect(j.ok).toBe(true);
|
|
113
|
-
expect(j.file).toBe('.design/ui/Onboarding Brief.tsx');
|
|
114
|
-
expect(j.rel).toBe('ui/Onboarding Brief.tsx');
|
|
115
|
-
expect(j.slug).toBe('ui-onboarding_brief');
|
|
116
|
-
|
|
117
|
-
const tsx = join(designRoot, 'ui', 'Onboarding Brief.tsx');
|
|
118
|
-
const meta = join(designRoot, 'ui', 'Onboarding Brief.meta.json');
|
|
119
|
-
expect(existsSync(tsx)).toBe(true);
|
|
120
|
-
expect(existsSync(meta)).toBe(true);
|
|
121
|
-
expect(readFileSync(tsx, 'utf8')).toContain('export default function OnboardingBrief()');
|
|
122
|
-
expect(readFileSync(tsx, 'utf8')).toContain('Drop sticky notes');
|
|
123
|
-
const m = JSON.parse(readFileSync(meta, 'utf8'));
|
|
124
|
-
expect(m.kind).toBe('brief-board');
|
|
125
|
-
expect(m.title).toBe('Onboarding Brief');
|
|
126
|
-
expect(m.brief_sha).toBeUndefined(); // a board has no generation brief
|
|
127
|
-
} finally {
|
|
128
|
-
await killProc(proc);
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test('assembles a video-comp (kind: video-comp) from dropped clips (DDR-150 P4 Task 12)', async () => {
|
|
133
|
-
const { root, designRoot } = makeSandbox();
|
|
134
|
-
const port = nextPort();
|
|
135
|
-
const proc = await bootServer(root, port);
|
|
136
|
-
try {
|
|
137
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
138
|
-
method: 'POST',
|
|
139
|
-
headers: { 'Content-Type': 'application/json' },
|
|
140
|
-
body: JSON.stringify({
|
|
141
|
-
name: 'My Reel',
|
|
142
|
-
kind: 'video-comp',
|
|
143
|
-
fps: 30,
|
|
144
|
-
clips: [
|
|
145
|
-
{ src: 'assets/a.mp4', mediaKind: 'video', durationInFrames: 60 },
|
|
146
|
-
{ src: 'assets/b.mp4', mediaKind: 'video' },
|
|
147
|
-
{ src: 'assets/music.mp3', mediaKind: 'audio' },
|
|
148
|
-
],
|
|
149
|
-
}),
|
|
150
|
-
});
|
|
151
|
-
expect(r.status).toBe(201);
|
|
152
|
-
const j = (await r.json()) as { ok: boolean; rel: string };
|
|
153
|
-
expect(j.ok).toBe(true);
|
|
154
|
-
const tsx = readFileSync(join(designRoot, 'ui', 'My Reel.tsx'), 'utf8');
|
|
155
|
-
expect(tsx).toContain('<VideoComp component={Comp}');
|
|
156
|
-
// <Video> from @remotion/media, not <OffthreadVideo> — the renderer
|
|
157
|
-
// rejects the latter and the export loses its audio (RCA
|
|
158
|
-
// issue-mp4-audio-export-html5audio-silent-degrade).
|
|
159
|
-
expect(tsx).toContain('<Video src="assets/a.mp4" />');
|
|
160
|
-
expect(tsx).toContain('<Audio src="assets/music.mp3" />');
|
|
161
|
-
expect(tsx).toContain('name="clip-1"');
|
|
162
|
-
const m = JSON.parse(readFileSync(join(designRoot, 'ui', 'My Reel.meta.json'), 'utf8'));
|
|
163
|
-
expect(m.kind).toBe('video-comp');
|
|
164
|
-
} finally {
|
|
165
|
-
await killProc(proc);
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test('accepts an empty clips[] as the greenfield "New video" (201) and rejects a traversal src (400)', async () => {
|
|
170
|
-
const { root } = makeSandbox();
|
|
171
|
-
const port = nextPort();
|
|
172
|
-
const proc = await bootServer(root, port);
|
|
173
|
-
try {
|
|
174
|
-
// enhanced-video-editing Task 20 — empty clips[] scaffolds an editable
|
|
175
|
-
// empty comp (drop-first cut building), no longer a 400.
|
|
176
|
-
const empty = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
177
|
-
method: 'POST',
|
|
178
|
-
headers: { 'Content-Type': 'application/json' },
|
|
179
|
-
body: JSON.stringify({ name: 'Empty', kind: 'video-comp', clips: [] }),
|
|
180
|
-
});
|
|
181
|
-
expect(empty.status).toBe(201);
|
|
182
|
-
const created = (await empty.json()) as { file?: string };
|
|
183
|
-
const tsx = await Bun.file(`${root}/${created.file}`).text();
|
|
184
|
-
expect(tsx).toContain('Drop clips on the timeline');
|
|
185
|
-
expect(tsx).toContain('<VideoComp');
|
|
186
|
-
const bad = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
187
|
-
method: 'POST',
|
|
188
|
-
headers: { 'Content-Type': 'application/json' },
|
|
189
|
-
body: JSON.stringify({
|
|
190
|
-
name: 'Bad',
|
|
191
|
-
kind: 'video-comp',
|
|
192
|
-
clips: [{ src: '../../etc/passwd', mediaKind: 'video' }],
|
|
193
|
-
}),
|
|
194
|
-
});
|
|
195
|
-
expect(bad.status).toBe(400);
|
|
196
|
-
} finally {
|
|
197
|
-
await killProc(proc);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test('rejects a cross-origin create (CSRF guard on /_api/canvas — DDR-150 P4)', async () => {
|
|
202
|
-
const { root } = makeSandbox();
|
|
203
|
-
const port = nextPort();
|
|
204
|
-
const proc = await bootServer(root, port);
|
|
205
|
-
try {
|
|
206
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
207
|
-
method: 'POST',
|
|
208
|
-
headers: { 'Content-Type': 'application/json', Origin: 'http://evil.example' },
|
|
209
|
-
body: JSON.stringify({ name: 'Forged', kind: 'brief-board' }),
|
|
210
|
-
});
|
|
211
|
-
expect(r.status).toBe(403);
|
|
212
|
-
} finally {
|
|
213
|
-
await killProc(proc);
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test('create + delete emit canvas-list-update over the inspector WS (Phase 30)', async () => {
|
|
218
|
-
const { root } = makeSandbox();
|
|
219
|
-
const port = nextPort();
|
|
220
|
-
const proc = await bootServer(root, port);
|
|
221
|
-
let socket: WebSocket | null = null;
|
|
222
|
-
try {
|
|
223
|
-
const messages: Array<{ type: string; payload?: { action?: string; slug?: string } }> = [];
|
|
224
|
-
const sock = new WebSocket(`ws://localhost:${port}/_ws`);
|
|
225
|
-
socket = sock;
|
|
226
|
-
await new Promise<void>((res, rej) => {
|
|
227
|
-
sock.addEventListener('open', () => res());
|
|
228
|
-
sock.addEventListener('error', () => rej(new Error('ws error')));
|
|
229
|
-
setTimeout(() => rej(new Error('ws open timeout')), 2000);
|
|
230
|
-
});
|
|
231
|
-
sock.addEventListener('message', (ev) => {
|
|
232
|
-
try {
|
|
233
|
-
messages.push(JSON.parse(String(ev.data)));
|
|
234
|
-
} catch {
|
|
235
|
-
/* ignore non-JSON */
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
const waitFor = async (pred: (m: (typeof messages)[number]) => boolean) => {
|
|
240
|
-
const deadline = Date.now() + 2000;
|
|
241
|
-
while (Date.now() < deadline) {
|
|
242
|
-
const hit = messages.find(pred);
|
|
243
|
-
if (hit) return hit;
|
|
244
|
-
await Bun.sleep(25);
|
|
245
|
-
}
|
|
246
|
-
return undefined;
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
const created = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
250
|
-
method: 'POST',
|
|
251
|
-
headers: { 'Content-Type': 'application/json' },
|
|
252
|
-
body: JSON.stringify({ name: 'Live Add', kind: 'brief-board' }),
|
|
253
|
-
});
|
|
254
|
-
expect(created.status).toBe(201);
|
|
255
|
-
const cj = (await created.json()) as { rel: string };
|
|
256
|
-
|
|
257
|
-
const added = await waitFor(
|
|
258
|
-
(m) => m.type === 'canvas-list-update' && m.payload?.action === 'added'
|
|
259
|
-
);
|
|
260
|
-
expect(added?.payload?.slug).toBe('ui-live_add');
|
|
261
|
-
|
|
262
|
-
const del = await fetch(
|
|
263
|
-
`http://localhost:${port}/_api/canvas?file=${encodeURIComponent(cj.rel)}`,
|
|
264
|
-
{ method: 'DELETE' }
|
|
265
|
-
);
|
|
266
|
-
expect(del.status).toBe(200);
|
|
267
|
-
|
|
268
|
-
const removed = await waitFor(
|
|
269
|
-
(m) => m.type === 'canvas-list-update' && m.payload?.action === 'removed'
|
|
270
|
-
);
|
|
271
|
-
expect(removed?.payload?.slug).toBe('ui-live_add');
|
|
272
|
-
} finally {
|
|
273
|
-
try {
|
|
274
|
-
socket?.close();
|
|
275
|
-
} catch {
|
|
276
|
-
/* ignore */
|
|
277
|
-
}
|
|
278
|
-
await killProc(proc);
|
|
279
|
-
}
|
|
280
|
-
}, 10_000);
|
|
281
|
-
|
|
282
|
-
test('defaults kind to brief-board when omitted', async () => {
|
|
283
|
-
const { root } = makeSandbox();
|
|
284
|
-
const port = nextPort();
|
|
285
|
-
const proc = await bootServer(root, port);
|
|
286
|
-
try {
|
|
287
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
288
|
-
method: 'POST',
|
|
289
|
-
headers: { 'Content-Type': 'application/json' },
|
|
290
|
-
body: JSON.stringify({ name: 'No Kind' }),
|
|
291
|
-
});
|
|
292
|
-
expect(r.status).toBe(201);
|
|
293
|
-
} finally {
|
|
294
|
-
await killProc(proc);
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test('rejects a traversal name with 400 and writes nothing', async () => {
|
|
299
|
-
const { root, designRoot } = makeSandbox();
|
|
300
|
-
const port = nextPort();
|
|
301
|
-
const proc = await bootServer(root, port);
|
|
302
|
-
try {
|
|
303
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
304
|
-
method: 'POST',
|
|
305
|
-
headers: { 'Content-Type': 'application/json' },
|
|
306
|
-
body: JSON.stringify({ name: '../../escape' }),
|
|
307
|
-
});
|
|
308
|
-
expect(r.status).toBe(400);
|
|
309
|
-
// No file landed anywhere near the design root's parent.
|
|
310
|
-
expect(existsSync(join(designRoot, '..', 'escape.tsx'))).toBe(false);
|
|
311
|
-
} finally {
|
|
312
|
-
await killProc(proc);
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
test('rejects an unknown group with 400', async () => {
|
|
317
|
-
const { root } = makeSandbox();
|
|
318
|
-
const port = nextPort();
|
|
319
|
-
const proc = await bootServer(root, port);
|
|
320
|
-
try {
|
|
321
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
322
|
-
method: 'POST',
|
|
323
|
-
headers: { 'Content-Type': 'application/json' },
|
|
324
|
-
body: JSON.stringify({ name: 'Valid', group: 'etc' }),
|
|
325
|
-
});
|
|
326
|
-
expect(r.status).toBe(400);
|
|
327
|
-
} finally {
|
|
328
|
-
await killProc(proc);
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
// Phase 22 security review F2 — a config whose newCanvasDir TRAVERSES out of
|
|
333
|
-
// the design root must NOT let the write escape, even though the traversing
|
|
334
|
-
// group is technically "allowlisted" (it came from config). The designRoot
|
|
335
|
-
// containment assert is the backstop.
|
|
336
|
-
test('a config with a traversing newCanvasDir cannot escape the design root', async () => {
|
|
337
|
-
const { root, designRoot } = makeSandbox();
|
|
338
|
-
// Poison the config: newCanvasDir points outside the design root.
|
|
339
|
-
writeFileSync(
|
|
340
|
-
join(designRoot, 'config.json'),
|
|
341
|
-
JSON.stringify({
|
|
342
|
-
name: 'test',
|
|
343
|
-
designRoot: '.design',
|
|
344
|
-
newCanvasDir: '../../../../tmp',
|
|
345
|
-
canvasGroups: [
|
|
346
|
-
{ label: 'System', path: 'system' },
|
|
347
|
-
{ label: 'UI', path: 'ui' },
|
|
348
|
-
],
|
|
349
|
-
})
|
|
350
|
-
);
|
|
351
|
-
const port = nextPort();
|
|
352
|
-
const proc = await bootServer(root, port);
|
|
353
|
-
try {
|
|
354
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
355
|
-
method: 'POST',
|
|
356
|
-
headers: { 'Content-Type': 'application/json' },
|
|
357
|
-
body: JSON.stringify({ name: 'Escape' }),
|
|
358
|
-
});
|
|
359
|
-
expect(r.status).toBe(400);
|
|
360
|
-
// Nothing was written up at the traversal target.
|
|
361
|
-
expect(existsSync(join(root, '..', '..', 'tmp', 'Escape.tsx'))).toBe(false);
|
|
362
|
-
} finally {
|
|
363
|
-
await killProc(proc);
|
|
364
|
-
}
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
test('rejects a non-brief-board kind with 400', async () => {
|
|
368
|
-
const { root } = makeSandbox();
|
|
369
|
-
const port = nextPort();
|
|
370
|
-
const proc = await bootServer(root, port);
|
|
371
|
-
try {
|
|
372
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
373
|
-
method: 'POST',
|
|
374
|
-
headers: { 'Content-Type': 'application/json' },
|
|
375
|
-
body: JSON.stringify({ name: 'Valid', kind: 'canvas' }),
|
|
376
|
-
});
|
|
377
|
-
expect(r.status).toBe(400);
|
|
378
|
-
} finally {
|
|
379
|
-
await killProc(proc);
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
test('a duplicate name returns 409', async () => {
|
|
384
|
-
const { root } = makeSandbox();
|
|
385
|
-
const port = nextPort();
|
|
386
|
-
const proc = await bootServer(root, port);
|
|
387
|
-
try {
|
|
388
|
-
const body = JSON.stringify({ name: 'Dup Board' });
|
|
389
|
-
const first = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
390
|
-
method: 'POST',
|
|
391
|
-
headers: { 'Content-Type': 'application/json' },
|
|
392
|
-
body,
|
|
393
|
-
});
|
|
394
|
-
expect(first.status).toBe(201);
|
|
395
|
-
const second = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
396
|
-
method: 'POST',
|
|
397
|
-
headers: { 'Content-Type': 'application/json' },
|
|
398
|
-
body,
|
|
399
|
-
});
|
|
400
|
-
expect(second.status).toBe(409);
|
|
401
|
-
} finally {
|
|
402
|
-
await killProc(proc);
|
|
403
|
-
}
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
test('unknown method (GET) returns 405', async () => {
|
|
407
|
-
const { root } = makeSandbox();
|
|
408
|
-
const port = nextPort();
|
|
409
|
-
const proc = await bootServer(root, port);
|
|
410
|
-
try {
|
|
411
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas`, { method: 'GET' });
|
|
412
|
-
expect(r.status).toBe(405);
|
|
413
|
-
} finally {
|
|
414
|
-
await killProc(proc);
|
|
415
|
-
}
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
describe('/_api/canvas — DELETE (soft-delete)', () => {
|
|
420
|
-
async function createBoard(port: number, name: string) {
|
|
421
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
422
|
-
method: 'POST',
|
|
423
|
-
headers: { 'Content-Type': 'application/json' },
|
|
424
|
-
body: JSON.stringify({ name }),
|
|
425
|
-
});
|
|
426
|
-
return (await r.json()) as { file: string; slug: string };
|
|
427
|
-
}
|
|
428
|
-
const del = (port: number, file: string) =>
|
|
429
|
-
fetch(`http://localhost:${port}/_api/canvas?file=${encodeURIComponent(file)}`, {
|
|
430
|
-
method: 'DELETE',
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
test('soft-deletes a canvas: moves .tsx + sidecars to _trash, gone from ui/', async () => {
|
|
434
|
-
const { root, designRoot } = makeSandbox();
|
|
435
|
-
const port = nextPort();
|
|
436
|
-
const proc = await bootServer(root, port);
|
|
437
|
-
try {
|
|
438
|
-
const created = await createBoard(port, 'Trash Me');
|
|
439
|
-
// Seed an annotation sidecar to prove the whole set travels.
|
|
440
|
-
writeFileSync(
|
|
441
|
-
join(designRoot, `${created.slug}.annotations.svg`),
|
|
442
|
-
'<svg xmlns="http://www.w3.org/2000/svg" data-mdcc-annotations="1"></svg>'
|
|
443
|
-
);
|
|
444
|
-
// Seed the per-machine camera view file (DDR-115) to prove it's swept too.
|
|
445
|
-
mkdirSync(join(designRoot, '_canvas-state'), { recursive: true });
|
|
446
|
-
const viewAbs = join(designRoot, '_canvas-state', `${created.slug}.view.json`);
|
|
447
|
-
writeFileSync(viewAbs, JSON.stringify({ viewport: { x: 1, y: 2, zoom: 1 } }));
|
|
448
|
-
const tsx = join(designRoot, 'ui', 'Trash Me.tsx');
|
|
449
|
-
expect(existsSync(tsx)).toBe(true);
|
|
450
|
-
|
|
451
|
-
const r = await del(port, created.file);
|
|
452
|
-
expect(r.status).toBe(200);
|
|
453
|
-
const j = (await r.json()) as { ok: boolean; trashed: string[] };
|
|
454
|
-
expect(j.ok).toBe(true);
|
|
455
|
-
expect(j.trashed.some((t) => t.endsWith('Trash Me.tsx'))).toBe(true);
|
|
456
|
-
expect(j.trashed.some((t) => t.endsWith('.annotations.svg'))).toBe(true);
|
|
457
|
-
expect(j.trashed.some((t) => t.endsWith('.view.json'))).toBe(true);
|
|
458
|
-
|
|
459
|
-
// Gone from the group; present (recoverable) under _trash/ with a manifest.
|
|
460
|
-
expect(existsSync(tsx)).toBe(false);
|
|
461
|
-
expect(existsSync(join(designRoot, `${created.slug}.annotations.svg`))).toBe(false);
|
|
462
|
-
expect(existsSync(viewAbs)).toBe(false);
|
|
463
|
-
const trashDirs = readdirSync(join(designRoot, '_trash'));
|
|
464
|
-
expect(trashDirs.length).toBe(1);
|
|
465
|
-
const td = join(designRoot, '_trash', trashDirs[0] as string);
|
|
466
|
-
expect(existsSync(join(td, 'Trash Me.tsx'))).toBe(true);
|
|
467
|
-
expect(existsSync(join(td, `_canvas-state__${created.slug}.view.json`))).toBe(true);
|
|
468
|
-
expect(existsSync(join(td, '_trash-manifest.json'))).toBe(true);
|
|
469
|
-
} finally {
|
|
470
|
-
await killProc(proc);
|
|
471
|
-
}
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
// Task 2 bug fix: deleteCanvas now consumes canvas-artifacts.ts's inventory,
|
|
475
|
-
// which closes two gaps the old hand-rolled sidecar list missed.
|
|
476
|
-
test('also trashes the .ydoc.bin cache and the footage EDL (bug fix — canvas-artifacts.ts inventory)', async () => {
|
|
477
|
-
const { root, designRoot } = makeSandbox();
|
|
478
|
-
const port = nextPort();
|
|
479
|
-
const proc = await bootServer(root, port);
|
|
480
|
-
try {
|
|
481
|
-
const created = await createBoard(port, 'Full Sweep');
|
|
482
|
-
mkdirSync(join(designRoot, '_state'), { recursive: true });
|
|
483
|
-
const ydocAbs = join(designRoot, '_state', `${created.slug}.ydoc.bin`);
|
|
484
|
-
writeFileSync(ydocAbs, Buffer.from([1, 2, 3]));
|
|
485
|
-
const edlAbs = join(designRoot, `${created.slug}.edl.json`);
|
|
486
|
-
writeFileSync(edlAbs, JSON.stringify({ beats: [] }));
|
|
487
|
-
|
|
488
|
-
const r = await del(port, created.file);
|
|
489
|
-
expect(r.status).toBe(200);
|
|
490
|
-
const j = (await r.json()) as { ok: boolean; trashed: string[] };
|
|
491
|
-
expect(j.trashed.some((t) => t.endsWith('.ydoc.bin'))).toBe(true);
|
|
492
|
-
expect(j.trashed.some((t) => t.endsWith('.edl.json'))).toBe(true);
|
|
493
|
-
expect(existsSync(ydocAbs)).toBe(false);
|
|
494
|
-
expect(existsSync(edlAbs)).toBe(false);
|
|
495
|
-
const trashDirs = readdirSync(join(designRoot, '_trash'));
|
|
496
|
-
const td = join(designRoot, '_trash', trashDirs[0] as string);
|
|
497
|
-
expect(existsSync(join(td, `_state__${created.slug}.ydoc.bin`))).toBe(true);
|
|
498
|
-
expect(existsSync(join(td, `${created.slug}.edl.json`))).toBe(true);
|
|
499
|
-
} finally {
|
|
500
|
-
await killProc(proc);
|
|
501
|
-
}
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
test('refuses to delete a design-system file (400, untouched)', async () => {
|
|
505
|
-
const { root, designRoot } = makeSandbox();
|
|
506
|
-
const port = nextPort();
|
|
507
|
-
const proc = await bootServer(root, port);
|
|
508
|
-
try {
|
|
509
|
-
mkdirSync(join(designRoot, 'system', 'project'), { recursive: true });
|
|
510
|
-
const dsFile = join(designRoot, 'system', 'project', 'Spec.tsx');
|
|
511
|
-
writeFileSync(dsFile, 'export default function S(){return null}');
|
|
512
|
-
const r = await del(port, '.design/system/project/Spec.tsx');
|
|
513
|
-
expect(r.status).toBe(400);
|
|
514
|
-
expect(existsSync(dsFile)).toBe(true);
|
|
515
|
-
} finally {
|
|
516
|
-
await killProc(proc);
|
|
517
|
-
}
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
test('refuses a non-.tsx root file like config.json (400)', async () => {
|
|
521
|
-
const { root } = makeSandbox();
|
|
522
|
-
const port = nextPort();
|
|
523
|
-
const proc = await bootServer(root, port);
|
|
524
|
-
try {
|
|
525
|
-
const r = await del(port, '.design/config.json');
|
|
526
|
-
expect(r.status).toBe(400);
|
|
527
|
-
} finally {
|
|
528
|
-
await killProc(proc);
|
|
529
|
-
}
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
test('a missing canvas returns 404', async () => {
|
|
533
|
-
const { root } = makeSandbox();
|
|
534
|
-
const port = nextPort();
|
|
535
|
-
const proc = await bootServer(root, port);
|
|
536
|
-
try {
|
|
537
|
-
const r = await del(port, 'ui/Nope.tsx');
|
|
538
|
-
expect(r.status).toBe(404);
|
|
539
|
-
} finally {
|
|
540
|
-
await killProc(proc);
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
|
-
|
|
544
|
-
test('a traversal file is rejected (400)', async () => {
|
|
545
|
-
const { root } = makeSandbox();
|
|
546
|
-
const port = nextPort();
|
|
547
|
-
const proc = await bootServer(root, port);
|
|
548
|
-
try {
|
|
549
|
-
const r = await del(port, 'ui/../../escape.tsx');
|
|
550
|
-
expect(r.status).toBe(400);
|
|
551
|
-
} finally {
|
|
552
|
-
await killProc(proc);
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
// feature-file-tree-drag-drop-folders (dogfood follow-up) — DELETE now
|
|
558
|
-
// auto-detects a non-.tsx target as a folder delete.
|
|
559
|
-
describe('/_api/canvas — DELETE (folder delete, dogfood follow-up)', () => {
|
|
560
|
-
async function createBoard(port: number, name: string) {
|
|
561
|
-
const r = await fetch(`http://localhost:${port}/_api/canvas`, {
|
|
562
|
-
method: 'POST',
|
|
563
|
-
headers: { 'Content-Type': 'application/json' },
|
|
564
|
-
body: JSON.stringify({ name }),
|
|
565
|
-
});
|
|
566
|
-
return (await r.json()) as { file: string; slug: string };
|
|
567
|
-
}
|
|
568
|
-
const del = (port: number, file: string) =>
|
|
569
|
-
fetch(`http://localhost:${port}/_api/canvas?file=${encodeURIComponent(file)}`, {
|
|
570
|
-
method: 'DELETE',
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
test('deletes a folder: trashes every nested canvas, folder itself is gone', async () => {
|
|
574
|
-
const { root, designRoot } = makeSandbox();
|
|
575
|
-
const port = nextPort();
|
|
576
|
-
const proc = await bootServer(root, port);
|
|
577
|
-
try {
|
|
578
|
-
const created = await createBoard(port, 'Inside');
|
|
579
|
-
const moved = await fetch(`http://localhost:${port}/_api/fs-move`, {
|
|
580
|
-
method: 'POST',
|
|
581
|
-
headers: { 'Content-Type': 'application/json' },
|
|
582
|
-
body: JSON.stringify({ file: created.file, toDir: 'ui/Box' }),
|
|
583
|
-
});
|
|
584
|
-
expect(moved.status).toBe(200);
|
|
585
|
-
expect(existsSync(join(designRoot, 'ui', 'Box', 'Inside.tsx'))).toBe(true);
|
|
586
|
-
|
|
587
|
-
const r = await del(port, 'ui/Box');
|
|
588
|
-
expect(r.status).toBe(200);
|
|
589
|
-
const j = (await r.json()) as { ok: boolean; trashed: string[] };
|
|
590
|
-
expect(j.ok).toBe(true);
|
|
591
|
-
expect(j.trashed.some((t) => t.endsWith('Inside.tsx'))).toBe(true);
|
|
592
|
-
expect(existsSync(join(designRoot, 'ui', 'Box'))).toBe(false);
|
|
593
|
-
const trashDirs = readdirSync(join(designRoot, '_trash'));
|
|
594
|
-
expect(trashDirs.length).toBe(1);
|
|
595
|
-
} finally {
|
|
596
|
-
await killProc(proc);
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
test('deletes an EMPTY folder (mkdir + delete, no canvases inside)', async () => {
|
|
601
|
-
const { root, designRoot } = makeSandbox();
|
|
602
|
-
const port = nextPort();
|
|
603
|
-
const proc = await bootServer(root, port);
|
|
604
|
-
try {
|
|
605
|
-
await fetch(`http://localhost:${port}/_api/fs-mkdir`, {
|
|
606
|
-
method: 'POST',
|
|
607
|
-
headers: { 'Content-Type': 'application/json' },
|
|
608
|
-
body: JSON.stringify({ parent: 'ui', name: 'Empty' }),
|
|
609
|
-
});
|
|
610
|
-
expect(existsSync(join(designRoot, 'ui', 'Empty'))).toBe(true);
|
|
611
|
-
const r = await del(port, 'ui/Empty');
|
|
612
|
-
expect(r.status).toBe(200);
|
|
613
|
-
expect(existsSync(join(designRoot, 'ui', 'Empty'))).toBe(false);
|
|
614
|
-
} finally {
|
|
615
|
-
await killProc(proc);
|
|
616
|
-
}
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
test('refuses deleting a canvas GROUP ROOT (400, untouched)', async () => {
|
|
620
|
-
const { root, designRoot } = makeSandbox();
|
|
621
|
-
const port = nextPort();
|
|
622
|
-
const proc = await bootServer(root, port);
|
|
623
|
-
try {
|
|
624
|
-
const r = await del(port, 'ui');
|
|
625
|
-
expect(r.status).toBe(400);
|
|
626
|
-
expect(existsSync(join(designRoot, 'ui'))).toBe(true);
|
|
627
|
-
} finally {
|
|
628
|
-
await killProc(proc);
|
|
629
|
-
}
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
test('refuses deleting a design-system folder (400, untouched)', async () => {
|
|
633
|
-
const { root, designRoot } = makeSandbox();
|
|
634
|
-
const port = nextPort();
|
|
635
|
-
const proc = await bootServer(root, port);
|
|
636
|
-
try {
|
|
637
|
-
mkdirSync(join(designRoot, 'system', 'sub'), { recursive: true });
|
|
638
|
-
const r = await del(port, 'system/sub');
|
|
639
|
-
expect(r.status).toBe(400);
|
|
640
|
-
expect(existsSync(join(designRoot, 'system', 'sub'))).toBe(true);
|
|
641
|
-
} finally {
|
|
642
|
-
await killProc(proc);
|
|
643
|
-
}
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
test('a missing folder returns 404', async () => {
|
|
647
|
-
const { root } = makeSandbox();
|
|
648
|
-
const port = nextPort();
|
|
649
|
-
const proc = await bootServer(root, port);
|
|
650
|
-
try {
|
|
651
|
-
const r = await del(port, 'ui/Nope');
|
|
652
|
-
expect(r.status).toBe(404);
|
|
653
|
-
} finally {
|
|
654
|
-
await killProc(proc);
|
|
655
|
-
}
|
|
656
|
-
});
|
|
657
|
-
|
|
658
|
-
// Security review finding — see the matching tests in canvas-move-api.test.ts
|
|
659
|
-
// and fs-mkdir-api.test.ts.
|
|
660
|
-
test('refuses deleting a symlinked "folder" (would rm -rf outside designRoot)', async () => {
|
|
661
|
-
const { root, designRoot } = makeSandbox();
|
|
662
|
-
const outside = join(root, 'OUTSIDE');
|
|
663
|
-
mkdirSync(outside, { recursive: true });
|
|
664
|
-
writeFileSync(join(outside, 'Secret.txt'), 'do not delete me');
|
|
665
|
-
const port = nextPort();
|
|
666
|
-
const proc = await bootServer(root, port);
|
|
667
|
-
try {
|
|
668
|
-
symlinkSync(outside, join(designRoot, 'ui', 'evil'));
|
|
669
|
-
const r = await del(port, 'ui/evil');
|
|
670
|
-
expect(r.status).toBe(400);
|
|
671
|
-
expect(existsSync(join(outside, 'Secret.txt'))).toBe(true);
|
|
672
|
-
} finally {
|
|
673
|
-
await killProc(proc);
|
|
674
|
-
}
|
|
675
|
-
});
|
|
676
|
-
});
|