@1agh/maude 0.42.0 → 0.44.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/.ai/cache/_stats.json +7 -0
- package/apps/studio/acp/bootstrap-brief.ts +12 -2
- package/apps/studio/acp/bridge.ts +44 -10
- package/apps/studio/acp/env.ts +23 -0
- package/apps/studio/acp/login-state.ts +263 -0
- package/apps/studio/acp/plugin-bootstrap.ts +31 -25
- package/apps/studio/acp/probe.ts +152 -1
- package/apps/studio/annotations-layer.tsx +1451 -324
- package/apps/studio/annotations-model.ts +58 -0
- package/apps/studio/api.ts +568 -24
- package/apps/studio/bin/_audio-search.mjs +140 -0
- package/apps/studio/bin/_import-asset-pdf-worker.mjs +34 -0
- package/apps/studio/bin/_import-asset.mjs +815 -0
- package/apps/studio/bin/_import-brand.mjs +635 -0
- package/apps/studio/bin/_import-tokens-alias-resolver.mjs +95 -0
- package/apps/studio/bin/_import-tokens.mjs +1201 -0
- package/apps/studio/bin/_ingest-footage.mjs +386 -0
- package/apps/studio/bin/_probe-footage-playwright.mjs +269 -0
- package/apps/studio/bin/_transcribe.mjs +419 -0
- package/apps/studio/bin/_transcribe.test.mjs +80 -0
- package/apps/studio/bin/_video-playwright.mjs +141 -71
- package/apps/studio/bin/audio-search.sh +28 -0
- package/apps/studio/bin/generate.sh +154 -0
- package/apps/studio/bin/import-asset.sh +34 -0
- package/apps/studio/bin/import-brand.sh +29 -0
- package/apps/studio/bin/import-tokens.sh +33 -0
- package/apps/studio/bin/ingest-footage.sh +27 -0
- package/apps/studio/bin/photo-adjust.sh +163 -0
- package/apps/studio/bin/photo-bg-remove.sh +258 -0
- package/apps/studio/bin/probe-footage.sh +28 -0
- package/apps/studio/bin/read-annotations.mjs +125 -2
- package/apps/studio/bin/transcribe.sh +39 -0
- package/apps/studio/canvas-edit.ts +656 -12
- package/apps/studio/canvas-lib.tsx +637 -10
- package/apps/studio/canvas-pipeline.ts +65 -7
- package/apps/studio/canvas-shell.tsx +156 -16
- package/apps/studio/client/app.jsx +2385 -699
- package/apps/studio/client/export-center.jsx +35 -1
- package/apps/studio/client/generate-dialog.jsx +352 -0
- package/apps/studio/client/github.js +18 -0
- package/apps/studio/client/inspector-controls.jsx +781 -0
- package/apps/studio/client/panels/BrandUploadPanel.jsx +233 -0
- package/apps/studio/client/panels/ChatPanel.jsx +15 -33
- package/apps/studio/client/panels/GitPanel.jsx +8 -0
- package/apps/studio/client/panels/IntroVideoDialog.jsx +44 -0
- package/apps/studio/client/panels/OnboardingWizard.jsx +12 -0
- package/apps/studio/client/panels/ReadinessList.jsx +227 -7
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +133 -21
- package/apps/studio/client/panels/SettingsPanel.jsx +828 -0
- package/apps/studio/client/panels/SetupChecklist.jsx +223 -0
- package/apps/studio/client/panels/StickerPicker.jsx +160 -0
- package/apps/studio/client/panels/TimelinePanel.jsx +15 -2
- package/apps/studio/client/photo-knobs.jsx +432 -0
- package/apps/studio/client/styles/3-shell-maude.css +376 -28
- package/apps/studio/client/styles/4-components.css +223 -0
- package/apps/studio/client/styles/6-acp-chat.css +86 -0
- package/apps/studio/client/tour/quick-setup-tour.js +39 -0
- package/apps/studio/commands/annotation-strokes-command.ts +13 -3
- package/apps/studio/commands/edit-source-command.ts +31 -4
- package/apps/studio/config.schema.json +70 -0
- package/apps/studio/context-menu.tsx +42 -8
- package/apps/studio/context.ts +16 -0
- package/apps/studio/design-setup-readiness.ts +115 -0
- package/apps/studio/dist/client.bundle.js +5572 -21
- package/apps/studio/dist/comment-mount.js +2 -2
- package/apps/studio/dist/runtime/.min-sizes.json +1 -0
- package/apps/studio/dist/runtime/@imgly_background-removal.js +5543 -0
- package/apps/studio/dist/runtime/pixi-js.js +519 -519
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/dom-selection.ts +25 -0
- package/apps/studio/draw/palette.ts +34 -0
- package/apps/studio/exporters/jobs.ts +47 -5
- package/apps/studio/exporters/video.ts +24 -4
- package/apps/studio/footage/schema.test.ts +287 -0
- package/apps/studio/footage/schema.ts +662 -0
- package/apps/studio/footage-store.ts +235 -0
- package/apps/studio/fs-watch.ts +10 -0
- package/apps/studio/generation/adapters/elevenlabs.ts +370 -0
- package/apps/studio/generation/adapters/gemini.ts +542 -0
- package/apps/studio/generation/adapters/groq.test.ts +80 -0
- package/apps/studio/generation/adapters/groq.ts +247 -0
- package/apps/studio/generation/audio-library.test.ts +95 -0
- package/apps/studio/generation/audio-library.ts +156 -0
- package/apps/studio/generation/captions.test.ts +78 -0
- package/apps/studio/generation/captions.ts +132 -0
- package/apps/studio/generation/download.ts +76 -0
- package/apps/studio/generation/elevenlabs.test.ts +223 -0
- package/apps/studio/generation/gemini.test.ts +350 -0
- package/apps/studio/generation/jobs.test.ts +128 -0
- package/apps/studio/generation/jobs.ts +243 -0
- package/apps/studio/generation/keys.test.ts +75 -0
- package/apps/studio/generation/keys.ts +162 -0
- package/apps/studio/generation/prefs.test.ts +95 -0
- package/apps/studio/generation/prefs.ts +82 -0
- package/apps/studio/generation/registry.ts +88 -0
- package/apps/studio/generation/types.test.ts +123 -0
- package/apps/studio/generation/types.ts +266 -0
- package/apps/studio/generation/whisper-models.test.ts +152 -0
- package/apps/studio/generation/whisper-models.ts +257 -0
- package/apps/studio/generation/whisper-spike-results.md +61 -0
- package/apps/studio/git/endpoints.ts +68 -7
- package/apps/studio/git/service.ts +199 -21
- package/apps/studio/github/service.ts +67 -0
- package/apps/studio/handoff.ts +3 -2
- package/apps/studio/http.ts +1039 -22
- package/apps/studio/input-router.tsx +10 -0
- package/apps/studio/inspect.ts +53 -1
- package/apps/studio/media/intro.mp4 +0 -0
- package/apps/studio/media-commit-chain.ts +119 -0
- package/apps/studio/paths.ts +20 -0
- package/apps/studio/photo/filters.ts +285 -0
- package/apps/studio/photo/pipeline.ts +607 -0
- package/apps/studio/photo/schema.ts +367 -0
- package/apps/studio/photo-store.ts +154 -0
- package/apps/studio/readiness.ts +108 -20
- package/apps/studio/runtime-bundle.ts +29 -6
- package/apps/studio/scaffold-design.ts +1145 -0
- package/apps/studio/server.ts +18 -2
- package/apps/studio/stickers/figjam-doodle/manifest.json +104 -0
- package/apps/studio/stickers/figjam-doodle/slice1.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice10.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice11.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice12.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice13.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice14.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice15.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice16.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice17.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice18.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice19.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice2.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice20.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice21.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice22.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice23.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice24.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice3.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice4.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice5.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice6.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice7.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice8.png +0 -0
- package/apps/studio/stickers/figjam-doodle/slice9.png +0 -0
- package/apps/studio/stickers/life-style/best.png +0 -0
- package/apps/studio/stickers/life-style/come-on.png +0 -0
- package/apps/studio/stickers/life-style/cut.png +0 -0
- package/apps/studio/stickers/life-style/detox.png +0 -0
- package/apps/studio/stickers/life-style/just.png +0 -0
- package/apps/studio/stickers/life-style/manifest.json +68 -0
- package/apps/studio/stickers/life-style/mirror.png +0 -0
- package/apps/studio/stickers/life-style/nice.png +0 -0
- package/apps/studio/stickers/life-style/objects.png +0 -0
- package/apps/studio/stickers/life-style/ok.png +0 -0
- package/apps/studio/stickers/life-style/queen.png +0 -0
- package/apps/studio/stickers/life-style/star.png +0 -0
- package/apps/studio/stickers/life-style/sun.png +0 -0
- package/apps/studio/stickers/life-style/water.png +0 -0
- package/apps/studio/stickers/life-style/yeah.png +0 -0
- package/apps/studio/stickers/life-style/you-can.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/check-the-deets.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/cut-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/dope.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/fresh.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/hot.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/idea.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/keep-exploring.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/killed-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/love-it.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/manifest.json +88 -0
- package/apps/studio/stickers/opposing-thoughts/not-sure.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/ok.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/pure-gold.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/save-for-later.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/steal-this.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/take-a-peek.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/this-or-that.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/thoughts.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/vibes.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/winner.png +0 -0
- package/apps/studio/stickers/opposing-thoughts/wip.png +0 -0
- package/apps/studio/stickers/project-status/group-100.png +0 -0
- package/apps/studio/stickers/project-status/group-101.png +0 -0
- package/apps/studio/stickers/project-status/group-102.png +0 -0
- package/apps/studio/stickers/project-status/group-103.png +0 -0
- package/apps/studio/stickers/project-status/group-104.png +0 -0
- package/apps/studio/stickers/project-status/group-105.png +0 -0
- package/apps/studio/stickers/project-status/group-106.png +0 -0
- package/apps/studio/stickers/project-status/group-107.png +0 -0
- package/apps/studio/stickers/project-status/group-108.png +0 -0
- package/apps/studio/stickers/project-status/group-109.png +0 -0
- package/apps/studio/stickers/project-status/group-110.png +0 -0
- package/apps/studio/stickers/project-status/group-111.png +0 -0
- package/apps/studio/stickers/project-status/group-112.png +0 -0
- package/apps/studio/stickers/project-status/group-113.png +0 -0
- package/apps/studio/stickers/project-status/group-114.png +0 -0
- package/apps/studio/stickers/project-status/group-115.png +0 -0
- package/apps/studio/stickers/project-status/group-117.png +0 -0
- package/apps/studio/stickers/project-status/group-118.png +0 -0
- package/apps/studio/stickers/project-status/group-119.png +0 -0
- package/apps/studio/stickers/project-status/group-120.png +0 -0
- package/apps/studio/stickers/project-status/group-121.png +0 -0
- package/apps/studio/stickers/project-status/group-122.png +0 -0
- package/apps/studio/stickers/project-status/group-41.png +0 -0
- package/apps/studio/stickers/project-status/group-42.png +0 -0
- package/apps/studio/stickers/project-status/group-43.png +0 -0
- package/apps/studio/stickers/project-status/group-44.png +0 -0
- package/apps/studio/stickers/project-status/group-45.png +0 -0
- package/apps/studio/stickers/project-status/group-46.png +0 -0
- package/apps/studio/stickers/project-status/group-47.png +0 -0
- package/apps/studio/stickers/project-status/group-48.png +0 -0
- package/apps/studio/stickers/project-status/group-49.png +0 -0
- package/apps/studio/stickers/project-status/group-50.png +0 -0
- package/apps/studio/stickers/project-status/group-51.png +0 -0
- package/apps/studio/stickers/project-status/group-52.png +0 -0
- package/apps/studio/stickers/project-status/group-53.png +0 -0
- package/apps/studio/stickers/project-status/group-54.png +0 -0
- package/apps/studio/stickers/project-status/group-55.png +0 -0
- package/apps/studio/stickers/project-status/group-56.png +0 -0
- package/apps/studio/stickers/project-status/group-57.png +0 -0
- package/apps/studio/stickers/project-status/group-58.png +0 -0
- package/apps/studio/stickers/project-status/group-59.png +0 -0
- package/apps/studio/stickers/project-status/group-60.png +0 -0
- package/apps/studio/stickers/project-status/group-61.png +0 -0
- package/apps/studio/stickers/project-status/group-62.png +0 -0
- package/apps/studio/stickers/project-status/group-63.png +0 -0
- package/apps/studio/stickers/project-status/group-64.png +0 -0
- package/apps/studio/stickers/project-status/group-65.png +0 -0
- package/apps/studio/stickers/project-status/group-66.png +0 -0
- package/apps/studio/stickers/project-status/group-67.png +0 -0
- package/apps/studio/stickers/project-status/group-68.png +0 -0
- package/apps/studio/stickers/project-status/group-69.png +0 -0
- package/apps/studio/stickers/project-status/group-70.png +0 -0
- package/apps/studio/stickers/project-status/group-71.png +0 -0
- package/apps/studio/stickers/project-status/group-72.png +0 -0
- package/apps/studio/stickers/project-status/group-73.png +0 -0
- package/apps/studio/stickers/project-status/group-74.png +0 -0
- package/apps/studio/stickers/project-status/group-75.png +0 -0
- package/apps/studio/stickers/project-status/group-76.png +0 -0
- package/apps/studio/stickers/project-status/group-77.png +0 -0
- package/apps/studio/stickers/project-status/group-78.png +0 -0
- package/apps/studio/stickers/project-status/group-79.png +0 -0
- package/apps/studio/stickers/project-status/group-80.png +0 -0
- package/apps/studio/stickers/project-status/group-81.png +0 -0
- package/apps/studio/stickers/project-status/group-82.png +0 -0
- package/apps/studio/stickers/project-status/group-83.png +0 -0
- package/apps/studio/stickers/project-status/group-84.png +0 -0
- package/apps/studio/stickers/project-status/group-85.png +0 -0
- package/apps/studio/stickers/project-status/group-86.png +0 -0
- package/apps/studio/stickers/project-status/group-87.png +0 -0
- package/apps/studio/stickers/project-status/group-88.png +0 -0
- package/apps/studio/stickers/project-status/group-89.png +0 -0
- package/apps/studio/stickers/project-status/group-90.png +0 -0
- package/apps/studio/stickers/project-status/group-91.png +0 -0
- package/apps/studio/stickers/project-status/group-92.png +0 -0
- package/apps/studio/stickers/project-status/group-93.png +0 -0
- package/apps/studio/stickers/project-status/group-94.png +0 -0
- package/apps/studio/stickers/project-status/group-96.png +0 -0
- package/apps/studio/stickers/project-status/group-97.png +0 -0
- package/apps/studio/stickers/project-status/group-98.png +0 -0
- package/apps/studio/stickers/project-status/group-99.png +0 -0
- package/apps/studio/stickers/project-status/manifest.json +328 -0
- package/apps/studio/test/_helpers.ts +15 -2
- package/apps/studio/test/acp-bootstrap-brief.test.ts +11 -0
- package/apps/studio/test/acp-bridge.test.ts +34 -1
- package/apps/studio/test/acp-commands.test.ts +2 -1
- package/apps/studio/test/acp-env.test.ts +30 -0
- package/apps/studio/test/acp-plugin-bootstrap.test.ts +23 -57
- package/apps/studio/test/acp-session-plugins.test.ts +72 -1
- package/apps/studio/test/annotation-strokes-command.test.ts +17 -0
- package/apps/studio/test/annotations-layer.test.ts +174 -0
- package/apps/studio/test/asset-api.test.ts +3 -3
- package/apps/studio/test/canvas-edit.test.ts +17 -6
- package/apps/studio/test/canvas-origin-gate.test.ts +50 -0
- package/apps/studio/test/csp-canvas-shell.test.ts +11 -0
- package/apps/studio/test/csrf-write-guard.test.ts +23 -1
- package/apps/studio/test/design-setup-readiness.test.ts +155 -0
- package/apps/studio/test/dynamic-text-edit.test.ts +162 -0
- package/apps/studio/test/edit-source-command.test.ts +23 -0
- package/apps/studio/test/element-structural-edit.test.ts +154 -0
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +13 -0
- package/apps/studio/test/footage-store.test.ts +100 -0
- package/apps/studio/test/generate-route.test.ts +106 -0
- package/apps/studio/test/git-branches.test.ts +97 -0
- package/apps/studio/test/github-api.test.ts +56 -1
- package/apps/studio/test/import-asset-browser.test.ts +64 -0
- package/apps/studio/test/import-asset-route.test.ts +71 -0
- package/apps/studio/test/import-asset.test.ts +399 -0
- package/apps/studio/test/import-brand-browser.test.ts +88 -0
- package/apps/studio/test/import-brand-route.test.ts +120 -0
- package/apps/studio/test/import-brand.test.ts +206 -0
- package/apps/studio/test/import-tokens.test.ts +722 -0
- package/apps/studio/test/input-router.test.ts +33 -0
- package/apps/studio/test/inspect-script-syntax.test.ts +53 -0
- package/apps/studio/test/media-commit-chain.test.ts +210 -0
- package/apps/studio/test/photo-bg-remove-validation.test.ts +71 -0
- package/apps/studio/test/photo-canvas-bundle.test.ts +61 -0
- package/apps/studio/test/photo-edit-api.test.ts +201 -0
- package/apps/studio/test/photo-filters.test.ts +149 -0
- package/apps/studio/test/photo-pipeline.test.ts +106 -0
- package/apps/studio/test/photo-taxonomy.test.ts +96 -0
- package/apps/studio/test/read-annotations.test.ts +164 -0
- package/apps/studio/test/readiness.test.ts +13 -11
- package/apps/studio/test/scaffold-design.test.ts +122 -0
- package/apps/studio/test/shell-importmap.test.ts +49 -0
- package/apps/studio/test/text-editability-stamp.test.ts +131 -0
- package/apps/studio/test/timeline-parse.test.ts +57 -0
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +5 -3
- package/apps/studio/test/tour-overlay.test.tsx +16 -0
- package/apps/studio/test/undo-stack.test.ts +33 -0
- package/apps/studio/test/video-asset.test.ts +5 -5
- package/apps/studio/test/video-render-bridge.test.ts +23 -1
- package/apps/studio/text-caret.ts +239 -0
- package/apps/studio/tool-palette.tsx +49 -21
- package/apps/studio/ui-prefs.ts +130 -0
- package/apps/studio/undo-stack.ts +94 -2
- package/apps/studio/use-annotation-resize.tsx +4 -4
- package/apps/studio/use-canvas-media-drop.tsx +40 -1
- package/apps/studio/use-chrome-visibility.tsx +8 -2
- package/apps/studio/use-selection-set.tsx +21 -0
- package/apps/studio/video-comp.tsx +48 -7
- package/apps/studio/whats-new.json +149 -0
- package/apps/studio/ws.ts +5 -0
- package/cli/bin/maude.mjs +6 -6
- package/cli/commands/design.mjs +67 -3
- package/cli/lib/gitignore-block.mjs +2 -0
- package/cli/lib/pkg-root.mjs +107 -0
- package/cli/lib/pkg-root.test.mjs +79 -0
- package/cli/lib/reconstruct-toolset.test.mjs +194 -0
- package/cli/lib/update-check.mjs +8 -0
- package/package.json +13 -11
- package/plugins/design/dependencies.json +18 -0
- package/plugins/design/templates/_shell.html +1 -0
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
gitFetchRemote,
|
|
15
15
|
gitFoldDraft,
|
|
16
16
|
gitListBranches,
|
|
17
|
+
gitPull,
|
|
18
|
+
gitPush,
|
|
17
19
|
remoteAheadBehind,
|
|
18
20
|
} from '../git/service.ts';
|
|
19
21
|
|
|
@@ -111,6 +113,30 @@ test('fold: merges the draft into the Shared version locally; a tokenless publis
|
|
|
111
113
|
expect((await gitListBranches(dir)).some((b) => b.name === 'nav')).toBe(true);
|
|
112
114
|
});
|
|
113
115
|
|
|
116
|
+
test('fold: a GitHub remote takes the PR path — pushes the draft, never locally merges main (DDR-162)', async () => {
|
|
117
|
+
// isGitHubRemote(github.com) → PR flow: publish the DRAFT branch (fails here — no
|
|
118
|
+
// real access) and NEVER merge/modify the local Shared version. `false` as the ssh
|
|
119
|
+
// command fails the transport instantly, so there's no network to github.com.
|
|
120
|
+
const prevSsh = process.env.GIT_SSH_COMMAND;
|
|
121
|
+
process.env.GIT_SSH_COMMAND = 'false';
|
|
122
|
+
const shared = (await gitListBranches(dir)).find((b) => b.current)?.name as string;
|
|
123
|
+
sh(['remote', 'add', 'origin', 'git@github.com:fake-org/fake-repo.git']);
|
|
124
|
+
await gitCreateBranch(dir, 'nav'); // now on 'nav'
|
|
125
|
+
writeFileSync(join(dir, 'b.txt'), 'draft work\n');
|
|
126
|
+
sh(['add', '.']);
|
|
127
|
+
sh(['commit', '-q', '-m', 'draft work']);
|
|
128
|
+
const r = await gitFoldDraft(dir, 'nav', undefined, {});
|
|
129
|
+
process.env.GIT_SSH_COMMAND = prevSsh ?? '';
|
|
130
|
+
// The draft push failed (fake repo) → not ok, and NOT the iso transport error…
|
|
131
|
+
expect(r.ok).toBe(false);
|
|
132
|
+
expect(r.error || '').not.toMatch(/unrecognized transport/i);
|
|
133
|
+
// …but crucially the Shared version was NEVER locally merged/modified…
|
|
134
|
+
sh(['checkout', shared]);
|
|
135
|
+
expect(existsSync(join(dir, 'b.txt'))).toBe(false);
|
|
136
|
+
// …and the draft still exists (nothing destroyed on a failed publish).
|
|
137
|
+
expect((await gitListBranches(dir)).some((b) => b.name === 'nav')).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
114
140
|
// ── remote drafts (phase: surface remote branches) ───────────────────────────
|
|
115
141
|
|
|
116
142
|
/** Stand up a bare "remote" with main + a teammate draft, clone it locally (system
|
|
@@ -197,6 +223,77 @@ test('an ssh remote routes to system git, never the iso transport error', async
|
|
|
197
223
|
expect(r.error || '').not.toMatch(/unrecognized transport/i); // didn't fall into iso
|
|
198
224
|
});
|
|
199
225
|
|
|
226
|
+
// ── publish / get-latest over ssh (DDR-131/DDR-133 parity — the gap that shipped) ──
|
|
227
|
+
// gitFetchRemote was hardened for ssh; gitPush/gitPull historically routed on the
|
|
228
|
+
// USE_SYSTEM_GIT env flag only (default off) and fell into iso → "unrecognized
|
|
229
|
+
// transport protocol: ssh". These prove the write paths now take the same gate.
|
|
230
|
+
|
|
231
|
+
test('gitPush: an ssh remote routes to system git, never the iso transport error', async () => {
|
|
232
|
+
process.env.GIT_SSH_COMMAND =
|
|
233
|
+
'ssh -o BatchMode=yes -o ConnectTimeout=2 -o StrictHostKeyChecking=no';
|
|
234
|
+
sh(['remote', 'add', 'origin', 'git@nonexistent.invalid:team/app.git']);
|
|
235
|
+
const r = await gitPush(dir, undefined, {});
|
|
236
|
+
process.env.GIT_SSH_COMMAND = '';
|
|
237
|
+
expect(r.ok).toBe(false);
|
|
238
|
+
expect(r.authRequired).toBeFalsy(); // an ssh failure is not a GitHub sign-in prompt
|
|
239
|
+
expect(r.error || '').not.toMatch(/unrecognized transport/i); // didn't fall into iso
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('gitPull: an ssh remote routes to system git, never the iso transport error', async () => {
|
|
243
|
+
process.env.GIT_SSH_COMMAND =
|
|
244
|
+
'ssh -o BatchMode=yes -o ConnectTimeout=2 -o StrictHostKeyChecking=no';
|
|
245
|
+
sh(['remote', 'add', 'origin', 'git@nonexistent.invalid:team/app.git']);
|
|
246
|
+
const r = await gitPull(dir, undefined, {});
|
|
247
|
+
process.env.GIT_SSH_COMMAND = '';
|
|
248
|
+
expect(r.ok).toBe(false);
|
|
249
|
+
expect(r.authRequired).toBeFalsy();
|
|
250
|
+
expect(r.error || '').not.toMatch(/unrecognized transport/i);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('SECURITY: gitPush refuses an unsafe (ext::) remote — no spawn, no RCE', async () => {
|
|
254
|
+
// A poisoned .git/config remote — must be refused BEFORE any git spawn (the payload
|
|
255
|
+
// never runs); classifyRemoteUrl reads the url via iso getConfig, not the git binary.
|
|
256
|
+
const sentinel = join(dir, 'PWNED_PUSH');
|
|
257
|
+
sh(['remote', 'add', 'origin', `ext::sh -c "touch ${sentinel}"`]);
|
|
258
|
+
const r = await gitPush(dir, 'tok_should_not_be_used', {});
|
|
259
|
+
expect(r.ok).toBe(false);
|
|
260
|
+
expect(r.error).toMatch(/github\.com/i); // "can only sync github.com…"
|
|
261
|
+
expect(existsSync(sentinel)).toBe(false); // payload NEVER executed
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test('SECURITY: gitPush does not send the GitHub token to a non-GitHub HTTPS host', async () => {
|
|
265
|
+
sh(['remote', 'add', 'origin', 'https://evil.example.com/repo.git']);
|
|
266
|
+
const r = await gitPush(dir, 'tok_secret', {});
|
|
267
|
+
expect(r.ok).toBe(false);
|
|
268
|
+
expect(r.error).toMatch(/github\.com/i); // refused by policy, token never attached
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('SECURITY: a backslash-@ host-confusion remote is refused, PAT never attached (F1)', async () => {
|
|
272
|
+
// `https://github.com\@evil` — new URL() parses host=github.com (backslash → '/'),
|
|
273
|
+
// but git/curl dials `evil` (backslash = path/userinfo char). The token-host allowlist
|
|
274
|
+
// must reject the parser differential and NOT lend the PAT — on every token path:
|
|
275
|
+
// explicit fetch, the unattended ahead/behind probe (the zero-click trigger), and push.
|
|
276
|
+
sh(['remote', 'add', 'origin', 'https://github.com\\@evil.example/x.git']);
|
|
277
|
+
const f = await gitFetchRemote(dir, 'tok_secret');
|
|
278
|
+
expect(f.ok).toBe(false);
|
|
279
|
+
expect(f.error).toMatch(/github\.com/i); // refused by policy — token never attached
|
|
280
|
+
const ab = await remoteAheadBehind(dir, 'tok_secret');
|
|
281
|
+
expect(ab).toEqual({ ahead: 0, behind: 0 }); // the unattended probe refuses too (0/0, no fetch)
|
|
282
|
+
const p = await gitPush(dir, 'tok_secret', {});
|
|
283
|
+
expect(p.ok).toBe(false);
|
|
284
|
+
expect(p.error).toMatch(/github\.com/i);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test('SECURITY: the unattended probe refuses a repo with a url.insteadOf rewrite (verify re-review)', async () => {
|
|
288
|
+
// A clean github `origin.url` (passes isTrustedTokenHost) + a poisoned
|
|
289
|
+
// `url.<attacker>.insteadOf` that redirects github.com → attacker (only the system
|
|
290
|
+
// engine honors it). git would dial the attacker; the zero-click probe must refuse.
|
|
291
|
+
sh(['remote', 'add', 'origin', 'https://github.com/owner/repo.git']);
|
|
292
|
+
sh(['config', '--local', 'url.https://attacker.example/.insteadOf', 'https://github.com/']);
|
|
293
|
+
const ab = await remoteAheadBehind(dir, 'tok_secret');
|
|
294
|
+
expect(ab).toEqual({ ahead: 0, behind: 0 });
|
|
295
|
+
});
|
|
296
|
+
|
|
200
297
|
// ── transport-injection hardening (adversarial review of 75a2f0d) ────────────
|
|
201
298
|
|
|
202
299
|
test('SECURITY: refuses to fetch an `ext::` (command-executing) remote — no spawn, no RCE', async () => {
|
|
@@ -18,6 +18,7 @@ import git from 'isomorphic-git';
|
|
|
18
18
|
import type { Context } from '../context.ts';
|
|
19
19
|
import { createGitHubEndpoints, __testing as epTesting } from '../github/endpoints.ts';
|
|
20
20
|
import {
|
|
21
|
+
createPullRequest,
|
|
21
22
|
createRepo,
|
|
22
23
|
GitHubApiError,
|
|
23
24
|
getIdentity,
|
|
@@ -98,6 +99,59 @@ describe('GitHub REST request construction', () => {
|
|
|
98
99
|
expect((err as GitHubApiError).message).toMatch(/already have a project with that name/i);
|
|
99
100
|
});
|
|
100
101
|
|
|
102
|
+
test('createPullRequest POSTs title/head/base/body and shapes the result', async () => {
|
|
103
|
+
const calls = stubFetch(() =>
|
|
104
|
+
json({ number: 12, html_url: 'https://github.com/octocat/acme/pull/12' }, 201)
|
|
105
|
+
);
|
|
106
|
+
const pr = await createPullRequest('t', 'octocat', 'acme', {
|
|
107
|
+
head: 'nav-redesign',
|
|
108
|
+
base: 'main',
|
|
109
|
+
title: 'Add draft',
|
|
110
|
+
body: 'hi',
|
|
111
|
+
});
|
|
112
|
+
expect(pr).toEqual({ number: 12, html_url: 'https://github.com/octocat/acme/pull/12' });
|
|
113
|
+
expect(calls[0].url).toBe('https://api.github.com/repos/octocat/acme/pulls');
|
|
114
|
+
expect(calls[0].init?.method).toBe('POST');
|
|
115
|
+
expect(JSON.parse(calls[0].init?.body as string)).toEqual({
|
|
116
|
+
title: 'Add draft',
|
|
117
|
+
head: 'nav-redesign',
|
|
118
|
+
base: 'main',
|
|
119
|
+
body: 'hi',
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('createPullRequest recovers the existing PR link on a 422 (already exists)', async () => {
|
|
124
|
+
const calls = stubFetch((_url, init) =>
|
|
125
|
+
init?.method === 'POST'
|
|
126
|
+
? json({ message: 'A pull request already exists for octocat:nav.' }, 422)
|
|
127
|
+
: json([{ number: 7, html_url: 'https://github.com/octocat/acme/pull/7' }])
|
|
128
|
+
);
|
|
129
|
+
const pr = await createPullRequest('t', 'octocat', 'acme', {
|
|
130
|
+
head: 'nav',
|
|
131
|
+
base: 'main',
|
|
132
|
+
title: 'x',
|
|
133
|
+
});
|
|
134
|
+
expect(pr).toEqual({ number: 7, html_url: 'https://github.com/octocat/acme/pull/7' });
|
|
135
|
+
// the recovery GET filters by head=owner:branch + base + state=open
|
|
136
|
+
expect(calls[1].url).toContain('head=octocat%3Anav');
|
|
137
|
+
expect(calls[1].url).toContain('base=main');
|
|
138
|
+
expect(calls[1].url).toContain('state=open');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('createPullRequest maps a 422 with no existing PR to a "nothing new" message', async () => {
|
|
142
|
+
stubFetch((_url, init) =>
|
|
143
|
+
init?.method === 'POST' ? json({ message: 'No commits between main and nav' }, 422) : json([])
|
|
144
|
+
);
|
|
145
|
+
const err = await createPullRequest('t', 'octocat', 'acme', {
|
|
146
|
+
head: 'nav',
|
|
147
|
+
base: 'main',
|
|
148
|
+
title: 'x',
|
|
149
|
+
}).catch((e) => e);
|
|
150
|
+
expect(err).toBeInstanceOf(GitHubApiError);
|
|
151
|
+
expect((err as GitHubApiError).status).toBe(422);
|
|
152
|
+
expect((err as GitHubApiError).message).toMatch(/nothing new to add/i);
|
|
153
|
+
});
|
|
154
|
+
|
|
101
155
|
test('inviteCollaborator PUTs permission=push, 201 = invited', async () => {
|
|
102
156
|
const calls = stubFetch(() => json({ id: 1 }, 201));
|
|
103
157
|
const r = await inviteCollaborator('t', 'octocat', 'acme', 'anna');
|
|
@@ -242,7 +296,8 @@ describe('token bridge fetch', () => {
|
|
|
242
296
|
const calls = stubFetch(() => new Response('gho_secret\n', { status: 200 }));
|
|
243
297
|
expect(tokenBridgeAvailable()).toBe(true);
|
|
244
298
|
expect(await getGithubToken()).toBe('gho_secret');
|
|
245
|
-
|
|
299
|
+
const headers = calls[0].init?.headers as Record<string, string>;
|
|
300
|
+
expect(headers['X-Maude-Token-Key']).toBe('k123');
|
|
246
301
|
});
|
|
247
302
|
|
|
248
303
|
test('returns null on 404 (not signed in) and 403 (bad key)', async () => {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Browser-driven pieces of local-file/SVG/PDF ingestion (DDR-167) — the SVG
|
|
2
|
+
// execution canary. Spawns real agent-browser sessions, so this is slower
|
|
3
|
+
// than import-asset.test.ts and is skipped (not failed) when agent-browser
|
|
4
|
+
// can't be resolved, mirroring how the rest of this codebase treats
|
|
5
|
+
// browser-dependent verification as a real-environment concern rather than a
|
|
6
|
+
// hard CI requirement (e.g. ensure-browser.test.ts's resolution-only tests).
|
|
7
|
+
//
|
|
8
|
+
// DDR-167 requires the SVG canary fixtures be a PERMANENT regression test,
|
|
9
|
+
// not a one-time pre-ship check — this file is that test. The PDF rasterize
|
|
10
|
+
// adversarial fixtures (OpenAction/JS canary, SubmitForm/URI/Launch) are NOT
|
|
11
|
+
// here: live spike-verification found the planned rasterization mechanism
|
|
12
|
+
// (headless-Chromium navigating a file://*.pdf URL) does not render PDF
|
|
13
|
+
// content under agent-browser automation (confirmed blank output on both
|
|
14
|
+
// chrome-headless-shell and full Chrome) — see DDR-167's addendum. PDF
|
|
15
|
+
// rasterization throws "not yet available" rather than shipping broken
|
|
16
|
+
// output; there is nothing to fixture-test until a follow-up mechanism lands.
|
|
17
|
+
|
|
18
|
+
import { describe, expect, test } from 'bun:test';
|
|
19
|
+
import { execFileSync } from 'node:child_process';
|
|
20
|
+
|
|
21
|
+
import { rasterizePdfPage, runSvgExecutionCanary } from '../bin/_import-asset.mjs';
|
|
22
|
+
|
|
23
|
+
function agentBrowserAvailable(): boolean {
|
|
24
|
+
try {
|
|
25
|
+
execFileSync(process.env.MAUDE_AGENT_BROWSER_BIN || 'agent-browser', ['--version'], {
|
|
26
|
+
stdio: 'ignore',
|
|
27
|
+
timeout: 5000,
|
|
28
|
+
});
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const HAS_AGENT_BROWSER = agentBrowserAvailable();
|
|
36
|
+
const d = HAS_AGENT_BROWSER ? describe : describe.skip;
|
|
37
|
+
|
|
38
|
+
d('SVG execution canary (Decision 1, step 6) — permanent regression fixtures', () => {
|
|
39
|
+
test('accepts a clean, allowlist-sanitized SVG (no false positive)', async () => {
|
|
40
|
+
const clean =
|
|
41
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M16 5l2.8 8.2L27 16l-8.2 2.8L16 27l-2.8-8.2L5 16l8.2-2.8z" fill="#4f46e5"/></svg>';
|
|
42
|
+
await expect(runSvgExecutionCanary(clean)).resolves.toBe(clean);
|
|
43
|
+
}, 30_000);
|
|
44
|
+
|
|
45
|
+
test('trips on a raw <script>-bearing SVG (defense-in-depth if the allowlist sanitizer were ever bypassed)', async () => {
|
|
46
|
+
const malicious =
|
|
47
|
+
'<svg xmlns="http://www.w3.org/2000/svg"><script>window.__MAUDE_IMPORT_CANARY__=true;</script></svg>';
|
|
48
|
+
await expect(runSvgExecutionCanary(malicious)).rejects.toThrow(/canary tripped/);
|
|
49
|
+
}, 30_000);
|
|
50
|
+
|
|
51
|
+
test('the sandboxed-render helper does not block the file:// navigation it needs (regression: a bare "*" network route also blocks local file loads)', async () => {
|
|
52
|
+
// A file:// SVG with no scripting at all must still load and be
|
|
53
|
+
// observable — this specifically guards against re-introducing the
|
|
54
|
+
// wildcard-route regression found during implementation.
|
|
55
|
+
const trivial = '<svg xmlns="http://www.w3.org/2000/svg"><rect width="1" height="1"/></svg>';
|
|
56
|
+
await expect(runSvgExecutionCanary(trivial)).resolves.toBeTruthy();
|
|
57
|
+
}, 30_000);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
d('PDF rasterization — explicitly NOT shipped (DDR-167 addendum)', () => {
|
|
61
|
+
test('rasterizePdfPage throws "not yet available" rather than producing output', async () => {
|
|
62
|
+
await expect(rasterizePdfPage('/nonexistent.pdf', 1)).rejects.toThrow(/not yet available/);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// POST /_api/import-asset — DDR-167 (Phase 3 / T10). Full HTTP round-trip
|
|
2
|
+
// against a real booted server (privilege/CSRF boundary is covered generically
|
|
3
|
+
// by canvas-origin-gate.test.ts — this file exercises the route's own
|
|
4
|
+
// success/error shapes).
|
|
5
|
+
|
|
6
|
+
import { describe, expect, test } from 'bun:test';
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
|
|
9
|
+
import { bootServer, killProc, makeSandbox, nextPort } from './_helpers.ts';
|
|
10
|
+
|
|
11
|
+
const MALICIOUS_SVG =
|
|
12
|
+
'<svg xmlns="http://www.w3.org/2000/svg"><script>alert(1)</script>' +
|
|
13
|
+
'<rect onclick="alert(2)" fill="#4f46e5" width="10" height="10"/></svg>';
|
|
14
|
+
|
|
15
|
+
describe('POST /_api/import-asset', () => {
|
|
16
|
+
test('imports and sanitizes an SVG, rejects a PDF kind as not-yet-available, rejects GET', async () => {
|
|
17
|
+
const { root, designRoot } = makeSandbox();
|
|
18
|
+
const port = nextPort();
|
|
19
|
+
const proc = await bootServer(root, port);
|
|
20
|
+
try {
|
|
21
|
+
const base = `http://localhost:${port}`;
|
|
22
|
+
|
|
23
|
+
const post = await fetch(`${base}/_api/import-asset`, {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: { 'X-Import-Kind': 'svg', Origin: base },
|
|
26
|
+
body: MALICIOUS_SVG,
|
|
27
|
+
});
|
|
28
|
+
expect(post.status).toBe(201);
|
|
29
|
+
const body = (await post.json()) as { ok: boolean; path: string };
|
|
30
|
+
expect(body.ok).toBe(true);
|
|
31
|
+
expect(body.path).toMatch(/^assets\/[a-z0-9]{8}\.svg$/);
|
|
32
|
+
const written = readFileSync(`${designRoot}/${body.path}`, 'utf8');
|
|
33
|
+
expect(written).not.toContain('script');
|
|
34
|
+
expect(written).not.toContain('onclick');
|
|
35
|
+
|
|
36
|
+
const pdfAttempt = await fetch(`${base}/_api/import-asset`, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: { 'X-Import-Kind': 'pdf', Origin: base },
|
|
39
|
+
body: '%PDF-1.4 fixture',
|
|
40
|
+
});
|
|
41
|
+
expect(pdfAttempt.status).toBe(501);
|
|
42
|
+
const pdfBody = (await pdfAttempt.json()) as { ok: boolean; error: string };
|
|
43
|
+
expect(pdfBody.ok).toBe(false);
|
|
44
|
+
expect(pdfBody.error).toContain('not yet available');
|
|
45
|
+
|
|
46
|
+
const getAttempt = await fetch(`${base}/_api/import-asset`, { method: 'GET' });
|
|
47
|
+
expect(getAttempt.status).toBe(405);
|
|
48
|
+
} finally {
|
|
49
|
+
await killProc(proc);
|
|
50
|
+
}
|
|
51
|
+
}, 30_000);
|
|
52
|
+
|
|
53
|
+
test('a malformed SVG is rejected with a clean 400, not a 500 crash', async () => {
|
|
54
|
+
const { root } = makeSandbox();
|
|
55
|
+
const port = nextPort();
|
|
56
|
+
const proc = await bootServer(root, port);
|
|
57
|
+
try {
|
|
58
|
+
const base = `http://localhost:${port}`;
|
|
59
|
+
const res = await fetch(`${base}/_api/import-asset`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: { 'X-Import-Kind': 'svg', Origin: base },
|
|
62
|
+
body: '<svg><rect',
|
|
63
|
+
});
|
|
64
|
+
expect(res.status).toBe(400);
|
|
65
|
+
const body = (await res.json()) as { ok: boolean };
|
|
66
|
+
expect(body.ok).toBe(false);
|
|
67
|
+
} finally {
|
|
68
|
+
await killProc(proc);
|
|
69
|
+
}
|
|
70
|
+
}, 30_000);
|
|
71
|
+
});
|