@webship.co/workspace 1.0.0-alpha1 → 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/{PLUGINS.md → core/workspace-app/PLUGINS.md} +1 -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/{plugin-loader.js → core/workspace-app/plugin-loader.js} +19 -1
- package/{plugins-page.js → core/workspace-app/plugins-page.js} +28 -2
- package/{row-layout.js → core/workspace-app/row-layout.js} +0 -1
- package/{server.js → core/workspace-app/server.js} +26 -225
- 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/{workspace-types.js → core/workspace-app/workspace-types.js} +2 -2
- package/{workspaces.js → core/workspace-app/workspaces.js} +1 -5
- package/package.json +38 -36
- package/actions/graphs.js +0 -107
- package/actions/obsidian.js +0 -59
- package/graphs.js +0 -104
- package/obsidian.js +0 -405
- /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-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/{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
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A recording's card: a default picture when it has none, and its voiceover script one click away.
|
|
3
|
+
*
|
|
4
|
+
* The script is found by name in docs/ (`<video>-voiceover-script.md`). The pure parts are tested
|
|
5
|
+
* directly; the rows, the player and the transcript route run on the real server and a temp tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { test, before, after } = require('node:test');
|
|
9
|
+
const assert = require('node:assert');
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const net = require('net');
|
|
14
|
+
const { spawn } = require('child_process');
|
|
15
|
+
|
|
16
|
+
const APP = path.join(__dirname, '../..');
|
|
17
|
+
const REPO = path.join(APP, '../..');
|
|
18
|
+
const { copyConfig } = require('./_isolated-config');
|
|
19
|
+
const tr = require('../../transcripts');
|
|
20
|
+
|
|
21
|
+
const SCRIPT = `# Demo: voiceover script (draft 1)
|
|
22
|
+
|
|
23
|
+
- **Version:** Draft 1
|
|
24
|
+
|
|
25
|
+
## Section timings
|
|
26
|
+
|
|
27
|
+
| # | Section |
|
|
28
|
+
|---|---|
|
|
29
|
+
| E01 | Opening |
|
|
30
|
+
|
|
31
|
+
## E01 Opening (00:00.0 to 00:20.0)
|
|
32
|
+
|
|
33
|
+
**On screen:** the home page.
|
|
34
|
+
|
|
35
|
+
> This is **Webship 11**, running on [Drupal](https://drupal.org) 11.
|
|
36
|
+
> *(optional)* A spare sentence.
|
|
37
|
+
|
|
38
|
+
## E02 Close (00:20.0 to 01:05.5)
|
|
39
|
+
|
|
40
|
+
> Thank you for <watching> & goodbye.
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
test('a script is found by the video\'s name, in the order of its suffixes', () => {
|
|
44
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-tr-'));
|
|
45
|
+
for (const n of ['a-voiceover-script.md', 'b-transcript.md', 'c-script.md', 'd.md', 'a-script.md']) fs.writeFileSync(path.join(dir, n), 'x');
|
|
46
|
+
assert.strictEqual(tr.findScript(dir, 'a'), 'a-voiceover-script.md');
|
|
47
|
+
assert.strictEqual(tr.findScript(dir, 'b'), 'b-transcript.md');
|
|
48
|
+
assert.strictEqual(tr.findScript(dir, 'c'), 'c-script.md');
|
|
49
|
+
assert.strictEqual(tr.findScript(dir, 'd'), null, 'a bare name is not a script');
|
|
50
|
+
assert.strictEqual(tr.findScript(path.join(dir, 'missing'), 'a'), null);
|
|
51
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('a timed script gives sections with only the words spoken', () => {
|
|
55
|
+
const p = tr.parseTranscript(SCRIPT);
|
|
56
|
+
assert.strictEqual(p.timed, true);
|
|
57
|
+
assert.strictEqual(p.title, 'Demo: voiceover script (draft 1)');
|
|
58
|
+
assert.deepStrictEqual(p.sections.map((s) => [s.id, s.title, s.start, s.end]), [['E01', 'Opening', 0, 20], ['E02', 'Close', 20, 65.5]]);
|
|
59
|
+
assert.deepStrictEqual(p.sections[0].lines, [
|
|
60
|
+
{ text: 'This is Webship 11, running on Drupal 11.', optional: false },
|
|
61
|
+
{ text: 'A spare sentence.', optional: true },
|
|
62
|
+
]);
|
|
63
|
+
assert.ok(!JSON.stringify(p).includes('On screen'));
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('a plain script gives its paragraphs, without lists, tables or headings', () => {
|
|
67
|
+
const p = tr.parseTranscript('# Title\n\n- a note\n\nFirst paragraph\nstill first.\n\n| a | b |\n\nSecond paragraph.\n');
|
|
68
|
+
assert.deepStrictEqual([p.timed, p.paragraphs], [false, ['First paragraph still first.', 'Second paragraph.']]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('the transcript is escaped, and a section time seeks only when asked to', () => {
|
|
72
|
+
const html = tr.transcriptHtml(tr.parseTranscript(SCRIPT), { file: 'd.md', href: '/files/docs/d.md', seek: true });
|
|
73
|
+
assert.match(html, /Thank you for <watching> & goodbye\./);
|
|
74
|
+
assert.ok(!/<watching>/.test(html));
|
|
75
|
+
assert.match(html, /<button type="button" class="transcript-seek" data-seek="20"[^>]*>00:20<\/button>/);
|
|
76
|
+
assert.match(html, /class="is-optional"/);
|
|
77
|
+
assert.ok(!/data-seek/.test(tr.transcriptHtml(tr.parseTranscript(SCRIPT), {})), 'no seek buttons without a player');
|
|
78
|
+
assert.strictEqual(tr.stamp(65.5), '01:06');
|
|
79
|
+
assert.strictEqual(tr.stamp(3725), '1:02:05');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ---- the running server ------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
let proc; let base; const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-tr-root-'));
|
|
85
|
+
|
|
86
|
+
const free = () => new Promise((resolve) => {
|
|
87
|
+
const s = net.createServer().listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => resolve(p)); });
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
before(async () => {
|
|
91
|
+
fs.mkdirSync(path.join(root, 'core'), { recursive: true });
|
|
92
|
+
copyConfig(path.join(REPO, 'core/config'), path.join(root, 'core/config'));
|
|
93
|
+
fs.symlinkSync(path.join(REPO, 'core/scripts'), path.join(root, 'core/scripts'));
|
|
94
|
+
fs.symlinkSync(APP, path.join(root, 'core/workspace-app'));
|
|
95
|
+
for (const d of ['videos', 'docs', 'audio']) fs.mkdirSync(path.join(root, d), { recursive: true });
|
|
96
|
+
fs.writeFileSync(path.join(root, 'videos/demo-draft-1.mp4'), Buffer.alloc(200, 1));
|
|
97
|
+
fs.writeFileSync(path.join(root, 'videos/posted.mp4'), Buffer.alloc(200, 2));
|
|
98
|
+
fs.writeFileSync(path.join(root, 'videos/posted-poster.jpg'), Buffer.alloc(50, 3));
|
|
99
|
+
fs.writeFileSync(path.join(root, 'audio/hello.mp3'), Buffer.alloc(200, 4));
|
|
100
|
+
fs.mkdirSync(path.join(root, 'audio/sounds'), { recursive: true });
|
|
101
|
+
const put = (name, body) => fs.writeFileSync(path.join(root, 'audio', name), body);
|
|
102
|
+
put('sounds/rain.mp3', Buffer.alloc(300, 5));
|
|
103
|
+
put('sounds/rain.txt', 'Rain on a tin roof\n');
|
|
104
|
+
put('sounds/rain.json', JSON.stringify({ kind: 'sound', engine: 'elevenlabs', duration: 4, loop: true, created_utc: '2026-10-07T12:00:00Z' }));
|
|
105
|
+
put('sounds/rain.jpg', Buffer.alloc(60, 6));
|
|
106
|
+
put('sounds/stray.bin', 'not a sound');
|
|
107
|
+
put('sounds/loose.txt', 'a text with no sound');
|
|
108
|
+
put('sounds/chime-cover.png', Buffer.alloc(60, 8));
|
|
109
|
+
put('sounds/chime.wav', Buffer.alloc(300, 9));
|
|
110
|
+
put('sounds/chime-transcript.md', 'A single chime.\n');
|
|
111
|
+
fs.mkdirSync(path.join(root, 'videos/older-takes'), { recursive: true });
|
|
112
|
+
fs.mkdirSync(path.join(root, 'docs/notes-folder'), { recursive: true });
|
|
113
|
+
fs.writeFileSync(path.join(root, 'docs/loose-note.md'), 'x');
|
|
114
|
+
fs.writeFileSync(path.join(root, 'docs/demo-draft-1-voiceover-script.md'), SCRIPT);
|
|
115
|
+
const port = await free();
|
|
116
|
+
base = `http://127.0.0.1:${port}`;
|
|
117
|
+
proc = spawn(process.execPath, ['server.js'], { cwd: APP, env: { ...process.env, PORT: String(port), WORKSPACE_ROOT: root }, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
118
|
+
await new Promise((resolve, reject) => {
|
|
119
|
+
const t = setTimeout(() => reject(new Error('server did not start')), 15000);
|
|
120
|
+
proc.stdout.on('data', (d) => { if (/listening/.test(String(d))) { clearTimeout(t); resolve(); } });
|
|
121
|
+
proc.on('exit', (c) => { clearTimeout(t); reject(new Error(`server exited ${c}`)); });
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
after(() => {
|
|
126
|
+
if (proc) proc.kill();
|
|
127
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const get = (p) => fetch(base + p);
|
|
131
|
+
|
|
132
|
+
test('a recording with no poster gets the default picture, one with a poster keeps its own', async () => {
|
|
133
|
+
const html = await (await get('/fragments/videos/items')).text();
|
|
134
|
+
const row = (name) => html.slice(html.indexOf(`title="${name}.mp4`) - 1500, html.indexOf(`title="${name}.mp4`) + 1500);
|
|
135
|
+
assert.match(row('demo-draft-1'), /fx-thumb is-default/);
|
|
136
|
+
assert.match(html, /<span class="fx-thumb is-video"[^>]*>\s*<img src="[^"]*posted-poster\.jpg/);
|
|
137
|
+
const posted = html.slice(html.indexOf('posted-poster.jpg') - 200, html.indexOf('posted-poster.jpg') + 600);
|
|
138
|
+
assert.ok(!/is-default/.test(posted));
|
|
139
|
+
assert.match(await (await get('/fragments/audio/items')).text(), /fx-thumb is-default/);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test('pressing the picture of a recording or a sound plays it, like its Play button', async () => {
|
|
143
|
+
const videos = await (await get('/fragments/videos/items')).text();
|
|
144
|
+
assert.match(videos, /<span class="fx-thumb is-default" role="button" tabindex="0" title="Play demo-draft-1\.mp4"\s+hx-get="\/fragments\/videos\/play\/demo-draft-1\.mp4"/);
|
|
145
|
+
assert.match(videos, /<span class="fx-thumb is-video" role="button" tabindex="0" title="Play posted\.mp4"\s+hx-get="\/fragments\/videos\/play\/posted\.mp4"/);
|
|
146
|
+
assert.match(videos, /hx-trigger="click, keyup\[key=='Enter'\]" hx-target="#editor-modal-body"/);
|
|
147
|
+
assert.match(await (await get('/fragments/audio/items')).text(), /fx-thumb is-default" role="button"[^>]*hx-get="\/fragments\/audio\/play\/hello\.mp3"/);
|
|
148
|
+
// The same address the button uses, and it answers.
|
|
149
|
+
assert.strictEqual((await get('/fragments/videos/play/posted.mp4')).status, 200);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('only a recording that has a script shows the Transcript button', async () => {
|
|
153
|
+
const html = await (await get('/fragments/videos/items')).text();
|
|
154
|
+
assert.strictEqual((html.match(/fx-transcript/g) || []).length, 1);
|
|
155
|
+
assert.match(html, /hx-get="\/fragments\/videos\/transcript\/demo-draft-1\.mp4"/);
|
|
156
|
+
assert.match(html, /Read the voiceover script: demo-draft-1-voiceover-script\.md/);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('the player shows the words under the video, with times that seek', async () => {
|
|
160
|
+
const withScript = await (await get('/fragments/videos/play/demo-draft-1.mp4')).text();
|
|
161
|
+
assert.match(withScript, /<details class="transcript-details" open><summary>Transcript<\/summary>/);
|
|
162
|
+
assert.match(withScript, /data-seek="20"/);
|
|
163
|
+
assert.ok(!/<h4[^>]*>Transcript/.test(withScript), 'the summary says it, the body does not repeat it');
|
|
164
|
+
assert.match(withScript, /This is Webship 11, running on Drupal 11\./);
|
|
165
|
+
const without = await (await get('/fragments/videos/play/posted.mp4')).text();
|
|
166
|
+
assert.ok(!/transcript/.test(without));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('the transcript opens on its own, and says nothing it should not', async () => {
|
|
170
|
+
const r = await get('/fragments/videos/transcript/demo-draft-1.mp4');
|
|
171
|
+
assert.strictEqual(r.status, 200);
|
|
172
|
+
const html = await r.text();
|
|
173
|
+
assert.match(html, /from <a href="\/files\/docs\/demo-draft-1-voiceover-script\.md"/);
|
|
174
|
+
assert.ok(!/data-seek|<video|On screen/.test(html));
|
|
175
|
+
assert.strictEqual((await get('/fragments/videos/transcript/posted.mp4')).status, 404, 'no script');
|
|
176
|
+
assert.strictEqual((await get('/fragments/videos/transcript/nothing.mp4')).status, 404, 'no video');
|
|
177
|
+
assert.strictEqual((await get('/fragments/videos/transcript/..%2Fdocs%2Fdemo-draft-1-voiceover-script.md')).status, 404);
|
|
178
|
+
assert.strictEqual((await get('/fragments/videos/transcript/demo-draft-1.mp4.txt')).status, 404);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('the seek buttons and the card styles are in the front end', () => {
|
|
182
|
+
assert.match(fs.readFileSync(path.join(APP, 'public/media-player.js'), 'utf8'), /\[data-seek\][\s\S]*media\.currentTime = /);
|
|
183
|
+
assert.match(fs.readFileSync(path.join(APP, 'public/css/components/project-rows.css'), 'utf8'), /\.fx-thumb\.is-default/);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('the audio workspace lists sounds and what belongs to them, nothing else', async () => {
|
|
187
|
+
const html = await (await get('/fragments/audio/items?path=sounds')).text();
|
|
188
|
+
assert.match(html, /title="Play rain\.mp3/);
|
|
189
|
+
assert.match(html, /title="Play chime\.wav/);
|
|
190
|
+
for (const hidden of ['rain.txt', 'rain.json', 'rain.jpg', 'stray.bin', 'loose.txt', 'chime-cover.png', 'chime-transcript.md']) {
|
|
191
|
+
assert.ok(!html.includes(`title="${hidden}`) && !html.includes(`/${hidden}"`) || /fx-thumb[^>]*>\s*<img src="[^"]*(rain\.jpg|chime-cover\.png)/.test(html) && !/title="[^"]*(rain\.txt|stray)/.test(html), hidden);
|
|
192
|
+
}
|
|
193
|
+
assert.ok(!/stray\.bin|loose/.test(html), 'a file that is not a sound is not listed');
|
|
194
|
+
assert.match(html, /<span class="fx-thumb is-video"[^>]*>\s*<img src="[^"]*rain\.jpg/, 'the cover is the picture');
|
|
195
|
+
assert.match(html, /<span class="fx-thumb is-video"[^>]*>\s*<img src="[^"]*chime-cover\.png/, 'a -cover picture too');
|
|
196
|
+
assert.strictEqual((html.match(/fx-transcript/g) || []).length, 2, 'both have text');
|
|
197
|
+
assert.match(html, /hx-get="\/fragments\/audio\/transcript\/sounds\/rain\.mp3"/);
|
|
198
|
+
assert.match(html, /See how it was made, and the text/);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('a sound shows how it was made, and the text, in the player and on its own', async () => {
|
|
202
|
+
const play = await (await get('/fragments/audio/play/sounds/rain.mp3')).text();
|
|
203
|
+
assert.match(play, /<summary>How it was made<\/summary>/);
|
|
204
|
+
assert.match(play, /Made as<\/span> sound/);
|
|
205
|
+
assert.match(play, /With<\/span> elevenlabs/);
|
|
206
|
+
assert.match(play, /Loop<\/span> yes/);
|
|
207
|
+
assert.match(play, /<h4 class="uk-margin-small-bottom">Description /);
|
|
208
|
+
assert.match(play, /Rain on a tin roof/);
|
|
209
|
+
assert.match(play, /data-sound-speed="5"/);
|
|
210
|
+
const own = await get('/fragments/audio/transcript/sounds/rain.mp3');
|
|
211
|
+
assert.strictEqual(own.status, 200);
|
|
212
|
+
assert.match(await own.text(), /sound-record[\s\S]*Rain on a tin roof/);
|
|
213
|
+
const plain = await (await get('/fragments/audio/transcript/sounds/chime.wav')).text();
|
|
214
|
+
assert.match(plain, /A single chime\./);
|
|
215
|
+
assert.ok(!/sound-record/.test(plain), 'no record, so no facts line');
|
|
216
|
+
assert.strictEqual((await get('/fragments/audio/transcript/hello.mp3')).status, 404, 'nothing belongs to it');
|
|
217
|
+
assert.strictEqual((await get('/fragments/audio/transcript/sounds/rain.txt')).status, 404, 'only a sound opens one');
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('videos and audio list the newest files first, then the folders; other workspaces keep folders first', async () => {
|
|
221
|
+
const order = (html, ...names) => names.map((n) => html.indexOf(n));
|
|
222
|
+
const videos = await (await get('/fragments/videos/items')).text();
|
|
223
|
+
const [file, folder] = order(videos, 'title="Play demo-draft-1.mp4', 'older-takes');
|
|
224
|
+
assert.ok(file > 0 && folder > 0 && file < folder, 'a recording comes before the folder');
|
|
225
|
+
assert.match(videos, /<option value="files-first" selected>Files first<\/option>/);
|
|
226
|
+
const folders = await (await get('/fragments/videos/items?kind=all')).text();
|
|
227
|
+
const [f2, d2] = order(folders, 'title="Play demo-draft-1.mp4', 'older-takes');
|
|
228
|
+
assert.ok(d2 > 0 && f2 > 0 && d2 < f2, 'choosing Folders first puts the folder first');
|
|
229
|
+
assert.match(folders, /<option value="all" selected>Folders first<\/option>/);
|
|
230
|
+
assert.match(folders, /kind=all/, 'the choice travels with the pager and the search');
|
|
231
|
+
const sounds = await (await get('/fragments/audio/items')).text();
|
|
232
|
+
assert.match(sounds, /<option value="files-first" selected>/);
|
|
233
|
+
assert.ok(sounds.indexOf('title="Play hello.mp3') < sounds.indexOf('items?path=sounds'));
|
|
234
|
+
const docs = await (await get('/fragments/docs/items')).text();
|
|
235
|
+
assert.match(docs, /<option value="all" selected>Folders first<\/option>/);
|
|
236
|
+
assert.ok(docs.indexOf('notes-folder') < docs.indexOf('loose-note'), 'docs still lists folders first');
|
|
237
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const { test } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const { waitingState } = require('../../waiting');
|
|
4
|
+
|
|
5
|
+
// The real footer of a working Claude Code session, captured from a live run.
|
|
6
|
+
const BUSY = [' Ran 1 shell command',
|
|
7
|
+
'6 Thinking… (6m 16s · 23.9k tokens)',
|
|
8
|
+
' ⏵⏵ bypass permissions on (shift+tab to cycle) · esc to interrupt'].join('\n');
|
|
9
|
+
|
|
10
|
+
test('the interrupt hint means working, whatever else is on screen', () => {
|
|
11
|
+
assert.equal(waitingState(BUSY).waiting, false);
|
|
12
|
+
// Even with a question ABOVE it: the footer is the authority.
|
|
13
|
+
assert.equal(waitingState(`Do you want to proceed?\n❯ 1. Yes\n${BUSY}`).waiting, false);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('a numbered choice with a caret is waiting', () => {
|
|
17
|
+
const s = waitingState('Do you want to proceed?\n❯ 1. Yes\n 2. No');
|
|
18
|
+
assert.equal(s.waiting, true);
|
|
19
|
+
assert.match(s.why, /choice/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('yes/no in the shapes CLIs actually ask', () => {
|
|
23
|
+
assert.equal(waitingState('Overwrite the file? (y/N)').waiting, true);
|
|
24
|
+
assert.equal(waitingState('Continue [Y/n]').waiting, true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('an idle Claude prompt is flagged, more gently', () => {
|
|
28
|
+
const s = waitingState('⏵⏵ bypass permissions on (shift+tab to cycle)\n? for shortcuts');
|
|
29
|
+
assert.equal(s.waiting, true);
|
|
30
|
+
assert.match(s.why, /idle/);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('ordinary output is not a question', () => {
|
|
34
|
+
assert.equal(waitingState('Ran 3 shell commands\nAll five components verified.').waiting, false);
|
|
35
|
+
assert.equal(waitingState('').waiting, false);
|
|
36
|
+
assert.equal(waitingState(null).waiting, false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('a report that merely mentions a question is not waiting', () => {
|
|
40
|
+
// An agent writing prose about a question must not raise the flag.
|
|
41
|
+
assert.equal(waitingState('The ticket asks: do you want the picker? I implemented it.\nRan 2 commands').waiting, false);
|
|
42
|
+
assert.equal(waitingState('I asked whether to continue? and decided yes.\nMoving on.').waiting, false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('the question has to be the last thing on screen, not buried above', () => {
|
|
46
|
+
const buried = ['Do you want to proceed?', 'yes', 'Ran 4 commands', 'Wrote 2 files', 'Done.'].join('\n');
|
|
47
|
+
assert.equal(waitingState(buried).waiting, false);
|
|
48
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const test = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { sortWith } = require('../../build-order');
|
|
6
|
+
const { suggestProjectName } = require('../../project-name');
|
|
7
|
+
|
|
8
|
+
const root = path.join(__dirname, '..', '..', '..', '..');
|
|
9
|
+
const BUILDERS = ['cmd-wordpress-project.sh', 'cmd-wpbedrock1-project.sh', 'cmd-odoo20-project.sh'];
|
|
10
|
+
|
|
11
|
+
test('dev, test and sandboxes carry the WordPress and Odoo examples, and no other folder does', () => {
|
|
12
|
+
for (const ws of ['dev', 'test', 'sandboxes']) {
|
|
13
|
+
for (const f of BUILDERS) assert.ok(fs.existsSync(path.join(root, ws, f)), `${ws}/${f}`);
|
|
14
|
+
}
|
|
15
|
+
for (const ws of ['demos', 'projects', 'site-templates']) {
|
|
16
|
+
for (const f of BUILDERS) assert.ok(!fs.existsSync(path.join(root, ws, f)), `${ws}/${f}`);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('they come last in the Build list, WordPress before Odoo', () => {
|
|
21
|
+
const dir = path.join(root, 'dev');
|
|
22
|
+
const all = fs.readdirSync(dir).filter((f) => /^cmd-.*-project\.sh$/.test(f) && !/^cmd-tools?-/.test(f)).sort();
|
|
23
|
+
assert.deepEqual(sortWith(all, []).slice(-3), BUILDERS);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('each proposes a name of its own', () => {
|
|
27
|
+
const dir = path.join(root, 'dev');
|
|
28
|
+
const names = BUILDERS.map((f) => suggestProjectName(dir, f, { existing: [] }));
|
|
29
|
+
assert.deepEqual(names, ['wordpress1', 'wpbedrock1x00b1', 'odoo20x00b1']);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('Odoo runs on PostgreSQL through DDEV, and WordPress never writes database settings', () => {
|
|
33
|
+
const odoo = fs.readFileSync(path.join(root, 'core/scripts/functions/fun-odoo.sh'), 'utf8');
|
|
34
|
+
const wp = fs.readFileSync(path.join(root, 'core/scripts/functions/fun-wordpress.sh'), 'utf8');
|
|
35
|
+
assert.match(odoo, /--database=postgres:16/);
|
|
36
|
+
assert.match(odoo, /proxy_pass http:\/\/odoo:8069;/);
|
|
37
|
+
assert.doesNotMatch(wp, /wp config create|define\(\s*'DB_/);
|
|
38
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The npm way in: `webship-workspace init` makes an EMPTY workspace from this package (the core, a
|
|
3
|
+
* settings file with no workspaces and no plugins, the dashboard's dependencies), never overwrites a
|
|
4
|
+
* settings file on a second run, and `catalog` says what can be added.
|
|
5
|
+
*/
|
|
6
|
+
const assert = require('node:assert');
|
|
7
|
+
const { test } = require('node:test');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { spawnSync } = require('child_process');
|
|
12
|
+
|
|
13
|
+
const BIN = path.join(__dirname, '..', '..', '..', 'bin', 'workspace.js');
|
|
14
|
+
const run = (args, env = {}) => spawnSync(process.execPath, [BIN, ...args], { encoding: 'utf8', env: { ...process.env, WEBSHIP_WORKSPACE_SKIP_NPM: '1', WEBSHIP_WORKSPACE_SKIP_DDEV: '1', ...env } });
|
|
15
|
+
|
|
16
|
+
test('init makes an empty workspace: the core, the lean settings, nothing else', () => {
|
|
17
|
+
const dir = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'ws-init-')), 'workspace');
|
|
18
|
+
const r = run(['init', dir]);
|
|
19
|
+
assert.strictEqual(r.status, 0, r.stderr);
|
|
20
|
+
assert.match(r.stdout, /settings\.yml written \(empty/);
|
|
21
|
+
for (const f of ['core/scripts/plugins.sh', 'core/scripts/bootstrap.sh', 'core/workspace-app/server.js', 'core/workspace-app/plugin-loader.js', 'core/workspace-app/package.json', 'core/workspace-app/.app-version', 'core/config/settings.yml', 'README.md']) {
|
|
22
|
+
assert.ok(fs.existsSync(path.join(dir, f)), `${f} is there`);
|
|
23
|
+
}
|
|
24
|
+
assert.ok(!fs.existsSync(path.join(dir, 'core', 'workspace-app', 'node_modules')), 'no node_modules copied');
|
|
25
|
+
const settings = fs.readFileSync(path.join(dir, 'core', 'config', 'settings.yml'), 'utf8');
|
|
26
|
+
assert.match(settings, /^workspaces: \[\]$/m, 'no workspace folders yet');
|
|
27
|
+
assert.match(settings, /^plugins: \{\}$/m, 'no plugins yet');
|
|
28
|
+
assert.ok(!fs.existsSync(path.join(dir, 'dev')), 'no folders made');
|
|
29
|
+
assert.match(r.stdout, /Workspace folders — /, 'the catalogue is suggested');
|
|
30
|
+
// Again: a settings file a person may have edited is kept.
|
|
31
|
+
fs.appendFileSync(path.join(dir, 'core', 'config', 'settings.yml'), '# mine\n');
|
|
32
|
+
const r2 = run(['update', dir]);
|
|
33
|
+
assert.strictEqual(r2.status, 0, r2.stderr);
|
|
34
|
+
assert.match(r2.stdout, /settings\.yml kept/);
|
|
35
|
+
assert.match(fs.readFileSync(path.join(dir, 'core', 'config', 'settings.yml'), 'utf8'), /# mine/);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('the catalogue names every family with its npm packages, and plugin refuses an unknown verb', () => {
|
|
39
|
+
const r = run(['catalog']);
|
|
40
|
+
assert.strictEqual(r.status, 0);
|
|
41
|
+
for (const f of ['Workspace folders', 'Hosting providers', 'Git forges', 'Container runtimes']) assert.match(r.stdout, new RegExp(f));
|
|
42
|
+
assert.match(r.stdout, /@webship\.co\/workspace-github/);
|
|
43
|
+
const bad = run(['plugin', 'explode', 'x']);
|
|
44
|
+
assert.strictEqual(bad.status, 1);
|
|
45
|
+
const none = run(['plugin', 'list'], { WORKSPACE_ROOT: fs.mkdtempSync(path.join(os.tmpdir(), 'ws-none-')) });
|
|
46
|
+
assert.strictEqual(none.status, 1);
|
|
47
|
+
assert.match(none.stderr, /webship-workspace init/);
|
|
48
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tools on a home page workspace card: Edit opens the card editor, Settings opens the workspace's own file.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { test } = require('node:test');
|
|
6
|
+
const assert = require('node:assert');
|
|
7
|
+
|
|
8
|
+
const tools = require('../../workspace-card-tools');
|
|
9
|
+
|
|
10
|
+
const w = { key: 'dev', label: 'Dev', icon: 'tabler:code' };
|
|
11
|
+
|
|
12
|
+
test('the corner offers Edit and Settings for the workspace it is on', () => {
|
|
13
|
+
const html = tools.cornerHtml(w);
|
|
14
|
+
assert.match(html, /hx-get="\/fragments\/workspace-edit\/dev"[\s\S]*hx-target="#editor-modal-body"/);
|
|
15
|
+
assert.match(html, /href="\/settings\/workspace\.dev\.settings\.yml"/);
|
|
16
|
+
assert.match(html, /data-tabmodal-open="\/fragments\/settings\/modal\?tab=workspace\.dev\.settings\.yml"/);
|
|
17
|
+
assert.doesNotMatch(html, /ws-backups/);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('the backups count joins the corner when there are backups', () => {
|
|
21
|
+
const html = tools.cornerHtml(w, { backupCount: 2, backupsUrl: 'https://dev.example/backups' });
|
|
22
|
+
assert.match(html, /class="ws-backups" href="https:\/\/dev\.example\/backups" title="View the 2 backups for Dev"/);
|
|
23
|
+
assert.ok(html.indexOf('ws-backups') < html.indexOf('ws-card-tool'));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('the edit modal names the card and wraps the presentation editor', () => {
|
|
27
|
+
const html = tools.editModalHtml(w, '<div id="ws-presentation"></div>');
|
|
28
|
+
assert.match(html, /Edit the Dev card/);
|
|
29
|
+
assert.match(html, /<div id="ws-presentation"><\/div>/);
|
|
30
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deleting a workspace, against a temporary tree: only one with nothing in it goes, and only with both typed answers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { test } = require('node:test');
|
|
6
|
+
const assert = require('node:assert');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
const ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-delete-'));
|
|
12
|
+
const CONFIG = path.join(ROOT, 'core', 'config');
|
|
13
|
+
fs.mkdirSync(CONFIG, { recursive: true });
|
|
14
|
+
process.env.WORKSPACE_ROOT = ROOT;
|
|
15
|
+
process.env.WORKSPACE_CONFIG = CONFIG;
|
|
16
|
+
|
|
17
|
+
fs.writeFileSync(path.join(CONFIG, 'settings.yml'), '# kept\nworkspaces:\n - dev\n - emptyws\n - busyws\n - docs\nother: 1\n');
|
|
18
|
+
for (const name of ['dev', 'emptyws', 'busyws', 'docs']) {
|
|
19
|
+
fs.writeFileSync(path.join(CONFIG, `workspace.${name}.settings.yml`), `doc:\n name: ${name}\n`);
|
|
20
|
+
fs.mkdirSync(path.join(ROOT, name));
|
|
21
|
+
}
|
|
22
|
+
fs.writeFileSync(path.join(ROOT, 'emptyws', 'cmd-tools-remove.sh'), '#!/bin/bash\n');
|
|
23
|
+
fs.writeFileSync(path.join(ROOT, 'emptyws', 'README.md'), '# emptyws\n');
|
|
24
|
+
fs.mkdirSync(path.join(ROOT, 'busyws', 'project1'));
|
|
25
|
+
fs.mkdirSync(path.join(ROOT, 'docs', '.hidden'));
|
|
26
|
+
|
|
27
|
+
const wsDelete = require('../../workspace-delete');
|
|
28
|
+
|
|
29
|
+
test('a folder with only tooling is unused; content, hidden folders, backups and sessions are not', () => {
|
|
30
|
+
assert.deepStrictEqual(wsDelete.usage('emptyws', { sessions: [] }).reasons, []);
|
|
31
|
+
assert.match(wsDelete.usage('busyws', { sessions: [] }).reasons.join(), /1 item in its folder \(project1\)/);
|
|
32
|
+
assert.match(wsDelete.usage('docs', { sessions: [] }).reasons.join(), /\.hidden/);
|
|
33
|
+
assert.match(wsDelete.usage('emptyws', { sessions: ['emptyws--shell'] }).reasons.join(), /1 screen session running/);
|
|
34
|
+
assert.deepStrictEqual(wsDelete.usage('dev', { sessions: ['emptyws--shell'] }).reasons, []);
|
|
35
|
+
fs.mkdirSync(path.join(ROOT, 'backups', 'dev'), { recursive: true });
|
|
36
|
+
fs.writeFileSync(path.join(ROOT, 'backups', 'dev', 'x.tar.gz'), '');
|
|
37
|
+
assert.match(wsDelete.usage('dev', { sessions: [] }).reasons.join(), /1 backup file/);
|
|
38
|
+
assert.match(wsDelete.usage('nope').reasons.join(), /not a workspace/);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('the last step needs the name typed back and YES in capitals', () => {
|
|
42
|
+
assert.strictEqual(wsDelete.confirmed('emptyws', { confirmName: 'emptyws', confirmYes: 'YES' }), true);
|
|
43
|
+
assert.strictEqual(wsDelete.confirmed('emptyws', { confirmName: 'emptyws', confirmYes: 'yes' }), false);
|
|
44
|
+
assert.strictEqual(wsDelete.confirmed('emptyws', { confirmName: 'busyws', confirmYes: 'YES' }), false);
|
|
45
|
+
assert.strictEqual(wsDelete.confirmed('emptyws', { confirmYes: 'YES' }), false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('a workspace in use is refused and left as it is', () => {
|
|
49
|
+
const r = wsDelete.deleteWorkspace('busyws');
|
|
50
|
+
assert.match(r.error, /in use/);
|
|
51
|
+
assert.ok(fs.existsSync(path.join(ROOT, 'busyws', 'project1')));
|
|
52
|
+
assert.ok(fs.existsSync(path.join(CONFIG, 'workspace.busyws.settings.yml')));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('deleting an unused one removes its line, settings file and folder, and nothing else', () => {
|
|
56
|
+
const r = wsDelete.deleteWorkspace('emptyws');
|
|
57
|
+
assert.ok(!r.error, r.error);
|
|
58
|
+
assert.strictEqual(fs.readFileSync(path.join(CONFIG, 'settings.yml'), 'utf8'),
|
|
59
|
+
'# kept\nworkspaces:\n - dev\n - busyws\n - docs\nother: 1\n');
|
|
60
|
+
assert.ok(!fs.existsSync(path.join(CONFIG, 'workspace.emptyws.settings.yml')));
|
|
61
|
+
assert.ok(!fs.existsSync(path.join(ROOT, 'emptyws')));
|
|
62
|
+
assert.ok(fs.existsSync(path.join(ROOT, 'dev')));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('the steps ask for the name, then YES', () => {
|
|
66
|
+
const use = { reasons: [], tooling: ['cmd-a.sh'] };
|
|
67
|
+
const two = wsDelete.stepHtml('demo-x', 'name', use);
|
|
68
|
+
assert.match(two, /Step 2 of 3/);
|
|
69
|
+
assert.match(two, /name="confirmName"/);
|
|
70
|
+
assert.match(two, /"step":"yes"/);
|
|
71
|
+
const three = wsDelete.stepHtml('demo-x', 'yes', use, 'Type YES');
|
|
72
|
+
assert.match(three, /Step 3 of 3/);
|
|
73
|
+
assert.match(three, /name="confirmYes"/);
|
|
74
|
+
assert.match(three, /"step":"delete","confirmName":"demo-x"/);
|
|
75
|
+
assert.match(wsDelete.rowControlHtml('demo-x', use), /arm-step[\s\S]*hx-trigger="confirmed-remove"/);
|
|
76
|
+
assert.match(wsDelete.rowControlHtml('dev', { reasons: ['2 items'], tooling: [] }), /in use/);
|
|
77
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The type layer: policy over which actions a workspace's project rows offer.
|
|
3
|
+
*
|
|
4
|
+
* The promise pinned here is "nothing changes until somebody edits a type" — every shipped
|
|
5
|
+
* folder must resolve to a default that carries exactly the interface it had before types
|
|
6
|
+
* existed, and an explicit `type:` must win over that default.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { test } = require('node:test');
|
|
10
|
+
const assert = require('node:assert');
|
|
11
|
+
|
|
12
|
+
const { TYPES, DEFAULT_TYPE_BY_KEY, isType, resolveType, typeActions } = require('../../workspace-types');
|
|
13
|
+
const { loadWorkspaces, CONFIG_DIR } = require('../../workspaces');
|
|
14
|
+
|
|
15
|
+
test('every default points at a real type', () => {
|
|
16
|
+
for (const [key, type] of Object.entries(DEFAULT_TYPE_BY_KEY)) {
|
|
17
|
+
assert.ok(isType(type), `${key} defaults to unknown type '${type}'`);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('an explicit type wins, a bad one falls back, kind decides the rest', () => {
|
|
22
|
+
assert.equal(resolveType('dev', 'code', 'projects'), 'code');
|
|
23
|
+
assert.equal(resolveType('dev', 'not-a-type', 'projects'), 'sites');
|
|
24
|
+
assert.equal(resolveType('brand-new', undefined, 'files'), 'files');
|
|
25
|
+
assert.equal(resolveType('brand-new', undefined, 'projects'), 'sites');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('sites carry the full toolbelt, code drops the site controls, lists carry none', () => {
|
|
29
|
+
const sites = typeActions('sites');
|
|
30
|
+
for (const a of ['build', 'ddev', 'backup', 'remove', 'ragify', 'tests', 'checks', 'users']) {
|
|
31
|
+
assert.ok(sites.has(a), `sites misses ${a}`);
|
|
32
|
+
}
|
|
33
|
+
const code = typeActions('code');
|
|
34
|
+
for (const a of ['ddev', 'users', 'tests']) assert.ok(!code.has(a), `code should not carry ${a}`);
|
|
35
|
+
for (const a of ['backup', 'remove', 'ragify', 'checks']) assert.ok(code.has(a), `code misses ${a}`);
|
|
36
|
+
// Lists carry no project actions; files and media take only New folder, a page action.
|
|
37
|
+
for (const t of ['files', 'media']) assert.deepEqual([...typeActions(t)], ['folder'], `${t} should carry only folder`);
|
|
38
|
+
assert.equal(typeActions('ai-items').size, 0, 'ai-items should carry no actions');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('every loaded workspace resolves a type and its action set', () => {
|
|
42
|
+
for (const [key, meta] of Object.entries(loadWorkspaces())) {
|
|
43
|
+
assert.ok(isType(meta.type), `${key} resolved to unknown type '${meta.type}'`);
|
|
44
|
+
assert.ok(meta.typeActions instanceof Set, `${key} has no typeActions set`);
|
|
45
|
+
// files-kind pages have no project rows to act on — their types agree.
|
|
46
|
+
if (meta.kind === 'files' && !meta.alsoFiles) {
|
|
47
|
+
assert.equal(meta.typeActions.has('ddev'), false, `${key} is a file list but allows ddev`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('the settings dropdown and the registry cannot drift', () => {
|
|
53
|
+
const fs = require('fs');
|
|
54
|
+
const path = require('path');
|
|
55
|
+
const labels = fs.readFileSync(path.join(CONFIG_DIR, 'settings.labels.yml'), 'utf8');
|
|
56
|
+
for (const type of Object.keys(TYPES)) {
|
|
57
|
+
assert.ok(labels.includes(` ${type}:`), `settings.labels.yml type options miss '${type}'`);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('a plugin can allow its action on the types it names, and only there', () => {
|
|
62
|
+
const { addTypeAction } = require('../../workspace-types');
|
|
63
|
+
addTypeAction('x-cloud', ['sites']);
|
|
64
|
+
assert.ok(typeActions('sites').has('x-cloud'));
|
|
65
|
+
assert.ok(!typeActions('code').has('x-cloud'));
|
|
66
|
+
addTypeAction('x-cloud', ['sites']);
|
|
67
|
+
assert.strictEqual([...typeActions('sites')].filter((a) => a === 'x-cloud').length, 1, 'registered once');
|
|
68
|
+
});
|