@webship.co/workspace 1.0.0-beta1 → 1.0.0-beta2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +339 -0
- package/README.md +28 -364
- package/core/bin/workspace.js +135 -0
- package/core/config/settings.labels.yml +514 -0
- package/core/config/settings.template.yml +292 -0
- package/core/config/style.yml +53 -0
- package/core/config/theme.default.yml +161 -0
- package/core/config/theme.dispatch.yml +166 -0
- package/core/config/theme.drupal.yml +175 -0
- package/core/config/theme.ember.yml +160 -0
- package/core/config/theme.github.yml +172 -0
- package/core/config/theme.gitlab.yml +168 -0
- package/core/config/theme.press.yml +161 -0
- package/core/config/theme.slate.yml +160 -0
- package/core/config/theme.upsun.yml +174 -0
- package/core/docs/architecture.svg +65 -0
- package/core/docs/getting-started.md +51 -0
- package/core/docs/plugins.md +27 -0
- package/core/docs/releases.md +23 -0
- package/core/docs/testing.md +15 -0
- package/core/docs/workspaces.md +25 -0
- package/core/scripts/args/arg-ai-items.sh +65 -0
- package/core/scripts/args/arg-autostart.sh +31 -0
- package/core/scripts/args/arg-avatar-agent.sh +28 -0
- package/core/scripts/args/arg-avatar-catalog.sh +12 -0
- package/core/scripts/args/arg-avatar-character.sh +68 -0
- package/core/scripts/args/arg-avatar-voice.sh +24 -0
- package/core/scripts/args/arg-avatar.sh +22 -0
- package/core/scripts/args/arg-checks.sh +36 -0
- package/core/scripts/args/arg-clone.sh +42 -0
- package/core/scripts/args/arg-commands.sh +51 -0
- package/core/scripts/args/arg-cucumber.sh +34 -0
- package/core/scripts/args/arg-distribution.sh +30 -0
- package/core/scripts/args/arg-drupal.sh +39 -0
- package/core/scripts/args/arg-drupal_cms.sh +18 -0
- package/core/scripts/args/arg-gitlab-ci-local.sh +25 -0
- package/core/scripts/args/arg-graphify.sh +36 -0
- package/core/scripts/args/arg-graphs.sh +12 -0
- package/core/scripts/args/arg-hostnames.sh +23 -0
- package/core/scripts/args/arg-milvus-backup.sh +8 -0
- package/core/scripts/args/arg-milvus-mcp.sh +12 -0
- package/core/scripts/args/arg-milvus.sh +32 -0
- package/core/scripts/args/arg-obsidian.sh +17 -0
- package/core/scripts/args/arg-odoo.sh +18 -0
- package/core/scripts/args/arg-penpot-backup.sh +8 -0
- package/core/scripts/args/arg-penpot-mcp.sh +15 -0
- package/core/scripts/args/arg-penpot-token.sh +17 -0
- package/core/scripts/args/arg-penpot.sh +44 -0
- package/core/scripts/args/arg-rag.sh +21 -0
- package/core/scripts/args/arg-ragify.sh +35 -0
- package/core/scripts/args/arg-reclaim.sh +18 -0
- package/core/scripts/args/arg-release.sh +21 -0
- package/core/scripts/args/arg-screen.sh +28 -0
- package/core/scripts/args/arg-site-template.sh +13 -0
- package/core/scripts/args/arg-spdd.sh +16 -0
- package/core/scripts/args/arg-specs.sh +22 -0
- package/core/scripts/args/arg-storage.sh +36 -0
- package/core/scripts/args/arg-testing.sh +36 -0
- package/core/scripts/args/arg-webship.sh +34 -0
- package/core/scripts/args/arg-webships-project.sh +22 -0
- package/core/scripts/args/arg-webships-site-template.sh +17 -0
- package/core/scripts/args/arg-webships.sh +34 -0
- package/core/scripts/args/arg-website-site-template.sh +17 -0
- package/core/scripts/args/arg-website.sh +25 -0
- package/core/scripts/args/arg-wordpress.sh +18 -0
- package/core/scripts/autostart-run.sh +162 -0
- package/core/scripts/autostart-stop.sh +30 -0
- package/core/scripts/autostart-watch.sh +43 -0
- package/core/scripts/autostart.sh +26 -0
- package/core/scripts/bootstrap-functions.sh +117 -0
- package/core/scripts/bootstrap-libraries.sh +10 -0
- package/core/scripts/bootstrap.sh +71 -0
- package/core/scripts/ddev-shim/ddev +122 -0
- package/core/scripts/docker-gid.sh +119 -0
- package/core/scripts/functions/arg-site-template.sh +28 -0
- package/core/scripts/functions/fun-ai-items-lock.sh +175 -0
- package/core/scripts/functions/fun-ai-items-sources.sh +298 -0
- package/core/scripts/functions/fun-ai-items.sh +621 -0
- package/core/scripts/functions/fun-ai-providers.sh +72 -0
- package/core/scripts/functions/fun-autostart.sh +429 -0
- package/core/scripts/functions/fun-avatar.sh +441 -0
- package/core/scripts/functions/fun-backup-item.sh +50 -0
- package/core/scripts/functions/fun-build-distribution.sh +226 -0
- package/core/scripts/functions/fun-build-site-template.sh +104 -0
- package/core/scripts/functions/fun-build-webships.sh +161 -0
- package/core/scripts/functions/fun-build-website.sh +199 -0
- package/core/scripts/functions/fun-checks.sh +243 -0
- package/core/scripts/functions/fun-clone-project.sh +289 -0
- package/core/scripts/functions/fun-commands.sh +521 -0
- package/core/scripts/functions/fun-database.sh +63 -0
- package/core/scripts/functions/fun-ddev-hostnames.sh +213 -0
- package/core/scripts/functions/fun-ddev-nodejs.sh +15 -0
- package/core/scripts/functions/fun-ddev-telemetry.sh +15 -0
- package/core/scripts/functions/fun-default-settings.sh +28 -0
- package/core/scripts/functions/fun-distribution-cucumber.sh +45 -0
- package/core/scripts/functions/fun-distribution-drupal.sh +25 -0
- package/core/scripts/functions/fun-distribution-drupal_cms.sh +35 -0
- package/core/scripts/functions/fun-distribution-webship.sh +28 -0
- package/core/scripts/functions/fun-distribution-webships.sh +33 -0
- package/core/scripts/functions/fun-distribution-website.sh +19 -0
- package/core/scripts/functions/fun-drush.sh +48 -0
- package/core/scripts/functions/fun-elevenlabs.sh +179 -0
- package/core/scripts/functions/fun-files.sh +19 -0
- package/core/scripts/functions/fun-git-worktrees.sh +142 -0
- package/core/scripts/functions/fun-gitlab-ci.sh +156 -0
- package/core/scripts/functions/fun-hostnames-cli.sh +88 -0
- package/core/scripts/functions/fun-milvus.sh +584 -0
- package/core/scripts/functions/fun-odoo.sh +121 -0
- package/core/scripts/functions/fun-penpot.sh +724 -0
- package/core/scripts/functions/fun-project-name.sh +20 -0
- package/core/scripts/functions/fun-rag.sh +253 -0
- package/core/scripts/functions/fun-ragify.sh +165 -0
- package/core/scripts/functions/fun-reclaim.sh +297 -0
- package/core/scripts/functions/fun-release.sh +159 -0
- package/core/scripts/functions/fun-remove-project.sh +319 -0
- package/core/scripts/functions/fun-screen.sh +96 -0
- package/core/scripts/functions/fun-spdd.sh +676 -0
- package/core/scripts/functions/fun-specs-share.sh +237 -0
- package/core/scripts/functions/fun-storage.sh +347 -0
- package/core/scripts/functions/fun-testing.sh +329 -0
- package/core/scripts/functions/fun-users.sh +87 -0
- package/core/scripts/functions/fun-version.sh +72 -0
- package/core/scripts/functions/fun-video-edit.sh +48 -0
- package/core/scripts/functions/fun-voice.sh +45 -0
- package/core/scripts/functions/fun-wordpress.sh +59 -0
- package/core/scripts/host-mounts.sh +161 -0
- package/core/scripts/install.sh +602 -0
- package/core/scripts/libs/__pycache__/video_edit.cpython-312.pyc +0 -0
- package/core/scripts/libs/argparse.sh +93 -0
- package/core/scripts/libs/bash-yaml.sh +52 -0
- package/core/scripts/libs/checks_plan.js +312 -0
- package/core/scripts/libs/elevenlabs.py +627 -0
- package/core/scripts/libs/progress-bar.sh +19 -0
- package/core/scripts/libs/rag_index.py +446 -0
- package/core/scripts/libs/video_edit.py +636 -0
- package/core/scripts/libs/voice.py +430 -0
- package/core/scripts/plugins/jira/host-install.sh +119 -0
- package/core/scripts/plugins/jira/host-mounts.txt +2 -0
- package/core/scripts/plugins/upsun/args/arg-upsun.sh +51 -0
- package/core/scripts/plugins/upsun/functions/fun-upsun.sh +304 -0
- package/core/scripts/plugins/upsun/host-install.sh +99 -0
- package/core/scripts/plugins/upsun/host-mounts.txt +1 -0
- package/core/scripts/plugins/upsun/upsun-ai.sh +133 -0
- package/core/scripts/plugins.sh +152 -0
- package/core/scripts/release.sh +69 -0
- package/core/scripts/storage.sh +39 -0
- package/core/scripts/sync-tooling.sh +246 -0
- package/core/scripts/tests/cmd-smoke-test.sh +109 -0
- package/core/scripts/trust-https.sh +257 -0
- package/core/workspace-app/.ddev/commands/web/test-dashboard +49 -0
- package/core/workspace-app/.ddev/config.yaml +400 -0
- package/core/workspace-app/.ddev/docker-compose.dood.yaml +45 -0
- package/core/workspace-app/.ddev/docker-compose.plugin-jira-twg.yaml +26 -0
- package/core/workspace-app/.ddev/docker-compose.plugin-upsun-upsun-cli.yaml +8 -0
- package/core/workspace-app/.ddev/docker-compose.screens.yaml +15 -0
- package/core/workspace-app/.ddev/nginx_full/nginx-site.conf +114 -0
- package/core/workspace-app/.ddev/web-build/Dockerfile.workspace-app +203 -0
- package/core/workspace-app/.ddev/web-build/ddev-shim-ddev +122 -0
- package/core/workspace-app/README.md +380 -0
- package/{actions → core/workspace-app/actions}/ddev.js +0 -1
- package/{actions → core/workspace-app/actions}/rag.js +0 -1
- package/{actions → core/workspace-app/actions}/testing.js +0 -1
- package/{assistant.js → core/workspace-app/assistant.js} +1 -10
- package/{git-info.js → core/workspace-app/git-info.js} +17 -1
- package/core/workspace-app/package.json +29 -0
- package/core/workspace-app/plugin-catalog.js +50 -0
- package/{plugins-page.js → core/workspace-app/plugins-page.js} +28 -2
- package/{server.js → core/workspace-app/server.js} +18 -0
- package/core/workspace-app/tests/features/actions-rail.feature +44 -0
- package/core/workspace-app/tests/features/ai-items.feature +23 -0
- package/core/workspace-app/tests/features/assistant-speech.feature +63 -0
- package/core/workspace-app/tests/features/checks.feature +39 -0
- package/core/workspace-app/tests/features/clone-project.feature +30 -0
- package/core/workspace-app/tests/features/command-authoring.feature +58 -0
- package/core/workspace-app/tests/features/dashboard.feature +57 -0
- package/core/workspace-app/tests/features/menus.feature +29 -0
- package/core/workspace-app/tests/features/plugins/jira/jira.feature +47 -0
- package/core/workspace-app/tests/features/plugins.feature +20 -0
- package/core/workspace-app/tests/features/project-actions.feature +40 -0
- package/core/workspace-app/tests/features/screens.feature +29 -0
- package/core/workspace-app/tests/features/settings.feature +46 -0
- package/core/workspace-app/tests/features/test-reports.feature +32 -0
- package/core/workspace-app/tests/features/theme.feature +40 -0
- package/core/workspace-app/tests/step-definitions/actions-post.steps.js +25 -0
- package/core/workspace-app/tests/step-definitions/assistant-speech.steps.js +152 -0
- package/core/workspace-app/tests/step-definitions/preconditions.steps.js +100 -0
- package/core/workspace-app/tests/unit/_isolated-config.js +31 -0
- package/core/workspace-app/tests/unit/accessibility.test.js +124 -0
- package/core/workspace-app/tests/unit/actions-rail-ux.test.js +56 -0
- package/core/workspace-app/tests/unit/actions-rail.test.js +51 -0
- package/core/workspace-app/tests/unit/ai-inventory.test.js +64 -0
- package/core/workspace-app/tests/unit/app-docs.test.js +64 -0
- package/core/workspace-app/tests/unit/assistant-avatar.test.js +110 -0
- package/core/workspace-app/tests/unit/assistant-cast.test.js +105 -0
- package/core/workspace-app/tests/unit/assistant-send-animation.test.js +15 -0
- package/core/workspace-app/tests/unit/assistant-suggestions.test.js +112 -0
- package/core/workspace-app/tests/unit/assistant-switches.test.js +53 -0
- package/core/workspace-app/tests/unit/assistant-views.test.js +146 -0
- package/core/workspace-app/tests/unit/assistant-working-indicator.test.js +49 -0
- package/core/workspace-app/tests/unit/assistant.test.js +61 -0
- package/core/workspace-app/tests/unit/audio-player.test.js +147 -0
- package/core/workspace-app/tests/unit/audio-voice.test.js +634 -0
- package/core/workspace-app/tests/unit/auth-google.test.js +180 -0
- package/core/workspace-app/tests/unit/autostart-watch.test.js +76 -0
- package/core/workspace-app/tests/unit/browsers.test.js +76 -0
- package/core/workspace-app/tests/unit/build-distribution.test.js +125 -0
- package/core/workspace-app/tests/unit/build-order.test.js +153 -0
- package/core/workspace-app/tests/unit/builder-args.test.js +113 -0
- package/core/workspace-app/tests/unit/builders.test.js +158 -0
- package/core/workspace-app/tests/unit/card-state-toggle.test.js +29 -0
- package/core/workspace-app/tests/unit/changelog.test.js +177 -0
- package/core/workspace-app/tests/unit/chat-thread.test.js +92 -0
- package/core/workspace-app/tests/unit/checks-plan.test.js +205 -0
- package/core/workspace-app/tests/unit/claude-args.test.js +182 -0
- package/core/workspace-app/tests/unit/claude-code-here.test.js +15 -0
- package/core/workspace-app/tests/unit/claude-resume.test.js +147 -0
- package/core/workspace-app/tests/unit/claude-screen-options.test.js +84 -0
- package/core/workspace-app/tests/unit/claude-session.test.js +41 -0
- package/core/workspace-app/tests/unit/command-flags.test.js +45 -0
- package/core/workspace-app/tests/unit/command-palette.test.js +105 -0
- package/core/workspace-app/tests/unit/commands-center.test.js +338 -0
- package/core/workspace-app/tests/unit/composer-menu.test.js +187 -0
- package/core/workspace-app/tests/unit/config-yaml.test.js +116 -0
- package/core/workspace-app/tests/unit/confirm-dialog.test.js +69 -0
- package/core/workspace-app/tests/unit/create-card.test.js +85 -0
- package/core/workspace-app/tests/unit/ddev-addons.test.js +144 -0
- package/core/workspace-app/tests/unit/ddev-auth-ssh.test.js +40 -0
- package/core/workspace-app/tests/unit/ddev-custom-commands.test.js +56 -0
- package/core/workspace-app/tests/unit/ddev-diagnose.test.js +57 -0
- package/core/workspace-app/tests/unit/ddev-domains.test.js +153 -0
- package/core/workspace-app/tests/unit/ddev-hostnames.test.js +143 -0
- package/core/workspace-app/tests/unit/ddev-import.test.js +82 -0
- package/core/workspace-app/tests/unit/ddev-mail.test.js +135 -0
- package/core/workspace-app/tests/unit/ddev-menu-and-create.test.js +28 -0
- package/core/workspace-app/tests/unit/ddev-mutagen.test.js +37 -0
- package/core/workspace-app/tests/unit/ddev-options.test.js +51 -0
- package/core/workspace-app/tests/unit/ddev-share.test.js +40 -0
- package/core/workspace-app/tests/unit/ddev-shim.test.js +57 -0
- package/core/workspace-app/tests/unit/ddev-snapshots.test.js +142 -0
- package/core/workspace-app/tests/unit/ddev-start-daemons.test.js +62 -0
- package/core/workspace-app/tests/unit/ddev-xdebug.test.js +41 -0
- package/core/workspace-app/tests/unit/deep-chat-origin.test.js +38 -0
- package/core/workspace-app/tests/unit/density.test.js +52 -0
- package/core/workspace-app/tests/unit/elevenlabs.test.js +453 -0
- package/core/workspace-app/tests/unit/event-log.test.js +102 -0
- package/core/workspace-app/tests/unit/favicon.test.js +68 -0
- package/core/workspace-app/tests/unit/files-browser.test.js +224 -0
- package/core/workspace-app/tests/unit/git-info.test.js +26 -0
- package/core/workspace-app/tests/unit/grid-and-list.test.js +103 -0
- package/core/workspace-app/tests/unit/host-tool-wrapper.test.js +18 -0
- package/core/workspace-app/tests/unit/html-patterns.test.js +51 -0
- package/core/workspace-app/tests/unit/ide-infra.test.js +28 -0
- package/core/workspace-app/tests/unit/images-ai.test.js +201 -0
- package/core/workspace-app/tests/unit/images-create.test.js +59 -0
- package/core/workspace-app/tests/unit/images-providers.test.js +248 -0
- package/core/workspace-app/tests/unit/images-svg.test.js +213 -0
- package/core/workspace-app/tests/unit/images-workspace.test.js +144 -0
- package/core/workspace-app/tests/unit/jobs-fragment.test.js +66 -0
- package/core/workspace-app/tests/unit/media-ux.test.js +164 -0
- package/core/workspace-app/tests/unit/modal-fit.test.js +87 -0
- package/core/workspace-app/tests/unit/modules-load.test.js +23 -0
- package/core/workspace-app/tests/unit/narration.test.js +186 -0
- package/core/workspace-app/tests/unit/navigation-editor.test.js +260 -0
- package/core/workspace-app/tests/unit/navigation.test.js +254 -0
- package/core/workspace-app/tests/unit/new-folder.test.js +121 -0
- package/core/workspace-app/tests/unit/new-screen-form.test.js +149 -0
- package/core/workspace-app/tests/unit/node-menu.test.js +161 -0
- package/core/workspace-app/tests/unit/notes.test.js +128 -0
- package/core/workspace-app/tests/unit/page-structure.test.js +40 -0
- package/core/workspace-app/tests/unit/page-width.test.js +59 -0
- package/core/workspace-app/tests/unit/php-version.test.js +90 -0
- package/core/workspace-app/tests/unit/plan-card.test.js +44 -0
- package/core/workspace-app/tests/unit/plugin-catalog.test.js +31 -0
- package/core/workspace-app/tests/unit/plugin-system.test.js +251 -0
- package/core/workspace-app/tests/unit/plugins-page.test.js +28 -0
- package/core/workspace-app/tests/unit/plugins.test.js +58 -0
- package/core/workspace-app/tests/unit/project-activity-filter.test.js +180 -0
- package/core/workspace-app/tests/unit/project-make.test.js +143 -0
- package/core/workspace-app/tests/unit/project-name.test.js +202 -0
- package/core/workspace-app/tests/unit/reader.test.js +62 -0
- package/core/workspace-app/tests/unit/release.test.js +92 -0
- package/core/workspace-app/tests/unit/remove-confirm.test.js +37 -0
- package/core/workspace-app/tests/unit/report-pdf.test.js +171 -0
- package/core/workspace-app/tests/unit/round-11.test.js +31 -0
- package/core/workspace-app/tests/unit/round-12.test.js +37 -0
- package/core/workspace-app/tests/unit/row-layout.test.js +585 -0
- package/core/workspace-app/tests/unit/row-menus-fold.test.js +29 -0
- package/core/workspace-app/tests/unit/rows-add-modal.test.js +68 -0
- package/core/workspace-app/tests/unit/screen-runner-queue.test.js +41 -0
- package/core/workspace-app/tests/unit/screen-window.test.js +116 -0
- package/core/workspace-app/tests/unit/screens-beside-running.test.js +19 -0
- package/core/workspace-app/tests/unit/screens-columns.test.js +79 -0
- package/core/workspace-app/tests/unit/screens-menu.test.js +84 -0
- package/core/workspace-app/tests/unit/screens-modal-overlay.test.js +14 -0
- package/core/workspace-app/tests/unit/screens-waiting.test.js +129 -0
- package/core/workspace-app/tests/unit/screens.test.js +74 -0
- package/core/workspace-app/tests/unit/second-storage.test.js +218 -0
- package/core/workspace-app/tests/unit/session-name.test.js +64 -0
- package/core/workspace-app/tests/unit/settings-file.test.js +173 -0
- package/core/workspace-app/tests/unit/settings-nested-list.test.js +54 -0
- package/core/workspace-app/tests/unit/settings-raw.test.js +177 -0
- package/core/workspace-app/tests/unit/shared-prefs.test.js +49 -0
- package/core/workspace-app/tests/unit/speech.test.js +290 -0
- package/core/workspace-app/tests/unit/static-caching.test.js +39 -0
- package/core/workspace-app/tests/unit/status-language.test.js +47 -0
- package/core/workspace-app/tests/unit/tab-modal.test.js +69 -0
- package/core/workspace-app/tests/unit/tab-title.test.js +37 -0
- package/core/workspace-app/tests/unit/template-literals.test.js +52 -0
- package/core/workspace-app/tests/unit/terminal-scrollback.test.js +61 -0
- package/core/workspace-app/tests/unit/terminal-size.test.js +103 -0
- package/core/workspace-app/tests/unit/terminal-style.test.js +84 -0
- package/core/workspace-app/tests/unit/terminal-windows.test.js +154 -0
- package/core/workspace-app/tests/unit/themes.test.js +453 -0
- package/core/workspace-app/tests/unit/tool-labels.test.js +34 -0
- package/core/workspace-app/tests/unit/topbar-icons.test.js +47 -0
- package/core/workspace-app/tests/unit/update-docker-gid.test.js +51 -0
- package/core/workspace-app/tests/unit/user-guide.test.js +60 -0
- package/core/workspace-app/tests/unit/version-check.test.js +98 -0
- package/core/workspace-app/tests/unit/version-link.test.js +50 -0
- package/core/workspace-app/tests/unit/video-borrow.test.js +198 -0
- package/core/workspace-app/tests/unit/video-edit-ask.test.js +93 -0
- package/core/workspace-app/tests/unit/video-edit-short.test.js +42 -0
- package/core/workspace-app/tests/unit/video-edit.test.js +417 -0
- package/core/workspace-app/tests/unit/video-transcript.test.js +237 -0
- package/core/workspace-app/tests/unit/waiting.test.js +48 -0
- package/core/workspace-app/tests/unit/wordpress-odoo-builders.test.js +38 -0
- package/core/workspace-app/tests/unit/workspace-bin.test.js +48 -0
- package/core/workspace-app/tests/unit/workspace-card-tools.test.js +30 -0
- package/core/workspace-app/tests/unit/workspace-delete.test.js +77 -0
- package/core/workspace-app/tests/unit/workspace-types.test.js +68 -0
- package/core/workspace-app/tests/unit/worktree-clone.test.js +250 -0
- package/package.json +38 -36
- /package/{PLUGINS.md → core/workspace-app/PLUGINS.md} +0 -0
- /package/{WORKSPACES.md → core/workspace-app/WORKSPACES.md} +0 -0
- /package/{actions → core/workspace-app/actions}/ai-items.js +0 -0
- /package/{actions → core/workspace-app/actions}/audio.js +0 -0
- /package/{actions → core/workspace-app/actions}/build-order.js +0 -0
- /package/{actions → core/workspace-app/actions}/checks.js +0 -0
- /package/{actions → core/workspace-app/actions}/claude-session.js +0 -0
- /package/{actions → core/workspace-app/actions}/commands-center.js +0 -0
- /package/{actions → core/workspace-app/actions}/composer.js +0 -0
- /package/{actions → core/workspace-app/actions}/ddev-addons.js +0 -0
- /package/{actions → core/workspace-app/actions}/ddev-mail.js +0 -0
- /package/{actions → core/workspace-app/actions}/folders.js +0 -0
- /package/{actions → core/workspace-app/actions}/images-svg.js +0 -0
- /package/{actions → core/workspace-app/actions}/images.js +0 -0
- /package/{actions → core/workspace-app/actions}/navigation.js +0 -0
- /package/{actions → core/workspace-app/actions}/node.js +0 -0
- /package/{actions → core/workspace-app/actions}/notes.js +0 -0
- /package/{actions → core/workspace-app/actions}/plugins.js +0 -0
- /package/{actions → core/workspace-app/actions}/project-make.js +0 -0
- /package/{actions → core/workspace-app/actions}/row-actions.js +0 -0
- /package/{actions → core/workspace-app/actions}/screens.js +0 -0
- /package/{actions → core/workspace-app/actions}/video-edit.js +0 -0
- /package/{ai-inventory.js → core/workspace-app/ai-inventory.js} +0 -0
- /package/{ai-items.js → core/workspace-app/ai-items.js} +0 -0
- /package/{assistant-cast.js → core/workspace-app/assistant-cast.js} +0 -0
- /package/{assistant-suggestions.js → core/workspace-app/assistant-suggestions.js} +0 -0
- /package/{audio-voice.js → core/workspace-app/audio-voice.js} +0 -0
- /package/{auth-google.js → core/workspace-app/auth-google.js} +0 -0
- /package/{build-ai.js → core/workspace-app/build-ai.js} +0 -0
- /package/{build-order-page.js → core/workspace-app/build-order-page.js} +0 -0
- /package/{build-order.js → core/workspace-app/build-order.js} +0 -0
- /package/{builder-args.js → core/workspace-app/builder-args.js} +0 -0
- /package/{changelog.js → core/workspace-app/changelog.js} +0 -0
- /package/{chat-threads.js → core/workspace-app/chat-threads.js} +0 -0
- /package/{checks.js → core/workspace-app/checks.js} +0 -0
- /package/{claude-args.js → core/workspace-app/claude-args.js} +0 -0
- /package/{claude-sessions.js → core/workspace-app/claude-sessions.js} +0 -0
- /package/{command-author.js → core/workspace-app/command-author.js} +0 -0
- /package/{command-block.js → core/workspace-app/command-block.js} +0 -0
- /package/{commands → core/workspace-app/commands}/defaults.yml +0 -0
- /package/{commands-page.js → core/workspace-app/commands-page.js} +0 -0
- /package/{commands.js → core/workspace-app/commands.js} +0 -0
- /package/{cucumber.js → core/workspace-app/cucumber.js} +0 -0
- /package/{ddev-addons-page.js → core/workspace-app/ddev-addons-page.js} +0 -0
- /package/{ddev-addons.js → core/workspace-app/ddev-addons.js} +0 -0
- /package/{ddev-custom-commands.js → core/workspace-app/ddev-custom-commands.js} +0 -0
- /package/{ddev-domains.js → core/workspace-app/ddev-domains.js} +0 -0
- /package/{ddev-import.js → core/workspace-app/ddev-import.js} +0 -0
- /package/{ddev-mail-page.js → core/workspace-app/ddev-mail-page.js} +0 -0
- /package/{ddev-mail.js → core/workspace-app/ddev-mail.js} +0 -0
- /package/{ddev-options.js → core/workspace-app/ddev-options.js} +0 -0
- /package/{ddev-snapshots.js → core/workspace-app/ddev-snapshots.js} +0 -0
- /package/{ddev.js → core/workspace-app/ddev.js} +0 -0
- /package/{docs → core/workspace-app/docs}/DEVELOPER-GUIDE.md +0 -0
- /package/{docs → core/workspace-app/docs}/README.md +0 -0
- /package/{docs → core/workspace-app/docs}/REFERENCE.md +0 -0
- /package/{docs → core/workspace-app/docs}/USER-GUIDE.md +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-board.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-coffee.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-colours.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-icons.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-moves.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-pick-cookieman.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-settings-voices.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-status.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/create-card/create-card.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/create-card/start-ai.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-address.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-off.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-on.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-ddev-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-domains.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-home.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-ide-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-remove-refusal.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-rows-worktree.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-screens.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-settings.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/guide-workspace-page.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/card-settings.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/drag-reorder.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/home/edit-card.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/claude-code-image.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/edit-with-ai.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/gallery.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/generate-description.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/generate-plan.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/providers.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/svg-form.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/svg-result.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/images/upload.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/2-form.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/8-ddev-options.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/new-folder/files-folder.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/projects/ddev-custom-commands.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/projects/state-chip-stop.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/ai.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/code.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/docs.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/services.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/sites.svg +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-scenarios.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/test-steps.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-dark.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-light.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/screens-menu.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/screens-new.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/auth-google-lists.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-1-row.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-2-confirm.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-3-type-name.png +0 -0
- /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-4-type-yes.png +0 -0
- /package/{event-log.js → core/workspace-app/event-log.js} +0 -0
- /package/{files-browser.js → core/workspace-app/files-browser.js} +0 -0
- /package/{folders.js → core/workspace-app/folders.js} +0 -0
- /package/{format.js → core/workspace-app/format.js} +0 -0
- /package/{github-auth.js → core/workspace-app/github-auth.js} +0 -0
- /package/{html.js → core/workspace-app/html.js} +0 -0
- /package/{icons.js → core/workspace-app/icons.js} +0 -0
- /package/{images-ai.js → core/workspace-app/images-ai.js} +0 -0
- /package/{images-create.js → core/workspace-app/images-create.js} +0 -0
- /package/{images-page.js → core/workspace-app/images-page.js} +0 -0
- /package/{images-records.js → core/workspace-app/images-records.js} +0 -0
- /package/{images-render.js → core/workspace-app/images-render.js} +0 -0
- /package/{images-svg-core.js → core/workspace-app/images-svg-core.js} +0 -0
- /package/{images-svg-sanitize.js → core/workspace-app/images-svg-sanitize.js} +0 -0
- /package/{images-svg.js → core/workspace-app/images-svg.js} +0 -0
- /package/{images.js → core/workspace-app/images.js} +0 -0
- /package/{jobs.js → core/workspace-app/jobs.js} +0 -0
- /package/{linger.js → core/workspace-app/linger.js} +0 -0
- /package/{lists.js → core/workspace-app/lists.js} +0 -0
- /package/{media-info.js → core/workspace-app/media-info.js} +0 -0
- /package/{navigation-page.js → core/workspace-app/navigation-page.js} +0 -0
- /package/{navigation.js → core/workspace-app/navigation.js} +0 -0
- /package/{notes.js → core/workspace-app/notes.js} +0 -0
- /package/{palette-index.js → core/workspace-app/palette-index.js} +0 -0
- /package/{playwright.config.js → core/workspace-app/playwright.config.js} +0 -0
- /package/{plugin-install.js → core/workspace-app/plugin-install.js} +0 -0
- /package/{plugin-loader.js → core/workspace-app/plugin-loader.js} +0 -0
- /package/{plugin-manifest.js → core/workspace-app/plugin-manifest.js} +0 -0
- /package/{plugin-settings.js → core/workspace-app/plugin-settings.js} +0 -0
- /package/{plugins → core/workspace-app/plugins}/workspace-notes/index.js +0 -0
- /package/{project-make.js → core/workspace-app/project-make.js} +0 -0
- /package/{project-name.js → core/workspace-app/project-name.js} +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar-moves.js +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar-ui.js +0 -0
- /package/{public → core/workspace-app/public}/assistant-avatar.js +0 -0
- /package/{public → core/workspace-app/public}/avatar/druplicon.vrm +0 -0
- /package/{public → core/workspace-app/public}/css/base.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/ai-assistant.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/assistant-avatar.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/assistant-views.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/build-order.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/clone-as-a-git-worktree.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/command-palette.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/commands-center.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/confirm-dialog.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/create-card.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/ddev-menu.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/deep-chat.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/density.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/floating-assistant-workspace-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/folders.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/full-height-left-assistant-sidebar-all-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/full-height-right-actions-rail-pages-that-have-actions.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/grid-and-list.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/help-page.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/hero.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/images.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/layout-rhythm-and-spacing-polish.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/mic-on-any-field-that-wants-typing.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/modal-fit.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/page-width.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/plugins-page.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/project-cards.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/project-rows.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/reordering-the-workspace-cards.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/responsive-home-and-the-workspace-pages.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/running-commands-as-toasts.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/settings-editor-core-config-yml.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/short-results-as-a-toast-at-the-top.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/sound-player.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/suggest-agents-skills-commands.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/tab-modal.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/terminals.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/test-report-media-one-file-per-row-paged.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/top-toolbar-logo-breadcrumb.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/video-edit.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/web-ide-open-with.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/working-indicators.css +0 -0
- /package/{public → core/workspace-app/public}/css/components/workspace-cards.css +0 -0
- /package/{public → core/workspace-app/public}/favicon.ico +0 -0
- /package/{public → core/workspace-app/public}/favicon.png +0 -0
- /package/{public → core/workspace-app/public}/htmx.min.js +0 -0
- /package/{public → core/workspace-app/public}/logo.png +0 -0
- /package/{public → core/workspace-app/public}/media-player.js +0 -0
- /package/{public → core/workspace-app/public}/mic.js +0 -0
- /package/{public → core/workspace-app/public}/narration.js +0 -0
- /package/{public → core/workspace-app/public}/palette.js +0 -0
- /package/{public → core/workspace-app/public}/sortable.js +0 -0
- /package/{public → core/workspace-app/public}/suggest.js +0 -0
- /package/{public → core/workspace-app/public}/terminal.js +0 -0
- /package/{public → core/workspace-app/public}/themes/default/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/ember/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/press/theme.css +0 -0
- /package/{public → core/workspace-app/public}/themes/slate/theme.css +0 -0
- /package/{public → core/workspace-app/public}/touch-icon.png +0 -0
- /package/{public → core/workspace-app/public}/ui.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ace.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ext-language_tools.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/ext-searchbox.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-css.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-html.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-javascript.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-markdown.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-sh.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/mode-yaml.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/theme-textmate.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/theme-tomorrow_night.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/ace/worker-yaml.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/deep-chat.LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/deep-chat.bundle.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
- /package/{public → core/workspace-app/public}/vendor/tabler-LICENSE.txt +0 -0
- /package/{public → core/workspace-app/public}/vendor/tabler-sprite.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/loaders/GLTFLoader.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/utils/BufferGeometryUtils.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/addons/utils/SkeletonUtils.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/three.core.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three/three.module.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/three-vrm/LICENSE +0 -0
- /package/{public → core/workspace-app/public}/vendor/three-vrm/three-vrm.module.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit-icons.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit.min.css +0 -0
- /package/{public → core/workspace-app/public}/vendor/uikit.min.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/bookmark-outlined.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/headset.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/menu.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/pause.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/play.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/share.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-off.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-on.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/stop.svg +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-fit.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-unicode11.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/addon-web-links.js +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/xterm.css +0 -0
- /package/{public → core/workspace-app/public}/vendor/xterm/xterm.js +0 -0
- /package/{rag.js → core/workspace-app/rag.js} +0 -0
- /package/{reader.js → core/workspace-app/reader.js} +0 -0
- /package/{repo-blobs.js → core/workspace-app/repo-blobs.js} +0 -0
- /package/{report-pdf.js → core/workspace-app/report-pdf.js} +0 -0
- /package/{row-layout.js → core/workspace-app/row-layout.js} +0 -0
- /package/{screenrc → core/workspace-app/screenrc} +0 -0
- /package/{screens.js → core/workspace-app/screens.js} +0 -0
- /package/{scripts → core/workspace-app/scripts}/code-server-start.sh +0 -0
- /package/{scripts → core/workspace-app/scripts}/images-claude-finish.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-host.sh +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-runner.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/screen-then.js +0 -0
- /package/{scripts → core/workspace-app/scripts}/update.sh +0 -0
- /package/{settings-file.js → core/workspace-app/settings-file.js} +0 -0
- /package/{speech.js → core/workspace-app/speech.js} +0 -0
- /package/{tab-modal.js → core/workspace-app/tab-modal.js} +0 -0
- /package/{test-reports.js → core/workspace-app/test-reports.js} +0 -0
- /package/{themes.js → core/workspace-app/themes.js} +0 -0
- /package/{transcripts.js → core/workspace-app/transcripts.js} +0 -0
- /package/{urls.js → core/workspace-app/urls.js} +0 -0
- /package/{validate.js → core/workspace-app/validate.js} +0 -0
- /package/{version-check.js → core/workspace-app/version-check.js} +0 -0
- /package/{video-edit.js → core/workspace-app/video-edit.js} +0 -0
- /package/{waiting.js → core/workspace-app/waiting.js} +0 -0
- /package/{workspace-card-tools.js → core/workspace-app/workspace-card-tools.js} +0 -0
- /package/{workspace-delete.js → core/workspace-app/workspace-delete.js} +0 -0
- /package/{workspace-types.js → core/workspace-app/workspace-types.js} +0 -0
- /package/{workspaces.js → core/workspace-app/workspaces.js} +0 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Second storage: whole workspace folders on another disk, each a symlink from the tree.
|
|
3
|
+
*
|
|
4
|
+
* What matters is that nothing is lost: a move swaps only after a checksum compare, keeps the original,
|
|
5
|
+
* refuses what it cannot do safely (DDEV projects, no room, a non-empty target), and the dashboard still
|
|
6
|
+
* serves files from a workspace that is a symlink while refusing a link that leads out of it.
|
|
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 fb = require('../../files-browser');
|
|
17
|
+
|
|
18
|
+
const ROOT = path.join(__dirname, '..', '..', '..', '..');
|
|
19
|
+
const FUN = path.join(ROOT, 'core', 'scripts', 'functions', 'fun-storage.sh');
|
|
20
|
+
const ARGS = path.join(ROOT, 'core', 'scripts', 'args', 'arg-storage.sh');
|
|
21
|
+
const CMD = path.join(ROOT, 'core', 'scripts', 'storage.sh');
|
|
22
|
+
const SYNC = path.join(ROOT, 'core', 'scripts', 'sync-tooling.sh');
|
|
23
|
+
|
|
24
|
+
/** A workspace root and a "drive", both in a temp dir. The drive counts as its own filesystem. */
|
|
25
|
+
function fixture(storage) {
|
|
26
|
+
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-storage-'));
|
|
27
|
+
const root = path.join(base, 'workspace');
|
|
28
|
+
const drive = path.join(base, 'drive', 'workspace');
|
|
29
|
+
fs.mkdirSync(path.join(root, 'videos', 'clips'), { recursive: true });
|
|
30
|
+
fs.writeFileSync(path.join(root, 'videos', 'a.mp4'), 'video bytes');
|
|
31
|
+
fs.writeFileSync(path.join(root, 'videos', 'clips', 'b.mp4'), 'more bytes');
|
|
32
|
+
fs.mkdirSync(path.join(root, 'dev', 'site1', '.ddev'), { recursive: true });
|
|
33
|
+
fs.writeFileSync(path.join(root, 'dev', 'site1', '.ddev', 'config.yaml'), 'name: site1\n');
|
|
34
|
+
fs.mkdirSync(drive, { recursive: true });
|
|
35
|
+
return { base, root, drive, storage: storage === undefined ? drive : storage };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Run bash with the functions loaded against the fixture. `prelude` can redefine a function. */
|
|
39
|
+
function sh(fx, snippet, prelude = 'function storage_same_fs() { return 1 ; } ; function storage_avail_bytes() { echo 999999999999 ; }') {
|
|
40
|
+
const script = [
|
|
41
|
+
`source ${JSON.stringify(FUN)}`,
|
|
42
|
+
`WORKSPACE_ROOT=${JSON.stringify(fx.root)}`,
|
|
43
|
+
'WORKSPACE_SCRIPTS=/x/core/scripts',
|
|
44
|
+
'workspaces=(dev videos docs)',
|
|
45
|
+
`storage_second=${JSON.stringify(JSON.stringify(fx.storage))}`,
|
|
46
|
+
'STORAGE_RSYNC_FLAGS=-aH',
|
|
47
|
+
prelude,
|
|
48
|
+
snippet,
|
|
49
|
+
].join('\n');
|
|
50
|
+
const r = spawnSync('bash', ['-c', script], { encoding: 'utf8' });
|
|
51
|
+
return { code: r.status, out: (r.stdout + r.stderr).trim() };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
test('the shell files are valid, and storage.sh is in the smoke test', () => {
|
|
55
|
+
for (const f of [FUN, ARGS, CMD, SYNC]) assert.strictEqual(spawnSync('bash', ['-n', f]).status, 0, f);
|
|
56
|
+
const smoke = fs.readFileSync(path.join(ROOT, 'core/scripts/tests/cmd-smoke-test.sh'), 'utf8');
|
|
57
|
+
assert.match(smoke, /MACHINE_SCRIPTS=\([^)]*\bstorage\.sh\b/);
|
|
58
|
+
const boot = fs.readFileSync(path.join(ROOT, 'core/scripts/bootstrap-functions.sh'), 'utf8');
|
|
59
|
+
assert.match(boot, /functions\/fun-storage\.sh/);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('the committed settings ship no machine path', () => {
|
|
63
|
+
const yml = fs.readFileSync(path.join(ROOT, 'core/config/settings.yml'), 'utf8');
|
|
64
|
+
assert.match(yml, /^storage:\n {2}second: ""$/m);
|
|
65
|
+
const ignore = fs.readFileSync(path.join(ROOT, '.gitignore'), 'utf8');
|
|
66
|
+
assert.match(ignore, /^core\/workspace-app\/\.ddev\/docker-compose\.second-storage\.yaml$/m);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('the path is read with parse_yaml quotes stripped, and empty means none', () => {
|
|
70
|
+
const fx = fixture('');
|
|
71
|
+
try {
|
|
72
|
+
assert.strictEqual(sh(fx, 'storage_path').out, '');
|
|
73
|
+
assert.strictEqual(sh({ ...fx, storage: '/mnt/x/workspace/' }, 'storage_path').out, '/mnt/x/workspace');
|
|
74
|
+
} finally { fs.rmSync(fx.base, { recursive: true, force: true }); }
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('a move copies, verifies, swaps to a symlink and keeps the original', () => {
|
|
78
|
+
const fx = fixture();
|
|
79
|
+
try {
|
|
80
|
+
const r = sh(fx, 'storage_move videos');
|
|
81
|
+
assert.strictEqual(r.code, 0, r.out);
|
|
82
|
+
const link = path.join(fx.root, 'videos');
|
|
83
|
+
assert.ok(fs.lstatSync(link).isSymbolicLink());
|
|
84
|
+
assert.strictEqual(fs.readlinkSync(link), path.join(fx.drive, 'videos'));
|
|
85
|
+
assert.strictEqual(fs.readFileSync(path.join(link, 'clips', 'b.mp4'), 'utf8'), 'more bytes');
|
|
86
|
+
assert.ok(fs.existsSync(path.join(fx.root, 'videos.pre-move', 'a.mp4')), 'the original is kept');
|
|
87
|
+
assert.strictEqual(sh(fx, 'storage_state videos').out, 'second');
|
|
88
|
+
assert.match(sh(fx, 'storage_status').out, /videos\s+second storage/);
|
|
89
|
+
assert.match(sh(fx, 'storage_status').out, /videos\.pre-move kept locally/);
|
|
90
|
+
} finally { fs.rmSync(fx.base, { recursive: true, force: true }); }
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('the pre-move copy goes only on request, and only while the link works', () => {
|
|
94
|
+
const fx = fixture();
|
|
95
|
+
try {
|
|
96
|
+
assert.strictEqual(sh(fx, 'storage_move videos').code, 0);
|
|
97
|
+
// Drive gone: the kept copy may be the only one.
|
|
98
|
+
fs.renameSync(fx.drive, fx.drive + '-away');
|
|
99
|
+
assert.strictEqual(sh(fx, 'storage_state videos').out, 'broken');
|
|
100
|
+
assert.strictEqual(sh(fx, 'storage_remove_pre_move videos').code, 1);
|
|
101
|
+
assert.ok(fs.existsSync(path.join(fx.root, 'videos.pre-move')));
|
|
102
|
+
fs.renameSync(fx.drive + '-away', fx.drive);
|
|
103
|
+
assert.strictEqual(sh(fx, 'storage_remove_pre_move videos').code, 0);
|
|
104
|
+
assert.ok(!fs.existsSync(path.join(fx.root, 'videos.pre-move')));
|
|
105
|
+
} finally { fs.rmSync(fx.base, { recursive: true, force: true }); }
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('back makes it a local folder again and keeps the drive copy', () => {
|
|
109
|
+
const fx = fixture();
|
|
110
|
+
try {
|
|
111
|
+
assert.strictEqual(sh(fx, 'storage_move videos').code, 0);
|
|
112
|
+
fs.writeFileSync(path.join(fx.root, 'videos', 'new.mp4'), 'made on the drive');
|
|
113
|
+
const r = sh(fx, 'storage_back videos');
|
|
114
|
+
assert.strictEqual(r.code, 0, r.out);
|
|
115
|
+
const dir = path.join(fx.root, 'videos');
|
|
116
|
+
assert.ok(!fs.lstatSync(dir).isSymbolicLink());
|
|
117
|
+
assert.strictEqual(fs.readFileSync(path.join(dir, 'new.mp4'), 'utf8'), 'made on the drive');
|
|
118
|
+
assert.ok(!fs.existsSync(path.join(fx.root, 'videos.pre-move')), 'the kept copy became the folder');
|
|
119
|
+
assert.ok(fs.existsSync(path.join(fx.drive, 'videos', 'a.mp4')), 'nothing on the drive is deleted');
|
|
120
|
+
} finally { fs.rmSync(fx.base, { recursive: true, force: true }); }
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('refused: DDEV projects, no room, a non-empty target, the same filesystem, not a workspace', () => {
|
|
124
|
+
const fx = fixture();
|
|
125
|
+
try {
|
|
126
|
+
let r = sh(fx, 'storage_move dev');
|
|
127
|
+
assert.strictEqual(r.code, 1); assert.match(r.out, /holds DDEV projects/);
|
|
128
|
+
|
|
129
|
+
r = sh(fx, 'storage_move videos', 'function storage_same_fs() { return 1 ; } ; function storage_avail_bytes() { echo 1000 ; }');
|
|
130
|
+
assert.strictEqual(r.code, 1); assert.match(r.out, /Not enough room/);
|
|
131
|
+
|
|
132
|
+
r = sh(fx, 'storage_move videos', 'function storage_avail_bytes() { echo 999999999999 ; }');
|
|
133
|
+
assert.strictEqual(r.code, 1); assert.match(r.out, /not mounted on its own filesystem/);
|
|
134
|
+
|
|
135
|
+
fs.mkdirSync(path.join(fx.drive, 'videos'));
|
|
136
|
+
fs.writeFileSync(path.join(fx.drive, 'videos', 'old.mp4'), 'an older copy');
|
|
137
|
+
r = sh(fx, 'storage_move videos');
|
|
138
|
+
assert.strictEqual(r.code, 1); assert.match(r.out, /already holds files/);
|
|
139
|
+
assert.strictEqual(fs.readFileSync(path.join(fx.drive, 'videos', 'old.mp4'), 'utf8'), 'an older copy');
|
|
140
|
+
|
|
141
|
+
r = sh(fx, 'storage_move core');
|
|
142
|
+
assert.strictEqual(r.code, 1); assert.match(r.out, /not a workspace/);
|
|
143
|
+
|
|
144
|
+
assert.ok(fs.lstatSync(path.join(fx.root, 'videos')).isDirectory(), 'every refusal left the folder alone');
|
|
145
|
+
} finally { fs.rmSync(fx.base, { recursive: true, force: true }); }
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('a copy that differs is never swapped in', () => {
|
|
149
|
+
const fx = fixture();
|
|
150
|
+
try {
|
|
151
|
+
const r = sh(fx, 'storage_move videos', 'function storage_same_fs() { return 1 ; } ; function storage_avail_bytes() { echo 999999999999 ; } ; function storage_diff() { echo ">f.st...... a.mp4" ; }');
|
|
152
|
+
assert.strictEqual(r.code, 1); assert.match(r.out, /differs from the source/);
|
|
153
|
+
assert.ok(!fs.lstatSync(path.join(fx.root, 'videos')).isSymbolicLink());
|
|
154
|
+
} finally { fs.rmSync(fx.base, { recursive: true, force: true }); }
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('relink follows the drive to a new mount point', () => {
|
|
158
|
+
const fx = fixture();
|
|
159
|
+
try {
|
|
160
|
+
assert.strictEqual(sh(fx, 'storage_move videos').code, 0);
|
|
161
|
+
const moved = path.join(fx.base, 'fixed', 'workspace');
|
|
162
|
+
fs.mkdirSync(path.dirname(moved), { recursive: true });
|
|
163
|
+
fs.renameSync(fx.drive, moved);
|
|
164
|
+
const r = sh({ ...fx, storage: moved }, 'storage_relink');
|
|
165
|
+
assert.strictEqual(r.code, 0, r.out);
|
|
166
|
+
assert.strictEqual(fs.readlinkSync(path.join(fx.root, 'videos')), path.join(moved, 'videos'));
|
|
167
|
+
} finally { fs.rmSync(fx.base, { recursive: true, force: true }); }
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test('the dashboard mounts: the media parent with rslave under /run/media, the path itself elsewhere', () => {
|
|
171
|
+
const fx = fixture('/run/media/someone/workspace1/workspace');
|
|
172
|
+
try {
|
|
173
|
+
// A link left on the old udisks name is covered by the same parent mount.
|
|
174
|
+
fs.renameSync(path.join(fx.root, 'videos'), path.join(fx.base, 'gone'));
|
|
175
|
+
fs.symlinkSync('/run/media/someone/workspace/workspace/videos', path.join(fx.root, 'videos'));
|
|
176
|
+
const yml = sh(fx, 'storage_compose').out;
|
|
177
|
+
assert.match(yml, /^ {6}- \/run\/media\/someone:\/run\/media\/someone:rslave$/m);
|
|
178
|
+
assert.strictEqual((yml.match(/^ {6}- /gm) || []).length, 1, 'one mount covers both names');
|
|
179
|
+
assert.match(yml, /^services:\n {2}web:\n {4}volumes:$/m);
|
|
180
|
+
|
|
181
|
+
const other = sh({ ...fx, storage: '/mnt/workspace/workspace' }, 'storage_mount_sources').out.split('\n');
|
|
182
|
+
assert.deepStrictEqual(other, ['/mnt/workspace/workspace', '/run/media/someone']);
|
|
183
|
+
assert.strictEqual(sh(fixture(''), 'storage_compose').out, '', 'nothing set, nothing linked: no file');
|
|
184
|
+
} finally { fs.rmSync(fx.base, { recursive: true, force: true }); }
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('under /run/media the stable fstab fix is printed, by UUID, and never run', () => {
|
|
188
|
+
const src = fs.readFileSync(FUN, 'utf8');
|
|
189
|
+
assert.match(src, /UUID=\$\{uuid\}\s+\$\{point\}/);
|
|
190
|
+
assert.match(src, /nofail/);
|
|
191
|
+
assert.doesNotMatch(src.replace(/^\s*echo .*$/gm, ''), /\bsudo\b/, 'sudo appears only in printed advice');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('the dashboard serves files from a workspace that is a symlink, and still refuses links out', () => {
|
|
195
|
+
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-files-link-'));
|
|
196
|
+
try {
|
|
197
|
+
const drive = path.join(base, 'drive', 'videos');
|
|
198
|
+
const secrets = path.join(base, 'secrets');
|
|
199
|
+
fs.mkdirSync(path.join(drive, 'sub'), { recursive: true });
|
|
200
|
+
fs.mkdirSync(secrets);
|
|
201
|
+
fs.writeFileSync(path.join(drive, 'sub', 'a.mp4'), 'x');
|
|
202
|
+
fs.writeFileSync(path.join(secrets, 'key'), 'k');
|
|
203
|
+
fs.symlinkSync(secrets, path.join(drive, 'escape'));
|
|
204
|
+
const root = path.join(base, 'workspace', 'videos');
|
|
205
|
+
fs.mkdirSync(path.dirname(root));
|
|
206
|
+
fs.symlinkSync(drive, root);
|
|
207
|
+
|
|
208
|
+
assert.strictEqual(fb.realFileIn(root, 'sub/a.mp4'), path.join(fs.realpathSync(drive), 'sub', 'a.mp4'));
|
|
209
|
+
assert.strictEqual(fb.realFileIn(root, 'escape/key'), '', 'a link out of the drive copy is refused');
|
|
210
|
+
assert.strictEqual(fb.realFileIn(root, 'sub'), '', 'a folder is not a file');
|
|
211
|
+
assert.strictEqual(fb.realFileIn(root, 'nope.mp4'), '');
|
|
212
|
+
assert.strictEqual(fb.safeSubIn(root, 'sub').ok, true, 'browsing a subfolder works');
|
|
213
|
+
assert.strictEqual(fb.safeSubIn(root, 'escape').ok, false);
|
|
214
|
+
|
|
215
|
+
const server = fs.readFileSync(path.join(__dirname, '..', '..', 'server.js'), 'utf8');
|
|
216
|
+
assert.match(server, /filesBrowser\.realFileIn\(workspaceDir\(fileMatch\[1\]\), name\)/, '/files uses the tested check');
|
|
217
|
+
} finally { fs.rmSync(base, { recursive: true, force: true }); }
|
|
218
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Screen session names (jobs.js). A name is workspace--project--verb, at most 60 characters. It used to
|
|
3
|
+
* be cut at 60, so on a long project name two commands shared one session: one was refused as "still
|
|
4
|
+
* running", or replaced the other's output. What must never drift: a name that fits is exactly what it
|
|
5
|
+
* always was; one that does not stays within 60, unique, stable, with the workspace in front and the verb
|
|
6
|
+
* whole at the end.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const assert = require('node:assert');
|
|
10
|
+
const { test } = require('node:test');
|
|
11
|
+
|
|
12
|
+
const { screenSessionName } = require('../../jobs');
|
|
13
|
+
|
|
14
|
+
// The name as it was built before, for every case where it fitted.
|
|
15
|
+
const before = (project, verb, workspace) => [workspace, project, verb].filter(Boolean).join('--')
|
|
16
|
+
.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/-{3,}/g, '--').replace(/^-+|-+$/g, '').slice(0, 60);
|
|
17
|
+
|
|
18
|
+
test('a name that fits is exactly what it always was', () => {
|
|
19
|
+
for (const [p, v, w] of [
|
|
20
|
+
['site1', 'composer-update', 'dev'], ['my-drupal-site-pulse', 'custom-say-hello', 'dev'],
|
|
21
|
+
['ace_editor', 'yarn-install', 'products'], ['vb11t1', 'tests', 'test'], ['x', 'jira-webship-1234', 'dev'],
|
|
22
|
+
['Some Name', 'git-status', 'dev'], ['', 'exec', 'dev'],
|
|
23
|
+
]) {
|
|
24
|
+
assert.ok(before(p, v, w).length <= 60);
|
|
25
|
+
assert.strictEqual(screenSessionName(p, v, w), before(p, v, w));
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('the real collisions are gone: every command on a long project gets its own session', () => {
|
|
30
|
+
const cases = [
|
|
31
|
+
['entity-definition-update-manager', 'products', ['composer-update-dry-run', 'composer-update-lock', 'composer-update-package', 'composer-update']],
|
|
32
|
+
['entity_browser_generic_embed_plugins', 'products', ['composer-show', 'composer-show-tree', 'composer-require', 'composer-require-dev']],
|
|
33
|
+
['paragraphs_asymmetric_translation_widgets', 'products', ['yarn-install', 'yarn-ci', 'npm-install', 'git-status', 'git-stashes']],
|
|
34
|
+
['amd-haven-1-0-x-site-template', 'dev', ['custom-update-database', 'custom-update-database-staging']],
|
|
35
|
+
];
|
|
36
|
+
for (const [p, w, verbs] of cases) {
|
|
37
|
+
const names = verbs.map((v) => screenSessionName(p, v, w));
|
|
38
|
+
assert.strictEqual(new Set(names).size, names.length, `${w}/${p}: ${names.join(' | ')}`);
|
|
39
|
+
names.forEach((n, i) => {
|
|
40
|
+
assert.ok(n.length <= 60, `${n} is ${n.length} characters`);
|
|
41
|
+
assert.match(n, /^[a-z0-9._-]+$/);
|
|
42
|
+
assert.ok(n.startsWith(`${w}--`), `the workspace stays in front: ${n}`);
|
|
43
|
+
assert.ok(n.endsWith(`--${verbs[i]}`), `the verb stays whole at the end: ${n}`);
|
|
44
|
+
assert.strictEqual(screenSessionName(p, verbs[i], w), n, 'the same name every time');
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('a Jira key is still read from the end of a long name, and the workspace is still in front', () => {
|
|
50
|
+
const n = screenSessionName('paragraphs_asymmetric_translation_widgets', 'jira-webship-12345', 'products');
|
|
51
|
+
assert.ok(n.length <= 60);
|
|
52
|
+
assert.ok(n.startsWith('products--'), n);
|
|
53
|
+
// screens.js reads the key with exactly this pattern; a name cut at 60 lost the end of it.
|
|
54
|
+
assert.strictEqual((n.match(/(?:^|--)jira-([a-z0-9]+-[0-9]+)$/) || [])[1], 'webship-12345');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('a verb too long to keep whole still gives a short, unique, stable name', () => {
|
|
58
|
+
const long = 'custom-an-action-with-a-very-long-identifier-indeed-x';
|
|
59
|
+
const a = screenSessionName('paragraphs_asymmetric_translation_widgets', `${long}1`, 'products');
|
|
60
|
+
const b = screenSessionName('paragraphs_asymmetric_translation_widgets', `${long}2`, 'products');
|
|
61
|
+
assert.ok(a.length <= 60 && b.length <= 60);
|
|
62
|
+
assert.notStrictEqual(a, b);
|
|
63
|
+
assert.ok(a.startsWith('products--'));
|
|
64
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The settings writers, against a real file in a temporary config directory.
|
|
3
|
+
*
|
|
4
|
+
* The contract they all share: an edit replaces the LINES it means to change and leaves the rest
|
|
5
|
+
* of the file byte-for-byte. These files are hand-maintained and heavily commented, so a writer
|
|
6
|
+
* that reflows one is worse than one that refuses.
|
|
7
|
+
*
|
|
8
|
+
* WORKSPACE_CONFIG is read when workspaces.js loads, so it is set before the require below —
|
|
9
|
+
* these tests never touch the real core/config.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { test } = require('node:test');
|
|
13
|
+
const assert = require('node:assert');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-settings-'));
|
|
19
|
+
process.env.WORKSPACE_CONFIG = TMP;
|
|
20
|
+
|
|
21
|
+
const { writeWorkspacePresentation, readListBlock, writeListBlock, writeSettingsValues,
|
|
22
|
+
readSettingsRows, isSecretKey, listSettingsFiles } = require('../../settings-file');
|
|
23
|
+
|
|
24
|
+
const write = (name, text) => { fs.writeFileSync(path.join(TMP, name), text); return name; };
|
|
25
|
+
const read = (name) => fs.readFileSync(path.join(TMP, name), 'utf8');
|
|
26
|
+
|
|
27
|
+
// The RAG workspace, as it actually is on disk: an icon and a subtitle the form knows about, and
|
|
28
|
+
// four keys plus a comment that it does not.
|
|
29
|
+
const RAG = `doc:
|
|
30
|
+
name: rag
|
|
31
|
+
|
|
32
|
+
presentation:
|
|
33
|
+
icon: tabler:database-search
|
|
34
|
+
# The folder is \`rag\`; the dashboard card says RAG, because that is what it is called —
|
|
35
|
+
# \`label\` is the presentational name and never the directory.
|
|
36
|
+
label: RAG
|
|
37
|
+
subtitle: Milvus Vector Databases & Project RAG Indexes
|
|
38
|
+
noun: instance
|
|
39
|
+
kind: projects
|
|
40
|
+
also_files: true
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
test('changing the icon keeps every other key in the block', () => {
|
|
44
|
+
const f = write('workspace.rag.settings.yml', RAG);
|
|
45
|
+
writeWorkspacePresentation(f, 'tabler:database', 'Milvus Vector Databases & Project RAG Indexes');
|
|
46
|
+
const out = read(f);
|
|
47
|
+
// The one thing that was asked for:
|
|
48
|
+
assert.match(out, /^ {2}icon: tabler:database$/m);
|
|
49
|
+
// And everything that was not:
|
|
50
|
+
for (const kept of ['label: RAG', 'noun: instance', 'kind: projects', 'also_files: true']) {
|
|
51
|
+
assert.ok(out.includes(kept), `${kept} was deleted`);
|
|
52
|
+
}
|
|
53
|
+
assert.ok(out.includes('`label` is the presentational name'), 'the comment was deleted');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('changing the subtitle rewrites one line and nothing else', () => {
|
|
57
|
+
const f = write('workspace.rag.settings.yml', RAG);
|
|
58
|
+
writeWorkspacePresentation(f, 'tabler:database-search', 'Vector databases');
|
|
59
|
+
const out = read(f);
|
|
60
|
+
assert.match(out, /^ {2}subtitle: "Vector databases"$/m);
|
|
61
|
+
assert.equal(out.split('\n').length, RAG.split('\n').length, 'the file changed length');
|
|
62
|
+
assert.ok(out.includes('also_files: true'));
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('an empty value removes that line only', () => {
|
|
66
|
+
const f = write('workspace.rag.settings.yml', RAG);
|
|
67
|
+
writeWorkspacePresentation(f, 'tabler:database-search', '');
|
|
68
|
+
const out = read(f);
|
|
69
|
+
assert.ok(!/^ {2}subtitle:/m.test(out), 'the subtitle should be gone');
|
|
70
|
+
assert.ok(out.includes('label: RAG'), 'and nothing else with it');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('a file with no presentation block gets one', () => {
|
|
74
|
+
const f = write('workspace.dev.settings.yml', 'doc:\n name: dev\n');
|
|
75
|
+
writeWorkspacePresentation(f, 'tabler:code', 'Development Projects');
|
|
76
|
+
const out = read(f);
|
|
77
|
+
assert.match(out, /^presentation:$/m);
|
|
78
|
+
assert.match(out, /^ {2}icon: tabler:code$/m);
|
|
79
|
+
assert.match(out, /^ {2}subtitle: "Development Projects"$/m);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('clearing the only two keys drops the block, since an empty one parses as null', () => {
|
|
83
|
+
const f = write('workspace.dev.settings.yml', 'doc:\n name: dev\npresentation:\n icon: tabler:code\n subtitle: Dev\n');
|
|
84
|
+
writeWorkspacePresentation(f, '', '');
|
|
85
|
+
assert.ok(!/presentation:/.test(read(f)), 'the empty block should be gone');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('a list block round-trips without disturbing the file around it', () => {
|
|
89
|
+
const f = write('settings.yml', '# top\nworkspaces:\n - dev\n - test\n\nstyle:\n theme: default\n');
|
|
90
|
+
assert.deepEqual(readListBlock(f, 'workspaces').items, ['dev', 'test']);
|
|
91
|
+
writeListBlock(f, 'workspaces', ['test', 'dev', 'rag']);
|
|
92
|
+
const out = read(f);
|
|
93
|
+
assert.deepEqual(readListBlock(f, 'workspaces').items, ['test', 'dev', 'rag']);
|
|
94
|
+
assert.ok(out.startsWith('# top\n'), 'the comment above it survived');
|
|
95
|
+
assert.ok(out.includes('style:\n theme: default'), 'the block below it survived');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('a value is written on its own line, with its comment kept', () => {
|
|
99
|
+
const f = write('settings.yml', 'style:\n theme: default # which palette\n page_size: 50\n');
|
|
100
|
+
const rows = readSettingsRows(f);
|
|
101
|
+
const theme = rows.find((r) => r.dotted === 'style.theme');
|
|
102
|
+
writeSettingsValues(f, [{ line: theme.line, key: 'theme', value: 'ember' }]);
|
|
103
|
+
const out = read(f);
|
|
104
|
+
assert.match(out, /^ {2}theme: ember {3}# which palette$/m);
|
|
105
|
+
assert.ok(out.includes('page_size: 50'));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('a value that would change type is quoted', () => {
|
|
109
|
+
const f = write('settings.yml', 'a:\n b: x\n');
|
|
110
|
+
const rows = readSettingsRows(f);
|
|
111
|
+
writeSettingsValues(f, [{ line: rows[0].line, key: 'b', value: 'no' }]);
|
|
112
|
+
assert.match(read(f), /^ {2}b: "no"$/m);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('account.pass is not a secret, because it is the admin password you log in with', () => {
|
|
116
|
+
assert.equal(isSecretKey('pass', 'account.pass'), false);
|
|
117
|
+
assert.equal(isSecretKey('pass', 'git.github.pass'), true);
|
|
118
|
+
assert.equal(isSecretKey('api_key', 'ai.api_key'), true);
|
|
119
|
+
assert.equal(isSecretKey('name', 'account.name'), false);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test('settings.yml is listed first, then the workspace files', () => {
|
|
123
|
+
write('settings.yml', 'a: 1\n');
|
|
124
|
+
write('workspace.zz.settings.yml', 'a: 1\n');
|
|
125
|
+
write('workspace.aa.settings.yml', 'a: 1\n');
|
|
126
|
+
write('not-a-settings-file.txt', 'x');
|
|
127
|
+
const files = listSettingsFiles();
|
|
128
|
+
assert.equal(files[0], 'settings.yml');
|
|
129
|
+
assert.ok(files.includes('workspace.aa.settings.yml'));
|
|
130
|
+
assert.ok(!files.includes('not-a-settings-file.txt'));
|
|
131
|
+
assert.ok(files.indexOf('workspace.aa.settings.yml') < files.indexOf('workspace.zz.settings.yml'));
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('upsertSettingsValue rewrites a key in place and creates what a file lacks, under its parent', () => {
|
|
135
|
+
const { upsertSettingsValue } = require('../../settings-file');
|
|
136
|
+
const file = path.join(TMP, 'settings.yml');
|
|
137
|
+
fs.writeFileSync(file, 'a: 1\n\ngoogle_drive:\n client_id: x # keep me\n destinations:\n docs: ""\n\nz: 2\n');
|
|
138
|
+
|
|
139
|
+
upsertSettingsValue('settings.yml', 'google_drive.client_id', 'yy');
|
|
140
|
+
assert.match(fs.readFileSync(file, 'utf8'), /client_id: yy # keep me/);
|
|
141
|
+
upsertSettingsValue('settings.yml', 'google_drive.destinations.videos', 'abc'); // a key beside an existing one
|
|
142
|
+
upsertSettingsValue('settings.yml', 'google_drive.root_folder', '1AbC_d-9'); // a key under an existing block
|
|
143
|
+
upsertSettingsValue('settings.yml', 'google_drive.sources.test', 'test-reports'); // a new map under it
|
|
144
|
+
upsertSettingsValue('settings.yml', 'brand_new.key', 'v'); // a new top-level block
|
|
145
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
146
|
+
const rows = Object.fromEntries(readSettingsRows('settings.yml').map((r) => [r.dotted, r.value]));
|
|
147
|
+
assert.strictEqual(rows['google_drive.destinations.videos'], 'abc');
|
|
148
|
+
assert.strictEqual(rows['google_drive.destinations.docs'], '""');
|
|
149
|
+
assert.strictEqual(rows['google_drive.root_folder'], '1AbC_d-9');
|
|
150
|
+
assert.strictEqual(rows['google_drive.sources.test'], 'test-reports');
|
|
151
|
+
assert.strictEqual(rows['brand_new.key'], 'v');
|
|
152
|
+
assert.strictEqual(rows.z, '2', 'what follows is untouched');
|
|
153
|
+
assert.ok(text.indexOf('google_drive.') < 0 && text.indexOf('brand_new:') > text.indexOf('z: 2'), 'a new top-level block goes at the end');
|
|
154
|
+
assert.ok(require('js-yaml').load(text).google_drive.sources.test === 'test-reports', 'and it still parses');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('upsertSettingsList replaces a list in place, creates one that is missing, and quotes what YAML would misread', () => {
|
|
158
|
+
const { upsertSettingsList } = require('../../settings-file');
|
|
159
|
+
const file = path.join(TMP, 'settings.yml');
|
|
160
|
+
fs.writeFileSync(file, 'a: 1\n\ngoogle_drive:\n account: me # keep\n targets:\n videos:\n - "*.mp4"\n - "*.mov"\n docs:\n - "*.pdf"\n exclude:\n - node_modules\n\nz: 2\n');
|
|
161
|
+
upsertSettingsList('settings.yml', 'google_drive.targets.videos', ['*.mp4', '*-poster.jpg', 'clips/']);
|
|
162
|
+
upsertSettingsList('settings.yml', 'google_drive.targets.specs', ['*.md']); // a new list beside the others
|
|
163
|
+
upsertSettingsList('settings.yml', 'google_drive.targets.docs', []); // emptied
|
|
164
|
+
upsertSettingsList('settings.yml', 'brand_new.list', ['x']); // a new block
|
|
165
|
+
const y = require('js-yaml').load(fs.readFileSync(file, 'utf8'));
|
|
166
|
+
assert.deepStrictEqual(y.google_drive.targets.videos, ['*.mp4', '*-poster.jpg', 'clips/']);
|
|
167
|
+
assert.deepStrictEqual(y.google_drive.targets.specs, ['*.md']);
|
|
168
|
+
assert.deepStrictEqual(y.google_drive.targets.docs, []);
|
|
169
|
+
assert.deepStrictEqual(y.google_drive.exclude, ['node_modules'], 'the list after it is untouched');
|
|
170
|
+
assert.deepStrictEqual(y.brand_new.list, ['x']);
|
|
171
|
+
assert.strictEqual(y.z, 2);
|
|
172
|
+
assert.match(fs.readFileSync(file, 'utf8'), /account: me # keep/);
|
|
173
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lists below the top level (auth.google.allowed_domains) are read and written by their dotted path,
|
|
3
|
+
* at their own indentation, and an entry that is kept keeps its own line.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { test } = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-nested-list-'));
|
|
13
|
+
process.env.WORKSPACE_CONFIG = TMP;
|
|
14
|
+
|
|
15
|
+
const { readListBlock, writeListBlock } = require('../../settings-file');
|
|
16
|
+
|
|
17
|
+
const FILE = 'settings.yml';
|
|
18
|
+
const TEXT = `workspaces:
|
|
19
|
+
- dev
|
|
20
|
+
- "images" # quoted on purpose
|
|
21
|
+
auth:
|
|
22
|
+
google:
|
|
23
|
+
enabled: false
|
|
24
|
+
allowed_domains:
|
|
25
|
+
- example.com
|
|
26
|
+
allowed_emails:
|
|
27
|
+
- someone@example.org
|
|
28
|
+
session_hours: 12
|
|
29
|
+
`;
|
|
30
|
+
const read = () => fs.readFileSync(path.join(TMP, FILE), 'utf8');
|
|
31
|
+
|
|
32
|
+
test('a nested list is found by its dotted path', () => {
|
|
33
|
+
fs.writeFileSync(path.join(TMP, FILE), TEXT);
|
|
34
|
+
const b = readListBlock(FILE, 'auth.google.allowed_domains');
|
|
35
|
+
assert.deepStrictEqual(b.items, ['example.com']);
|
|
36
|
+
assert.strictEqual(b.indent, ' ');
|
|
37
|
+
assert.deepStrictEqual(readListBlock(FILE, 'auth.google.allowed_emails').items, ['someone@example.org']);
|
|
38
|
+
assert.strictEqual(readListBlock(FILE, 'allowed_domains'), null);
|
|
39
|
+
assert.strictEqual(readListBlock(FILE, 'auth.google.enabled'), null);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('adding to a nested list writes at its indentation and leaves the rest alone', () => {
|
|
43
|
+
fs.writeFileSync(path.join(TMP, FILE), TEXT);
|
|
44
|
+
writeListBlock(FILE, 'auth.google.allowed_domains', ['example.com', 'example.org']);
|
|
45
|
+
assert.strictEqual(read(), TEXT.replace(' - example.com\n', ' - example.com\n - example.org\n'));
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('a kept entry keeps its quotes and comment; a removed one goes', () => {
|
|
49
|
+
fs.writeFileSync(path.join(TMP, FILE), TEXT);
|
|
50
|
+
writeListBlock(FILE, 'workspaces', ['images', 'dev', 'docs']);
|
|
51
|
+
assert.ok(read().startsWith('workspaces:\n - "images" # quoted on purpose\n - dev\n - docs\nauth:\n'));
|
|
52
|
+
writeListBlock(FILE, 'workspaces', ['images']);
|
|
53
|
+
assert.ok(read().startsWith('workspaces:\n - "images" # quoted on purpose\nauth:\n'));
|
|
54
|
+
});
|