@webship.co/workspace 1.0.0-beta1 → 1.0.0-rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (632) hide show
  1. package/LICENSE +339 -0
  2. package/README.md +28 -364
  3. package/core/bin/workspace.js +135 -0
  4. package/core/config/settings.labels.yml +514 -0
  5. package/core/config/settings.template.yml +292 -0
  6. package/core/config/style.yml +53 -0
  7. package/core/config/theme.default.yml +161 -0
  8. package/core/config/theme.dispatch.yml +166 -0
  9. package/core/config/theme.drupal.yml +175 -0
  10. package/core/config/theme.ember.yml +160 -0
  11. package/core/config/theme.github.yml +172 -0
  12. package/core/config/theme.gitlab.yml +168 -0
  13. package/core/config/theme.press.yml +161 -0
  14. package/core/config/theme.slate.yml +160 -0
  15. package/core/config/theme.upsun.yml +174 -0
  16. package/core/docs/architecture.svg +65 -0
  17. package/core/docs/getting-started.md +51 -0
  18. package/core/docs/plugins.md +27 -0
  19. package/core/docs/releases.md +23 -0
  20. package/core/docs/testing.md +15 -0
  21. package/core/docs/workspaces.md +25 -0
  22. package/core/scripts/args/arg-ai-items.sh +65 -0
  23. package/core/scripts/args/arg-autostart.sh +31 -0
  24. package/core/scripts/args/arg-avatar-agent.sh +28 -0
  25. package/core/scripts/args/arg-avatar-catalog.sh +12 -0
  26. package/core/scripts/args/arg-avatar-character.sh +68 -0
  27. package/core/scripts/args/arg-avatar-voice.sh +24 -0
  28. package/core/scripts/args/arg-avatar.sh +22 -0
  29. package/core/scripts/args/arg-checks.sh +36 -0
  30. package/core/scripts/args/arg-clone.sh +42 -0
  31. package/core/scripts/args/arg-commands.sh +51 -0
  32. package/core/scripts/args/arg-cucumber.sh +34 -0
  33. package/core/scripts/args/arg-distribution.sh +30 -0
  34. package/core/scripts/args/arg-drupal.sh +39 -0
  35. package/core/scripts/args/arg-drupal_cms.sh +18 -0
  36. package/core/scripts/args/arg-gitlab-ci-local.sh +25 -0
  37. package/core/scripts/args/arg-graphify.sh +36 -0
  38. package/core/scripts/args/arg-graphs.sh +12 -0
  39. package/core/scripts/args/arg-hostnames.sh +23 -0
  40. package/core/scripts/args/arg-milvus-backup.sh +8 -0
  41. package/core/scripts/args/arg-milvus-mcp.sh +12 -0
  42. package/core/scripts/args/arg-milvus.sh +32 -0
  43. package/core/scripts/args/arg-obsidian.sh +17 -0
  44. package/core/scripts/args/arg-odoo.sh +18 -0
  45. package/core/scripts/args/arg-penpot-backup.sh +8 -0
  46. package/core/scripts/args/arg-penpot-mcp.sh +15 -0
  47. package/core/scripts/args/arg-penpot-token.sh +17 -0
  48. package/core/scripts/args/arg-penpot.sh +44 -0
  49. package/core/scripts/args/arg-rag.sh +21 -0
  50. package/core/scripts/args/arg-ragify.sh +35 -0
  51. package/core/scripts/args/arg-reclaim.sh +18 -0
  52. package/core/scripts/args/arg-release.sh +21 -0
  53. package/core/scripts/args/arg-screen.sh +28 -0
  54. package/core/scripts/args/arg-site-template.sh +13 -0
  55. package/core/scripts/args/arg-spdd.sh +16 -0
  56. package/core/scripts/args/arg-specs.sh +22 -0
  57. package/core/scripts/args/arg-storage.sh +36 -0
  58. package/core/scripts/args/arg-testing.sh +36 -0
  59. package/core/scripts/args/arg-webship.sh +34 -0
  60. package/core/scripts/args/arg-webships-project.sh +22 -0
  61. package/core/scripts/args/arg-webships-site-template.sh +17 -0
  62. package/core/scripts/args/arg-webships.sh +34 -0
  63. package/core/scripts/args/arg-website-site-template.sh +17 -0
  64. package/core/scripts/args/arg-website.sh +25 -0
  65. package/core/scripts/args/arg-wordpress.sh +18 -0
  66. package/core/scripts/autostart-run.sh +162 -0
  67. package/core/scripts/autostart-stop.sh +30 -0
  68. package/core/scripts/autostart-watch.sh +43 -0
  69. package/core/scripts/autostart.sh +26 -0
  70. package/core/scripts/bootstrap-functions.sh +117 -0
  71. package/core/scripts/bootstrap-libraries.sh +10 -0
  72. package/core/scripts/bootstrap.sh +71 -0
  73. package/core/scripts/ddev-shim/ddev +122 -0
  74. package/core/scripts/docker-gid.sh +119 -0
  75. package/core/scripts/functions/arg-site-template.sh +28 -0
  76. package/core/scripts/functions/fun-ai-items-lock.sh +175 -0
  77. package/core/scripts/functions/fun-ai-items-sources.sh +298 -0
  78. package/core/scripts/functions/fun-ai-items.sh +621 -0
  79. package/core/scripts/functions/fun-ai-providers.sh +72 -0
  80. package/core/scripts/functions/fun-autostart.sh +429 -0
  81. package/core/scripts/functions/fun-avatar.sh +441 -0
  82. package/core/scripts/functions/fun-backup-item.sh +50 -0
  83. package/core/scripts/functions/fun-build-distribution.sh +226 -0
  84. package/core/scripts/functions/fun-build-site-template.sh +104 -0
  85. package/core/scripts/functions/fun-build-webships.sh +161 -0
  86. package/core/scripts/functions/fun-build-website.sh +199 -0
  87. package/core/scripts/functions/fun-checks.sh +243 -0
  88. package/core/scripts/functions/fun-clone-project.sh +289 -0
  89. package/core/scripts/functions/fun-commands.sh +521 -0
  90. package/core/scripts/functions/fun-database.sh +63 -0
  91. package/core/scripts/functions/fun-ddev-hostnames.sh +213 -0
  92. package/core/scripts/functions/fun-ddev-nodejs.sh +15 -0
  93. package/core/scripts/functions/fun-ddev-telemetry.sh +15 -0
  94. package/core/scripts/functions/fun-default-settings.sh +28 -0
  95. package/core/scripts/functions/fun-distribution-cucumber.sh +45 -0
  96. package/core/scripts/functions/fun-distribution-drupal.sh +25 -0
  97. package/core/scripts/functions/fun-distribution-drupal_cms.sh +35 -0
  98. package/core/scripts/functions/fun-distribution-webship.sh +28 -0
  99. package/core/scripts/functions/fun-distribution-webships.sh +33 -0
  100. package/core/scripts/functions/fun-distribution-website.sh +19 -0
  101. package/core/scripts/functions/fun-drush.sh +48 -0
  102. package/core/scripts/functions/fun-elevenlabs.sh +179 -0
  103. package/core/scripts/functions/fun-files.sh +19 -0
  104. package/core/scripts/functions/fun-git-worktrees.sh +142 -0
  105. package/core/scripts/functions/fun-gitlab-ci.sh +156 -0
  106. package/core/scripts/functions/fun-hostnames-cli.sh +88 -0
  107. package/core/scripts/functions/fun-milvus.sh +584 -0
  108. package/core/scripts/functions/fun-odoo.sh +121 -0
  109. package/core/scripts/functions/fun-penpot.sh +724 -0
  110. package/core/scripts/functions/fun-project-name.sh +20 -0
  111. package/core/scripts/functions/fun-rag.sh +253 -0
  112. package/core/scripts/functions/fun-ragify.sh +165 -0
  113. package/core/scripts/functions/fun-reclaim.sh +297 -0
  114. package/core/scripts/functions/fun-release.sh +159 -0
  115. package/core/scripts/functions/fun-remove-project.sh +319 -0
  116. package/core/scripts/functions/fun-screen.sh +96 -0
  117. package/core/scripts/functions/fun-spdd.sh +676 -0
  118. package/core/scripts/functions/fun-specs-share.sh +237 -0
  119. package/core/scripts/functions/fun-storage.sh +347 -0
  120. package/core/scripts/functions/fun-testing.sh +329 -0
  121. package/core/scripts/functions/fun-users.sh +87 -0
  122. package/core/scripts/functions/fun-version.sh +72 -0
  123. package/core/scripts/functions/fun-video-edit.sh +48 -0
  124. package/core/scripts/functions/fun-voice.sh +45 -0
  125. package/core/scripts/functions/fun-wordpress.sh +59 -0
  126. package/core/scripts/host-mounts.sh +161 -0
  127. package/core/scripts/install.sh +602 -0
  128. package/core/scripts/libs/__pycache__/video_edit.cpython-312.pyc +0 -0
  129. package/core/scripts/libs/argparse.sh +93 -0
  130. package/core/scripts/libs/bash-yaml.sh +52 -0
  131. package/core/scripts/libs/checks_plan.js +312 -0
  132. package/core/scripts/libs/elevenlabs.py +627 -0
  133. package/core/scripts/libs/progress-bar.sh +19 -0
  134. package/core/scripts/libs/rag_index.py +446 -0
  135. package/core/scripts/libs/video_edit.py +636 -0
  136. package/core/scripts/libs/voice.py +430 -0
  137. package/core/scripts/plugins/jira/host-install.sh +119 -0
  138. package/core/scripts/plugins/jira/host-mounts.txt +2 -0
  139. package/core/scripts/plugins/upsun/args/arg-upsun.sh +51 -0
  140. package/core/scripts/plugins/upsun/functions/fun-upsun.sh +304 -0
  141. package/core/scripts/plugins/upsun/host-install.sh +99 -0
  142. package/core/scripts/plugins/upsun/host-mounts.txt +1 -0
  143. package/core/scripts/plugins/upsun/upsun-ai.sh +133 -0
  144. package/core/scripts/plugins.sh +152 -0
  145. package/core/scripts/release.sh +69 -0
  146. package/core/scripts/storage.sh +39 -0
  147. package/core/scripts/sync-tooling.sh +246 -0
  148. package/core/scripts/tests/cmd-smoke-test.sh +109 -0
  149. package/core/scripts/trust-https.sh +257 -0
  150. package/core/workspace-app/.ddev/commands/web/test-dashboard +49 -0
  151. package/core/workspace-app/.ddev/config.yaml +400 -0
  152. package/core/workspace-app/.ddev/docker-compose.dood.yaml +45 -0
  153. package/core/workspace-app/.ddev/docker-compose.plugin-jira-twg.yaml +26 -0
  154. package/core/workspace-app/.ddev/docker-compose.plugin-upsun-upsun-cli.yaml +8 -0
  155. package/core/workspace-app/.ddev/docker-compose.screens.yaml +15 -0
  156. package/core/workspace-app/.ddev/nginx_full/nginx-site.conf +114 -0
  157. package/core/workspace-app/.ddev/web-build/Dockerfile.workspace-app +203 -0
  158. package/core/workspace-app/.ddev/web-build/ddev-shim-ddev +122 -0
  159. package/{PLUGINS.md → core/workspace-app/PLUGINS.md} +9 -1
  160. package/core/workspace-app/README.md +380 -0
  161. package/{actions → core/workspace-app/actions}/ddev.js +0 -1
  162. package/{actions → core/workspace-app/actions}/rag.js +0 -1
  163. package/{actions → core/workspace-app/actions}/testing.js +0 -1
  164. package/{assistant.js → core/workspace-app/assistant.js} +1 -10
  165. package/{git-info.js → core/workspace-app/git-info.js} +17 -1
  166. package/core/workspace-app/package.json +29 -0
  167. package/core/workspace-app/plugin-catalog.js +50 -0
  168. package/{plugins-page.js → core/workspace-app/plugins-page.js} +28 -2
  169. package/{server.js → core/workspace-app/server.js} +18 -0
  170. package/core/workspace-app/tests/features/actions-rail.feature +44 -0
  171. package/core/workspace-app/tests/features/ai-items.feature +23 -0
  172. package/core/workspace-app/tests/features/assistant-speech.feature +63 -0
  173. package/core/workspace-app/tests/features/checks.feature +40 -0
  174. package/core/workspace-app/tests/features/clone-project.feature +30 -0
  175. package/core/workspace-app/tests/features/command-authoring.feature +58 -0
  176. package/core/workspace-app/tests/features/dashboard.feature +57 -0
  177. package/core/workspace-app/tests/features/menus.feature +29 -0
  178. package/core/workspace-app/tests/features/plugins/jira/jira.feature +47 -0
  179. package/core/workspace-app/tests/features/plugins.feature +20 -0
  180. package/core/workspace-app/tests/features/project-actions.feature +41 -0
  181. package/core/workspace-app/tests/features/screens.feature +29 -0
  182. package/core/workspace-app/tests/features/settings.feature +46 -0
  183. package/core/workspace-app/tests/features/test-reports.feature +32 -0
  184. package/core/workspace-app/tests/features/theme.feature +40 -0
  185. package/core/workspace-app/tests/step-definitions/actions-post.steps.js +25 -0
  186. package/core/workspace-app/tests/step-definitions/assistant-speech.steps.js +157 -0
  187. package/core/workspace-app/tests/step-definitions/clone-project.steps.js +47 -0
  188. package/core/workspace-app/tests/step-definitions/preconditions.steps.js +168 -0
  189. package/core/workspace-app/tests/unit/_isolated-config.js +31 -0
  190. package/core/workspace-app/tests/unit/accessibility.test.js +124 -0
  191. package/core/workspace-app/tests/unit/actions-rail-ux.test.js +56 -0
  192. package/core/workspace-app/tests/unit/actions-rail.test.js +51 -0
  193. package/core/workspace-app/tests/unit/ai-inventory.test.js +64 -0
  194. package/core/workspace-app/tests/unit/app-docs.test.js +64 -0
  195. package/core/workspace-app/tests/unit/assistant-avatar.test.js +110 -0
  196. package/core/workspace-app/tests/unit/assistant-cast.test.js +105 -0
  197. package/core/workspace-app/tests/unit/assistant-send-animation.test.js +15 -0
  198. package/core/workspace-app/tests/unit/assistant-suggestions.test.js +112 -0
  199. package/core/workspace-app/tests/unit/assistant-switches.test.js +53 -0
  200. package/core/workspace-app/tests/unit/assistant-views.test.js +146 -0
  201. package/core/workspace-app/tests/unit/assistant-working-indicator.test.js +49 -0
  202. package/core/workspace-app/tests/unit/assistant.test.js +61 -0
  203. package/core/workspace-app/tests/unit/audio-player.test.js +147 -0
  204. package/core/workspace-app/tests/unit/audio-voice.test.js +634 -0
  205. package/core/workspace-app/tests/unit/auth-google.test.js +180 -0
  206. package/core/workspace-app/tests/unit/autostart-watch.test.js +76 -0
  207. package/core/workspace-app/tests/unit/browsers.test.js +76 -0
  208. package/core/workspace-app/tests/unit/build-distribution.test.js +125 -0
  209. package/core/workspace-app/tests/unit/build-order.test.js +153 -0
  210. package/core/workspace-app/tests/unit/builder-args.test.js +113 -0
  211. package/core/workspace-app/tests/unit/builders.test.js +158 -0
  212. package/core/workspace-app/tests/unit/card-state-toggle.test.js +29 -0
  213. package/core/workspace-app/tests/unit/changelog.test.js +223 -0
  214. package/core/workspace-app/tests/unit/chat-thread.test.js +92 -0
  215. package/core/workspace-app/tests/unit/checks-plan.test.js +205 -0
  216. package/core/workspace-app/tests/unit/claude-args.test.js +182 -0
  217. package/core/workspace-app/tests/unit/claude-code-here.test.js +15 -0
  218. package/core/workspace-app/tests/unit/claude-resume.test.js +147 -0
  219. package/core/workspace-app/tests/unit/claude-screen-options.test.js +84 -0
  220. package/core/workspace-app/tests/unit/claude-session.test.js +41 -0
  221. package/core/workspace-app/tests/unit/command-flags.test.js +45 -0
  222. package/core/workspace-app/tests/unit/command-palette.test.js +105 -0
  223. package/core/workspace-app/tests/unit/commands-center.test.js +338 -0
  224. package/core/workspace-app/tests/unit/composer-menu.test.js +187 -0
  225. package/core/workspace-app/tests/unit/config-yaml.test.js +116 -0
  226. package/core/workspace-app/tests/unit/confirm-dialog.test.js +69 -0
  227. package/core/workspace-app/tests/unit/create-card.test.js +85 -0
  228. package/core/workspace-app/tests/unit/ddev-addons.test.js +144 -0
  229. package/core/workspace-app/tests/unit/ddev-auth-ssh.test.js +40 -0
  230. package/core/workspace-app/tests/unit/ddev-custom-commands.test.js +56 -0
  231. package/core/workspace-app/tests/unit/ddev-diagnose.test.js +57 -0
  232. package/core/workspace-app/tests/unit/ddev-domains.test.js +179 -0
  233. package/core/workspace-app/tests/unit/ddev-hostnames.test.js +143 -0
  234. package/core/workspace-app/tests/unit/ddev-import.test.js +82 -0
  235. package/core/workspace-app/tests/unit/ddev-mail.test.js +135 -0
  236. package/core/workspace-app/tests/unit/ddev-menu-and-create.test.js +28 -0
  237. package/core/workspace-app/tests/unit/ddev-mutagen.test.js +37 -0
  238. package/core/workspace-app/tests/unit/ddev-options.test.js +51 -0
  239. package/core/workspace-app/tests/unit/ddev-share.test.js +40 -0
  240. package/core/workspace-app/tests/unit/ddev-shim.test.js +57 -0
  241. package/core/workspace-app/tests/unit/ddev-snapshots.test.js +142 -0
  242. package/core/workspace-app/tests/unit/ddev-start-daemons.test.js +62 -0
  243. package/core/workspace-app/tests/unit/ddev-xdebug.test.js +41 -0
  244. package/core/workspace-app/tests/unit/deep-chat-origin.test.js +38 -0
  245. package/core/workspace-app/tests/unit/density.test.js +52 -0
  246. package/core/workspace-app/tests/unit/elevenlabs.test.js +453 -0
  247. package/core/workspace-app/tests/unit/event-log.test.js +102 -0
  248. package/core/workspace-app/tests/unit/favicon.test.js +68 -0
  249. package/core/workspace-app/tests/unit/files-browser.test.js +224 -0
  250. package/core/workspace-app/tests/unit/git-info.test.js +26 -0
  251. package/core/workspace-app/tests/unit/grid-and-list.test.js +103 -0
  252. package/core/workspace-app/tests/unit/host-tool-wrapper.test.js +18 -0
  253. package/core/workspace-app/tests/unit/html-patterns.test.js +51 -0
  254. package/core/workspace-app/tests/unit/ide-infra.test.js +28 -0
  255. package/core/workspace-app/tests/unit/images-ai.test.js +201 -0
  256. package/core/workspace-app/tests/unit/images-create.test.js +59 -0
  257. package/core/workspace-app/tests/unit/images-providers.test.js +248 -0
  258. package/core/workspace-app/tests/unit/images-svg.test.js +213 -0
  259. package/core/workspace-app/tests/unit/images-workspace.test.js +144 -0
  260. package/core/workspace-app/tests/unit/jobs-fragment.test.js +66 -0
  261. package/core/workspace-app/tests/unit/media-ux.test.js +164 -0
  262. package/core/workspace-app/tests/unit/modal-fit.test.js +87 -0
  263. package/core/workspace-app/tests/unit/modules-load.test.js +23 -0
  264. package/core/workspace-app/tests/unit/narration.test.js +186 -0
  265. package/core/workspace-app/tests/unit/navigation-editor.test.js +260 -0
  266. package/core/workspace-app/tests/unit/navigation.test.js +254 -0
  267. package/core/workspace-app/tests/unit/new-folder.test.js +121 -0
  268. package/core/workspace-app/tests/unit/new-screen-form.test.js +149 -0
  269. package/core/workspace-app/tests/unit/node-menu.test.js +161 -0
  270. package/core/workspace-app/tests/unit/notes.test.js +128 -0
  271. package/core/workspace-app/tests/unit/page-structure.test.js +40 -0
  272. package/core/workspace-app/tests/unit/page-width.test.js +59 -0
  273. package/core/workspace-app/tests/unit/php-version.test.js +90 -0
  274. package/core/workspace-app/tests/unit/plan-card.test.js +44 -0
  275. package/core/workspace-app/tests/unit/plugin-catalog.test.js +31 -0
  276. package/core/workspace-app/tests/unit/plugin-system.test.js +251 -0
  277. package/core/workspace-app/tests/unit/plugins-page.test.js +28 -0
  278. package/core/workspace-app/tests/unit/plugins.test.js +58 -0
  279. package/core/workspace-app/tests/unit/preconditions.test.js +21 -0
  280. package/core/workspace-app/tests/unit/project-activity-filter.test.js +180 -0
  281. package/core/workspace-app/tests/unit/project-make.test.js +143 -0
  282. package/core/workspace-app/tests/unit/project-name.test.js +202 -0
  283. package/core/workspace-app/tests/unit/reader.test.js +62 -0
  284. package/core/workspace-app/tests/unit/release.test.js +92 -0
  285. package/core/workspace-app/tests/unit/remove-confirm.test.js +37 -0
  286. package/core/workspace-app/tests/unit/report-pdf.test.js +171 -0
  287. package/core/workspace-app/tests/unit/round-11.test.js +31 -0
  288. package/core/workspace-app/tests/unit/round-12.test.js +37 -0
  289. package/core/workspace-app/tests/unit/row-layout.test.js +585 -0
  290. package/core/workspace-app/tests/unit/row-menus-fold.test.js +29 -0
  291. package/core/workspace-app/tests/unit/rows-add-modal.test.js +68 -0
  292. package/core/workspace-app/tests/unit/screen-runner-queue.test.js +41 -0
  293. package/core/workspace-app/tests/unit/screen-window.test.js +116 -0
  294. package/core/workspace-app/tests/unit/screens-beside-running.test.js +19 -0
  295. package/core/workspace-app/tests/unit/screens-columns.test.js +79 -0
  296. package/core/workspace-app/tests/unit/screens-menu.test.js +84 -0
  297. package/core/workspace-app/tests/unit/screens-modal-overlay.test.js +14 -0
  298. package/core/workspace-app/tests/unit/screens-waiting.test.js +129 -0
  299. package/core/workspace-app/tests/unit/screens.test.js +93 -0
  300. package/core/workspace-app/tests/unit/second-storage.test.js +218 -0
  301. package/core/workspace-app/tests/unit/session-name.test.js +64 -0
  302. package/core/workspace-app/tests/unit/settings-file.test.js +173 -0
  303. package/core/workspace-app/tests/unit/settings-nested-list.test.js +54 -0
  304. package/core/workspace-app/tests/unit/settings-raw.test.js +177 -0
  305. package/core/workspace-app/tests/unit/shared-prefs.test.js +49 -0
  306. package/core/workspace-app/tests/unit/speech.test.js +290 -0
  307. package/core/workspace-app/tests/unit/static-caching.test.js +39 -0
  308. package/core/workspace-app/tests/unit/status-language.test.js +47 -0
  309. package/core/workspace-app/tests/unit/tab-modal.test.js +69 -0
  310. package/core/workspace-app/tests/unit/tab-title.test.js +37 -0
  311. package/core/workspace-app/tests/unit/template-literals.test.js +52 -0
  312. package/core/workspace-app/tests/unit/terminal-scrollback.test.js +61 -0
  313. package/core/workspace-app/tests/unit/terminal-size.test.js +103 -0
  314. package/core/workspace-app/tests/unit/terminal-style.test.js +84 -0
  315. package/core/workspace-app/tests/unit/terminal-windows.test.js +154 -0
  316. package/core/workspace-app/tests/unit/themes.test.js +453 -0
  317. package/core/workspace-app/tests/unit/tool-labels.test.js +34 -0
  318. package/core/workspace-app/tests/unit/topbar-icons.test.js +47 -0
  319. package/core/workspace-app/tests/unit/update-docker-gid.test.js +51 -0
  320. package/core/workspace-app/tests/unit/user-guide.test.js +60 -0
  321. package/core/workspace-app/tests/unit/version-check.test.js +98 -0
  322. package/core/workspace-app/tests/unit/version-link.test.js +50 -0
  323. package/core/workspace-app/tests/unit/video-borrow.test.js +220 -0
  324. package/core/workspace-app/tests/unit/video-edit-ask.test.js +93 -0
  325. package/core/workspace-app/tests/unit/video-edit-short.test.js +42 -0
  326. package/core/workspace-app/tests/unit/video-edit.test.js +436 -0
  327. package/core/workspace-app/tests/unit/video-transcript.test.js +237 -0
  328. package/core/workspace-app/tests/unit/waiting.test.js +48 -0
  329. package/core/workspace-app/tests/unit/wordpress-odoo-builders.test.js +38 -0
  330. package/core/workspace-app/tests/unit/workspace-bin.test.js +48 -0
  331. package/core/workspace-app/tests/unit/workspace-card-tools.test.js +30 -0
  332. package/core/workspace-app/tests/unit/workspace-delete.test.js +77 -0
  333. package/core/workspace-app/tests/unit/workspace-types.test.js +68 -0
  334. package/core/workspace-app/tests/unit/worktree-clone.test.js +250 -0
  335. package/package.json +38 -36
  336. /package/{WORKSPACES.md → core/workspace-app/WORKSPACES.md} +0 -0
  337. /package/{actions → core/workspace-app/actions}/ai-items.js +0 -0
  338. /package/{actions → core/workspace-app/actions}/audio.js +0 -0
  339. /package/{actions → core/workspace-app/actions}/build-order.js +0 -0
  340. /package/{actions → core/workspace-app/actions}/checks.js +0 -0
  341. /package/{actions → core/workspace-app/actions}/claude-session.js +0 -0
  342. /package/{actions → core/workspace-app/actions}/commands-center.js +0 -0
  343. /package/{actions → core/workspace-app/actions}/composer.js +0 -0
  344. /package/{actions → core/workspace-app/actions}/ddev-addons.js +0 -0
  345. /package/{actions → core/workspace-app/actions}/ddev-mail.js +0 -0
  346. /package/{actions → core/workspace-app/actions}/folders.js +0 -0
  347. /package/{actions → core/workspace-app/actions}/images-svg.js +0 -0
  348. /package/{actions → core/workspace-app/actions}/images.js +0 -0
  349. /package/{actions → core/workspace-app/actions}/navigation.js +0 -0
  350. /package/{actions → core/workspace-app/actions}/node.js +0 -0
  351. /package/{actions → core/workspace-app/actions}/notes.js +0 -0
  352. /package/{actions → core/workspace-app/actions}/plugins.js +0 -0
  353. /package/{actions → core/workspace-app/actions}/project-make.js +0 -0
  354. /package/{actions → core/workspace-app/actions}/row-actions.js +0 -0
  355. /package/{actions → core/workspace-app/actions}/screens.js +0 -0
  356. /package/{actions → core/workspace-app/actions}/video-edit.js +0 -0
  357. /package/{ai-inventory.js → core/workspace-app/ai-inventory.js} +0 -0
  358. /package/{ai-items.js → core/workspace-app/ai-items.js} +0 -0
  359. /package/{assistant-cast.js → core/workspace-app/assistant-cast.js} +0 -0
  360. /package/{assistant-suggestions.js → core/workspace-app/assistant-suggestions.js} +0 -0
  361. /package/{audio-voice.js → core/workspace-app/audio-voice.js} +0 -0
  362. /package/{auth-google.js → core/workspace-app/auth-google.js} +0 -0
  363. /package/{build-ai.js → core/workspace-app/build-ai.js} +0 -0
  364. /package/{build-order-page.js → core/workspace-app/build-order-page.js} +0 -0
  365. /package/{build-order.js → core/workspace-app/build-order.js} +0 -0
  366. /package/{builder-args.js → core/workspace-app/builder-args.js} +0 -0
  367. /package/{changelog.js → core/workspace-app/changelog.js} +0 -0
  368. /package/{chat-threads.js → core/workspace-app/chat-threads.js} +0 -0
  369. /package/{checks.js → core/workspace-app/checks.js} +0 -0
  370. /package/{claude-args.js → core/workspace-app/claude-args.js} +0 -0
  371. /package/{claude-sessions.js → core/workspace-app/claude-sessions.js} +0 -0
  372. /package/{command-author.js → core/workspace-app/command-author.js} +0 -0
  373. /package/{command-block.js → core/workspace-app/command-block.js} +0 -0
  374. /package/{commands → core/workspace-app/commands}/defaults.yml +0 -0
  375. /package/{commands-page.js → core/workspace-app/commands-page.js} +0 -0
  376. /package/{commands.js → core/workspace-app/commands.js} +0 -0
  377. /package/{cucumber.js → core/workspace-app/cucumber.js} +0 -0
  378. /package/{ddev-addons-page.js → core/workspace-app/ddev-addons-page.js} +0 -0
  379. /package/{ddev-addons.js → core/workspace-app/ddev-addons.js} +0 -0
  380. /package/{ddev-custom-commands.js → core/workspace-app/ddev-custom-commands.js} +0 -0
  381. /package/{ddev-domains.js → core/workspace-app/ddev-domains.js} +0 -0
  382. /package/{ddev-import.js → core/workspace-app/ddev-import.js} +0 -0
  383. /package/{ddev-mail-page.js → core/workspace-app/ddev-mail-page.js} +0 -0
  384. /package/{ddev-mail.js → core/workspace-app/ddev-mail.js} +0 -0
  385. /package/{ddev-options.js → core/workspace-app/ddev-options.js} +0 -0
  386. /package/{ddev-snapshots.js → core/workspace-app/ddev-snapshots.js} +0 -0
  387. /package/{ddev.js → core/workspace-app/ddev.js} +0 -0
  388. /package/{docs → core/workspace-app/docs}/DEVELOPER-GUIDE.md +0 -0
  389. /package/{docs → core/workspace-app/docs}/README.md +0 -0
  390. /package/{docs → core/workspace-app/docs}/REFERENCE.md +0 -0
  391. /package/{docs → core/workspace-app/docs}/USER-GUIDE.md +0 -0
  392. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-board.png +0 -0
  393. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-coffee.png +0 -0
  394. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-colours.png +0 -0
  395. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-icons.png +0 -0
  396. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-moves.png +0 -0
  397. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-pick-cookieman.png +0 -0
  398. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-settings-voices.png +0 -0
  399. /package/{docs → core/workspace-app/docs}/screenshots/assistant/avatar-status.png +0 -0
  400. /package/{docs → core/workspace-app/docs}/screenshots/create-card/create-card.png +0 -0
  401. /package/{docs → core/workspace-app/docs}/screenshots/create-card/start-ai.png +0 -0
  402. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-address.png +0 -0
  403. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-off.png +0 -0
  404. /package/{docs → core/workspace-app/docs}/screenshots/guide-clone-worktree-on.png +0 -0
  405. /package/{docs → core/workspace-app/docs}/screenshots/guide-ddev-menu.png +0 -0
  406. /package/{docs → core/workspace-app/docs}/screenshots/guide-domains.png +0 -0
  407. /package/{docs → core/workspace-app/docs}/screenshots/guide-home.png +0 -0
  408. /package/{docs → core/workspace-app/docs}/screenshots/guide-ide-menu.png +0 -0
  409. /package/{docs → core/workspace-app/docs}/screenshots/guide-remove-refusal.png +0 -0
  410. /package/{docs → core/workspace-app/docs}/screenshots/guide-rows-worktree.png +0 -0
  411. /package/{docs → core/workspace-app/docs}/screenshots/guide-screens.png +0 -0
  412. /package/{docs → core/workspace-app/docs}/screenshots/guide-settings.png +0 -0
  413. /package/{docs → core/workspace-app/docs}/screenshots/guide-workspace-page.png +0 -0
  414. /package/{docs → core/workspace-app/docs}/screenshots/home/card-settings.png +0 -0
  415. /package/{docs → core/workspace-app/docs}/screenshots/home/drag-reorder.png +0 -0
  416. /package/{docs → core/workspace-app/docs}/screenshots/home/edit-card.png +0 -0
  417. /package/{docs → core/workspace-app/docs}/screenshots/images/claude-code-image.png +0 -0
  418. /package/{docs → core/workspace-app/docs}/screenshots/images/edit-with-ai.png +0 -0
  419. /package/{docs → core/workspace-app/docs}/screenshots/images/gallery.png +0 -0
  420. /package/{docs → core/workspace-app/docs}/screenshots/images/generate-description.png +0 -0
  421. /package/{docs → core/workspace-app/docs}/screenshots/images/generate-plan.png +0 -0
  422. /package/{docs → core/workspace-app/docs}/screenshots/images/providers.png +0 -0
  423. /package/{docs → core/workspace-app/docs}/screenshots/images/svg-form.png +0 -0
  424. /package/{docs → core/workspace-app/docs}/screenshots/images/svg-result.png +0 -0
  425. /package/{docs → core/workspace-app/docs}/screenshots/images/upload.png +0 -0
  426. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/2-form.png +0 -0
  427. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/8-ddev-options.png +0 -0
  428. /package/{docs → core/workspace-app/docs}/screenshots/new-folder/files-folder.png +0 -0
  429. /package/{docs → core/workspace-app/docs}/screenshots/projects/ddev-custom-commands.png +0 -0
  430. /package/{docs → core/workspace-app/docs}/screenshots/projects/state-chip-stop.png +0 -0
  431. /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-dark.png +0 -0
  432. /package/{docs → core/workspace-app/docs}/screenshots/readme/dashboard-light.png +0 -0
  433. /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-dark.png +0 -0
  434. /package/{docs → core/workspace-app/docs}/screenshots/readme/dev-light.png +0 -0
  435. /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-dark.png +0 -0
  436. /package/{docs → core/workspace-app/docs}/screenshots/readme/docs-light.png +0 -0
  437. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/ai.svg +0 -0
  438. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/code.svg +0 -0
  439. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/docs.svg +0 -0
  440. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/services.svg +0 -0
  441. /package/{docs → core/workspace-app/docs}/screenshots/readme/icons/sites.svg +0 -0
  442. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-dark.png +0 -0
  443. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-report-light.png +0 -0
  444. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-scenarios.png +0 -0
  445. /package/{docs → core/workspace-app/docs}/screenshots/readme/test-steps.png +0 -0
  446. /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-dark.png +0 -0
  447. /package/{docs → core/workspace-app/docs}/screenshots/readme/videos-light.png +0 -0
  448. /package/{docs → core/workspace-app/docs}/screenshots/screens-menu.png +0 -0
  449. /package/{docs → core/workspace-app/docs}/screenshots/screens-new.png +0 -0
  450. /package/{docs → core/workspace-app/docs}/screenshots/settings/auth-google-lists.png +0 -0
  451. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-1-row.png +0 -0
  452. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-2-confirm.png +0 -0
  453. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-3-type-name.png +0 -0
  454. /package/{docs → core/workspace-app/docs}/screenshots/settings/delete-4-type-yes.png +0 -0
  455. /package/{event-log.js → core/workspace-app/event-log.js} +0 -0
  456. /package/{files-browser.js → core/workspace-app/files-browser.js} +0 -0
  457. /package/{folders.js → core/workspace-app/folders.js} +0 -0
  458. /package/{format.js → core/workspace-app/format.js} +0 -0
  459. /package/{github-auth.js → core/workspace-app/github-auth.js} +0 -0
  460. /package/{html.js → core/workspace-app/html.js} +0 -0
  461. /package/{icons.js → core/workspace-app/icons.js} +0 -0
  462. /package/{images-ai.js → core/workspace-app/images-ai.js} +0 -0
  463. /package/{images-create.js → core/workspace-app/images-create.js} +0 -0
  464. /package/{images-page.js → core/workspace-app/images-page.js} +0 -0
  465. /package/{images-records.js → core/workspace-app/images-records.js} +0 -0
  466. /package/{images-render.js → core/workspace-app/images-render.js} +0 -0
  467. /package/{images-svg-core.js → core/workspace-app/images-svg-core.js} +0 -0
  468. /package/{images-svg-sanitize.js → core/workspace-app/images-svg-sanitize.js} +0 -0
  469. /package/{images-svg.js → core/workspace-app/images-svg.js} +0 -0
  470. /package/{images.js → core/workspace-app/images.js} +0 -0
  471. /package/{jobs.js → core/workspace-app/jobs.js} +0 -0
  472. /package/{linger.js → core/workspace-app/linger.js} +0 -0
  473. /package/{lists.js → core/workspace-app/lists.js} +0 -0
  474. /package/{media-info.js → core/workspace-app/media-info.js} +0 -0
  475. /package/{navigation-page.js → core/workspace-app/navigation-page.js} +0 -0
  476. /package/{navigation.js → core/workspace-app/navigation.js} +0 -0
  477. /package/{notes.js → core/workspace-app/notes.js} +0 -0
  478. /package/{palette-index.js → core/workspace-app/palette-index.js} +0 -0
  479. /package/{playwright.config.js → core/workspace-app/playwright.config.js} +0 -0
  480. /package/{plugin-install.js → core/workspace-app/plugin-install.js} +0 -0
  481. /package/{plugin-loader.js → core/workspace-app/plugin-loader.js} +0 -0
  482. /package/{plugin-manifest.js → core/workspace-app/plugin-manifest.js} +0 -0
  483. /package/{plugin-settings.js → core/workspace-app/plugin-settings.js} +0 -0
  484. /package/{plugins → core/workspace-app/plugins}/workspace-notes/index.js +0 -0
  485. /package/{project-make.js → core/workspace-app/project-make.js} +0 -0
  486. /package/{project-name.js → core/workspace-app/project-name.js} +0 -0
  487. /package/{public → core/workspace-app/public}/assistant-avatar-moves.js +0 -0
  488. /package/{public → core/workspace-app/public}/assistant-avatar-ui.js +0 -0
  489. /package/{public → core/workspace-app/public}/assistant-avatar.js +0 -0
  490. /package/{public → core/workspace-app/public}/avatar/druplicon.vrm +0 -0
  491. /package/{public → core/workspace-app/public}/css/base.css +0 -0
  492. /package/{public → core/workspace-app/public}/css/components/ai-assistant.css +0 -0
  493. /package/{public → core/workspace-app/public}/css/components/assistant-avatar.css +0 -0
  494. /package/{public → core/workspace-app/public}/css/components/assistant-views.css +0 -0
  495. /package/{public → core/workspace-app/public}/css/components/build-order.css +0 -0
  496. /package/{public → core/workspace-app/public}/css/components/clone-as-a-git-worktree.css +0 -0
  497. /package/{public → core/workspace-app/public}/css/components/command-palette.css +0 -0
  498. /package/{public → core/workspace-app/public}/css/components/commands-center.css +0 -0
  499. /package/{public → core/workspace-app/public}/css/components/confirm-dialog.css +0 -0
  500. /package/{public → core/workspace-app/public}/css/components/create-card.css +0 -0
  501. /package/{public → core/workspace-app/public}/css/components/ddev-menu.css +0 -0
  502. /package/{public → core/workspace-app/public}/css/components/deep-chat.css +0 -0
  503. /package/{public → core/workspace-app/public}/css/components/density.css +0 -0
  504. /package/{public → core/workspace-app/public}/css/components/floating-assistant-workspace-pages.css +0 -0
  505. /package/{public → core/workspace-app/public}/css/components/folders.css +0 -0
  506. /package/{public → core/workspace-app/public}/css/components/full-height-left-assistant-sidebar-all-pages.css +0 -0
  507. /package/{public → core/workspace-app/public}/css/components/full-height-right-actions-rail-pages-that-have-actions.css +0 -0
  508. /package/{public → core/workspace-app/public}/css/components/grid-and-list.css +0 -0
  509. /package/{public → core/workspace-app/public}/css/components/help-page.css +0 -0
  510. /package/{public → core/workspace-app/public}/css/components/hero.css +0 -0
  511. /package/{public → core/workspace-app/public}/css/components/images.css +0 -0
  512. /package/{public → core/workspace-app/public}/css/components/layout-rhythm-and-spacing-polish.css +0 -0
  513. /package/{public → core/workspace-app/public}/css/components/mic-on-any-field-that-wants-typing.css +0 -0
  514. /package/{public → core/workspace-app/public}/css/components/modal-fit.css +0 -0
  515. /package/{public → core/workspace-app/public}/css/components/page-width.css +0 -0
  516. /package/{public → core/workspace-app/public}/css/components/plugins-page.css +0 -0
  517. /package/{public → core/workspace-app/public}/css/components/project-cards.css +0 -0
  518. /package/{public → core/workspace-app/public}/css/components/project-rows.css +0 -0
  519. /package/{public → core/workspace-app/public}/css/components/reordering-the-workspace-cards.css +0 -0
  520. /package/{public → core/workspace-app/public}/css/components/responsive-home-and-the-workspace-pages.css +0 -0
  521. /package/{public → core/workspace-app/public}/css/components/running-commands-as-toasts.css +0 -0
  522. /package/{public → core/workspace-app/public}/css/components/settings-editor-core-config-yml.css +0 -0
  523. /package/{public → core/workspace-app/public}/css/components/short-results-as-a-toast-at-the-top.css +0 -0
  524. /package/{public → core/workspace-app/public}/css/components/sound-player.css +0 -0
  525. /package/{public → core/workspace-app/public}/css/components/suggest-agents-skills-commands.css +0 -0
  526. /package/{public → core/workspace-app/public}/css/components/tab-modal.css +0 -0
  527. /package/{public → core/workspace-app/public}/css/components/terminals.css +0 -0
  528. /package/{public → core/workspace-app/public}/css/components/test-report-media-one-file-per-row-paged.css +0 -0
  529. /package/{public → core/workspace-app/public}/css/components/top-toolbar-logo-breadcrumb.css +0 -0
  530. /package/{public → core/workspace-app/public}/css/components/video-edit.css +0 -0
  531. /package/{public → core/workspace-app/public}/css/components/web-ide-open-with.css +0 -0
  532. /package/{public → core/workspace-app/public}/css/components/working-indicators.css +0 -0
  533. /package/{public → core/workspace-app/public}/css/components/workspace-cards.css +0 -0
  534. /package/{public → core/workspace-app/public}/favicon.ico +0 -0
  535. /package/{public → core/workspace-app/public}/favicon.png +0 -0
  536. /package/{public → core/workspace-app/public}/htmx.min.js +0 -0
  537. /package/{public → core/workspace-app/public}/logo.png +0 -0
  538. /package/{public → core/workspace-app/public}/media-player.js +0 -0
  539. /package/{public → core/workspace-app/public}/mic.js +0 -0
  540. /package/{public → core/workspace-app/public}/narration.js +0 -0
  541. /package/{public → core/workspace-app/public}/palette.js +0 -0
  542. /package/{public → core/workspace-app/public}/sortable.js +0 -0
  543. /package/{public → core/workspace-app/public}/suggest.js +0 -0
  544. /package/{public → core/workspace-app/public}/terminal.js +0 -0
  545. /package/{public → core/workspace-app/public}/themes/default/theme.css +0 -0
  546. /package/{public → core/workspace-app/public}/themes/ember/theme.css +0 -0
  547. /package/{public → core/workspace-app/public}/themes/press/theme.css +0 -0
  548. /package/{public → core/workspace-app/public}/themes/slate/theme.css +0 -0
  549. /package/{public → core/workspace-app/public}/touch-icon.png +0 -0
  550. /package/{public → core/workspace-app/public}/ui.js +0 -0
  551. /package/{public → core/workspace-app/public}/vendor/ace/LICENSE.txt +0 -0
  552. /package/{public → core/workspace-app/public}/vendor/ace/ace.js +0 -0
  553. /package/{public → core/workspace-app/public}/vendor/ace/ext-language_tools.js +0 -0
  554. /package/{public → core/workspace-app/public}/vendor/ace/ext-searchbox.js +0 -0
  555. /package/{public → core/workspace-app/public}/vendor/ace/mode-css.js +0 -0
  556. /package/{public → core/workspace-app/public}/vendor/ace/mode-html.js +0 -0
  557. /package/{public → core/workspace-app/public}/vendor/ace/mode-javascript.js +0 -0
  558. /package/{public → core/workspace-app/public}/vendor/ace/mode-markdown.js +0 -0
  559. /package/{public → core/workspace-app/public}/vendor/ace/mode-sh.js +0 -0
  560. /package/{public → core/workspace-app/public}/vendor/ace/mode-yaml.js +0 -0
  561. /package/{public → core/workspace-app/public}/vendor/ace/theme-textmate.js +0 -0
  562. /package/{public → core/workspace-app/public}/vendor/ace/theme-tomorrow_night.js +0 -0
  563. /package/{public → core/workspace-app/public}/vendor/ace/worker-yaml.js +0 -0
  564. /package/{public → core/workspace-app/public}/vendor/deep-chat.LICENSE +0 -0
  565. /package/{public → core/workspace-app/public}/vendor/deep-chat.bundle.js +0 -0
  566. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-LICENSE.txt +0 -0
  567. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-italic.woff2 +0 -0
  568. /package/{public → core/workspace-app/public}/vendor/fonts/dm-serif-text-latin-400-normal.woff2 +0 -0
  569. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-LICENSE.txt +0 -0
  570. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-italic.woff2 +0 -0
  571. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-400-normal.woff2 +0 -0
  572. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-italic.woff2 +0 -0
  573. /package/{public → core/workspace-app/public}/vendor/fonts/figtree-latin-600-normal.woff2 +0 -0
  574. /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-LICENSE.txt +0 -0
  575. /package/{public → core/workspace-app/public}/vendor/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
  576. /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-LICENSE.txt +0 -0
  577. /package/{public → core/workspace-app/public}/vendor/fonts/ubuntu-mono-latin-wght-normal.woff2 +0 -0
  578. /package/{public → core/workspace-app/public}/vendor/tabler-LICENSE.txt +0 -0
  579. /package/{public → core/workspace-app/public}/vendor/tabler-sprite.svg +0 -0
  580. /package/{public → core/workspace-app/public}/vendor/three/LICENSE +0 -0
  581. /package/{public → core/workspace-app/public}/vendor/three/addons/loaders/GLTFLoader.js +0 -0
  582. /package/{public → core/workspace-app/public}/vendor/three/addons/utils/BufferGeometryUtils.js +0 -0
  583. /package/{public → core/workspace-app/public}/vendor/three/addons/utils/SkeletonUtils.js +0 -0
  584. /package/{public → core/workspace-app/public}/vendor/three/three.core.min.js +0 -0
  585. /package/{public → core/workspace-app/public}/vendor/three/three.module.min.js +0 -0
  586. /package/{public → core/workspace-app/public}/vendor/three-vrm/LICENSE +0 -0
  587. /package/{public → core/workspace-app/public}/vendor/three-vrm/three-vrm.module.min.js +0 -0
  588. /package/{public → core/workspace-app/public}/vendor/uikit-icons.min.js +0 -0
  589. /package/{public → core/workspace-app/public}/vendor/uikit.min.css +0 -0
  590. /package/{public → core/workspace-app/public}/vendor/uikit.min.js +0 -0
  591. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/bookmark-outlined.svg +0 -0
  592. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/headset.svg +0 -0
  593. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/menu.svg +0 -0
  594. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/pause.svg +0 -0
  595. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/play.svg +0 -0
  596. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/share.svg +0 -0
  597. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-off.svg +0 -0
  598. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/sound-on.svg +0 -0
  599. /package/{public → core/workspace-app/public}/vendor/webship-icons/ui/stop.svg +0 -0
  600. /package/{public → core/workspace-app/public}/vendor/xterm/addon-fit.js +0 -0
  601. /package/{public → core/workspace-app/public}/vendor/xterm/addon-unicode11.js +0 -0
  602. /package/{public → core/workspace-app/public}/vendor/xterm/addon-web-links.js +0 -0
  603. /package/{public → core/workspace-app/public}/vendor/xterm/xterm.css +0 -0
  604. /package/{public → core/workspace-app/public}/vendor/xterm/xterm.js +0 -0
  605. /package/{rag.js → core/workspace-app/rag.js} +0 -0
  606. /package/{reader.js → core/workspace-app/reader.js} +0 -0
  607. /package/{repo-blobs.js → core/workspace-app/repo-blobs.js} +0 -0
  608. /package/{report-pdf.js → core/workspace-app/report-pdf.js} +0 -0
  609. /package/{row-layout.js → core/workspace-app/row-layout.js} +0 -0
  610. /package/{screenrc → core/workspace-app/screenrc} +0 -0
  611. /package/{screens.js → core/workspace-app/screens.js} +0 -0
  612. /package/{scripts → core/workspace-app/scripts}/code-server-start.sh +0 -0
  613. /package/{scripts → core/workspace-app/scripts}/images-claude-finish.js +0 -0
  614. /package/{scripts → core/workspace-app/scripts}/screen-host.sh +0 -0
  615. /package/{scripts → core/workspace-app/scripts}/screen-runner.js +0 -0
  616. /package/{scripts → core/workspace-app/scripts}/screen-then.js +0 -0
  617. /package/{scripts → core/workspace-app/scripts}/update.sh +0 -0
  618. /package/{settings-file.js → core/workspace-app/settings-file.js} +0 -0
  619. /package/{speech.js → core/workspace-app/speech.js} +0 -0
  620. /package/{tab-modal.js → core/workspace-app/tab-modal.js} +0 -0
  621. /package/{test-reports.js → core/workspace-app/test-reports.js} +0 -0
  622. /package/{themes.js → core/workspace-app/themes.js} +0 -0
  623. /package/{transcripts.js → core/workspace-app/transcripts.js} +0 -0
  624. /package/{urls.js → core/workspace-app/urls.js} +0 -0
  625. /package/{validate.js → core/workspace-app/validate.js} +0 -0
  626. /package/{version-check.js → core/workspace-app/version-check.js} +0 -0
  627. /package/{video-edit.js → core/workspace-app/video-edit.js} +0 -0
  628. /package/{waiting.js → core/workspace-app/waiting.js} +0 -0
  629. /package/{workspace-card-tools.js → core/workspace-app/workspace-card-tools.js} +0 -0
  630. /package/{workspace-delete.js → core/workspace-app/workspace-delete.js} +0 -0
  631. /package/{workspace-types.js → core/workspace-app/workspace-types.js} +0 -0
  632. /package/{workspaces.js → core/workspace-app/workspaces.js} +0 -0
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The screens manager's own logic: which places a session may be started in,
3
+ * how a target token resolves to a directory, and what the session-name rules
4
+ * actually admit. These are the guards that keep a browser-supplied name out
5
+ * of `screen`, so they are pinned here rather than left to the browser tests.
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 { copyConfig } = require('./_isolated-config');
14
+
15
+ // The targets are read from a tree built here, not from this checkout: a fresh clone holds the
16
+ // workspace folders and no project in any of them. The modules read their root once, at load, so
17
+ // it is set before they are required.
18
+ const APP = path.join(__dirname, '../..');
19
+ const REPO = path.join(APP, '../..');
20
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-screens-root-'));
21
+ process.env.WORKSPACE_ROOT = root;
22
+
23
+ const { screenTargets, screenTargetDir } = require('../../screens');
24
+ const { ROOT, loadWorkspaces } = require('../../workspaces');
25
+
26
+ before(() => {
27
+ fs.mkdirSync(path.join(root, 'core'), { recursive: true });
28
+ copyConfig(path.join(REPO, 'core/config'), path.join(root, 'core/config'));
29
+ fs.symlinkSync(path.join(REPO, 'core/scripts'), path.join(root, 'core/scripts'));
30
+ fs.symlinkSync(APP, path.join(root, 'core/workspace-app'));
31
+ // Every workspace folder, as a clone has them; one project in dev/; a dot folder, which is not one.
32
+ for (const w of Object.values(loadWorkspaces())) fs.mkdirSync(w.dir, { recursive: true });
33
+ fs.mkdirSync(path.join(root, 'dev', 'blog1', '.ddev'), { recursive: true });
34
+ fs.mkdirSync(path.join(root, 'dev', '.cache'), { recursive: true });
35
+ });
36
+
37
+ after(() => fs.rmSync(root, { recursive: true, force: true }));
38
+
39
+ test('the workspace root is always a place to start a session', () => {
40
+ const root = screenTargets().find((t) => t.value === 'root');
41
+ assert.ok(root);
42
+ assert.strictEqual(root.dir, ROOT);
43
+ });
44
+
45
+ test('every target resolves to a real directory inside the workspace', () => {
46
+ for (const t of screenTargets().slice(0, 40)) {
47
+ const dir = screenTargetDir(t.value);
48
+ assert.ok(dir, `${t.value} resolves`);
49
+ assert.ok(dir.startsWith(ROOT), `${dir} is inside the workspace`);
50
+ }
51
+ });
52
+
53
+ test('a target token the server did not offer resolves to nothing', () => {
54
+ for (const bad of ['', 'root/../..', 'p:dev:../../etc', '/etc', 'ws:no-such', 'p:no:no']) {
55
+ assert.strictEqual(screenTargetDir(bad), null, `${bad} is refused`);
56
+ }
57
+ });
58
+
59
+ test('workspace folders and projects are offered separately', () => {
60
+ const targets = screenTargets();
61
+ assert.ok(targets.some((t) => t.group === 'Workspace folders'));
62
+ assert.ok(targets.some((t) => t.group === 'Projects'), 'at least one project is offered');
63
+ const dev = targets.find((t) => t.value === 'ws:dev');
64
+ assert.ok(dev && dev.dir === path.join(ROOT, 'dev'));
65
+ const blog = targets.find((t) => t.value === 'p:dev:blog1');
66
+ assert.ok(blog && blog.group === 'Projects' && blog.dir === path.join(ROOT, 'dev', 'blog1'),
67
+ 'the project in dev/ is offered under Projects');
68
+ assert.ok(!targets.some((t) => t.value === 'p:dev:.cache'), 'a dot folder is not a project');
69
+ });
70
+
71
+ // The name rule is shared by the form, the actions and both /fragments routes:
72
+ // it must never admit a leading '-', which `screen` would read as an option.
73
+ const NAME_RE = /^[a-z0-9][a-zA-Z0-9_.-]*$/;
74
+
75
+ test('a session name may not start with a dash, and holds no shell characters', () => {
76
+ for (const ok of ['build1', 'jira-webship-2307', 'a.b_c-d', 'x']) assert.ok(NAME_RE.test(ok), ok);
77
+ for (const bad of ['-d', '-S', '; rm -rf /', 'a b', '../x', 'a$b', 'A-first', '']) {
78
+ assert.ok(!NAME_RE.test(bad), `${bad} is refused`);
79
+ }
80
+ });
81
+
82
+ // The machine's sessions reach the dashboard through the folder both sides
83
+ // share: the runner republishes its list every two seconds, so a stale file
84
+ // means the runner is gone and nothing new may be started.
85
+ test('a published session list is trusted only while it is fresh', () => {
86
+ const file = path.join(ROOT, '.screens', 'host-sessions.json');
87
+ const { hostSessionsFile } = require('../../jobs');
88
+ fs.mkdirSync(path.dirname(file), { recursive: true });
89
+ fs.writeFileSync(file, JSON.stringify({ at: Date.now(), host: os.hostname(), sessions: [{ session: 'a--b--c' }] }));
90
+ assert.equal(hostSessionsFile().sessions.length, 1, 'a list written now is the truth');
91
+ fs.writeFileSync(file, JSON.stringify({ at: Date.now() - 60000, sessions: [{ session: 'a--b--c' }] }));
92
+ assert.equal(hostSessionsFile(), null, 'a minute-old list means the runner stopped');
93
+ });
@@ -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
+ });