@webship.co/workspace 1.0.0-beta1 → 1.0.0-rc1
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/{PLUGINS.md → core/workspace-app/PLUGINS.md} +9 -1
- 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 +40 -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 +41 -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 +157 -0
- package/core/workspace-app/tests/step-definitions/clone-project.steps.js +47 -0
- package/core/workspace-app/tests/step-definitions/preconditions.steps.js +168 -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 +223 -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 +179 -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/preconditions.test.js +21 -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 +93 -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 +220 -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 +436 -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/{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,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sign in with Google for the dashboard: who gets in, what the cookie proves, and what a request meets
|
|
3
|
+
* when it has none. Google is a local stand-in; the module's own handler is wrapped in a bare http server,
|
|
4
|
+
* so what is tested is the gate, not the rest of the dashboard.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const { test } = require('node:test');
|
|
9
|
+
const http = require('http');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const CONFIG = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-auth-'));
|
|
15
|
+
process.env.WORKSPACE_CONFIG = CONFIG;
|
|
16
|
+
process.env.WORKSPACE_AUTH_DIR = path.join(CONFIG, '.auth');
|
|
17
|
+
const HUB = 'hub.example.test';
|
|
18
|
+
|
|
19
|
+
// The OAuth client lives with the Google Drive plugin, in its own settings file.
|
|
20
|
+
fs.writeFileSync(path.join(CONFIG, 'plugin.google-drive-sync.settings.yml'), 'client_id: cid.apps.googleusercontent.com\nclient_secret: csecret1234\n');
|
|
21
|
+
const setSettings = (extra = '') => fs.writeFileSync(path.join(CONFIG, 'settings.yml'), [`hub_domain: ${HUB}`, extra].join('\n'));
|
|
22
|
+
const signinOn = (more = '') => setSettings(`auth:\n google:\n enabled: true\n allowed_domains:\n - example.com\n${more}`);
|
|
23
|
+
|
|
24
|
+
const auth = require('../../auth-google');
|
|
25
|
+
|
|
26
|
+
const b64 = (o) => Buffer.from(JSON.stringify(o)).toString('base64url');
|
|
27
|
+
const idToken = (claims) => `h.${b64(claims)}.s`;
|
|
28
|
+
|
|
29
|
+
function appServer() {
|
|
30
|
+
const server = http.createServer(async (req, res) => {
|
|
31
|
+
const { pathname } = new URL(req.url, 'http://x');
|
|
32
|
+
if (await auth.gate(req, res, pathname)) return;
|
|
33
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
34
|
+
res.end('the dashboard');
|
|
35
|
+
});
|
|
36
|
+
return new Promise((r) => server.listen(0, '127.0.0.1', () => r(server)));
|
|
37
|
+
}
|
|
38
|
+
const get = (server, p, headers = {}) => new Promise((resolve, reject) => {
|
|
39
|
+
http.get({ host: '127.0.0.1', port: server.address().port, path: p, headers: { host: `dev.${HUB}`, ...headers } }, (res) => {
|
|
40
|
+
let body = ''; res.on('data', (d) => { body += d; }); res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body }));
|
|
41
|
+
}).on('error', reject);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('with sign-in off the dashboard is open, as it always was', async () => {
|
|
45
|
+
setSettings();
|
|
46
|
+
const s = await appServer();
|
|
47
|
+
try {
|
|
48
|
+
assert.strictEqual((await get(s, '/')).body, 'the dashboard');
|
|
49
|
+
assert.deepStrictEqual(JSON.parse((await get(s, '/auth/me')).body), { enabled: false });
|
|
50
|
+
} finally { s.close(); }
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('with sign-in on, a page goes to sign in, an htmx call is told to, and an action gets 401', async () => {
|
|
54
|
+
signinOn();
|
|
55
|
+
const s = await appServer();
|
|
56
|
+
try {
|
|
57
|
+
const page = await get(s, '/dev');
|
|
58
|
+
assert.strictEqual(page.status, 302);
|
|
59
|
+
assert.match(page.headers.location, new RegExp(`^https://${HUB}/auth/signin\\?next=`));
|
|
60
|
+
assert.match(decodeURIComponent(page.headers.location), new RegExp(`next=https://dev\\.${HUB}/dev$`));
|
|
61
|
+
const htmx = await get(s, '/fragments/anything', { 'hx-request': 'true' });
|
|
62
|
+
assert.strictEqual(htmx.status, 401);
|
|
63
|
+
assert.match(htmx.headers['hx-redirect'], /\/auth\/signin/);
|
|
64
|
+
const post = await new Promise((resolve) => { const r = http.request({ host: '127.0.0.1', port: s.address().port, path: '/actions/build', method: 'POST', headers: { host: `dev.${HUB}` } }, (res) => { res.resume(); resolve(res.statusCode); }); r.end(); });
|
|
65
|
+
assert.strictEqual(post, 401);
|
|
66
|
+
} finally { s.close(); }
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('it fails closed: on with no client, or with nobody allowed, it lets nobody in and says why', async () => {
|
|
70
|
+
// No client: the Drive plugin's file is gone for this case, and put back after.
|
|
71
|
+
const pluginFile = path.join(CONFIG, 'plugin.google-drive-sync.settings.yml');
|
|
72
|
+
const client = fs.readFileSync(pluginFile, 'utf8');
|
|
73
|
+
fs.unlinkSync(pluginFile);
|
|
74
|
+
fs.writeFileSync(path.join(CONFIG, 'settings.yml'), `hub_domain: ${HUB}\nauth:\n google:\n enabled: true\n allowed_domains:\n - example.com\n`);
|
|
75
|
+
let s = await appServer();
|
|
76
|
+
try {
|
|
77
|
+
const r = await get(s, '/dev');
|
|
78
|
+
assert.strictEqual(r.status, 503);
|
|
79
|
+
assert.match(r.body, /letting nobody in/);
|
|
80
|
+
} finally { s.close(); fs.writeFileSync(pluginFile, client); }
|
|
81
|
+
setSettings('auth:\n google:\n enabled: true');
|
|
82
|
+
s = await appServer();
|
|
83
|
+
try { assert.strictEqual((await get(s, '/dev')).status, 503); } finally { s.close(); }
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('the cookie proves who you are, and a forged, edited or expired one proves nothing', () => {
|
|
87
|
+
signinOn();
|
|
88
|
+
const good = auth.makeSession({ email: 'a@example.com', name: 'A' }, 1);
|
|
89
|
+
const req = (c) => ({ headers: { cookie: `x=1; ws-session=${c}` } });
|
|
90
|
+
assert.deepStrictEqual(auth.sessionOf(req(good)), { email: 'a@example.com', name: 'A' });
|
|
91
|
+
const [body, mac] = good.split('.');
|
|
92
|
+
const edited = `${b64({ ...JSON.parse(Buffer.from(body, 'base64url')), e: 'boss@example.com' })}.${mac}`;
|
|
93
|
+
assert.strictEqual(auth.sessionOf(req(edited)), null, 'editing the email breaks the signature');
|
|
94
|
+
assert.strictEqual(auth.sessionOf(req(`${body}.${'A'.repeat(mac.length)}`)), null);
|
|
95
|
+
assert.strictEqual(auth.sessionOf(req(auth.makeSession({ email: 'a@example.com' }, -1))), null, 'expired');
|
|
96
|
+
assert.strictEqual(auth.sessionOf({ headers: {} }), null);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('who is allowed: a verified address on an allowed Workspace domain, or one named address', () => {
|
|
100
|
+
const cfg = { domains: ['example.com'], emails: ['guest@example.org'] };
|
|
101
|
+
const ok = (c) => auth.allowed(c, cfg).ok;
|
|
102
|
+
assert.ok(ok({ email: 'a@example.com', email_verified: true, hd: 'example.com' }));
|
|
103
|
+
assert.ok(ok({ email: 'A@Example.com', email_verified: true, hd: 'example.com' }), 'case does not matter');
|
|
104
|
+
assert.ok(!ok({ email: 'a@example.com', email_verified: false, hd: 'example.com' }), 'unverified');
|
|
105
|
+
assert.ok(!ok({ email: 'a@example.com', email_verified: true }), 'a plain gmail-style account cannot claim the domain: no hd');
|
|
106
|
+
assert.ok(!ok({ email: 'a@evil.com', email_verified: true, hd: 'evil.com' }));
|
|
107
|
+
assert.ok(!ok({ email: 'a@example.com.evil.com', email_verified: true, hd: 'example.com.evil.com' }), 'a suffix is not the domain');
|
|
108
|
+
assert.ok(ok({ email: 'guest@example.org', email_verified: true }), 'a named address needs no domain');
|
|
109
|
+
assert.ok(!auth.allowed({ email: 'a@example.com', email_verified: true, hd: 'example.com' }, { domains: [], emails: [] }).ok, 'nothing allowed, nobody in');
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('a full sign-in, against a stand-in for Google, ends in a cookie that opens the dashboard', async () => {
|
|
113
|
+
signinOn();
|
|
114
|
+
let seen;
|
|
115
|
+
const google = http.createServer((req, res) => {
|
|
116
|
+
let b = ''; req.on('data', (d) => { b += d; });
|
|
117
|
+
req.on('end', () => { seen = new URLSearchParams(b); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ id_token: idToken(google.claims) })); });
|
|
118
|
+
});
|
|
119
|
+
await new Promise((r) => google.listen(0, '127.0.0.1', r));
|
|
120
|
+
process.env.GOOGLE_TOKEN_URL = `http://127.0.0.1:${google.address().port}/token`;
|
|
121
|
+
const s = await appServer();
|
|
122
|
+
try {
|
|
123
|
+
const start = await get(s, `/auth/google/login?next=${encodeURIComponent(`https://dev.${HUB}/dev`)}`);
|
|
124
|
+
assert.strictEqual(start.status, 302);
|
|
125
|
+
const to = new URL(start.headers.location);
|
|
126
|
+
assert.strictEqual(to.origin + to.pathname, 'https://accounts.google.com/o/oauth2/v2/auth');
|
|
127
|
+
assert.strictEqual(to.searchParams.get('scope'), 'openid email profile');
|
|
128
|
+
assert.strictEqual(to.searchParams.get('redirect_uri'), `https://${HUB}/auth/google/callback`);
|
|
129
|
+
assert.strictEqual(to.searchParams.get('hd'), 'example.com');
|
|
130
|
+
const state = to.searchParams.get('state');
|
|
131
|
+
const claims = { iss: 'https://accounts.google.com', aud: 'cid.apps.googleusercontent.com', exp: Math.floor(Date.now() / 1000) + 600, nonce: to.searchParams.get('nonce'), email: 'member@example.com', email_verified: true, hd: 'example.com', name: 'Team Member' };
|
|
132
|
+
|
|
133
|
+
google.claims = { ...claims, nonce: 'someone-elses' };
|
|
134
|
+
const replayed = await get(s, `/auth/google/callback?code=c&state=${state}`);
|
|
135
|
+
assert.strictEqual(replayed.status, 403, 'a token for a different request is refused');
|
|
136
|
+
assert.match(replayed.body, /does not match/);
|
|
137
|
+
|
|
138
|
+
const again = await get(s, `/auth/google/login?next=${encodeURIComponent(`https://dev.${HUB}/dev`)}`);
|
|
139
|
+
const t2 = new URL(again.headers.location);
|
|
140
|
+
google.claims = { ...claims, nonce: t2.searchParams.get('nonce') };
|
|
141
|
+
const done = await get(s, `/auth/google/callback?code=thecode&state=${t2.searchParams.get('state')}`);
|
|
142
|
+
assert.strictEqual(done.status, 302, done.body);
|
|
143
|
+
assert.strictEqual(done.headers.location, `https://dev.${HUB}/dev`);
|
|
144
|
+
assert.strictEqual(seen.get('code'), 'thecode');
|
|
145
|
+
const cookie = done.headers['set-cookie'][0];
|
|
146
|
+
assert.match(cookie, new RegExp(`Domain=\\.${HUB.replace(/\./g, '\\.')}; HttpOnly; Secure; SameSite=Lax`));
|
|
147
|
+
const inside = await get(s, '/dev', { cookie: cookie.split(';')[0] });
|
|
148
|
+
assert.strictEqual(inside.body, 'the dashboard');
|
|
149
|
+
assert.deepStrictEqual(JSON.parse((await get(s, '/auth/me', { cookie: cookie.split(';')[0] })).body), { enabled: true, email: 'member@example.com', name: 'Team Member' });
|
|
150
|
+
|
|
151
|
+
const out = await get(s, '/auth/signout', { cookie: cookie.split(';')[0] });
|
|
152
|
+
assert.match(out.headers['set-cookie'][0], /Max-Age=0/);
|
|
153
|
+
|
|
154
|
+
const third = await get(s, '/auth/google/login');
|
|
155
|
+
const t3 = new URL(third.headers.location);
|
|
156
|
+
google.claims = { ...claims, nonce: t3.searchParams.get('nonce'), email: 'x@gmail.com', hd: undefined };
|
|
157
|
+
const denied = await get(s, `/auth/google/callback?code=c&state=${t3.searchParams.get('state')}`);
|
|
158
|
+
assert.strictEqual(denied.status, 403);
|
|
159
|
+
assert.ok(!denied.headers['set-cookie'], 'no cookie for a refused account');
|
|
160
|
+
assert.match(denied.body, /not allowed/);
|
|
161
|
+
} finally { s.close(); google.close(); delete process.env.GOOGLE_TOKEN_URL; }
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('after sign-in only the hub or a workspace page is a place to go back to', () => {
|
|
165
|
+
signinOn();
|
|
166
|
+
assert.strictEqual(auth.safeNext(`https://dev.${HUB}/dev`), `https://dev.${HUB}/dev`);
|
|
167
|
+
assert.strictEqual(auth.safeNext('https://evil.example/'), `https://${HUB}/`);
|
|
168
|
+
assert.strictEqual(auth.safeNext(`https://${HUB}.evil.example/`), `https://${HUB}/`);
|
|
169
|
+
assert.strictEqual(auth.safeNext(`http://dev.${HUB}/`), `https://${HUB}/`, 'not downgraded to http');
|
|
170
|
+
assert.strictEqual(auth.safeNext('//evil.example/x'), `https://${HUB}/`);
|
|
171
|
+
assert.strictEqual(auth.safeNext('/settings'), `https://${HUB}/settings`);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('a terminal socket needs the session too', () => {
|
|
175
|
+
signinOn();
|
|
176
|
+
assert.strictEqual(auth.upgradeAllowed({ headers: {} }), false);
|
|
177
|
+
assert.strictEqual(auth.upgradeAllowed({ headers: { cookie: `ws-session=${auth.makeSession({ email: 'a@example.com' }, 1)}` } }), true);
|
|
178
|
+
setSettings();
|
|
179
|
+
assert.strictEqual(auth.upgradeAllowed({ headers: {} }), true, 'and open when sign-in is off');
|
|
180
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Docker-restart watcher.
|
|
3
|
+
*
|
|
4
|
+
* The boot service starts the dashboard once; a Docker restart afterwards stopped every container and nothing
|
|
5
|
+
* started them again (workspace.ddev.site answered nothing until somebody noticed). The watcher remembers
|
|
6
|
+
* when Docker last started and runs the start script once when that changes. It must never start anything
|
|
7
|
+
* only because the dashboard is down: a dashboard stopped on purpose stays stopped.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { test } = require('node:test');
|
|
11
|
+
const assert = require('node:assert');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { spawnSync } = require('child_process');
|
|
16
|
+
|
|
17
|
+
const SCRIPTS = path.join(__dirname, '../../../scripts');
|
|
18
|
+
const WATCH = path.join(SCRIPTS, 'autostart-watch.sh');
|
|
19
|
+
|
|
20
|
+
function sandbox() {
|
|
21
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-watch-'));
|
|
22
|
+
const ran = path.join(dir, 'ran');
|
|
23
|
+
const look = (stamp) => spawnSync('bash', [WATCH], {
|
|
24
|
+
encoding: 'utf8',
|
|
25
|
+
env: { PATH: process.env.PATH, HOME: dir, AUTOSTART_WATCH_STATE: path.join(dir, 'stamp'),
|
|
26
|
+
AUTOSTART_WATCH_RUN: `echo RAN >> '${ran}'`, AUTOSTART_WATCH_DOCKER_STAMP: stamp },
|
|
27
|
+
});
|
|
28
|
+
const runs = () => (fs.existsSync(ran) ? fs.readFileSync(ran, 'utf8').trim().split('\n').length : 0);
|
|
29
|
+
return { look, runs, dir };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test('the first look only remembers; a restart runs the start script once', () => {
|
|
33
|
+
const s = sandbox();
|
|
34
|
+
s.look('Wed 2026-10-14 10:51:26 +03');
|
|
35
|
+
assert.strictEqual(s.runs(), 0, 'nothing is started just because it looked');
|
|
36
|
+
s.look('Wed 2026-10-14 10:51:26 +03');
|
|
37
|
+
assert.strictEqual(s.runs(), 0, 'the same Docker start is not a restart');
|
|
38
|
+
const out = s.look('Wed 2026-10-14 14:02:00 +03');
|
|
39
|
+
assert.strictEqual(s.runs(), 1);
|
|
40
|
+
assert.match(out.stdout, /Docker restarted \(Wed 2026-10-14 10:51:26 \+03 -> Wed 2026-10-14 14:02:00 \+03\)/);
|
|
41
|
+
s.look('Wed 2026-10-14 14:02:00 +03');
|
|
42
|
+
assert.strictEqual(s.runs(), 1, 'and only once for that restart');
|
|
43
|
+
s.look('Wed 2026-10-14 16:30:00 +03');
|
|
44
|
+
assert.strictEqual(s.runs(), 2, 'a later restart is a new one');
|
|
45
|
+
fs.rmSync(s.dir, { recursive: true, force: true });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('Docker not answering does nothing', () => {
|
|
49
|
+
const s = sandbox();
|
|
50
|
+
s.look('Wed 10:51');
|
|
51
|
+
s.look('');
|
|
52
|
+
assert.strictEqual(s.runs(), 0);
|
|
53
|
+
assert.strictEqual(fs.readFileSync(path.join(s.dir, 'stamp'), 'utf8').trim(), 'Wed 10:51', 'and forgets nothing');
|
|
54
|
+
fs.rmSync(s.dir, { recursive: true, force: true });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('a start that fails is not retried every two minutes', () => {
|
|
58
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-watch-'));
|
|
59
|
+
const ran = path.join(dir, 'ran');
|
|
60
|
+
const look = (stamp) => spawnSync('bash', [WATCH], { encoding: 'utf8', env: { PATH: process.env.PATH, HOME: dir,
|
|
61
|
+
AUTOSTART_WATCH_STATE: path.join(dir, 'stamp'), AUTOSTART_WATCH_RUN: `echo RAN >> '${ran}'; exit 1`, AUTOSTART_WATCH_DOCKER_STAMP: stamp } });
|
|
62
|
+
look('A');
|
|
63
|
+
look('B');
|
|
64
|
+
look('B');
|
|
65
|
+
look('B');
|
|
66
|
+
assert.strictEqual(fs.readFileSync(ran, 'utf8').trim().split('\n').length, 1, 'one attempt for that restart, however it ended');
|
|
67
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('enable installs the timer and disable removes it', () => {
|
|
71
|
+
const fn = fs.readFileSync(path.join(SCRIPTS, 'functions/fun-autostart.sh'), 'utf8');
|
|
72
|
+
assert.match(fn, /function autostart_write_watch\(\)/);
|
|
73
|
+
assert.match(fn, /OnUnitActiveSec=2min/);
|
|
74
|
+
assert.match(fn, /systemctl --user enable --now "\$\{AUTOSTART_WATCH_NAME\}\.timer"/);
|
|
75
|
+
assert.match(fn, /systemctl --user disable --now "\$\{AUTOSTART_WATCH_NAME\}\.timer"/);
|
|
76
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Screens page lists the automation browsers a session leaves running. The
|
|
3
|
+
* listing itself needs a live browser; the PARSING does not, so the real
|
|
4
|
+
* command lines are pinned here — including the ones that must NOT be listed.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { test } = require('node:test');
|
|
8
|
+
const assert = require('node:assert');
|
|
9
|
+
|
|
10
|
+
const { parseBrowserLine } = require('../../screens');
|
|
11
|
+
|
|
12
|
+
// A real Playwright MCP browser, as `ps -eo pid,etime,args` prints it.
|
|
13
|
+
const MCP_CHROME = '1359368 12:03 /opt/google/chrome/chrome --disable-field-trial-config --no-first-run '
|
|
14
|
+
+ '--user-data-dir=/home/rajab/.cache/ms-playwright-mcp/mcp-chrome-fe954a1 --remote-debugging-pipe about:blank';
|
|
15
|
+
|
|
16
|
+
test('a Playwright MCP browser is recognised, with its profile', () => {
|
|
17
|
+
const b = parseBrowserLine(MCP_CHROME);
|
|
18
|
+
assert.ok(b, 'the line is a browser');
|
|
19
|
+
assert.strictEqual(b.pid, '1359368');
|
|
20
|
+
assert.strictEqual(b.profile, 'mcp-chrome-fe954a1');
|
|
21
|
+
assert.strictEqual(b.pipe, true, 'driven over a pipe');
|
|
22
|
+
assert.strictEqual(b.port, '', 'so there is no DevTools port to offer');
|
|
23
|
+
assert.strictEqual(b.headless, false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('a headless browser with a debugging port reports both', () => {
|
|
27
|
+
const b = parseBrowserLine('4242 00:31 /usr/lib/chromium/chromium --headless=new --no-sandbox '
|
|
28
|
+
+ '--remote-debugging-port=9222 --user-data-dir=/tmp/pw-profile about:blank');
|
|
29
|
+
assert.ok(b);
|
|
30
|
+
assert.strictEqual(b.headless, true);
|
|
31
|
+
assert.strictEqual(b.port, '9222');
|
|
32
|
+
assert.strictEqual(b.profile, 'pw-profile');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('renderer processes and ordinary browsers are not listed', () => {
|
|
36
|
+
// A renderer child: same binary, but it is not the browser.
|
|
37
|
+
assert.strictEqual(parseBrowserLine('1359377 12:03 /opt/google/chrome/chrome --type=zygote '
|
|
38
|
+
+ '--user-data-dir=/home/rajab/.cache/ms-playwright-mcp/mcp-chrome-fe954a1'), null);
|
|
39
|
+
// The docs tooling's screenshot chromium: no automation flags, no profile.
|
|
40
|
+
assert.strictEqual(parseBrowserLine('68819 11:00 /usr/lib/chromium/chromium --show-component-extension-options'), null);
|
|
41
|
+
// Not a browser at all.
|
|
42
|
+
assert.strictEqual(parseBrowserLine('999 00:02 /usr/bin/node server.js'), null);
|
|
43
|
+
assert.strictEqual(parseBrowserLine('not a ps line'), null);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// After the sessions moved to the machine the dashboard shares its pid
|
|
47
|
+
// namespace, so `ps` in here also lists the suite's browser inside a project's
|
|
48
|
+
// web container, and the wrappers that merely mention chromium on their command
|
|
49
|
+
// line. Neither is a browser on the desktop.
|
|
50
|
+
test('the suite runner and its docker wrapper are not browsers', () => {
|
|
51
|
+
const lines = [
|
|
52
|
+
' 841992 02:26 ddev exec PLAYWRIGHT_BROWSERS_PATH=/mnt/ddev-global-cache/ms-playwright BROWSER=chromium yarn run test:chromium',
|
|
53
|
+
' 842061 02:26 /home/rajab/.ddev/bin/docker-compose -f /home/rajab/workspace/dev/v11x00b1/.ddev/.ddev-docker-compose-full.yaml exec web bash -c set -eu && ( PLAYWRIGHT_BROWSERS_PATH=/mnt/ddev-global-cache/ms-playwright BROWSER=chromium yarn run test:chromium)',
|
|
54
|
+
' 842084 02:26 bash -c set -eu && ( PLAYWRIGHT_BROWSERS_PATH=/mnt/ddev-global-cache/ms-playwright BROWSER=chromium yarn run test:chromium)',
|
|
55
|
+
];
|
|
56
|
+
for (const line of lines) {
|
|
57
|
+
assert.equal(parseBrowserLine(line), null, `not a browser: ${line.slice(0, 60)}`);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('a headless shell is headless even without the flag', () => {
|
|
62
|
+
const b = parseBrowserLine(' 855556 00:04 /mnt/ddev-global-cache/ms-playwright/chromium_headless_shell-1223/chrome-headless-shell-linux64/chrome-headless-shell --remote-debugging-pipe --user-data-dir=/tmp/playwright_chromiumdev_profile-ZAMCsO');
|
|
63
|
+
assert.ok(b, 'the headless shell is a browser');
|
|
64
|
+
assert.equal(b.headless, true, 'the binary alone says it has no window');
|
|
65
|
+
assert.equal(b.pipe, true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// A browser inside a project's web container: this process cannot read its
|
|
69
|
+
// cmdline, so `ps` prints the executable path alone — cut, here, at the
|
|
70
|
+
// directory that names the build.
|
|
71
|
+
test('a browser in another container is still listed', () => {
|
|
72
|
+
const b = parseBrowserLine(' 860652 00:21 /mnt/ddev-global-cache/ms-playwright/chromium_headless_shell');
|
|
73
|
+
assert.ok(b, 'the path alone is enough to know what it is');
|
|
74
|
+
assert.equal(b.headless, true, 'the build directory says headless shell');
|
|
75
|
+
assert.equal(b.profile, 'test browser');
|
|
76
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What build_distribution runs, in what order, for a release line that needs more than one composer step.
|
|
3
|
+
*
|
|
4
|
+
* ddev and the helpers around it are stubbed, so the test reads the calls rather than building a site.
|
|
5
|
+
* A line that cannot resolve on its own declares what it needs (an alias, a patch to ignore), and
|
|
6
|
+
* a failed drush site:install used to end with "has been installed", over a half-installed site.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const assert = require('node:assert');
|
|
10
|
+
const { test } = require('node:test');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const { spawnSync } = require('child_process');
|
|
15
|
+
|
|
16
|
+
const FUNCTION = path.join(__dirname, '..', '..', '..', 'scripts', 'functions', 'fun-build-distribution.sh');
|
|
17
|
+
const NODEJS = path.join(__dirname, '..', '..', '..', 'scripts', 'functions', 'fun-ddev-nodejs.sh');
|
|
18
|
+
|
|
19
|
+
// Runs build_distribution with a fake ddev that records its arguments, one call per line.
|
|
20
|
+
function build({ declare = '', installFails = false, composerFailsOnce = false, broken = false } = {}) {
|
|
21
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-build-'));
|
|
22
|
+
const log = path.join(dir, 'ddev.log');
|
|
23
|
+
fs.mkdirSync(path.join(dir, 'ws'));
|
|
24
|
+
const script = `
|
|
25
|
+
WORKSPACE_SCRIPTS=/nowhere; WORKSPACE_ROOT=${dir}; doc_name=ws; PROJECT_NAME=p1; SKIP_DROP_DATABASE=yes; INSTALL=yes; REQUIRE=(_none_); ENABLE=(_none_)
|
|
26
|
+
distribution_name=stub; distribution_webroot=docroot; distribution_project_template=drupal/stub; site_version=1.0.x-dev
|
|
27
|
+
${declare}
|
|
28
|
+
ddev() { printf '%s\\n' "$*" >> ${log}; if [ "${composerFailsOnce ? 'yes' : 'no'}" = yes ] && [ "$1 $2" = 'composer install' ] && [ ! -e ${dir}/failed-once ]; then touch ${dir}/failed-once; return 1; fi; if [ "$1 $2" = 'drush status' ]; then echo ${broken ? 'Failed' : 'Successful'}; fi; if [ "$1" = config ]; then mkdir -p .ddev; printf 'docroot: docroot\\ndatabase:\\n version: "11.8"\\n' > .ddev/config.yaml; fi; return 0; }
|
|
29
|
+
ddev_up() { :; }; set_default_settings() { :; }; add_drush() { :; }; drush_set_debug_on() { :; }; drush_cr() { :; }; add_users() { :; }
|
|
30
|
+
source ${FUNCTION}; source ${NODEJS}
|
|
31
|
+
source() { if [ "$1" = "\${WORKSPACE_SCRIPTS}/functions/fun-distribution-stub.sh" ]; then
|
|
32
|
+
install_stub_with_drush() { ${installFails ? 'return 1' : 'ddev drush site:install stub'}; }; enable_stub_extra_components() { :; }
|
|
33
|
+
else builtin source "$@"; fi; }
|
|
34
|
+
build_distribution; echo "exit=$?"
|
|
35
|
+
`;
|
|
36
|
+
const run = spawnSync('bash', ['-c', script], { cwd: dir, encoding: 'utf8' });
|
|
37
|
+
const calls = fs.existsSync(log) ? fs.readFileSync(log, 'utf8').trim().split('\n') : [];
|
|
38
|
+
return { calls, out: run.stdout, code: Number((run.stdout.match(/exit=(\d+)/) || [])[1]) };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const index = (calls, needle) => calls.findIndex((c) => c.includes(needle));
|
|
42
|
+
|
|
43
|
+
test('a builder that declares nothing creates the project in one step, as before', () => {
|
|
44
|
+
const { calls, code } = build();
|
|
45
|
+
assert.strictEqual(code, 0);
|
|
46
|
+
const create = calls.find((c) => c.startsWith('composer create-project'));
|
|
47
|
+
assert.ok(create && !create.includes('--no-install'), create);
|
|
48
|
+
assert.ok(index(calls, 'composer require') < 0 && index(calls, 'composer install') < 0 && index(calls, 'patches-ignore') < 0);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('every ddev config of a build pins Node.js 24, and the first one still gets its PHP version and --auto', () => {
|
|
52
|
+
const { calls, code } = build({ declare: 'site_php_version=8.5' });
|
|
53
|
+
assert.strictEqual(code, 0);
|
|
54
|
+
const configs = calls.filter((c) => c.startsWith('config '));
|
|
55
|
+
assert.strictEqual(configs.length, 2, configs.join('\n'));
|
|
56
|
+
for (const c of configs) assert.ok(c.includes('--nodejs-version=24'), c);
|
|
57
|
+
// #767 put ddev_telemetry_off inside this call's line continuation, so --php-version and --auto ran as a command.
|
|
58
|
+
assert.ok(configs[0].includes('--php-version=8.5') && configs[0].endsWith('--auto'), configs[0]);
|
|
59
|
+
assert.ok(!configs[0].includes('ddev_telemetry_off'), configs[0]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('a builder that declares site_nodejs_version gets that version instead', () => {
|
|
63
|
+
const { calls } = build({ declare: 'site_nodejs_version=22' });
|
|
64
|
+
const configs = calls.filter((c) => c.startsWith('config '));
|
|
65
|
+
for (const c of configs) assert.ok(c.includes('--nodejs-version=22') && !c.includes('--nodejs-version=24'), c);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('declared requirements and patches to ignore are applied between create-project and the first install', () => {
|
|
69
|
+
const { calls, code } = build({
|
|
70
|
+
declare: `site_composer_requires=("drupal/stub_editor:1.2.3 as 1.2.x-dev"); site_patches_ignore=("drupal/pp https://x.test/a.patch" "drupal/qq https://x.test/b.patch")`,
|
|
71
|
+
});
|
|
72
|
+
assert.strictEqual(code, 0);
|
|
73
|
+
const create = index(calls, 'composer create-project');
|
|
74
|
+
assert.ok(calls[create].includes('--no-install') && calls[create].includes('--no-scripts'), calls[create]);
|
|
75
|
+
const order = ['composer create-project', 'composer require', 'composer config', 'composer install', 'composer run-script post-create-project-cmd', 'drush site:install']
|
|
76
|
+
.map((n) => index(calls, n));
|
|
77
|
+
assert.deepStrictEqual(order, [...order].sort((a, b) => a - b), `in order: ${calls.join(' | ')}`);
|
|
78
|
+
assert.ok(order.every((i) => i >= 0));
|
|
79
|
+
assert.match(calls[index(calls, 'composer require')], /^composer require drupal\/stub_editor:1\.2\.3 as 1\.2\.x-dev --no-update/);
|
|
80
|
+
// The ignore list is keyed on the patches package the distributions share, webship/patches.
|
|
81
|
+
const config = calls[index(calls, 'composer config')];
|
|
82
|
+
assert.strictEqual(config, 'composer config --json extra.patches-ignore {"webship/patches": {"drupal/pp": ["https://x.test/a.patch"], "drupal/qq": ["https://x.test/b.patch"]}}');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('a failed drush site:install stops the build instead of reporting the site as installed', () => {
|
|
86
|
+
const { out, code, calls } = build({ installFails: true });
|
|
87
|
+
assert.strictEqual(code, 1);
|
|
88
|
+
assert.match(out, /install of ws p1 failed/);
|
|
89
|
+
assert.ok(!/has been installed/.test(out), out);
|
|
90
|
+
assert.ok(index(calls, 'drush') < 0, 'nothing runs after a failed install');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('the first composer install is tried once more, because it can fail on a git cache hiccup and pass at once', () => {
|
|
94
|
+
const declare = 'site_patches_ignore=("drupal/pp https://x.test/a.patch")';
|
|
95
|
+
const { calls, code } = build({ declare, composerFailsOnce: true });
|
|
96
|
+
assert.strictEqual(code, 0);
|
|
97
|
+
assert.strictEqual(calls.filter((c) => c === 'composer install --no-interaction').length, 2);
|
|
98
|
+
assert.ok(index(calls, 'drush site:install') > 0, 'and the build carries on to the install');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('drush is waited for, since DDEV rewrites its shared command directory whenever any project starts', () => {
|
|
102
|
+
const DRUSH = path.join(__dirname, '..', '..', '..', 'scripts', 'functions', 'fun-drush.sh');
|
|
103
|
+
const run = (failures) => {
|
|
104
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-drush-'));
|
|
105
|
+
const script = `
|
|
106
|
+
n=0; mkdir -p ${dir}/ws/p/vendor/drush/drush; WORKSPACE_ROOT=${dir}; doc_name=ws; PROJECT_NAME=p
|
|
107
|
+
ddev() { if [ "$1 $2" = 'drush --version' ]; then n=$((n+1)); [ $n -gt ${failures} ]; return; fi; }
|
|
108
|
+
sleep() { :; }
|
|
109
|
+
source ${DRUSH}; add_drush; echo "exit=$? tries=$n"
|
|
110
|
+
`;
|
|
111
|
+
return spawnSync('bash', ['-c', script], { encoding: 'utf8' }).stdout;
|
|
112
|
+
};
|
|
113
|
+
assert.match(run(0), /exit=0 tries=1/);
|
|
114
|
+
assert.match(run(2), /exit=0 tries=3/, 'two misses, then it is there');
|
|
115
|
+
assert.match(run(99), /still not available[\s\S]*exit=1 tries=10/, 'but not for ever');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('a site that installed but no longer bootstraps fails the build instead of being announced as installed', () => {
|
|
119
|
+
const ok = build();
|
|
120
|
+
assert.strictEqual(ok.code, 0);
|
|
121
|
+
const bad = build({ broken: true });
|
|
122
|
+
assert.strictEqual(bad.code, 1);
|
|
123
|
+
assert.match(bad.out, /does not bootstrap/);
|
|
124
|
+
assert.ok(!/has been installed/.test(bad.out), bad.out);
|
|
125
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings → Command order: the order of a workspace's builders and tools.
|
|
3
|
+
*
|
|
4
|
+
* The part worth testing hardest is not the sorting — it is what gets WRITTEN. A page that recorded all
|
|
5
|
+
* forty rows every time you moved one would freeze the order of everything nobody has an opinion about,
|
|
6
|
+
* so a command added next month would land at the bottom instead of in its shipped place. The first
|
|
7
|
+
* attempt at that compared positions and got it exactly wrong: moving one builder from the bottom to
|
|
8
|
+
* the top shifts every other row down by one, so nothing sits where the defaults put it and all forty
|
|
9
|
+
* names look like decisions. One move wrote twenty-five lines. That is what `minimalOrder` is for, and
|
|
10
|
+
* what most of this file is about.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { test } = require('node:test');
|
|
14
|
+
const assert = require('node:assert');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
|
|
19
|
+
// A file of its own per run, so these never touch the machine's own order.
|
|
20
|
+
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-build-order-'));
|
|
21
|
+
process.env.BUILD_ORDER_FILE = path.join(TMP, 'build-order.yml');
|
|
22
|
+
|
|
23
|
+
const order = require('../../build-order');
|
|
24
|
+
|
|
25
|
+
const B = {
|
|
26
|
+
d11: 'cmd-drupal11-recommended-project.sh',
|
|
27
|
+
d12: 'cmd-drupal12-recommended-project.sh',
|
|
28
|
+
cms: 'cmd-drupalcms2-x-project.sh',
|
|
29
|
+
ws11x: 'cmd-webship11-0-x-project.sh',
|
|
30
|
+
cucumber: 'cmd-cucumber11-0-x-project.sh',
|
|
31
|
+
starter: 'cmd-website_starter-project.sh',
|
|
32
|
+
cmsOther: 'cmd-drupalcms2-x-starter-project.sh',
|
|
33
|
+
d9: 'cmd-drupal9-recommended-project.sh',
|
|
34
|
+
testing: 'cmd-automated-testing-webship11-0-x-project.sh',
|
|
35
|
+
};
|
|
36
|
+
const T = {
|
|
37
|
+
backup: 'cmd-tools-backup-dev.sh',
|
|
38
|
+
clone: 'cmd-tools-clone.sh',
|
|
39
|
+
upsun: 'cmd-tools-upsun.sh',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const write = (obj) => fs.writeFileSync(process.env.BUILD_ORDER_FILE, require('js-yaml').dump(obj));
|
|
43
|
+
const clear = () => { try { fs.unlinkSync(process.env.BUILD_ORDER_FILE); } catch (_) { /* gone */ } };
|
|
44
|
+
|
|
45
|
+
test('a builder is a cmd-*-project.sh; a tool is every other command', () => {
|
|
46
|
+
assert.ok(order.isBuilder(B.d12));
|
|
47
|
+
assert.ok(!order.isBuilder(T.backup), 'cmd-tools-backup-dev.sh is not a builder');
|
|
48
|
+
assert.ok(!order.isBuilder('cmd-tools-something-project.sh'), 'a tool that ends in -project.sh is still a tool');
|
|
49
|
+
assert.ok(order.isTool(T.clone));
|
|
50
|
+
assert.ok(!order.isTool(B.d11), 'the two kinds do not overlap');
|
|
51
|
+
// The remainder rule: a command shaped like nothing anticipated is still arrangeable.
|
|
52
|
+
assert.ok(order.isTool('cmd-all-front-end-build-themes.sh'));
|
|
53
|
+
assert.ok(!order.isCommand('AGENTS.md'));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('the shipped order: the three bases, then the templates grouped by base', () => {
|
|
57
|
+
clear();
|
|
58
|
+
const all = [B.cmsOther, B.testing, B.starter, B.d9, B.cucumber, B.ws11x, B.cms, B.d12, B.d11];
|
|
59
|
+
assert.deepEqual(order.sortBuilders(all, 'dev'), [
|
|
60
|
+
B.d11, B.d12, B.cms, B.ws11x, // the bases
|
|
61
|
+
B.cucumber, // and the lines beside Webship
|
|
62
|
+
B.starter, // the site templates on Website, the reference one first
|
|
63
|
+
B.cmsOther, // then anything else on Drupal CMS, together
|
|
64
|
+
B.testing, B.d9, // everything unnamed, alphabetical
|
|
65
|
+
]);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('Drupal CMS itself is a base, not one of its own templates', () => {
|
|
69
|
+
clear();
|
|
70
|
+
// `^cmd-drupalcms2-x-` would swallow cmd-drupalcms2-x-project.sh and put the base last; it is
|
|
71
|
+
// anchored exactly for that reason.
|
|
72
|
+
const got = order.sortBuilders([B.cmsOther, B.cms, B.d12], 'dev');
|
|
73
|
+
assert.deepEqual(got, [B.d12, B.cms, B.cmsOther]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("a workspace's own order comes before the global one, and both before the shipped default", () => {
|
|
77
|
+
write({ global: { builders: [B.d9] }, workspaces: { test: { builders: [B.testing] } } });
|
|
78
|
+
const all = [B.d11, B.d12, B.d9, B.testing];
|
|
79
|
+
assert.deepEqual(order.sortBuilders(all, 'test'), [B.testing, B.d9, B.d11, B.d12],
|
|
80
|
+
'test first, then global, then the shipped order');
|
|
81
|
+
assert.deepEqual(order.sortBuilders(all, 'dev'), [B.d9, B.d11, B.d12, B.testing],
|
|
82
|
+
'a workspace that says nothing gets the global order and nothing else');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('builders and tools are separate lists, and saving one leaves the other alone', () => {
|
|
86
|
+
write({ workspaces: { dev: { builders: [B.d9], tools: [T.upsun] } } });
|
|
87
|
+
assert.deepEqual(order.sortTools([T.backup, T.clone, T.upsun], 'dev'), [T.upsun, T.backup, T.clone]);
|
|
88
|
+
// Save only the builders back.
|
|
89
|
+
const { raw } = order.rawOrder();
|
|
90
|
+
order.saveOrder(order.withScope(raw, 'dev', { builders: [B.d12] }));
|
|
91
|
+
const after = order.scopeOrder(order.loadOrder().raw, 'dev');
|
|
92
|
+
assert.deepEqual(after.builders, [B.d12]);
|
|
93
|
+
assert.deepEqual(after.tools, [T.upsun], 'the tools it was not given survive the save');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('a mixed list is builders first, then tools', () => {
|
|
97
|
+
clear();
|
|
98
|
+
assert.deepEqual(order.sortCommands([T.backup, B.d12, T.clone, B.d11], 'dev'),
|
|
99
|
+
[B.d11, B.d12, T.backup, T.clone],
|
|
100
|
+
'alphabetical used to drop cmd-tools-backup-dev.sh between two builders');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('minimalOrder writes ONE name for one move, not the whole list', () => {
|
|
104
|
+
clear();
|
|
105
|
+
const shipped = order.sortBuilders([B.d11, B.d12, B.cms, B.ws11x, B.d9], 'dev');
|
|
106
|
+
const moved = [B.d9, ...shipped.filter((f) => f !== B.d9)];
|
|
107
|
+
assert.deepEqual(order.minimalOrder(moved, 'dev'), [B.d9],
|
|
108
|
+
'moving one builder to the top shifts every other row, which is why comparing positions got this wrong');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('minimalOrder writes nothing at all when nothing moved', () => {
|
|
112
|
+
clear();
|
|
113
|
+
const shipped = order.sortBuilders([B.d11, B.d12, B.cms, B.ws11x], 'dev');
|
|
114
|
+
assert.deepEqual(order.minimalOrder(shipped, 'dev'), [],
|
|
115
|
+
'a scope that names nothing is what lets a command added later arrive in its shipped place');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('minimalOrder reproduces exactly what was posted, however it was arranged', () => {
|
|
119
|
+
clear();
|
|
120
|
+
const all = [B.d11, B.d12, B.cms, B.ws11x, B.d9, B.starter, B.cmsOther];
|
|
121
|
+
// Reversed is the worst case: nothing is where a default would put it.
|
|
122
|
+
const posted = [...all].reverse();
|
|
123
|
+
const kept = order.minimalOrder(posted, 'dev');
|
|
124
|
+
write({ workspaces: { dev: { builders: kept } } });
|
|
125
|
+
assert.deepEqual(order.sortBuilders(all, 'dev'), posted,
|
|
126
|
+
'what is written must reproduce what was on the screen');
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('what is written is filtered and deduplicated, whatever was posted', () => {
|
|
130
|
+
clear();
|
|
131
|
+
const { raw } = order.rawOrder();
|
|
132
|
+
order.saveOrder(order.withScope(raw, 'global', { builders: [B.d12, B.d12, 'cmd-tools-evil.sh', '../../etc/passwd'] }));
|
|
133
|
+
assert.deepEqual(order.scopeOrder(order.loadOrder().raw, 'global').builders, [B.d12],
|
|
134
|
+
'a repeat would give one copy a rank it can never reach; a non-builder is not a builder');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('an empty scope is dropped from the file rather than left as an empty key', () => {
|
|
138
|
+
write({ global: { builders: [B.d12] }, workspaces: { dev: { builders: [B.d11] } } });
|
|
139
|
+
const { raw } = order.rawOrder();
|
|
140
|
+
order.saveOrder(order.withScope(raw, 'dev', { builders: [], tools: [] }));
|
|
141
|
+
const text = fs.readFileSync(process.env.BUILD_ORDER_FILE, 'utf8');
|
|
142
|
+
assert.ok(!/workspaces:/.test(text), 'the last workspace going empty takes the whole key with it');
|
|
143
|
+
assert.ok(/cmd-drupal12/.test(text), 'and the global order is untouched');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('a missing or unreadable file is the shipped order, not an error', () => {
|
|
147
|
+
clear();
|
|
148
|
+
assert.deepEqual(order.sortBuilders([B.d12, B.d11], 'dev'), [B.d11, B.d12]);
|
|
149
|
+
fs.writeFileSync(process.env.BUILD_ORDER_FILE, ':\n not: [valid\n');
|
|
150
|
+
assert.deepEqual(order.sortBuilders([B.d12, B.d11], 'dev'), [B.d11, B.d12],
|
|
151
|
+
'a broken order file must never cost you the Build form');
|
|
152
|
+
clear();
|
|
153
|
+
});
|