@webship.co/workspace 1.0.0-beta1 → 1.0.0-beta2
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/LICENSE +339 -0
- package/README.md +28 -364
- package/core/bin/workspace.js +135 -0
- package/core/config/settings.labels.yml +514 -0
- package/core/config/settings.template.yml +292 -0
- package/core/config/style.yml +53 -0
- package/core/config/theme.default.yml +161 -0
- package/core/config/theme.dispatch.yml +166 -0
- package/core/config/theme.drupal.yml +175 -0
- package/core/config/theme.ember.yml +160 -0
- package/core/config/theme.github.yml +172 -0
- package/core/config/theme.gitlab.yml +168 -0
- package/core/config/theme.press.yml +161 -0
- package/core/config/theme.slate.yml +160 -0
- package/core/config/theme.upsun.yml +174 -0
- package/core/docs/architecture.svg +65 -0
- package/core/docs/getting-started.md +51 -0
- package/core/docs/plugins.md +27 -0
- package/core/docs/releases.md +23 -0
- package/core/docs/testing.md +15 -0
- package/core/docs/workspaces.md +25 -0
- package/core/scripts/args/arg-ai-items.sh +65 -0
- package/core/scripts/args/arg-autostart.sh +31 -0
- package/core/scripts/args/arg-avatar-agent.sh +28 -0
- package/core/scripts/args/arg-avatar-catalog.sh +12 -0
- package/core/scripts/args/arg-avatar-character.sh +68 -0
- package/core/scripts/args/arg-avatar-voice.sh +24 -0
- package/core/scripts/args/arg-avatar.sh +22 -0
- package/core/scripts/args/arg-checks.sh +36 -0
- package/core/scripts/args/arg-clone.sh +42 -0
- package/core/scripts/args/arg-commands.sh +51 -0
- package/core/scripts/args/arg-cucumber.sh +34 -0
- package/core/scripts/args/arg-distribution.sh +30 -0
- package/core/scripts/args/arg-drupal.sh +39 -0
- package/core/scripts/args/arg-drupal_cms.sh +18 -0
- package/core/scripts/args/arg-gitlab-ci-local.sh +25 -0
- package/core/scripts/args/arg-graphify.sh +36 -0
- package/core/scripts/args/arg-graphs.sh +12 -0
- package/core/scripts/args/arg-hostnames.sh +23 -0
- package/core/scripts/args/arg-milvus-backup.sh +8 -0
- package/core/scripts/args/arg-milvus-mcp.sh +12 -0
- package/core/scripts/args/arg-milvus.sh +32 -0
- package/core/scripts/args/arg-obsidian.sh +17 -0
- package/core/scripts/args/arg-odoo.sh +18 -0
- package/core/scripts/args/arg-penpot-backup.sh +8 -0
- package/core/scripts/args/arg-penpot-mcp.sh +15 -0
- package/core/scripts/args/arg-penpot-token.sh +17 -0
- package/core/scripts/args/arg-penpot.sh +44 -0
- package/core/scripts/args/arg-rag.sh +21 -0
- package/core/scripts/args/arg-ragify.sh +35 -0
- package/core/scripts/args/arg-reclaim.sh +18 -0
- package/core/scripts/args/arg-release.sh +21 -0
- package/core/scripts/args/arg-screen.sh +28 -0
- package/core/scripts/args/arg-site-template.sh +13 -0
- package/core/scripts/args/arg-spdd.sh +16 -0
- package/core/scripts/args/arg-specs.sh +22 -0
- package/core/scripts/args/arg-storage.sh +36 -0
- package/core/scripts/args/arg-testing.sh +36 -0
- package/core/scripts/args/arg-webship.sh +34 -0
- package/core/scripts/args/arg-webships-project.sh +22 -0
- package/core/scripts/args/arg-webships-site-template.sh +17 -0
- package/core/scripts/args/arg-webships.sh +34 -0
- package/core/scripts/args/arg-website-site-template.sh +17 -0
- package/core/scripts/args/arg-website.sh +25 -0
- package/core/scripts/args/arg-wordpress.sh +18 -0
- package/core/scripts/autostart-run.sh +162 -0
- package/core/scripts/autostart-stop.sh +30 -0
- package/core/scripts/autostart-watch.sh +43 -0
- package/core/scripts/autostart.sh +26 -0
- package/core/scripts/bootstrap-functions.sh +117 -0
- package/core/scripts/bootstrap-libraries.sh +10 -0
- package/core/scripts/bootstrap.sh +71 -0
- package/core/scripts/ddev-shim/ddev +122 -0
- package/core/scripts/docker-gid.sh +119 -0
- package/core/scripts/functions/arg-site-template.sh +28 -0
- package/core/scripts/functions/fun-ai-items-lock.sh +175 -0
- package/core/scripts/functions/fun-ai-items-sources.sh +298 -0
- package/core/scripts/functions/fun-ai-items.sh +621 -0
- package/core/scripts/functions/fun-ai-providers.sh +72 -0
- package/core/scripts/functions/fun-autostart.sh +429 -0
- package/core/scripts/functions/fun-avatar.sh +441 -0
- package/core/scripts/functions/fun-backup-item.sh +50 -0
- package/core/scripts/functions/fun-build-distribution.sh +226 -0
- package/core/scripts/functions/fun-build-site-template.sh +104 -0
- package/core/scripts/functions/fun-build-webships.sh +161 -0
- package/core/scripts/functions/fun-build-website.sh +199 -0
- package/core/scripts/functions/fun-checks.sh +243 -0
- package/core/scripts/functions/fun-clone-project.sh +289 -0
- package/core/scripts/functions/fun-commands.sh +521 -0
- package/core/scripts/functions/fun-database.sh +63 -0
- package/core/scripts/functions/fun-ddev-hostnames.sh +213 -0
- package/core/scripts/functions/fun-ddev-nodejs.sh +15 -0
- package/core/scripts/functions/fun-ddev-telemetry.sh +15 -0
- package/core/scripts/functions/fun-default-settings.sh +28 -0
- package/core/scripts/functions/fun-distribution-cucumber.sh +45 -0
- package/core/scripts/functions/fun-distribution-drupal.sh +25 -0
- package/core/scripts/functions/fun-distribution-drupal_cms.sh +35 -0
- package/core/scripts/functions/fun-distribution-webship.sh +28 -0
- package/core/scripts/functions/fun-distribution-webships.sh +33 -0
- package/core/scripts/functions/fun-distribution-website.sh +19 -0
- package/core/scripts/functions/fun-drush.sh +48 -0
- package/core/scripts/functions/fun-elevenlabs.sh +179 -0
- package/core/scripts/functions/fun-files.sh +19 -0
- package/core/scripts/functions/fun-git-worktrees.sh +142 -0
- package/core/scripts/functions/fun-gitlab-ci.sh +156 -0
- package/core/scripts/functions/fun-hostnames-cli.sh +88 -0
- package/core/scripts/functions/fun-milvus.sh +584 -0
- package/core/scripts/functions/fun-odoo.sh +121 -0
- package/core/scripts/functions/fun-penpot.sh +724 -0
- package/core/scripts/functions/fun-project-name.sh +20 -0
- package/core/scripts/functions/fun-rag.sh +253 -0
- package/core/scripts/functions/fun-ragify.sh +165 -0
- package/core/scripts/functions/fun-reclaim.sh +297 -0
- package/core/scripts/functions/fun-release.sh +159 -0
- package/core/scripts/functions/fun-remove-project.sh +319 -0
- package/core/scripts/functions/fun-screen.sh +96 -0
- package/core/scripts/functions/fun-spdd.sh +676 -0
- package/core/scripts/functions/fun-specs-share.sh +237 -0
- package/core/scripts/functions/fun-storage.sh +347 -0
- package/core/scripts/functions/fun-testing.sh +329 -0
- package/core/scripts/functions/fun-users.sh +87 -0
- package/core/scripts/functions/fun-version.sh +72 -0
- package/core/scripts/functions/fun-video-edit.sh +48 -0
- package/core/scripts/functions/fun-voice.sh +45 -0
- package/core/scripts/functions/fun-wordpress.sh +59 -0
- package/core/scripts/host-mounts.sh +161 -0
- package/core/scripts/install.sh +602 -0
- package/core/scripts/libs/__pycache__/video_edit.cpython-312.pyc +0 -0
- package/core/scripts/libs/argparse.sh +93 -0
- package/core/scripts/libs/bash-yaml.sh +52 -0
- package/core/scripts/libs/checks_plan.js +312 -0
- package/core/scripts/libs/elevenlabs.py +627 -0
- package/core/scripts/libs/progress-bar.sh +19 -0
- package/core/scripts/libs/rag_index.py +446 -0
- package/core/scripts/libs/video_edit.py +636 -0
- package/core/scripts/libs/voice.py +430 -0
- package/core/scripts/plugins/jira/host-install.sh +119 -0
- package/core/scripts/plugins/jira/host-mounts.txt +2 -0
- package/core/scripts/plugins/upsun/args/arg-upsun.sh +51 -0
- package/core/scripts/plugins/upsun/functions/fun-upsun.sh +304 -0
- package/core/scripts/plugins/upsun/host-install.sh +99 -0
- package/core/scripts/plugins/upsun/host-mounts.txt +1 -0
- package/core/scripts/plugins/upsun/upsun-ai.sh +133 -0
- package/core/scripts/plugins.sh +152 -0
- package/core/scripts/release.sh +69 -0
- package/core/scripts/storage.sh +39 -0
- package/core/scripts/sync-tooling.sh +246 -0
- package/core/scripts/tests/cmd-smoke-test.sh +109 -0
- package/core/scripts/trust-https.sh +257 -0
- package/core/workspace-app/.ddev/commands/web/test-dashboard +49 -0
- package/core/workspace-app/.ddev/config.yaml +400 -0
- package/core/workspace-app/.ddev/docker-compose.dood.yaml +45 -0
- package/core/workspace-app/.ddev/docker-compose.plugin-jira-twg.yaml +26 -0
- package/core/workspace-app/.ddev/docker-compose.plugin-upsun-upsun-cli.yaml +8 -0
- package/core/workspace-app/.ddev/docker-compose.screens.yaml +15 -0
- package/core/workspace-app/.ddev/nginx_full/nginx-site.conf +114 -0
- package/core/workspace-app/.ddev/web-build/Dockerfile.workspace-app +203 -0
- package/core/workspace-app/.ddev/web-build/ddev-shim-ddev +122 -0
- package/core/workspace-app/README.md +380 -0
- package/{actions → core/workspace-app/actions}/ddev.js +0 -1
- package/{actions → core/workspace-app/actions}/rag.js +0 -1
- package/{actions → core/workspace-app/actions}/testing.js +0 -1
- package/{assistant.js → core/workspace-app/assistant.js} +1 -10
- package/{git-info.js → core/workspace-app/git-info.js} +17 -1
- package/core/workspace-app/package.json +29 -0
- package/core/workspace-app/plugin-catalog.js +50 -0
- package/{plugins-page.js → core/workspace-app/plugins-page.js} +28 -2
- package/{server.js → core/workspace-app/server.js} +18 -0
- package/core/workspace-app/tests/features/actions-rail.feature +44 -0
- package/core/workspace-app/tests/features/ai-items.feature +23 -0
- package/core/workspace-app/tests/features/assistant-speech.feature +63 -0
- package/core/workspace-app/tests/features/checks.feature +39 -0
- package/core/workspace-app/tests/features/clone-project.feature +30 -0
- package/core/workspace-app/tests/features/command-authoring.feature +58 -0
- package/core/workspace-app/tests/features/dashboard.feature +57 -0
- package/core/workspace-app/tests/features/menus.feature +29 -0
- package/core/workspace-app/tests/features/plugins/jira/jira.feature +47 -0
- package/core/workspace-app/tests/features/plugins.feature +20 -0
- package/core/workspace-app/tests/features/project-actions.feature +40 -0
- package/core/workspace-app/tests/features/screens.feature +29 -0
- package/core/workspace-app/tests/features/settings.feature +46 -0
- package/core/workspace-app/tests/features/test-reports.feature +32 -0
- package/core/workspace-app/tests/features/theme.feature +40 -0
- package/core/workspace-app/tests/step-definitions/actions-post.steps.js +25 -0
- package/core/workspace-app/tests/step-definitions/assistant-speech.steps.js +152 -0
- package/core/workspace-app/tests/step-definitions/preconditions.steps.js +100 -0
- package/core/workspace-app/tests/unit/_isolated-config.js +31 -0
- package/core/workspace-app/tests/unit/accessibility.test.js +124 -0
- package/core/workspace-app/tests/unit/actions-rail-ux.test.js +56 -0
- package/core/workspace-app/tests/unit/actions-rail.test.js +51 -0
- package/core/workspace-app/tests/unit/ai-inventory.test.js +64 -0
- package/core/workspace-app/tests/unit/app-docs.test.js +64 -0
- package/core/workspace-app/tests/unit/assistant-avatar.test.js +110 -0
- package/core/workspace-app/tests/unit/assistant-cast.test.js +105 -0
- package/core/workspace-app/tests/unit/assistant-send-animation.test.js +15 -0
- package/core/workspace-app/tests/unit/assistant-suggestions.test.js +112 -0
- package/core/workspace-app/tests/unit/assistant-switches.test.js +53 -0
- package/core/workspace-app/tests/unit/assistant-views.test.js +146 -0
- package/core/workspace-app/tests/unit/assistant-working-indicator.test.js +49 -0
- package/core/workspace-app/tests/unit/assistant.test.js +61 -0
- package/core/workspace-app/tests/unit/audio-player.test.js +147 -0
- package/core/workspace-app/tests/unit/audio-voice.test.js +634 -0
- package/core/workspace-app/tests/unit/auth-google.test.js +180 -0
- package/core/workspace-app/tests/unit/autostart-watch.test.js +76 -0
- package/core/workspace-app/tests/unit/browsers.test.js +76 -0
- package/core/workspace-app/tests/unit/build-distribution.test.js +125 -0
- package/core/workspace-app/tests/unit/build-order.test.js +153 -0
- package/core/workspace-app/tests/unit/builder-args.test.js +113 -0
- package/core/workspace-app/tests/unit/builders.test.js +158 -0
- package/core/workspace-app/tests/unit/card-state-toggle.test.js +29 -0
- package/core/workspace-app/tests/unit/changelog.test.js +177 -0
- package/core/workspace-app/tests/unit/chat-thread.test.js +92 -0
- package/core/workspace-app/tests/unit/checks-plan.test.js +205 -0
- package/core/workspace-app/tests/unit/claude-args.test.js +182 -0
- package/core/workspace-app/tests/unit/claude-code-here.test.js +15 -0
- package/core/workspace-app/tests/unit/claude-resume.test.js +147 -0
- package/core/workspace-app/tests/unit/claude-screen-options.test.js +84 -0
- package/core/workspace-app/tests/unit/claude-session.test.js +41 -0
- package/core/workspace-app/tests/unit/command-flags.test.js +45 -0
- package/core/workspace-app/tests/unit/command-palette.test.js +105 -0
- package/core/workspace-app/tests/unit/commands-center.test.js +338 -0
- package/core/workspace-app/tests/unit/composer-menu.test.js +187 -0
- package/core/workspace-app/tests/unit/config-yaml.test.js +116 -0
- package/core/workspace-app/tests/unit/confirm-dialog.test.js +69 -0
- package/core/workspace-app/tests/unit/create-card.test.js +85 -0
- package/core/workspace-app/tests/unit/ddev-addons.test.js +144 -0
- package/core/workspace-app/tests/unit/ddev-auth-ssh.test.js +40 -0
- package/core/workspace-app/tests/unit/ddev-custom-commands.test.js +56 -0
- package/core/workspace-app/tests/unit/ddev-diagnose.test.js +57 -0
- package/core/workspace-app/tests/unit/ddev-domains.test.js +153 -0
- package/core/workspace-app/tests/unit/ddev-hostnames.test.js +143 -0
- package/core/workspace-app/tests/unit/ddev-import.test.js +82 -0
- package/core/workspace-app/tests/unit/ddev-mail.test.js +135 -0
- package/core/workspace-app/tests/unit/ddev-menu-and-create.test.js +28 -0
- package/core/workspace-app/tests/unit/ddev-mutagen.test.js +37 -0
- package/core/workspace-app/tests/unit/ddev-options.test.js +51 -0
- package/core/workspace-app/tests/unit/ddev-share.test.js +40 -0
- package/core/workspace-app/tests/unit/ddev-shim.test.js +57 -0
- package/core/workspace-app/tests/unit/ddev-snapshots.test.js +142 -0
- package/core/workspace-app/tests/unit/ddev-start-daemons.test.js +62 -0
- package/core/workspace-app/tests/unit/ddev-xdebug.test.js +41 -0
- package/core/workspace-app/tests/unit/deep-chat-origin.test.js +38 -0
- package/core/workspace-app/tests/unit/density.test.js +52 -0
- package/core/workspace-app/tests/unit/elevenlabs.test.js +453 -0
- package/core/workspace-app/tests/unit/event-log.test.js +102 -0
- package/core/workspace-app/tests/unit/favicon.test.js +68 -0
- package/core/workspace-app/tests/unit/files-browser.test.js +224 -0
- package/core/workspace-app/tests/unit/git-info.test.js +26 -0
- package/core/workspace-app/tests/unit/grid-and-list.test.js +103 -0
- package/core/workspace-app/tests/unit/host-tool-wrapper.test.js +18 -0
- package/core/workspace-app/tests/unit/html-patterns.test.js +51 -0
- package/core/workspace-app/tests/unit/ide-infra.test.js +28 -0
- package/core/workspace-app/tests/unit/images-ai.test.js +201 -0
- package/core/workspace-app/tests/unit/images-create.test.js +59 -0
- package/core/workspace-app/tests/unit/images-providers.test.js +248 -0
- package/core/workspace-app/tests/unit/images-svg.test.js +213 -0
- package/core/workspace-app/tests/unit/images-workspace.test.js +144 -0
- package/core/workspace-app/tests/unit/jobs-fragment.test.js +66 -0
- package/core/workspace-app/tests/unit/media-ux.test.js +164 -0
- package/core/workspace-app/tests/unit/modal-fit.test.js +87 -0
- package/core/workspace-app/tests/unit/modules-load.test.js +23 -0
- package/core/workspace-app/tests/unit/narration.test.js +186 -0
- package/core/workspace-app/tests/unit/navigation-editor.test.js +260 -0
- package/core/workspace-app/tests/unit/navigation.test.js +254 -0
- package/core/workspace-app/tests/unit/new-folder.test.js +121 -0
- package/core/workspace-app/tests/unit/new-screen-form.test.js +149 -0
- package/core/workspace-app/tests/unit/node-menu.test.js +161 -0
- package/core/workspace-app/tests/unit/notes.test.js +128 -0
- package/core/workspace-app/tests/unit/page-structure.test.js +40 -0
- package/core/workspace-app/tests/unit/page-width.test.js +59 -0
- package/core/workspace-app/tests/unit/php-version.test.js +90 -0
- package/core/workspace-app/tests/unit/plan-card.test.js +44 -0
- package/core/workspace-app/tests/unit/plugin-catalog.test.js +31 -0
- package/core/workspace-app/tests/unit/plugin-system.test.js +251 -0
- package/core/workspace-app/tests/unit/plugins-page.test.js +28 -0
- package/core/workspace-app/tests/unit/plugins.test.js +58 -0
- package/core/workspace-app/tests/unit/project-activity-filter.test.js +180 -0
- package/core/workspace-app/tests/unit/project-make.test.js +143 -0
- package/core/workspace-app/tests/unit/project-name.test.js +202 -0
- package/core/workspace-app/tests/unit/reader.test.js +62 -0
- package/core/workspace-app/tests/unit/release.test.js +92 -0
- package/core/workspace-app/tests/unit/remove-confirm.test.js +37 -0
- package/core/workspace-app/tests/unit/report-pdf.test.js +171 -0
- package/core/workspace-app/tests/unit/round-11.test.js +31 -0
- package/core/workspace-app/tests/unit/round-12.test.js +37 -0
- package/core/workspace-app/tests/unit/row-layout.test.js +585 -0
- package/core/workspace-app/tests/unit/row-menus-fold.test.js +29 -0
- package/core/workspace-app/tests/unit/rows-add-modal.test.js +68 -0
- package/core/workspace-app/tests/unit/screen-runner-queue.test.js +41 -0
- package/core/workspace-app/tests/unit/screen-window.test.js +116 -0
- package/core/workspace-app/tests/unit/screens-beside-running.test.js +19 -0
- package/core/workspace-app/tests/unit/screens-columns.test.js +79 -0
- package/core/workspace-app/tests/unit/screens-menu.test.js +84 -0
- package/core/workspace-app/tests/unit/screens-modal-overlay.test.js +14 -0
- package/core/workspace-app/tests/unit/screens-waiting.test.js +129 -0
- package/core/workspace-app/tests/unit/screens.test.js +74 -0
- package/core/workspace-app/tests/unit/second-storage.test.js +218 -0
- package/core/workspace-app/tests/unit/session-name.test.js +64 -0
- package/core/workspace-app/tests/unit/settings-file.test.js +173 -0
- package/core/workspace-app/tests/unit/settings-nested-list.test.js +54 -0
- package/core/workspace-app/tests/unit/settings-raw.test.js +177 -0
- package/core/workspace-app/tests/unit/shared-prefs.test.js +49 -0
- package/core/workspace-app/tests/unit/speech.test.js +290 -0
- package/core/workspace-app/tests/unit/static-caching.test.js +39 -0
- package/core/workspace-app/tests/unit/status-language.test.js +47 -0
- package/core/workspace-app/tests/unit/tab-modal.test.js +69 -0
- package/core/workspace-app/tests/unit/tab-title.test.js +37 -0
- package/core/workspace-app/tests/unit/template-literals.test.js +52 -0
- package/core/workspace-app/tests/unit/terminal-scrollback.test.js +61 -0
- package/core/workspace-app/tests/unit/terminal-size.test.js +103 -0
- package/core/workspace-app/tests/unit/terminal-style.test.js +84 -0
- package/core/workspace-app/tests/unit/terminal-windows.test.js +154 -0
- package/core/workspace-app/tests/unit/themes.test.js +453 -0
- package/core/workspace-app/tests/unit/tool-labels.test.js +34 -0
- package/core/workspace-app/tests/unit/topbar-icons.test.js +47 -0
- package/core/workspace-app/tests/unit/update-docker-gid.test.js +51 -0
- package/core/workspace-app/tests/unit/user-guide.test.js +60 -0
- package/core/workspace-app/tests/unit/version-check.test.js +98 -0
- package/core/workspace-app/tests/unit/version-link.test.js +50 -0
- package/core/workspace-app/tests/unit/video-borrow.test.js +198 -0
- package/core/workspace-app/tests/unit/video-edit-ask.test.js +93 -0
- package/core/workspace-app/tests/unit/video-edit-short.test.js +42 -0
- package/core/workspace-app/tests/unit/video-edit.test.js +417 -0
- package/core/workspace-app/tests/unit/video-transcript.test.js +237 -0
- package/core/workspace-app/tests/unit/waiting.test.js +48 -0
- package/core/workspace-app/tests/unit/wordpress-odoo-builders.test.js +38 -0
- package/core/workspace-app/tests/unit/workspace-bin.test.js +48 -0
- package/core/workspace-app/tests/unit/workspace-card-tools.test.js +30 -0
- package/core/workspace-app/tests/unit/workspace-delete.test.js +77 -0
- package/core/workspace-app/tests/unit/workspace-types.test.js +68 -0
- package/core/workspace-app/tests/unit/worktree-clone.test.js +250 -0
- package/package.json +38 -36
- /package/{PLUGINS.md → core/workspace-app/PLUGINS.md} +0 -0
- /package/{WORKSPACES.md → core/workspace-app/WORKSPACES.md} +0 -0
- /package/{actions → core/workspace-app/actions}/ai-items.js +0 -0
- /package/{actions → core/workspace-app/actions}/audio.js +0 -0
- /package/{actions → core/workspace-app/actions}/build-order.js +0 -0
- /package/{actions → core/workspace-app/actions}/checks.js +0 -0
- /package/{actions → core/workspace-app/actions}/claude-session.js +0 -0
- /package/{actions → core/workspace-app/actions}/commands-center.js +0 -0
- /package/{actions → core/workspace-app/actions}/composer.js +0 -0
- /package/{actions → core/workspace-app/actions}/ddev-addons.js +0 -0
- /package/{actions → core/workspace-app/actions}/ddev-mail.js +0 -0
- /package/{actions → core/workspace-app/actions}/folders.js +0 -0
- /package/{actions → core/workspace-app/actions}/images-svg.js +0 -0
- /package/{actions → core/workspace-app/actions}/images.js +0 -0
- /package/{actions → core/workspace-app/actions}/navigation.js +0 -0
- /package/{actions → core/workspace-app/actions}/node.js +0 -0
- /package/{actions → core/workspace-app/actions}/notes.js +0 -0
- /package/{actions → core/workspace-app/actions}/plugins.js +0 -0
- /package/{actions → core/workspace-app/actions}/project-make.js +0 -0
- /package/{actions → core/workspace-app/actions}/row-actions.js +0 -0
- /package/{actions → core/workspace-app/actions}/screens.js +0 -0
- /package/{actions → core/workspace-app/actions}/video-edit.js +0 -0
- /package/{ai-inventory.js → core/workspace-app/ai-inventory.js} +0 -0
- /package/{ai-items.js → core/workspace-app/ai-items.js} +0 -0
- /package/{assistant-cast.js → core/workspace-app/assistant-cast.js} +0 -0
- /package/{assistant-suggestions.js → core/workspace-app/assistant-suggestions.js} +0 -0
- /package/{audio-voice.js → core/workspace-app/audio-voice.js} +0 -0
- /package/{auth-google.js → core/workspace-app/auth-google.js} +0 -0
- /package/{build-ai.js → core/workspace-app/build-ai.js} +0 -0
- /package/{build-order-page.js → core/workspace-app/build-order-page.js} +0 -0
- /package/{build-order.js → core/workspace-app/build-order.js} +0 -0
- /package/{builder-args.js → core/workspace-app/builder-args.js} +0 -0
- /package/{changelog.js → core/workspace-app/changelog.js} +0 -0
- /package/{chat-threads.js → core/workspace-app/chat-threads.js} +0 -0
- /package/{checks.js → core/workspace-app/checks.js} +0 -0
- /package/{claude-args.js → core/workspace-app/claude-args.js} +0 -0
- /package/{claude-sessions.js → core/workspace-app/claude-sessions.js} +0 -0
- /package/{command-author.js → core/workspace-app/command-author.js} +0 -0
- /package/{command-block.js → core/workspace-app/command-block.js} +0 -0
- /package/{commands → core/workspace-app/commands}/defaults.yml +0 -0
- /package/{commands-page.js → core/workspace-app/commands-page.js} +0 -0
- /package/{commands.js → core/workspace-app/commands.js} +0 -0
- /package/{cucumber.js → core/workspace-app/cucumber.js} +0 -0
- /package/{ddev-addons-page.js → core/workspace-app/ddev-addons-page.js} +0 -0
- /package/{ddev-addons.js → core/workspace-app/ddev-addons.js} +0 -0
- /package/{ddev-custom-commands.js → core/workspace-app/ddev-custom-commands.js} +0 -0
- /package/{ddev-domains.js → core/workspace-app/ddev-domains.js} +0 -0
- /package/{ddev-import.js → core/workspace-app/ddev-import.js} +0 -0
- /package/{ddev-mail-page.js → core/workspace-app/ddev-mail-page.js} +0 -0
- /package/{ddev-mail.js → core/workspace-app/ddev-mail.js} +0 -0
- /package/{ddev-options.js → core/workspace-app/ddev-options.js} +0 -0
- /package/{ddev-snapshots.js → core/workspace-app/ddev-snapshots.js} +0 -0
- /package/{ddev.js → core/workspace-app/ddev.js} +0 -0
- /package/{docs → core/workspace-app/docs}/DEVELOPER-GUIDE.md +0 -0
- /package/{docs → core/workspace-app/docs}/README.md +0 -0
- /package/{docs → core/workspace-app/docs}/REFERENCE.md +0 -0
- /package/{docs → core/workspace-app/docs}/USER-GUIDE.md +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-board.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-coffee.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-colours.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-icons.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-moves.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-pick-cookieman.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-settings-voices.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-status.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/create-card/create-card.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/create-card/start-ai.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-address.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-off.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-on.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-ddev-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-domains.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-home.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-ide-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-remove-refusal.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-rows-worktree.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-screens.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-settings.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-workspace-page.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/card-settings.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/drag-reorder.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/edit-card.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/claude-code-image.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/edit-with-ai.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/gallery.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/generate-description.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/generate-plan.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/providers.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/svg-form.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/svg-result.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/upload.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/2-form.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/8-ddev-options.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/files-folder.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/projects/ddev-custom-commands.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/projects/state-chip-stop.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/ai.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/code.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/docs.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/services.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/sites.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-scenarios.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-steps.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/screens-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/screens-new.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/auth-google-lists.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-1-row.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-2-confirm.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-3-type-name.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-4-type-yes.png +0 -0
- /package/{event-log.js → core/workspace-app/event-log.js} +0 -0
- /package/{files-browser.js → core/workspace-app/files-browser.js} +0 -0
- /package/{folders.js → core/workspace-app/folders.js} +0 -0
- /package/{format.js → core/workspace-app/format.js} +0 -0
- /package/{github-auth.js → core/workspace-app/github-auth.js} +0 -0
- /package/{html.js → core/workspace-app/html.js} +0 -0
- /package/{icons.js → core/workspace-app/icons.js} +0 -0
- /package/{images-ai.js → core/workspace-app/images-ai.js} +0 -0
- /package/{images-create.js → core/workspace-app/images-create.js} +0 -0
- /package/{images-page.js → core/workspace-app/images-page.js} +0 -0
- /package/{images-records.js → core/workspace-app/images-records.js} +0 -0
- /package/{images-render.js → core/workspace-app/images-render.js} +0 -0
- /package/{images-svg-core.js → core/workspace-app/images-svg-core.js} +0 -0
- /package/{images-svg-sanitize.js → core/workspace-app/images-svg-sanitize.js} +0 -0
- /package/{images-svg.js → core/workspace-app/images-svg.js} +0 -0
- /package/{images.js → core/workspace-app/images.js} +0 -0
- /package/{jobs.js → core/workspace-app/jobs.js} +0 -0
- /package/{linger.js → core/workspace-app/linger.js} +0 -0
- /package/{lists.js → core/workspace-app/lists.js} +0 -0
- /package/{media-info.js → core/workspace-app/media-info.js} +0 -0
- /package/{navigation-page.js → core/workspace-app/navigation-page.js} +0 -0
- /package/{navigation.js → core/workspace-app/navigation.js} +0 -0
- /package/{notes.js → core/workspace-app/notes.js} +0 -0
- /package/{palette-index.js → core/workspace-app/palette-index.js} +0 -0
- /package/{playwright.config.js → core/workspace-app/playwright.config.js} +0 -0
- /package/{plugin-install.js → core/workspace-app/plugin-install.js} +0 -0
- /package/{plugin-loader.js → core/workspace-app/plugin-loader.js} +0 -0
- /package/{plugin-manifest.js → core/workspace-app/plugin-manifest.js} +0 -0
- /package/{plugin-settings.js → core/workspace-app/plugin-settings.js} +0 -0
- /package/{plugins → core/workspace-app/plugins}/workspace-notes/index.js +0 -0
- /package/{project-make.js → core/workspace-app/project-make.js} +0 -0
- /package/{project-name.js → core/workspace-app/project-name.js} +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar-moves.js +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar-ui.js +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar.js +0 -0
- /package/{public → core/workspace-app/public}/avatar/druplicon.vrm +0 -0
- /package/{public → core/workspace-app/public}/css/base.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/ai-assistant.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/assistant-avatar.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/assistant-views.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/build-order.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/clone-as-a-git-worktree.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/command-palette.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/commands-center.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/confirm-dialog.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/create-card.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/ddev-menu.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/deep-chat.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/density.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/floating-assistant-workspace-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/folders.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/full-height-left-assistant-sidebar-all-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/full-height-right-actions-rail-pages-that-have-actions.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/grid-and-list.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/help-page.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/hero.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/images.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/layout-rhythm-and-spacing-polish.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/mic-on-any-field-that-wants-typing.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/modal-fit.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/page-width.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/plugins-page.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/project-cards.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/project-rows.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/reordering-the-workspace-cards.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/responsive-home-and-the-workspace-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/running-commands-as-toasts.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/settings-editor-core-config-yml.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/short-results-as-a-toast-at-the-top.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/sound-player.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/suggest-agents-skills-commands.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/tab-modal.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/terminals.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/test-report-media-one-file-per-row-paged.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/top-toolbar-logo-breadcrumb.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/video-edit.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/web-ide-open-with.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/working-indicators.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/workspace-cards.css +0 -0
- /package/{public → core/workspace-app/public}/favicon.ico +0 -0
- /package/{public → core/workspace-app/public}/favicon.png +0 -0
- /package/{public → core/workspace-app/public}/htmx.min.js +0 -0
- /package/{public → core/workspace-app/public}/logo.png +0 -0
- /package/{public → core/workspace-app/public}/media-player.js +0 -0
- /package/{public → core/workspace-app/public}/mic.js +0 -0
- /package/{public → core/workspace-app/public}/narration.js +0 -0
- /package/{public → core/workspace-app/public}/palette.js +0 -0
- /package/{public → core/workspace-app/public}/sortable.js +0 -0
- /package/{public → core/workspace-app/public}/suggest.js +0 -0
- /package/{public → core/workspace-app/public}/terminal.js +0 -0
- /package/{public → core/workspace-app/public}/themes/default/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/ember/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/press/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/slate/theme.css +0 -0
- /package/{public → core/workspace-app/public}/touch-icon.png +0 -0
- /package/{public → core/workspace-app/public}/ui.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ace.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ext-language_tools.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ext-searchbox.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-css.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-html.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-javascript.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-markdown.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-sh.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-yaml.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/theme-textmate.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/theme-tomorrow_night.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/worker-yaml.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/deep-chat.LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/deep-chat.bundle.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/tabler-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/tabler-sprite.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/loaders/GLTFLoader.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/utils/BufferGeometryUtils.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/utils/SkeletonUtils.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/three.core.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/three.module.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three-vrm/LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/three-vrm/three-vrm.module.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit-icons.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit.min.css +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/bookmark-outlined.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/headset.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/menu.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/pause.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/play.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/share.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-off.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-on.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/stop.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-fit.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-unicode11.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-web-links.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/xterm.css +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/xterm.js +0 -0
- /package/{rag.js → core/workspace-app/rag.js} +0 -0
- /package/{reader.js → core/workspace-app/reader.js} +0 -0
- /package/{repo-blobs.js → core/workspace-app/repo-blobs.js} +0 -0
- /package/{report-pdf.js → core/workspace-app/report-pdf.js} +0 -0
- /package/{row-layout.js → core/workspace-app/row-layout.js} +0 -0
- /package/{screenrc → core/workspace-app/screenrc} +0 -0
- /package/{screens.js → core/workspace-app/screens.js} +0 -0
- /package/{scripts → core/workspace-app/scripts}/code-server-start.sh +0 -0
- /package/{scripts → core/workspace-app/scripts}/images-claude-finish.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-host.sh +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-runner.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-then.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/update.sh +0 -0
- /package/{settings-file.js → core/workspace-app/settings-file.js} +0 -0
- /package/{speech.js → core/workspace-app/speech.js} +0 -0
- /package/{tab-modal.js → core/workspace-app/tab-modal.js} +0 -0
- /package/{test-reports.js → core/workspace-app/test-reports.js} +0 -0
- /package/{themes.js → core/workspace-app/themes.js} +0 -0
- /package/{transcripts.js → core/workspace-app/transcripts.js} +0 -0
- /package/{urls.js → core/workspace-app/urls.js} +0 -0
- /package/{validate.js → core/workspace-app/validate.js} +0 -0
- /package/{version-check.js → core/workspace-app/version-check.js} +0 -0
- /package/{video-edit.js → core/workspace-app/video-edit.js} +0 -0
- /package/{waiting.js → core/workspace-app/waiting.js} +0 -0
- /package/{workspace-card-tools.js → core/workspace-app/workspace-card-tools.js} +0 -0
- /package/{workspace-delete.js → core/workspace-app/workspace-delete.js} +0 -0
- /package/{workspace-types.js → core/workspace-app/workspace-types.js} +0 -0
- /package/{workspaces.js → core/workspace-app/workspaces.js} +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Managing a project's captured mail, not just linking to it.
|
|
3
|
+
*
|
|
4
|
+
* DDEV captures outgoing email instead of delivering it, so Mailpit is where every password reset,
|
|
5
|
+
* webform notification and cron warning actually went. A link got you there; the things you do
|
|
6
|
+
* repeatedly — is the mail I just triggered there, who was it addressed to, clear this out before the
|
|
7
|
+
* next run — belong on the row.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { test } = require('node:test');
|
|
11
|
+
const assert = require('node:assert');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const mail = require('../../ddev-mail');
|
|
16
|
+
|
|
17
|
+
const APP = path.join(__dirname, '../..');
|
|
18
|
+
const server = fs.readFileSync(path.join(APP, 'server.js'), 'utf8');
|
|
19
|
+
const page = fs.readFileSync(path.join(APP, 'ddev-mail-page.js'), 'utf8');
|
|
20
|
+
const actions = fs.readFileSync(path.join(APP, 'actions/ddev-mail.js'), 'utf8');
|
|
21
|
+
const mod = fs.readFileSync(path.join(APP, 'ddev-mail.js'), 'utf8');
|
|
22
|
+
|
|
23
|
+
test('a write answers `ok` in plain text, and that is a success', () => {
|
|
24
|
+
// Found by deleting a real message: Mailpit answers reads with JSON and writes with the word `ok`,
|
|
25
|
+
// so parsing everything as JSON reported "Unexpected token 'o'" for a delete that had WORKED — the
|
|
26
|
+
// message gone and an error on the screen, which is the worst of both.
|
|
27
|
+
assert.match(mod, /if \(!\/\^\[\[\{\]\/\.test\(text\)\) return \{ ok: true, data: text \};/);
|
|
28
|
+
assert.match(mod, /Mailpit sent something this could not read/, 'and malformed JSON is still a failure');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('an unreachable Mailpit is not an empty mailbox', () => {
|
|
32
|
+
// The distinction the whole panel rests on: "no mail" would send somebody looking for a bug in
|
|
33
|
+
// their site, when the truth is the project is stopped. Verified live against a hostname that does
|
|
34
|
+
// not resolve.
|
|
35
|
+
assert.match(mod, /AbortError' \? 'Mailpit did not answer in time'/);
|
|
36
|
+
assert.match(page, /Mailpit is not answering/);
|
|
37
|
+
assert.match(page, /This is not the same as\s*\n?\s*an empty mailbox/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('every call is bounded', () => {
|
|
41
|
+
// A stopped project must not hang the panel: the fetch carries an abort signal, not a hope.
|
|
42
|
+
assert.match(mod, /const MAIL_TIMEOUT_MS = \d+;/);
|
|
43
|
+
assert.match(mod, /new AbortController\(\)/);
|
|
44
|
+
assert.match(mod, /signal: controller\.signal/);
|
|
45
|
+
assert.match(mod, /clearTimeout\(timer\)/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('a message id is checked before it is used', () => {
|
|
49
|
+
for (const good of ['6KkYd2wXXZBR9COLnVTj29', '3s1V4blfdr7oNTtblQ27qO']) {
|
|
50
|
+
assert.ok(mail.MAIL_ID_RE.test(good), good);
|
|
51
|
+
}
|
|
52
|
+
for (const bad of ['../../etc', 'a b', '', 'short', '<script>', 'x'.repeat(65)]) {
|
|
53
|
+
assert.equal(mail.MAIL_ID_RE.test(bad), false, bad);
|
|
54
|
+
}
|
|
55
|
+
assert.match(actions, /if \(!mail\.MAIL_ID_RE\.test\(id\)\)/);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('the URL rides along with the status refresh, https first', () => {
|
|
59
|
+
// No extra call per row: the listing that answers "is it running" answers this too, and https is
|
|
60
|
+
// the same service on the other port with the certificate DDEV issued. (This and the two tests
|
|
61
|
+
// below were tests/unit/ddev-mailpit.test.js, which described the wiring before it moved into the
|
|
62
|
+
// module — the assertions kept, the file folded in.)
|
|
63
|
+
assert.match(page, /urlFromListing = \(row\) => String\(\(row && \(row\.mailpit_https_url \|\| row\.mailpit_url\)\) \|\| ''\)/);
|
|
64
|
+
assert.match(server, /mailpit: ddevMail\.urlFromListing\(p\)/);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('it is only offered while the project is up', () => {
|
|
68
|
+
// DDEV reports a Mailpit URL for stopped projects too — 51 of them here — but the service only
|
|
69
|
+
// answers while the containers are running, so an entry on a stopped row leads nowhere.
|
|
70
|
+
assert.match(page, /if \(!running \|\| !mailpit\) return '';/);
|
|
71
|
+
assert.match(page, /entry && entry\.status === 'running'/);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('the label says captured, not sent', () => {
|
|
75
|
+
// The distinction is the whole point: nothing left the machine, and that is why the mail is
|
|
76
|
+
// findable at all.
|
|
77
|
+
assert.match(page, /Every mail this project sends is captured rather than delivered/);
|
|
78
|
+
assert.match(page, /Mail it captured \(Mailpit\)…/);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('the mailbox is found from the status listing, never from the request', () => {
|
|
82
|
+
// A URL in a form would let a request point this at anything; the row's own listing is the only
|
|
83
|
+
// source, and only while the project is running.
|
|
84
|
+
assert.match(page, /entry\.status === 'running' \? \(entry\.mailpit \|\| ''\) : ''/);
|
|
85
|
+
assert.match(actions, /const base = await deps\.mailpitFor\(workspace, projectName\);/);
|
|
86
|
+
assert.ok(!/form\.base|form\.mailpit|form\.url/.test(actions), 'no URL is ever read from the form');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('the lookup awaits the status map rather than reading whatever is in memory', () => {
|
|
90
|
+
// Straight after a restart the cache is empty for a second or two, and a synchronous read answered
|
|
91
|
+
// "this project has no Mailpit — is it running?" about a project that was running perfectly well.
|
|
92
|
+
// Reproduced, then fixed by awaiting the dashboard's own getter, which refreshes a cold cache.
|
|
93
|
+
assert.match(page, /return async \(workspace, project\) => \{/);
|
|
94
|
+
assert.match(page, /const st = \(await statusMap\(\)\) \|\| \{\};/);
|
|
95
|
+
assert.match(server, /mailpitFor\(ddevStatusMap\)/, 'and the getter it is given is the refreshing one');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('the feature owns its wiring: server.js holds four lines and no markup', () => {
|
|
99
|
+
// One module per area, and "area" includes the route and the menu entry — not just the rendering.
|
|
100
|
+
// Otherwise every new column or verb is another edit to a 7,000-line file.
|
|
101
|
+
const mailLines = server.split('\n')
|
|
102
|
+
.filter((l) => !/^\s*(\/\/|\*|\/\*)/.test(l))
|
|
103
|
+
.filter((l) => /ddevMail|mailpit/i.test(l));
|
|
104
|
+
assert.ok(mailLines.length <= 8, `server.js wires mail on ${mailLines.length} lines:\n${mailLines.join('\n')}`);
|
|
105
|
+
assert.ok(!mailLines.some((l) => /<li>|<a |hx-get="\/fragments\/projects\/mail/.test(l)),
|
|
106
|
+
'no mail markup in server.js');
|
|
107
|
+
// Each of the four is a call into the module.
|
|
108
|
+
assert.match(server, /const ddevMail = require\('\.\/ddev-mail-page'\);/);
|
|
109
|
+
assert.match(server, /mailpit: ddevMail\.urlFromListing\(p\)/);
|
|
110
|
+
assert.match(server, /\$\{ddevMail\.menuItemHtml\(\{ workspace: key, project: p, running, mailpit \}\)\}/);
|
|
111
|
+
assert.match(server, /if \(await ddevMail\.handleFragment\(\{/);
|
|
112
|
+
// And the module answers "not mine" rather than the server matching the path for it.
|
|
113
|
+
assert.match(page, /if \(!m \|\| !isValidWorkspace\(m\[1\]\) \|\| !NAME_RE\.test\(m\[2\]\)\) return false;/);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('deleting and emptying are confirmed, and each answers with the panel', () => {
|
|
117
|
+
// A delete that leaves the list showing what it deleted is a control you have to refresh to
|
|
118
|
+
// believe — so every verb answers with the panel rather than a toast.
|
|
119
|
+
assert.match(actions, /if \(form\.confirm !== 'yes'\) return send\('<div class="msg error">Deleting a message needs confirmation/);
|
|
120
|
+
assert.match(actions, /Emptying the mailbox needs confirmation/);
|
|
121
|
+
assert.match(actions, /return send\(await panel\(workspace, c\.projectName, c\.base\)\);/);
|
|
122
|
+
assert.match(page, /hx-trigger="confirmed-remove"/);
|
|
123
|
+
assert.match(page, /hx-target="#editor-modal-body"/);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('the panel says what a message is, and leaves reading it to Mailpit', () => {
|
|
127
|
+
// Subject, who it was really for, when and how big — then out to Mailpit, which renders HTML mail
|
|
128
|
+
// and attachments far better than this panel would.
|
|
129
|
+
assert.match(page, /mail-subject/);
|
|
130
|
+
assert.match(page, /mail\.messageUrl\(base, m\.id\)/);
|
|
131
|
+
assert.match(page, /Open Mailpit/);
|
|
132
|
+
assert.match(page, /Nothing here has left\s*\n?\s*the machine/);
|
|
133
|
+
// An empty mailbox is stated as a fact worth having, not as a shrug.
|
|
134
|
+
assert.match(page, /The mailbox is empty/);
|
|
135
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const test = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const root = path.join(__dirname, '..', '..');
|
|
7
|
+
const read = (...p) => fs.readFileSync(path.join(root, ...p), 'utf8');
|
|
8
|
+
const ui = read('public', 'ui.js');
|
|
9
|
+
const css = read('public', 'css', 'components', 'ddev-menu.css');
|
|
10
|
+
const server = read('server.js');
|
|
11
|
+
|
|
12
|
+
test('the DDEV menu folds its groups, keeps the first and this project open, and is one column', () => {
|
|
13
|
+
assert.match(ui, /function foldDdevMenus\(root\)/);
|
|
14
|
+
assert.match(ui, /\/this project\/i\.test/);
|
|
15
|
+
assert.match(ui, /setAttribute\('aria-expanded'/);
|
|
16
|
+
assert.match(css, /\.act-menu-ddev \.ddev-nav-columns \{ column-count: 1; \}/);
|
|
17
|
+
assert.match(css, /width: 18rem/);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('a menu opened in the project list is drawn above the assistant and under the top bar', () => {
|
|
21
|
+
assert.match(css, /#webship-workspace-projects:has\(\.uk-dropdown\.uk-open\) \{ position: relative; z-index: 35; \}/);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('each workspace offers its create actions in its card, from the same entries as the rail', () => {
|
|
25
|
+
assert.match(server, /What would you like to create\?/);
|
|
26
|
+
assert.match(server, /pageActionGroups\(`workspace:\$\{key\}`\)\.find\(\(g\) => g\.id === 'create'\)/);
|
|
27
|
+
assert.doesNotMatch(read('public', 'css', 'components', 'create-card.css'), /#[0-9a-fA-F]{3,8}\b/);
|
|
28
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutagen beyond `sync`.
|
|
3
|
+
*
|
|
4
|
+
* The menu could ask for a sync and nothing else — so a project that felt slow or stale left you with
|
|
5
|
+
* no way to ask whether the sync was BEHIND or BROKEN, and no way to repair it.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { test } = require('node:test');
|
|
9
|
+
const assert = require('node:assert');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
const APP = path.join(__dirname, '../..');
|
|
14
|
+
const ddev = fs.readFileSync(path.join(APP, 'ddev.js'), 'utf8');
|
|
15
|
+
const actions = fs.readFileSync(path.join(APP, 'actions/ddev.js'), 'utf8');
|
|
16
|
+
const server = fs.readFileSync(path.join(APP, 'server.js'), 'utf8');
|
|
17
|
+
|
|
18
|
+
test('status reads, and is not gated', () => {
|
|
19
|
+
assert.match(ddev, /'\/actions\/ddev-mutagen-status':[^\n]*args: \['mutagen', 'status'\]/);
|
|
20
|
+
const line = ddev.match(/'\/actions\/ddev-mutagen-status':[^\n]*/)[0];
|
|
21
|
+
assert.ok(!line.includes('destructive'));
|
|
22
|
+
// It answers "Mutagen is not enabled on project X" too, which is worth knowing before anybody
|
|
23
|
+
// blames the sync for something else.
|
|
24
|
+
assert.match(actions, /'\/actions\/ddev-mutagen-status': ddev_verb,/);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('reset asks first, and says what it really does', () => {
|
|
28
|
+
// It STOPS THE PROJECT and removes the volume. That is not a thing to click while somebody is
|
|
29
|
+
// working, and "destroys data" would have been the wrong reason to give: the files on disk are the
|
|
30
|
+
// source, and the volume is rebuilt on the next start.
|
|
31
|
+
assert.match(ddev, /'\/actions\/ddev-mutagen-reset':[^\n]*destructive: true/);
|
|
32
|
+
assert.match(ddev, /confirmWhy: 'stops the project and removes its sync volume'/);
|
|
33
|
+
assert.match(server, /Reset the sync…/);
|
|
34
|
+
assert.match(server, /the volume is rebuilt on the next start/);
|
|
35
|
+
const item = server.slice(server.indexOf('/actions/ddev-mutagen-reset'));
|
|
36
|
+
assert.match(item.slice(0, 300), /hx-trigger="confirmed-remove"/);
|
|
37
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const test = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
|
|
4
|
+
const o = require('../../ddev-options');
|
|
5
|
+
|
|
6
|
+
test('every project type in the DDEV docs is offered, and nothing else', () => {
|
|
7
|
+
const docs = ['asterios', 'backdrop', 'cakephp', 'codeigniter', 'craftcms', 'drupal', 'drupal6', 'drupal7', 'drupal8', 'drupal9',
|
|
8
|
+
'drupal10', 'drupal11', 'drupal12', 'generic', 'joomla', 'laravel', 'magento', 'magento2', 'maho', 'modx', 'php',
|
|
9
|
+
'shopware6', 'silverstripe', 'symfony', 'typo3', 'wordpress', 'wp-bedrock'];
|
|
10
|
+
assert.deepEqual([...o.PROJECT_TYPES].sort(), [...docs].sort());
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('PHP 5.6 to 8.5, and the database versions the docs list', () => {
|
|
14
|
+
assert.ok(o.PHP_VERSIONS.includes('5.6') && o.PHP_VERSIONS.includes('8.5'));
|
|
15
|
+
for (const db of ['mariadb:11.8', 'mariadb:12.3', 'mariadb:10.11', 'mysql:8.4', 'mysql:9.7', 'postgres:9', 'postgres:18']) {
|
|
16
|
+
assert.ok(o.DATABASES.includes(db), db);
|
|
17
|
+
}
|
|
18
|
+
assert.ok(!o.DATABASES.includes('mariadb:11.0'));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('a form becomes checked flags, and anything off the lists is dropped', () => {
|
|
22
|
+
assert.deepEqual(o.configArgs({ project_type: 'typo3', docroot: 'public', php_version: '8.3', database: 'postgres:16',
|
|
23
|
+
webserver_type: 'apache-fpm', nodejs_version: 'auto', composer_version: '2.2', omit_db: '1', python: '1' }), [
|
|
24
|
+
'--project-type=typo3', '--docroot=public', '--php-version=8.3', '--database=postgres:16', '--webserver-type=apache-fpm',
|
|
25
|
+
'--nodejs-version=auto', '--composer-version=2.2', '--omit-containers=db', '--webimage-extra-packages=python3-pip,python3-venv']);
|
|
26
|
+
assert.deepEqual(o.configArgs({ project_type: 'x; rm -rf', docroot: '../etc', php_version: '__default', database: 'oracle:1' }), []);
|
|
27
|
+
assert.equal(o.shellArgs(["--docroot=it's"]), "'--docroot=it'\\''s'");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('the quickstart presets only use values DDEV accepts', () => {
|
|
31
|
+
for (const p of o.PRESETS) {
|
|
32
|
+
assert.ok(o.PROJECT_TYPES.includes(p.project_type), p.id);
|
|
33
|
+
if (p.php_version) assert.ok(o.PHP_VERSIONS.includes(p.php_version), p.id);
|
|
34
|
+
if (p.database) assert.ok(o.DATABASES.includes(p.database), p.id);
|
|
35
|
+
if (p.webserver_type) assert.ok(o.WEBSERVERS.includes(p.webserver_type), p.id);
|
|
36
|
+
}
|
|
37
|
+
for (const id of ['typo3', 'laravel', 'symfony', 'craftcms', 'wagtail', 'nodejs', 'shopware6', 'joomla']) {
|
|
38
|
+
assert.ok(o.PRESETS.some((p) => p.id === id), id);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('the fields start on DDEV defaults, or on what the project already uses', () => {
|
|
43
|
+
const fresh = o.fieldsHtml({ prefix: 't' });
|
|
44
|
+
assert.match(fresh, /data-ddev-preset/);
|
|
45
|
+
assert.match(fresh, /<option value="php" selected>php<\/option>/);
|
|
46
|
+
assert.match(fresh, /name="php_version"><option value="__default" selected>/);
|
|
47
|
+
const now = o.fieldsHtml({ prefix: 't', current: { project_type: 'drupal11', php_version: '8.3', omit_db: true } });
|
|
48
|
+
assert.match(now, /<option value="drupal11" selected>/);
|
|
49
|
+
assert.match(now, /<option value="8.3" selected>/);
|
|
50
|
+
assert.match(now, /name="omit_db" value="1" checked/);
|
|
51
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publishing a project on the internet, on purpose.
|
|
3
|
+
*
|
|
4
|
+
* `ddev share` opens a tunnel (ngrok by default) to a local project so somebody else can look at it.
|
|
5
|
+
* It is the only verb on this menu whose effect reaches people who are not on this machine — the
|
|
6
|
+
* whole site, including its admin, at an address anyone with it can use — and it runs until it is
|
|
7
|
+
* stopped rather than finishing.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { test } = require('node:test');
|
|
11
|
+
const assert = require('node:assert');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
const APP = path.join(__dirname, '../..');
|
|
16
|
+
const ddev = fs.readFileSync(path.join(APP, 'ddev.js'), 'utf8');
|
|
17
|
+
const actions = fs.readFileSync(path.join(APP, 'actions/ddev.js'), 'utf8');
|
|
18
|
+
const server = fs.readFileSync(path.join(APP, 'server.js'), 'utf8');
|
|
19
|
+
|
|
20
|
+
test('it asks first, and for the right reason', () => {
|
|
21
|
+
assert.match(ddev, /'\/actions\/ddev-share':[^\n]*destructive: true/);
|
|
22
|
+
assert.match(ddev, /confirmWhy: 'puts this project on the open internet'/);
|
|
23
|
+
// The gate used to say "destroys data" for everything behind it. Share destroys nothing, and a
|
|
24
|
+
// gate that misstates why it is a gate teaches people to click through it.
|
|
25
|
+
assert.match(actions, /\$\{esc\(act\.confirmWhy \|\| 'destroys data'\)\}/);
|
|
26
|
+
assert.match(server, /hx-trigger="confirmed-remove"[\s\S]{0,400}Share on the internet/);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('it runs in a screen, because it does not finish', () => {
|
|
30
|
+
// The address is printed in that session, and closing a browser tab must not take somebody's demo
|
|
31
|
+
// down with it.
|
|
32
|
+
assert.match(ddev, /'\/actions\/ddev-share':[^\n]*inScreen: true/);
|
|
33
|
+
assert.match(ddev, /'\/actions\/ddev-share':[^\n]*needsRunning: true/, 'a tunnel to a stopped project shows nothing');
|
|
34
|
+
assert.match(actions, /'\/actions\/ddev-share': ddev_verb,/);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('the item says what it exposes', () => {
|
|
38
|
+
assert.match(server, /Anyone with the address can reach the site, including its admin/);
|
|
39
|
+
assert.match(server, /until the session is stopped/);
|
|
40
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const { test } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execFileSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const APP = path.join(__dirname, '..', '..');
|
|
8
|
+
const SOURCE = path.join(APP, '..', 'scripts', 'ddev-shim', 'ddev');
|
|
9
|
+
const IN_CONTEXT = path.join(APP, '.ddev', 'web-build', 'ddev-shim-ddev');
|
|
10
|
+
|
|
11
|
+
test('the build-context copy is byte-identical to the source', () => {
|
|
12
|
+
// Docker cannot COPY from outside its build context, so the shim exists twice. This is
|
|
13
|
+
// the only thing stopping them drifting: edit core/scripts/ddev-shim/ddev, then
|
|
14
|
+
// cp core/scripts/ddev-shim/ddev core/workspace-app/.ddev/web-build/ddev-shim-ddev
|
|
15
|
+
assert.equal(fs.readFileSync(IN_CONTEXT, 'utf8'), fs.readFileSync(SOURCE, 'utf8'),
|
|
16
|
+
'the shim in .ddev/web-build differs from core/scripts/ddev-shim/ddev — copy it across');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('the shim is valid bash', () => {
|
|
20
|
+
// A broken shim breaks EVERY ddev call in the container, the dashboard's own status
|
|
21
|
+
// reads included, and it is not exercised by any other test.
|
|
22
|
+
execFileSync('bash', ['-n', SOURCE]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('it never resolves the real ddev through PATH', () => {
|
|
26
|
+
// Resolving by PATH would find the shim again and fork until the machine gives up.
|
|
27
|
+
const src = fs.readFileSync(SOURCE, 'utf8');
|
|
28
|
+
assert.ok(!/command -v ddev|which ddev/.test(src), 'resolves ddev through PATH — that recurses');
|
|
29
|
+
assert.match(src, /for candidate in \/usr\/local\/bin\/ddev/, 'no absolute-path candidate list');
|
|
30
|
+
assert.match(src, /== "\$\{SELF\}" \]\] |== "\$\{SELF\}" \] && continue/, 'does not exclude itself from the candidates');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('exactly the router-mutating verbs are locked', () => {
|
|
34
|
+
const src = fs.readFileSync(SOURCE, 'utf8');
|
|
35
|
+
assert.match(src, /^\s*start\|stop\|restart\|poweroff\|delete\)/m,
|
|
36
|
+
'the locked verb list changed — every one of these recreates the shared router');
|
|
37
|
+
// Everything else must exec straight through: `ddev list --json-output` runs every 20s.
|
|
38
|
+
assert.match(src, /\*\)\s+exec "\$\{REAL_DDEV\}" "\$@" ;;/);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('a lock already held is not taken again', () => {
|
|
42
|
+
// The dashboard wraps its own calls in flock; re-locking from inside deadlocks for the
|
|
43
|
+
// full timeout. Verified by experiment before this interlock existed.
|
|
44
|
+
const src = fs.readFileSync(SOURCE, 'utf8');
|
|
45
|
+
assert.match(src, /DDEV_ROUTER_LOCK_HELD/);
|
|
46
|
+
const ddevjs = fs.readFileSync(path.join(APP, 'ddev.js'), 'utf8');
|
|
47
|
+
assert.ok(ddevjs.includes('env DDEV_ROUTER_LOCK_HELD=1'),
|
|
48
|
+
'the dashboard no longer announces that it holds the lock — its flock will deadlock the shim');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('nothing about a missing flock or lock file is fatal', () => {
|
|
52
|
+
// Refusing to run ddev because a lock could not be taken is a worse failure than the
|
|
53
|
+
// one being prevented.
|
|
54
|
+
const src = fs.readFileSync(SOURCE, 'utf8');
|
|
55
|
+
const fallbacks = (src.match(/exec "\$\{REAL_DDEV\}" "\$@"/g) || []).length;
|
|
56
|
+
assert.ok(fallbacks >= 4, `expected pass-through fallbacks, found ${fallbacks}`);
|
|
57
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database snapshots: taking one was all the row could do.
|
|
3
|
+
*
|
|
4
|
+
* There was a "Take a snapshot" item and nothing else — no list, no restore, no way to remove one. A
|
|
5
|
+
* backup nobody can restore is a file, and restoring is the whole point of a snapshot: it is a
|
|
6
|
+
* physical copy of the database's own files, so it goes back in seconds where a SQL dump is replayed
|
|
7
|
+
* statement by statement. Measured through the dashboard on a real project: restored in 41s.
|
|
8
|
+
*
|
|
9
|
+
* Two facts shape the whole feature, both from DDEV's own behaviour:
|
|
10
|
+
* - a snapshot belongs to a database engine AND version, and will not restore into another;
|
|
11
|
+
* - restoring REPLACES the database — no merge, no undo.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { test } = require('node:test');
|
|
15
|
+
const assert = require('node:assert');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const snaps = require('../../ddev-snapshots');
|
|
21
|
+
|
|
22
|
+
const APP = path.join(__dirname, '../..');
|
|
23
|
+
const server = fs.readFileSync(path.join(APP, 'server.js'), 'utf8');
|
|
24
|
+
const actions = fs.readFileSync(path.join(APP, 'actions/ddev.js'), 'utf8');
|
|
25
|
+
const ddev = fs.readFileSync(path.join(APP, 'ddev.js'), 'utf8');
|
|
26
|
+
|
|
27
|
+
/** A project with snapshots of the shape DDEV writes: `<name>-<engine>_<version>.<ext>`. */
|
|
28
|
+
function project({ dbType = 'mariadb', dbVersion = '11.8' } = {}) {
|
|
29
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-snap-'));
|
|
30
|
+
fs.mkdirSync(path.join(dir, '.ddev', 'db_snapshots'), { recursive: true });
|
|
31
|
+
fs.writeFileSync(path.join(dir, '.ddev', 'config.yaml'),
|
|
32
|
+
`name: demo\ntype: drupal\ndatabase:\n type: ${dbType}\n version: "${dbVersion}"\nwebserver_type: nginx-fpm\n`);
|
|
33
|
+
return dir;
|
|
34
|
+
}
|
|
35
|
+
const put = (dir, file, bytes = 10) =>
|
|
36
|
+
fs.writeFileSync(path.join(dir, '.ddev', 'db_snapshots', file), Buffer.alloc(bytes));
|
|
37
|
+
|
|
38
|
+
test('a snapshot is read off disk, engine and version and all', () => {
|
|
39
|
+
// From a real one taken on a real project: `zz-claude-probe-mariadb_11.8.zst`.
|
|
40
|
+
const dir = project();
|
|
41
|
+
put(dir, 'before-updb-mariadb_11.8.zst', 1024);
|
|
42
|
+
const [one] = snaps.listSnapshots(dir);
|
|
43
|
+
assert.equal(one.name, 'before-updb');
|
|
44
|
+
assert.equal(one.dbVersion, 'mariadb_11.8');
|
|
45
|
+
assert.equal(one.compression, 'zstd');
|
|
46
|
+
assert.equal(one.size, 1024);
|
|
47
|
+
assert.ok(one.mtime instanceof Date);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('a name with dashes in it survives the parse', () => {
|
|
51
|
+
// The engine and version are the LAST two pieces, so a name that has its own dashes — which is most
|
|
52
|
+
// of them — must not be cut at the first one.
|
|
53
|
+
const dir = project();
|
|
54
|
+
put(dir, 'before-the-2026-09-22-config-import-mariadb_11.8.zst');
|
|
55
|
+
assert.equal(snaps.listSnapshots(dir)[0].name, 'before-the-2026-09-22-config-import');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('an uncompressed snapshot is a folder, and it is still a snapshot', () => {
|
|
59
|
+
const dir = project();
|
|
60
|
+
const folder = path.join(dir, '.ddev', 'db_snapshots', 'plain-mariadb_11.8');
|
|
61
|
+
fs.mkdirSync(folder);
|
|
62
|
+
fs.writeFileSync(path.join(folder, 'ibdata1'), Buffer.alloc(2048));
|
|
63
|
+
const [one] = snaps.listSnapshots(dir);
|
|
64
|
+
assert.equal(one.name, 'plain');
|
|
65
|
+
assert.equal(one.compression, 'none');
|
|
66
|
+
assert.equal(one.size, 2048, 'its size is what is inside it');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('newest first, and nothing that is not a snapshot', () => {
|
|
70
|
+
const dir = project();
|
|
71
|
+
put(dir, 'old-mariadb_11.8.zst');
|
|
72
|
+
put(dir, 'new-mariadb_11.8.zst');
|
|
73
|
+
fs.utimesSync(path.join(dir, '.ddev', 'db_snapshots', 'old-mariadb_11.8.zst'), new Date(0), new Date(0));
|
|
74
|
+
put(dir, 'README.txt');
|
|
75
|
+
put(dir, '.keep');
|
|
76
|
+
assert.deepEqual(snaps.listSnapshots(dir).map((s) => s.name), ['new', 'old']);
|
|
77
|
+
assert.deepEqual(snaps.listSnapshots(project()).map((s) => s.name), [], 'a project with none says none');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('a snapshot from another engine or version cannot restore here, and says both', () => {
|
|
81
|
+
// DDEV refuses it; the row says so first, because its refusal otherwise arrives inside a screen
|
|
82
|
+
// session somebody has to open to read.
|
|
83
|
+
const dir = project({ dbVersion: '11.8' });
|
|
84
|
+
put(dir, 'ours-mariadb_11.8.zst');
|
|
85
|
+
put(dir, 'theirs-mariadb_10.11.zst');
|
|
86
|
+
put(dir, 'postgres-postgres_16.zst');
|
|
87
|
+
const version = snaps.projectDbVersion(dir);
|
|
88
|
+
assert.equal(version, 'mariadb_11.8');
|
|
89
|
+
assert.equal(snaps.restorable(snaps.findSnapshot(dir, 'ours'), version).ok, true);
|
|
90
|
+
const no = snaps.restorable(snaps.findSnapshot(dir, 'theirs'), version);
|
|
91
|
+
assert.equal(no.ok, false);
|
|
92
|
+
assert.match(no.why, /mariadb_10\.11/);
|
|
93
|
+
assert.match(no.why, /mariadb_11\.8/, 'and what the project actually runs');
|
|
94
|
+
assert.equal(snaps.restorable(snaps.findSnapshot(dir, 'postgres'), version).ok, false);
|
|
95
|
+
// An unknown project version is not a reason to refuse: DDEV is the judge then.
|
|
96
|
+
assert.equal(snaps.restorable(snaps.findSnapshot(dir, 'theirs'), '').ok, true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('a name from the browser is checked against what is on disk', () => {
|
|
100
|
+
const dir = project();
|
|
101
|
+
put(dir, 'real-mariadb_11.8.zst');
|
|
102
|
+
assert.ok(snaps.findSnapshot(dir, 'real'));
|
|
103
|
+
assert.equal(snaps.findSnapshot(dir, 'invented'), null);
|
|
104
|
+
// And the pattern refuses a path before the lookup ever runs.
|
|
105
|
+
for (const bad of ['../../etc/passwd', 'a/b', '.hidden', '']) {
|
|
106
|
+
assert.equal(snaps.SNAPSHOT_NAME_RE.test(bad), false, bad);
|
|
107
|
+
}
|
|
108
|
+
for (const good of ['before-updb', 'v11.8_check', 'X']) assert.ok(snaps.SNAPSHOT_NAME_RE.test(good), good);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('restore takes the name positionally; taking and cleaning take --name', () => {
|
|
112
|
+
// Given `--name`, `ddev snapshot restore` prints its help and the screen log reads like a crash.
|
|
113
|
+
// Found by running it.
|
|
114
|
+
const named = actions.slice(actions.indexOf('if (act.named)'), actions.indexOf('const removed', actions.indexOf('if (act.named)')) || undefined);
|
|
115
|
+
assert.match(named, /args = \[\.\.\.args, raw\];/);
|
|
116
|
+
assert.match(named, /args = \[\.\.\.args, '--name', raw\];/);
|
|
117
|
+
assert.match(ddev, /'\/actions\/ddev-snapshot-restore':[^\n]*args: \['snapshot', 'restore'\]/);
|
|
118
|
+
assert.match(ddev, /'\/actions\/ddev-snapshot-delete':[^\n]*args: \['snapshot', '--cleanup', '-y'\]/);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('restoring and deleting are destructive, and are treated as such', () => {
|
|
122
|
+
// The guard is in the handler, not only in the markup: the dashboard is not the only door.
|
|
123
|
+
assert.match(ddev, /'\/actions\/ddev-snapshot-restore':[^\n]*destructive: true/);
|
|
124
|
+
assert.match(ddev, /'\/actions\/ddev-snapshot-delete':[^\n]*destructive: true/);
|
|
125
|
+
// A restore of a large database takes minutes, so it runs in a screen and can be watched.
|
|
126
|
+
assert.match(ddev, /'\/actions\/ddev-snapshot-restore':[^\n]*inScreen: true/);
|
|
127
|
+
// And the buttons go through the confirm dialog. The rows are built above the heading, so the
|
|
128
|
+
// slice starts where the fragment does.
|
|
129
|
+
const frag = server.slice(server.indexOf("const sn = pathname.match(/^\\/fragments\\/projects\\/snapshots"));
|
|
130
|
+
const rows = frag.slice(0, frag.indexOf('Snapshots of'));
|
|
131
|
+
assert.equal((rows.match(/hx-trigger="confirmed-remove"/g) || []).length, 2, 'restore and delete both ask');
|
|
132
|
+
assert.match(rows, /arm-step/);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('the modal says what a snapshot is, and what restoring costs', () => {
|
|
136
|
+
const modal = server.slice(server.indexOf('<h3 class="uk-margin-small-bottom">Snapshots of'), server.indexOf('} }', server.indexOf('Snapshots of')));
|
|
137
|
+
assert.match(modal, /physical copy of the database's own files/);
|
|
138
|
+
assert.match(modal, /Restoring <strong>replaces<\/strong>/);
|
|
139
|
+
assert.match(modal, /will not restore into it/, 'the engine-and-version rule, before it bites');
|
|
140
|
+
assert.match(modal, /\.ddev\/db_snapshots\//, 'and where they live');
|
|
141
|
+
assert.match(modal, /Taking one of a stopped\n project starts it first/, 'observed, and worth not springing on anyone');
|
|
142
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A start that says it worked, and a dashboard that answers 502.
|
|
3
|
+
*
|
|
4
|
+
* DDEV starts `web_extra_daemons` itself at the end of `ddev start`, and that step races supervisord
|
|
5
|
+
* coming up. When it loses, ddev prints
|
|
6
|
+
*
|
|
7
|
+
* Unable to start web_extra_daemons using supervisorctl,
|
|
8
|
+
* stdout=error: <class 'http.client.RemoteDisconnected'>, Remote end closed connection …
|
|
9
|
+
*
|
|
10
|
+
* and then reports **Successfully started workspace** anyway. The daemons are left "Not started", so
|
|
11
|
+
* nginx answers **502 Bad Gateway**: the proxy is up and the app behind it never came up.
|
|
12
|
+
*
|
|
13
|
+
* That is the worst shape a failure can take. The command said it worked, so nothing points at the
|
|
14
|
+
* start; the browser says 502, which reads as "the dashboard is broken". Reproduced on this machine
|
|
15
|
+
* by stopping the daemons and running `ddev start`: the warning appeared, the start "succeeded", and
|
|
16
|
+
* the site stayed 502 until the daemons were started by hand.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const { test } = require('node:test');
|
|
20
|
+
const assert = require('node:assert');
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
const config = fs.readFileSync(path.join(__dirname, '../../.ddev/config.yaml'), 'utf8');
|
|
25
|
+
|
|
26
|
+
test('every start ends by making sure the daemons are actually up', () => {
|
|
27
|
+
const post = config.slice(config.indexOf('post-start:'), config.indexOf('\ndatabase:'));
|
|
28
|
+
assert.match(post, /supervisorctl status webextradaemons:\*/);
|
|
29
|
+
assert.match(post, /xargs -r supervisorctl start/);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('it asks which are down rather than starting everything', () => {
|
|
33
|
+
// A healthy start must do nothing here: restarting a daemon that is already serving would drop
|
|
34
|
+
// whatever it was doing — and this runs on every single `ddev start`.
|
|
35
|
+
const post = config.slice(config.indexOf('post-start:'), config.indexOf('\ndatabase:'));
|
|
36
|
+
assert.match(post, /grep -v RUNNING/);
|
|
37
|
+
assert.ok(!/supervisorctl restart/.test(post), 'nothing is restarted, only started');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('it covers every daemon, not just the dashboard', () => {
|
|
41
|
+
// The Web IDE loses the same race, and a dashboard that comes back while the IDE stays down is
|
|
42
|
+
// half a fix that nobody would think to check.
|
|
43
|
+
const post = config.slice(config.indexOf('post-start:'), config.indexOf('\ndatabase:'));
|
|
44
|
+
assert.match(post, /webextradaemons:\*/);
|
|
45
|
+
assert.ok(!/webextradaemons:workspace-app\b(?!\*)/.test(post.replace(/#.*/g, '')),
|
|
46
|
+
'not scoped to one daemon by name');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('a daemon that will not start does not fail the project start', () => {
|
|
50
|
+
// The 502 says the rest. A `ddev start` that exits non-zero because one daemon is unhappy would
|
|
51
|
+
// stop the other projects in the autostart chain behind it.
|
|
52
|
+
const post = config.slice(config.indexOf('post-start:'), config.indexOf('\ndatabase:'));
|
|
53
|
+
assert.match(post, /\|\| true/);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('the reason is recorded where the line is', () => {
|
|
57
|
+
// Whoever removes this line as "belt and braces" should see, in the same place, that it is the
|
|
58
|
+
// difference between a working dashboard and a 502 after a reboot.
|
|
59
|
+
const post = config.slice(config.indexOf('post-start:'), config.indexOf('\ndatabase:'));
|
|
60
|
+
assert.match(post, /RemoteDisconnected/);
|
|
61
|
+
assert.match(post, /502 Bad Gateway/);
|
|
62
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Step debugging, on and off, from the row.
|
|
3
|
+
*
|
|
4
|
+
* `ddev xdebug on` is a toggle inside the web container — no config change, no restart — which is
|
|
5
|
+
* exactly why it belongs on a menu rather than in somebody's shell history. And Xdebug costs about
|
|
6
|
+
* 3x the PHP runtime while it is on, so turning it OFF is the half people forget.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { test } = require('node:test');
|
|
10
|
+
const assert = require('node:assert');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const APP = path.join(__dirname, '../..');
|
|
15
|
+
const ddev = fs.readFileSync(path.join(APP, 'ddev.js'), 'utf8');
|
|
16
|
+
const actions = fs.readFileSync(path.join(APP, 'actions/ddev.js'), 'utf8');
|
|
17
|
+
const server = fs.readFileSync(path.join(APP, 'server.js'), 'utf8');
|
|
18
|
+
|
|
19
|
+
test('all three verbs are the CLI\'s own', () => {
|
|
20
|
+
// `ddev xdebug on|off|status`, as `ddev xdebug --help` documents them.
|
|
21
|
+
assert.match(ddev, /'\/actions\/ddev-xdebug-on':[^\n]*args: \['xdebug', 'on'\]/);
|
|
22
|
+
assert.match(ddev, /'\/actions\/ddev-xdebug-off':[^\n]*args: \['xdebug', 'off'\]/);
|
|
23
|
+
assert.match(ddev, /'\/actions\/ddev-xdebug-status':[^\n]*args: \['xdebug', 'status'\]/);
|
|
24
|
+
for (const a of ['on', 'off', 'status']) {
|
|
25
|
+
assert.match(actions, new RegExp(`'/actions/ddev-xdebug-${a}': ddev_verb,`), a);
|
|
26
|
+
// The container has to be up for a toggle inside it to mean anything.
|
|
27
|
+
assert.match(ddev, new RegExp(`'/actions/ddev-xdebug-${a}':[^\\n]*needsRunning: true`), a);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('the menu offers both states rather than a toggle', () => {
|
|
32
|
+
// `ddev describe --json-output` carries no xdebug field — checked — so a row cannot show the
|
|
33
|
+
// current state without a subprocess of its own. Both are offered, and asking is a click.
|
|
34
|
+
const menu = server.slice(server.indexOf('<li class="uk-nav-header">debugging</li>'));
|
|
35
|
+
assert.match(menu.slice(0, 1200), /Xdebug on/);
|
|
36
|
+
assert.match(menu.slice(0, 1200), /Xdebug off/);
|
|
37
|
+
assert.match(menu.slice(0, 1200), /Which is it now\?/);
|
|
38
|
+
assert.ok(!/xdebug-toggle/.test(server), 'no toggle, because the state is not known');
|
|
39
|
+
// And the cost of leaving it on is on the item itself, not in a doc nobody opens.
|
|
40
|
+
assert.match(menu.slice(0, 1200), /3× slower/);
|
|
41
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The assistant's route is an action like any other: a request from another site is refused before anything reads its
|
|
3
|
+
* body, and only JSON is taken. It used to be handled above the same-origin check and return before reaching it, and
|
|
4
|
+
* the assistant it starts can use Bash — so a page on any site could hand it a prompt.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const { test } = require('node:test');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const src = fs.readFileSync(path.join(__dirname, '..', '..', 'server.js'), 'utf8');
|
|
13
|
+
|
|
14
|
+
test('the assistant route refuses another site before it reads the body, and takes only JSON', () => {
|
|
15
|
+
const at = src.indexOf("pathname === '/actions/deep-chat'");
|
|
16
|
+
assert.ok(at > 0, 'no assistant route');
|
|
17
|
+
const beforeBody = src.slice(at, src.indexOf("req.on('data'", at));
|
|
18
|
+
assert.match(beforeBody, /if \(foreignOrigin\(req\)\) \{[\s\S]*?writeHead\(403/);
|
|
19
|
+
assert.match(beforeBody, /application\\\/json[\s\S]*?writeHead\(415/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('foreignOrigin: another host is foreign; the same host, and no Origin at all, are not', () => {
|
|
23
|
+
const start = src.indexOf('function foreignOrigin(req) {');
|
|
24
|
+
const fn = src.slice(start, src.indexOf('\n}\n', start) + 2);
|
|
25
|
+
const foreignOrigin = new Function(`${fn}; return foreignOrigin;`)();
|
|
26
|
+
const req = (origin, host = 'workspace.ddev.site') => ({ headers: { origin, host } });
|
|
27
|
+
assert.strictEqual(foreignOrigin(req('https://evil.example')), true);
|
|
28
|
+
assert.strictEqual(foreignOrigin(req('https://workspace.ddev.site.evil.example')), true);
|
|
29
|
+
assert.strictEqual(foreignOrigin(req('not a url')), true);
|
|
30
|
+
assert.strictEqual(foreignOrigin(req('https://workspace.ddev.site')), false);
|
|
31
|
+
assert.strictEqual(foreignOrigin(req(undefined)), false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('every other action keeps the same check', () => {
|
|
35
|
+
const at = src.indexOf("req.method === 'POST' && pathname.startsWith('/actions/')");
|
|
36
|
+
assert.ok(at > 0);
|
|
37
|
+
assert.match(src.slice(at, at + 900), /if \(foreignOrigin\(req\)\) \{/);
|
|
38
|
+
});
|