@hydranium/cli 1.0.0-next.40 → 1.0.0-next.401

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 (339) hide show
  1. package/README.md +102 -204
  2. package/bin/hydranium-cli.js +13 -0
  3. package/heap-analysis/heap/core/load.mjs +2 -2
  4. package/heap-analysis/heap/langium/langium.classifier.mjs +22 -11
  5. package/lib/cli-version.js.map +1 -1
  6. package/lib/cli.js +7 -2
  7. package/lib/cli.js.map +1 -1
  8. package/lib/commands/analyze-heap-args.d.ts.map +1 -1
  9. package/lib/commands/analyze-heap-args.js +1 -2
  10. package/lib/commands/analyze-heap-args.js.map +1 -1
  11. package/lib/commands/analyze-heap.d.ts +3 -0
  12. package/lib/commands/analyze-heap.d.ts.map +1 -1
  13. package/lib/commands/analyze-heap.js +2 -1
  14. package/lib/commands/analyze-heap.js.map +1 -1
  15. package/lib/commands/ast-ground-truth-args.d.ts +3 -2
  16. package/lib/commands/ast-ground-truth-args.d.ts.map +1 -1
  17. package/lib/commands/ast-ground-truth-args.js +11 -9
  18. package/lib/commands/ast-ground-truth-args.js.map +1 -1
  19. package/lib/commands/ast-ground-truth-driver.js +4 -2
  20. package/lib/commands/ast-ground-truth-driver.js.map +1 -1
  21. package/lib/commands/ast-ground-truth.js +1 -1
  22. package/lib/commands/ast-ground-truth.js.map +1 -1
  23. package/lib/commands/client-session.d.ts +17 -0
  24. package/lib/commands/client-session.d.ts.map +1 -0
  25. package/lib/commands/client-session.js +27 -0
  26. package/lib/commands/client-session.js.map +1 -0
  27. package/lib/commands/create-args.d.ts +17 -0
  28. package/lib/commands/create-args.d.ts.map +1 -0
  29. package/lib/commands/create-args.js +53 -0
  30. package/lib/commands/create-args.js.map +1 -0
  31. package/lib/commands/create.d.ts +38 -0
  32. package/lib/commands/create.d.ts.map +1 -0
  33. package/lib/commands/create.js +43 -0
  34. package/lib/commands/create.js.map +1 -0
  35. package/lib/commands/delete-args.d.ts +17 -0
  36. package/lib/commands/delete-args.d.ts.map +1 -0
  37. package/lib/commands/delete-args.js +49 -0
  38. package/lib/commands/delete-args.js.map +1 -0
  39. package/lib/commands/delete.d.ts +30 -0
  40. package/lib/commands/delete.d.ts.map +1 -0
  41. package/lib/commands/delete.js +32 -0
  42. package/lib/commands/delete.js.map +1 -0
  43. package/lib/commands/generate-ast-builder.d.ts.map +1 -1
  44. package/lib/commands/generate-ast-builder.js +1 -10
  45. package/lib/commands/generate-ast-builder.js.map +1 -1
  46. package/lib/commands/generate-languages.d.ts +35 -0
  47. package/lib/commands/generate-languages.d.ts.map +1 -0
  48. package/lib/commands/generate-languages.js +105 -0
  49. package/lib/commands/generate-languages.js.map +1 -0
  50. package/lib/commands/generate-transfer-builder.d.ts +33 -0
  51. package/lib/commands/generate-transfer-builder.d.ts.map +1 -0
  52. package/lib/commands/generate-transfer-builder.js +86 -0
  53. package/lib/commands/generate-transfer-builder.js.map +1 -0
  54. package/lib/commands/generate-transfer-model-args.d.ts.map +1 -1
  55. package/lib/commands/generate-transfer-model-args.js +20 -5
  56. package/lib/commands/generate-transfer-model-args.js.map +1 -1
  57. package/lib/commands/generate-transfer-model-config.d.ts.map +1 -1
  58. package/lib/commands/generate-transfer-model-config.js +8 -3
  59. package/lib/commands/generate-transfer-model-config.js.map +1 -1
  60. package/lib/commands/generate-transfer-model.d.ts +40 -9
  61. package/lib/commands/generate-transfer-model.d.ts.map +1 -1
  62. package/lib/commands/generate-transfer-model.js +219 -67
  63. package/lib/commands/generate-transfer-model.js.map +1 -1
  64. package/lib/commands/generated-module-path.d.ts +16 -0
  65. package/lib/commands/generated-module-path.d.ts.map +1 -0
  66. package/lib/commands/generated-module-path.js +21 -0
  67. package/lib/commands/generated-module-path.js.map +1 -0
  68. package/lib/commands/harness-args.d.ts +44 -42
  69. package/lib/commands/harness-args.d.ts.map +1 -1
  70. package/lib/commands/harness-args.js +109 -27
  71. package/lib/commands/harness-args.js.map +1 -1
  72. package/lib/commands/headless-harness.d.ts +37 -20
  73. package/lib/commands/headless-harness.d.ts.map +1 -1
  74. package/lib/commands/headless-harness.js +71 -19
  75. package/lib/commands/headless-harness.js.map +1 -1
  76. package/lib/commands/init-args.d.ts +2 -0
  77. package/lib/commands/init-args.d.ts.map +1 -1
  78. package/lib/commands/init-args.js +74 -31
  79. package/lib/commands/init-args.js.map +1 -1
  80. package/lib/commands/init-hosts.d.ts +24 -0
  81. package/lib/commands/init-hosts.d.ts.map +1 -0
  82. package/lib/commands/init-hosts.js +37 -0
  83. package/lib/commands/init-hosts.js.map +1 -0
  84. package/lib/commands/init-prompt.d.ts +3 -3
  85. package/lib/commands/init-prompt.d.ts.map +1 -1
  86. package/lib/commands/init-prompt.js +4 -5
  87. package/lib/commands/init-prompt.js.map +1 -1
  88. package/lib/commands/init-templates.d.ts +20 -14
  89. package/lib/commands/init-templates.d.ts.map +1 -1
  90. package/lib/commands/init-templates.js +2983 -752
  91. package/lib/commands/init-templates.js.map +1 -1
  92. package/lib/commands/init-wizard.d.ts +3 -0
  93. package/lib/commands/init-wizard.d.ts.map +1 -1
  94. package/lib/commands/init-wizard.js +46 -31
  95. package/lib/commands/init-wizard.js.map +1 -1
  96. package/lib/commands/init-workspace.d.ts +13 -11
  97. package/lib/commands/init-workspace.d.ts.map +1 -1
  98. package/lib/commands/init-workspace.js +21 -7
  99. package/lib/commands/init-workspace.js.map +1 -1
  100. package/lib/commands/init.d.ts +58 -22
  101. package/lib/commands/init.d.ts.map +1 -1
  102. package/lib/commands/init.js +203 -48
  103. package/lib/commands/init.js.map +1 -1
  104. package/lib/commands/kebab.d.ts +15 -0
  105. package/lib/commands/kebab.d.ts.map +1 -0
  106. package/lib/commands/kebab.js +17 -0
  107. package/lib/commands/kebab.js.map +1 -0
  108. package/lib/commands/lint-grammar-args.d.ts +3 -2
  109. package/lib/commands/lint-grammar-args.d.ts.map +1 -1
  110. package/lib/commands/lint-grammar-args.js +13 -10
  111. package/lib/commands/lint-grammar-args.js.map +1 -1
  112. package/lib/commands/lint-grammar-driver.js +2 -1
  113. package/lib/commands/lint-grammar-driver.js.map +1 -1
  114. package/lib/commands/lint-grammar-report.js.map +1 -1
  115. package/lib/commands/lint-grammar.js +1 -1
  116. package/lib/commands/lint-grammar.js.map +1 -1
  117. package/lib/commands/measure-memory-args.d.ts +3 -2
  118. package/lib/commands/measure-memory-args.d.ts.map +1 -1
  119. package/lib/commands/measure-memory-args.js +22 -11
  120. package/lib/commands/measure-memory-args.js.map +1 -1
  121. package/lib/commands/measure-memory-driver.js +5 -3
  122. package/lib/commands/measure-memory-driver.js.map +1 -1
  123. package/lib/commands/measure-memory.js +1 -1
  124. package/lib/commands/measure-memory.js.map +1 -1
  125. package/lib/commands/model-docs-args.d.ts +4 -3
  126. package/lib/commands/model-docs-args.d.ts.map +1 -1
  127. package/lib/commands/model-docs-args.js +12 -10
  128. package/lib/commands/model-docs-args.js.map +1 -1
  129. package/lib/commands/model-docs-driver.js +2 -1
  130. package/lib/commands/model-docs-driver.js.map +1 -1
  131. package/lib/commands/model-docs.js +1 -1
  132. package/lib/commands/model-docs.js.map +1 -1
  133. package/lib/commands/projects-args.d.ts.map +1 -1
  134. package/lib/commands/projects-args.js +5 -5
  135. package/lib/commands/projects-args.js.map +1 -1
  136. package/lib/commands/projects.d.ts +3 -4
  137. package/lib/commands/projects.d.ts.map +1 -1
  138. package/lib/commands/projects.js +2 -2
  139. package/lib/commands/projects.js.map +1 -1
  140. package/lib/commands/query-args.d.ts.map +1 -1
  141. package/lib/commands/query-args.js +6 -5
  142. package/lib/commands/query-args.js.map +1 -1
  143. package/lib/commands/query.d.ts +6 -5
  144. package/lib/commands/query.d.ts.map +1 -1
  145. package/lib/commands/query.js +5 -4
  146. package/lib/commands/query.js.map +1 -1
  147. package/lib/commands/reflect-args.d.ts +3 -2
  148. package/lib/commands/reflect-args.d.ts.map +1 -1
  149. package/lib/commands/reflect-args.js +13 -10
  150. package/lib/commands/reflect-args.js.map +1 -1
  151. package/lib/commands/reflect-driver.js +2 -1
  152. package/lib/commands/reflect-driver.js.map +1 -1
  153. package/lib/commands/reflect-report.js.map +1 -1
  154. package/lib/commands/reflect.js +1 -1
  155. package/lib/commands/reflect.js.map +1 -1
  156. package/lib/commands/save-args.d.ts.map +1 -1
  157. package/lib/commands/save-args.js +7 -4
  158. package/lib/commands/save-args.js.map +1 -1
  159. package/lib/commands/save.d.ts +8 -4
  160. package/lib/commands/save.d.ts.map +1 -1
  161. package/lib/commands/save.js +12 -23
  162. package/lib/commands/save.js.map +1 -1
  163. package/lib/commands/validate-args.d.ts +3 -2
  164. package/lib/commands/validate-args.d.ts.map +1 -1
  165. package/lib/commands/validate-args.js +13 -10
  166. package/lib/commands/validate-args.js.map +1 -1
  167. package/lib/commands/validate-driver.js +2 -1
  168. package/lib/commands/validate-driver.js.map +1 -1
  169. package/lib/commands/validate-report.js.map +1 -1
  170. package/lib/commands/validate.js +1 -1
  171. package/lib/commands/validate.js.map +1 -1
  172. package/lib/commands/watch-args.d.ts.map +1 -1
  173. package/lib/commands/watch-args.js +6 -4
  174. package/lib/commands/watch-args.js.map +1 -1
  175. package/lib/commands/watch.d.ts +3 -4
  176. package/lib/commands/watch.d.ts.map +1 -1
  177. package/lib/commands/watch.js +5 -2
  178. package/lib/commands/watch.js.map +1 -1
  179. package/lib/driver-heap.d.ts +52 -0
  180. package/lib/driver-heap.d.ts.map +1 -0
  181. package/lib/driver-heap.js +51 -0
  182. package/lib/driver-heap.js.map +1 -0
  183. package/lib/index.d.ts +2 -0
  184. package/lib/index.d.ts.map +1 -1
  185. package/lib/index.js +2 -0
  186. package/lib/index.js.map +1 -1
  187. package/lib/log-level.d.ts +21 -11
  188. package/lib/log-level.d.ts.map +1 -1
  189. package/lib/log-level.js +25 -14
  190. package/lib/log-level.js.map +1 -1
  191. package/lib/spawn-data-server.d.ts +9 -4
  192. package/lib/spawn-data-server.d.ts.map +1 -1
  193. package/lib/spawn-data-server.js +5 -4
  194. package/lib/spawn-data-server.js.map +1 -1
  195. package/lib/stderr-connection-logger.d.ts +12 -0
  196. package/lib/stderr-connection-logger.d.ts.map +1 -0
  197. package/lib/stderr-connection-logger.js +16 -0
  198. package/lib/stderr-connection-logger.js.map +1 -0
  199. package/lib/testing/echo-server.js +9 -1
  200. package/lib/testing/echo-server.js.map +1 -1
  201. package/package.json +90 -39
  202. package/src/cli.ts +7 -2
  203. package/src/commands/analyze-heap-args.ts +1 -2
  204. package/src/commands/analyze-heap.ts +4 -1
  205. package/src/commands/ast-ground-truth-args.ts +15 -12
  206. package/src/commands/ast-ground-truth-driver.ts +4 -2
  207. package/src/commands/ast-ground-truth.ts +1 -1
  208. package/src/commands/client-session.ts +33 -0
  209. package/src/commands/create-args.ts +68 -0
  210. package/src/commands/create.ts +83 -0
  211. package/src/commands/delete-args.ts +64 -0
  212. package/src/commands/delete.ts +61 -0
  213. package/src/commands/generate-ast-builder.ts +1 -11
  214. package/src/commands/generate-languages.ts +132 -0
  215. package/src/commands/generate-transfer-builder.ts +101 -0
  216. package/src/commands/generate-transfer-model-args.ts +20 -5
  217. package/src/commands/generate-transfer-model-config.ts +8 -3
  218. package/src/commands/generate-transfer-model.ts +291 -65
  219. package/src/commands/generated-module-path.ts +22 -0
  220. package/src/commands/harness-args.ts +99 -31
  221. package/src/commands/headless-harness.ts +89 -28
  222. package/src/commands/init-args.ts +76 -31
  223. package/src/commands/init-hosts.ts +51 -0
  224. package/src/commands/init-prompt.ts +5 -6
  225. package/src/commands/init-templates.ts +3150 -761
  226. package/src/commands/init-wizard.ts +55 -31
  227. package/src/commands/init-workspace.ts +31 -16
  228. package/src/commands/init.ts +261 -63
  229. package/src/commands/kebab.ts +17 -0
  230. package/src/commands/lint-grammar-args.ts +17 -13
  231. package/src/commands/lint-grammar-driver.ts +2 -1
  232. package/src/commands/lint-grammar.ts +1 -1
  233. package/src/commands/measure-memory-args.ts +27 -14
  234. package/src/commands/measure-memory-driver.ts +5 -3
  235. package/src/commands/measure-memory.ts +1 -1
  236. package/src/commands/model-docs-args.ts +16 -13
  237. package/src/commands/model-docs-driver.ts +2 -1
  238. package/src/commands/model-docs.ts +1 -1
  239. package/src/commands/projects-args.ts +5 -4
  240. package/src/commands/projects.ts +4 -6
  241. package/src/commands/query-args.ts +6 -4
  242. package/src/commands/query.ts +9 -9
  243. package/src/commands/reflect-args.ts +17 -13
  244. package/src/commands/reflect-driver.ts +2 -1
  245. package/src/commands/reflect.ts +1 -1
  246. package/src/commands/save-args.ts +7 -3
  247. package/src/commands/save.ts +24 -27
  248. package/src/commands/validate-args.ts +17 -13
  249. package/src/commands/validate-driver.ts +2 -1
  250. package/src/commands/validate.ts +1 -1
  251. package/src/commands/watch-args.ts +6 -3
  252. package/src/commands/watch.ts +7 -6
  253. package/src/driver-heap.ts +65 -0
  254. package/src/index.ts +2 -0
  255. package/src/log-level.ts +42 -14
  256. package/src/spawn-data-server.ts +17 -5
  257. package/src/stderr-connection-logger.ts +18 -0
  258. package/src/testing/echo-server.ts +11 -2
  259. package/templates/client/gitignore +3 -0
  260. package/templates/client/src/__GRAMMAR_ID__-diagram-module.ts +82 -0
  261. package/templates/editorconfig +15 -0
  262. package/templates/gitignore +1 -0
  263. package/templates/host-browser/esbuild.mjs +82 -0
  264. package/templates/host-browser/gitignore +4 -0
  265. package/templates/host-browser/hydranium-mark.svg +40 -0
  266. package/templates/host-browser/index.html +287 -0
  267. package/templates/host-browser/playwright.config.mts +24 -0
  268. package/templates/host-browser/src/data-status.ts +15 -0
  269. package/templates/host-browser/src/diagram-save.ts +60 -0
  270. package/templates/host-browser/src/diagram.ts +121 -0
  271. package/templates/host-browser/src/editor.ts +102 -0
  272. package/templates/host-browser/src/log.ts +14 -0
  273. package/templates/host-browser/src/monaco-editor-core-worker.d.ts +7 -0
  274. package/templates/host-browser/src/monaco-editor-worker.ts +10 -0
  275. package/templates/host-browser/src/posix-path-shim.ts +23 -0
  276. package/templates/host-browser/src/shown-file.ts +61 -0
  277. package/templates/host-browser/src/workspace.ts +5 -0
  278. package/templates/host-browser/tsconfig.e2e.json +16 -0
  279. package/templates/host-browser/tsconfig.page.json +16 -0
  280. package/templates/host-browser/tsconfig.worker.json +16 -0
  281. package/templates/host-theia-browser/esbuild.mjs +24 -0
  282. package/templates/host-theia-browser/gitignore +9 -0
  283. package/templates/host-theia-browser/playwright.config.mts +42 -0
  284. package/templates/host-theia-browser/start.mjs +50 -0
  285. package/templates/host-theia-browser/tsconfig.e2e.json +16 -0
  286. package/templates/host-theia-electron/esbuild.mjs +27 -0
  287. package/templates/host-theia-electron/gitignore +8 -0
  288. package/templates/host-theia-electron/playwright.config.mts +12 -0
  289. package/templates/host-theia-electron/start.mjs +47 -0
  290. package/templates/host-theia-electron/tsconfig.e2e.json +16 -0
  291. package/templates/host-vscode/esbuild.mjs +38 -0
  292. package/templates/host-vscode/gitignore +7 -0
  293. package/templates/host-vscode/playwright.config.mts +14 -0
  294. package/templates/host-vscode/src/__GRAMMAR_ID__-diagram-editor.ts +19 -0
  295. package/templates/host-vscode/src/show-model-command.ts +24 -0
  296. package/templates/host-vscode/src/webview/__GRAMMAR_ID__-diagram.ts +18 -0
  297. package/templates/host-vscode/src/webview/diagram.css +40 -0
  298. package/templates/host-vscode/start.mjs +22 -0
  299. package/templates/host-vscode/test/install-vscode.mts +21 -0
  300. package/templates/host-vscode/test/vscode-version.mts +10 -0
  301. package/templates/host-vscode/tsconfig.e2e.json +17 -0
  302. package/templates/host-vscode/tsconfig.json +19 -0
  303. package/templates/host-vscode/tsconfig.webview.json +17 -0
  304. package/templates/oxlintrc.json +4 -0
  305. package/templates/protocol/gitignore +3 -0
  306. package/templates/protocol/src/__GRAMMAR_ID__-diagram-types.ts +47 -0
  307. package/templates/server/gitignore +6 -0
  308. package/templates/server/src/glsp/__GRAMMAR_ID__/create-node-operation-handler.ts +62 -0
  309. package/templates/server/src/glsp/__GRAMMAR_ID__/delete-operation-handler.ts +63 -0
  310. package/templates/server/src/glsp/__GRAMMAR_ID__/gmodel-factory.ts +144 -0
  311. package/templates/server/src/glsp/__GRAMMAR_ID__/state.ts +18 -0
  312. package/templates/server/src/glsp/__GRAMMAR_ID__/storage.ts +16 -0
  313. package/templates/server/src/glsp/__GRAMMAR_ID__/submission-handler.ts +24 -0
  314. package/templates/server/src/grammar/common.langium +12 -0
  315. package/templates/server/src/language-server/__GRAMMAR_ID__-serializer.ts +53 -0
  316. package/templates/server/src/language-server/__GRAMMAR_ID__-validation.ts +30 -0
  317. package/templates/server/src/language-server/ast.ts +23 -0
  318. package/templates/server/src/node/services.ts +16 -0
  319. package/templates/server/test/__GRAMMAR_ID__-diagram.test.ts +403 -0
  320. package/templates/server/test/tsconfig.json +4 -0
  321. package/templates/server/tsconfig.test.json +10 -0
  322. package/templates/server/vitest.config.ts +14 -0
  323. package/templates/theia/gitignore +3 -0
  324. package/templates/theia/src/browser/__GRAMMAR_ID__-diagram-frontend-module.ts +65 -0
  325. package/templates/theia/src/browser/__PROJECT_ID__-connection-preload-module.ts +15 -0
  326. package/templates/theia/src/browser/__PROJECT_ID__-data-connection.ts +15 -0
  327. package/templates/theia/src/browser/__PROJECT_ID__-data-port.ts +11 -0
  328. package/templates/theia/src/browser/__PROJECT_ID__-glsp-client-contribution.ts +13 -0
  329. package/templates/theia/src/browser/__PROJECT_ID__-show-model-command.ts +51 -0
  330. package/templates/theia/src/common/__PROJECT_ID__-glsp-contribution.ts +3 -0
  331. package/templates/theia/src/node/__PROJECT_ID__-connection-backend-module.ts +12 -0
  332. package/templates/theia/src/node/__PROJECT_ID__-data-backend-module.ts +15 -0
  333. package/templates/theia/src/node/__PROJECT_ID__-glsp-backend-module.ts +16 -0
  334. package/templates/theia/style/diagram.css +20 -0
  335. package/templates/theia/tsconfig.json +24 -0
  336. package/templates/vscode-servers/gitignore +4 -0
  337. package/templates/vscode-servers/tsconfig.json +19 -0
  338. package/templates/workspace/nodes.__EXTENSION__ +5 -0
  339. package/templates/workspace/references.__EXTENSION__ +2 -0
@@ -0,0 +1,10 @@
1
+ // Monaco's own editor worker, a third worker hosting no head: Monaco runs link
2
+ // detection, diffs and minimal edits there. `monaco-editor-core` ships no
3
+ // entry for it, so this is one: wait for the first message, then start, which
4
+ // replaces this handler with Monaco's own.
5
+
6
+ import { start } from 'monaco-editor-core/esm/vs/editor/editor.worker.start.js';
7
+
8
+ self.onmessage = (): void => {
9
+ start(() => ({}));
10
+ };
@@ -0,0 +1,23 @@
1
+ // The one function of Node's `path` the framework imports, for the bundle to
2
+ // resolve: `esbuild.mjs` points the bare `path` specifier here, while a
3
+ // `node:path` import still fails the bundle. Only a workspace folder given as
4
+ // a filesystem path reaches it, which the page, naming a URI, never does.
5
+
6
+ /** Resolve `segments` right to left into one absolute path, as `path.posix.resolve` does from `/`. */
7
+ export function resolve(...segments: string[]): string {
8
+ let resolved = '';
9
+ for (let i = segments.length - 1; i >= 0 && !resolved.startsWith('/'); i--) {
10
+ if (segments[i].length > 0) {
11
+ resolved = resolved.length === 0 ? segments[i] : `${segments[i]}/${resolved}`;
12
+ }
13
+ }
14
+ const parts: string[] = [];
15
+ for (const part of resolved.split('/')) {
16
+ if (part === '..') {
17
+ parts.pop();
18
+ } else if (part !== '' && part !== '.') {
19
+ parts.push(part);
20
+ }
21
+ }
22
+ return `/${parts.join('/')}`;
23
+ }
@@ -0,0 +1,61 @@
1
+ // The file the page shows: the switch above the panes, between the workspace
2
+ // files the worker holds. Each part follows it, and any part can switch it, so
3
+ // a page with one part fewer still has it.
4
+
5
+ import type { WorkerHeads } from '@hydranium/client-browser';
6
+ import { WORKSPACE_ROOT_URI } from './workspace.js';
7
+
8
+ /** The file the page shows, for each part that follows it. */
9
+ export interface ShownFile {
10
+ /** Each workspace file a language claims, by URI, with the text the worker holds. */
11
+ readonly files: ReadonlyMap<string, string>;
12
+ /** The URI of the file shown now. */
13
+ current(): string;
14
+ /** Switch the page to `uri`, one of {@link files}. */
15
+ show(uri: string): void;
16
+ /** Call `listener` with the URI of each file the page switches to. */
17
+ onDidChange(listener: (uri: string) => void): void;
18
+ }
19
+
20
+ /**
21
+ * Fill the switch with the files of `languages` the worker sent once its heads
22
+ * were up, and show the first.
23
+ */
24
+ export async function startShownFile(
25
+ heads: WorkerHeads,
26
+ languages: ReadonlyArray<{ readonly fileExtensions: readonly string[] }>
27
+ ): Promise<ShownFile> {
28
+ // The text comes from the worker rather than the seed, since a saved edit
29
+ // is laid over the seed there.
30
+ const { workspace } = await heads.ready;
31
+ if (workspace === undefined) {
32
+ throw new Error('the worker sent no workspace files');
33
+ }
34
+ // Only the files a part of the page can show: one of another language would show nothing.
35
+ const claimed = (path: string): boolean =>
36
+ languages.some(language => language.fileExtensions.some(extension => path.endsWith(extension)));
37
+ const paths = Object.keys(workspace.files).filter(claimed).sort();
38
+ if (paths.length === 0) {
39
+ throw new Error('no workspace file has an extension the page shows');
40
+ }
41
+ const files = new Map(paths.map(path => [`${WORKSPACE_ROOT_URI}/${path}`, workspace.files[path]]));
42
+
43
+ const select = document.getElementById('file') as HTMLSelectElement;
44
+ // The worker keys each file by its encoded path, as in its URI: shown decoded.
45
+ select.append(...paths.map(path => new Option(decodeURIComponent(path), `${WORKSPACE_ROOT_URI}/${path}`)));
46
+ let shown = select.value;
47
+ const listeners: Array<(uri: string) => void> = [];
48
+ const show = (uri: string): void => {
49
+ if (!files.has(uri)) {
50
+ throw new Error(`${uri} is not a workspace file the page shows`);
51
+ }
52
+ select.value = uri;
53
+ if (uri !== shown) {
54
+ shown = uri;
55
+ listeners.forEach(listener => listener(uri));
56
+ }
57
+ };
58
+ select.addEventListener('change', () => show(select.value));
59
+ select.hidden = false;
60
+ return { files, current: () => shown, show, onDidChange: listener => listeners.push(listener) };
61
+ }
@@ -0,0 +1,5 @@
1
+ // The workspace root both ends name: the worker seeds its filesystem under it
2
+ // and the page opens documents by URIs below it.
3
+
4
+ /** The URI of the workspace root, which holds the files of `../workspace`. */
5
+ export const WORKSPACE_ROOT_URI = 'file:///workspace';
@@ -0,0 +1,16 @@
1
+ // The smoke tier's half, which runs in Node under Playwright. `test:e2e` passes
2
+ // it with `--tsconfig`, since Playwright otherwise looks for a `tsconfig.json`
3
+ // in the directories above.
4
+ {
5
+ "compilerOptions": {
6
+ "target": "ES2022",
7
+ "lib": ["ES2022", "DOM"],
8
+ "module": "NodeNext",
9
+ "moduleResolution": "NodeNext",
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "noEmit": true,
13
+ "types": ["node"]
14
+ },
15
+ "include": ["test", "playwright.config.mts"]
16
+ }
@@ -0,0 +1,16 @@
1
+ // The page's half, everything in `src` but the worker, with the DOM library.
2
+ // esbuild emits, so this only type-checks.
3
+ {
4
+ "compilerOptions": {
5
+ "target": "ES2022",
6
+ "lib": ["ES2022", "DOM"],
7
+ "module": "ESNext",
8
+ "moduleResolution": "Bundler",
9
+ "strict": true,
10
+ "skipLibCheck": true,
11
+ "noEmit": true,
12
+ "types": []
13
+ },
14
+ "include": ["src"],
15
+ "exclude": ["src/worker.ts"]
16
+ }
@@ -0,0 +1,16 @@
1
+ // The worker's half: the heads run in a worker, which has no DOM, and the
2
+ // worker and DOM libraries declare conflicting globals, so the page is a
3
+ // project of its own. esbuild emits, so this only type-checks.
4
+ {
5
+ "compilerOptions": {
6
+ "target": "ES2022",
7
+ "lib": ["ES2022", "WebWorker"],
8
+ "module": "ESNext",
9
+ "moduleResolution": "Bundler",
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "noEmit": true,
13
+ "types": []
14
+ },
15
+ "files": ["src/worker.ts"]
16
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * This file can be edited to adjust the ESBuild build process.
3
+ * To reset, delete this file and rerun theia build again.
4
+ */
5
+ import { browserOptions, watch } from './gen-esbuild.browser.mjs';
6
+ import { nodeOptions } from './gen-esbuild.node.mjs';
7
+
8
+ import esbuild from 'esbuild';
9
+
10
+ const browserContext = await esbuild.context(browserOptions);
11
+ const nodeContext = await esbuild.context(nodeOptions);
12
+
13
+ if (watch) {
14
+ await Promise.all([browserContext.watch(), nodeContext.watch()]);
15
+ } else {
16
+ try {
17
+ await browserContext.rebuild();
18
+ await browserContext.dispose();
19
+ await nodeContext.rebuild();
20
+ await nodeContext.dispose();
21
+ } catch {
22
+ process.exit(1);
23
+ }
24
+ }
@@ -0,0 +1,9 @@
1
+ node_modules/
2
+ # What `theia build` generates.
3
+ lib/
4
+ src-gen/
5
+ gen-esbuild.*.mjs
6
+ plugins/
7
+ smoke-workspace/
8
+ test-results/
9
+ playwright-report/
@@ -0,0 +1,42 @@
1
+ // The smoke tier: Playwright starts the application on a copy of the starter
2
+ // workspace, since the server writes its repairs to the folder it opens, and
3
+ // drives it in Chromium, which `npm run test:e2e:install` downloads once.
4
+ // `npm start` serves what `npm run build` bundled, so build first.
5
+
6
+ import { defineConfig, devices } from '@playwright/test';
7
+ import { cpSync, mkdtempSync, rmSync } from 'node:fs';
8
+ import { tmpdir } from 'node:os';
9
+ import { join } from 'node:path';
10
+
11
+ /**
12
+ * The port the smoke serves on, not `npm start`'s 3000, so a running
13
+ * application doesn't block it; set `THEIA_PORT` to choose another.
14
+ */
15
+ const PORT = Number(process.env.THEIA_PORT || 4938);
16
+
17
+ // Copied once, by the runner: its workers load this file again, and inherit the flag.
18
+ if (process.env.SMOKE_WORKSPACE_COPIED === undefined) {
19
+ process.env.SMOKE_WORKSPACE_COPIED = 'true';
20
+ const workspace = new URL('smoke-workspace', import.meta.url);
21
+ rmSync(workspace, { recursive: true, force: true });
22
+ cpSync(new URL('../workspace', import.meta.url), workspace, { recursive: true });
23
+ // Settings of its own too, so a run never writes `~/.theia/settings.json`.
24
+ process.env.THEIA_CONFIG_DIR ??= mkdtempSync(join(tmpdir(), 'theia-smoke-config-'));
25
+ }
26
+
27
+ export default defineConfig({
28
+ testDir: './test',
29
+ // The first request waits for the backend to start the server and build the workspace.
30
+ timeout: 120_000,
31
+ expect: { timeout: 60_000 },
32
+ use: { ...devices['Desktop Chrome'], baseURL: `http://localhost:${PORT}` },
33
+ webServer: {
34
+ // Relative to this folder, the server's working directory.
35
+ command: 'node start.mjs smoke-workspace',
36
+ url: `http://localhost:${PORT}`,
37
+ env: { THEIA_PORT: String(PORT) },
38
+ // A server already on the port fails the run rather than being tested in this one's place.
39
+ reuseExistingServer: false,
40
+ timeout: 120_000
41
+ }
42
+ });
@@ -0,0 +1,50 @@
1
+ // `npm start`: the Theia backend on `THEIA_PORT`, 3000 when it is unset, on the
2
+ // folder given as the argument, `../workspace` when there is none. A script
3
+ // rather than `--port=${THEIA_PORT:-3000}` in package.json: only a POSIX shell
4
+ // expands that, and npm also runs scripts under cmd.exe.
5
+
6
+ import { spawn } from 'node:child_process';
7
+ import fs from 'node:fs';
8
+ import { createRequire } from 'node:module';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ // Paths from this file, not the working directory, which a direct `node` run can set anywhere.
13
+ const here = path.dirname(fileURLToPath(import.meta.url));
14
+ const link = path.join(here, 'plugins', '__PROJECT_ID__-vscode-servers');
15
+
16
+ // The servers extension, sideloaded from plugins/ beside any other plugin there.
17
+ // Linked here rather than in the build, which a build cache can replay without
18
+ // it; replaced every time, since a dangling link left by a moved extension
19
+ // fails `symlinkSync`.
20
+ fs.mkdirSync(path.dirname(link), { recursive: true });
21
+ const existing = fs.lstatSync(link, { throwIfNoEntry: false });
22
+ // A link, a junction included, is unlinked, never handed to a recursive remove
23
+ // that might reach `../vscode-servers` through it.
24
+ if (existing?.isSymbolicLink()) {
25
+ fs.unlinkSync(link);
26
+ } else {
27
+ fs.rmSync(link, { recursive: true, force: true });
28
+ }
29
+ fs.symlinkSync(path.join(here, '../vscode-servers'), link, 'junction');
30
+
31
+ const port = Number(process.env.THEIA_PORT || 3000);
32
+ const workspace = path.resolve(process.argv[2] ?? path.join(here, '../workspace'));
33
+ // The CLI run by this Node, with no shell: the workspace path reaches Theia as it is.
34
+ // `local-dir:` relative to `cwd`, since Theia reads an absolute path as a URI.
35
+ const cli = createRequire(import.meta.url).resolve('@theia/cli/bin/theia.js');
36
+ const theia = spawn(process.execPath, [cli, 'start', '--plugins=local-dir:./plugins', `--port=${port}`, workspace], {
37
+ cwd: here,
38
+ stdio: 'inherit'
39
+ });
40
+ for (const signal of ['SIGINT', 'SIGTERM']) {
41
+ process.on(signal, () => theia.kill(signal));
42
+ }
43
+ theia.on('exit', (code, signal) => {
44
+ if (signal === null) {
45
+ process.exit(code ?? 1);
46
+ }
47
+ // Ended by a signal, so end by it too: Ctrl+C is a stop, not a failure.
48
+ process.removeAllListeners(signal);
49
+ process.kill(process.pid, signal);
50
+ });
@@ -0,0 +1,16 @@
1
+ // The smoke tier, which runs in Node under Playwright. `test:e2e` passes it
2
+ // with `--tsconfig`, since Playwright otherwise looks for a `tsconfig.json` in
3
+ // the directories above.
4
+ {
5
+ "compilerOptions": {
6
+ "target": "ES2022",
7
+ "lib": ["ES2022", "DOM"],
8
+ "module": "NodeNext",
9
+ "moduleResolution": "NodeNext",
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "noEmit": true,
13
+ "types": ["node"]
14
+ },
15
+ "include": ["test", "playwright.config.mts"]
16
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * This file can be edited to adjust the ESBuild build process.
3
+ * To reset, delete this file and rerun theia build again.
4
+ */
5
+ import { browserOptions, watch } from './gen-esbuild.browser.mjs';
6
+ import { nodeOptions } from './gen-esbuild.node.mjs';
7
+ import { electronOptions } from './gen-esbuild.electron.mjs';
8
+ import esbuild from 'esbuild';
9
+
10
+ const browserContext = await esbuild.context(browserOptions);
11
+ const nodeContext = await esbuild.context(nodeOptions);
12
+ const electronContext = await esbuild.context(electronOptions);
13
+
14
+ if (watch) {
15
+ await Promise.all([browserContext.watch(), nodeContext.watch(), electronContext.watch()]);
16
+ } else {
17
+ try {
18
+ await browserContext.rebuild();
19
+ await browserContext.dispose();
20
+ await nodeContext.rebuild();
21
+ await nodeContext.dispose();
22
+ await electronContext.rebuild();
23
+ await electronContext.dispose();
24
+ } catch {
25
+ process.exit(1);
26
+ }
27
+ }
@@ -0,0 +1,8 @@
1
+ node_modules/
2
+ # What `theia build` generates.
3
+ lib/
4
+ src-gen/
5
+ gen-esbuild.*.mjs
6
+ plugins/
7
+ test-results/
8
+ playwright-report/
@@ -0,0 +1,12 @@
1
+ // The smoke tier: the test launches the application in Electron itself, so
2
+ // there is no server to start here. It runs what `npm run build` bundled, so
3
+ // build first.
4
+
5
+ import { defineConfig } from '@playwright/test';
6
+
7
+ export default defineConfig({
8
+ testDir: './test',
9
+ // The first window waits for the backend to start the server and build the workspace.
10
+ timeout: 120_000,
11
+ expect: { timeout: 60_000 }
12
+ });
@@ -0,0 +1,47 @@
1
+ // `npm start`: the Theia desktop application on the folder given as the
2
+ // argument, `../workspace` when there is none.
3
+
4
+ import { spawn } from 'node:child_process';
5
+ import fs from 'node:fs';
6
+ import { createRequire } from 'node:module';
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ // Paths from this file, not the working directory, which a direct `node` run can set anywhere.
11
+ const here = path.dirname(fileURLToPath(import.meta.url));
12
+ const link = path.join(here, 'plugins', '__PROJECT_ID__-vscode-servers');
13
+
14
+ // The servers extension, sideloaded from plugins/ beside any other plugin there.
15
+ // Linked here rather than in the build, which a build cache can replay without
16
+ // it; replaced every time, since a dangling link left by a moved extension
17
+ // fails `symlinkSync`.
18
+ fs.mkdirSync(path.dirname(link), { recursive: true });
19
+ const existing = fs.lstatSync(link, { throwIfNoEntry: false });
20
+ // A link, a junction included, is unlinked, never handed to a recursive remove
21
+ // that might reach `../vscode-servers` through it.
22
+ if (existing?.isSymbolicLink()) {
23
+ fs.unlinkSync(link);
24
+ } else {
25
+ fs.rmSync(link, { recursive: true, force: true });
26
+ }
27
+ fs.symlinkSync(path.join(here, '../vscode-servers'), link, 'junction');
28
+
29
+ const workspace = path.resolve(process.argv[2] ?? path.join(here, '../workspace'));
30
+ // The CLI run by this Node, with no shell: the workspace path reaches Theia as it is.
31
+ // `local-dir:` relative to `cwd`, since Theia reads an absolute path as a URI.
32
+ const cli = createRequire(import.meta.url).resolve('@theia/cli/bin/theia.js');
33
+ const theia = spawn(process.execPath, [cli, 'start', '--plugins=local-dir:./plugins', workspace], {
34
+ cwd: here,
35
+ stdio: 'inherit'
36
+ });
37
+ for (const signal of ['SIGINT', 'SIGTERM']) {
38
+ process.on(signal, () => theia.kill(signal));
39
+ }
40
+ theia.on('exit', (code, signal) => {
41
+ if (signal === null) {
42
+ process.exit(code ?? 1);
43
+ }
44
+ // Ended by a signal, so end by it too: Ctrl+C is a stop, not a failure.
45
+ process.removeAllListeners(signal);
46
+ process.kill(process.pid, signal);
47
+ });
@@ -0,0 +1,16 @@
1
+ // The smoke tier, which runs in Node under Playwright. `test:e2e` passes it
2
+ // with `--tsconfig`, since Playwright otherwise looks for a `tsconfig.json` in
3
+ // the directories above.
4
+ {
5
+ "compilerOptions": {
6
+ "target": "ES2022",
7
+ "lib": ["ES2022", "DOM"],
8
+ "module": "NodeNext",
9
+ "moduleResolution": "NodeNext",
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "noEmit": true,
13
+ "types": ["node"]
14
+ },
15
+ "include": ["test", "playwright.config.mts"]
16
+ }
@@ -0,0 +1,38 @@
1
+ // Bundles each diagram webview in `src/webview/` into `out/webview/`, its
2
+ // stylesheet beside it: once, or on every change with `--watch`. The extension
3
+ // itself is `tsc`'s, since the extension host loads it with its `node_modules`.
4
+ //
5
+ // At `platform: 'browser'` esbuild refuses a `node:*` import rather than
6
+ // shimming it, so a Node import reaching a webview fails here rather than in
7
+ // the webview's own console.
8
+
9
+ import { context } from 'esbuild';
10
+ import { readdirSync } from 'node:fs';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ const root = fileURLToPath(new URL('.', import.meta.url));
14
+
15
+ const build = await context({
16
+ absWorkingDir: root,
17
+ entryPoints: readdirSync(new URL('src/webview/', import.meta.url))
18
+ .filter(file => file.endsWith('.ts'))
19
+ .map(file => `src/webview/${file}`),
20
+ outdir: 'out/webview',
21
+ bundle: true,
22
+ platform: 'browser',
23
+ // The editor loads the bundle with a plain `<script>`.
24
+ format: 'iife',
25
+ target: 'es2022',
26
+ sourcemap: true,
27
+ // The icon font GLSP imports, inlined: a webview loads only the URIs the
28
+ // extension mints at runtime, which a bundled `url()` cannot name.
29
+ loader: { '.ttf': 'dataurl' },
30
+ logLevel: 'info'
31
+ });
32
+
33
+ if (process.argv.includes('--watch')) {
34
+ await build.watch();
35
+ } else {
36
+ await build.rebuild();
37
+ await build.dispose();
38
+ }
@@ -0,0 +1,7 @@
1
+ node_modules/
2
+ out/
3
+ syntaxes/
4
+ *.tsbuildinfo
5
+ .vscode-test/
6
+ test-results/
7
+ playwright-report/
@@ -0,0 +1,14 @@
1
+ // The smoke tier: each test launches the VS Code `npm run test:e2e:install`
2
+ // downloaded, with this extension loaded from `out/`, so build first. On Linux
3
+ // without a display, run it under `xvfb-run -a`.
4
+
5
+ import { defineConfig } from '@playwright/test';
6
+
7
+ export default defineConfig({
8
+ testDir: './test',
9
+ // The first request waits for the server to build the workspace.
10
+ timeout: 120_000,
11
+ expect: { timeout: 60_000 },
12
+ // One VS Code at a time.
13
+ workers: 1
14
+ });
@@ -0,0 +1,19 @@
1
+ // The __GRAMMAR__ diagram's custom editor: a webview over the diagram bundle
2
+ // esbuild writes to `out/webview/`.
3
+
4
+ import { HydraniumGlspEditorProvider } from '@hydranium/glsp-client-vscode/node';
5
+ import { __GRAMMAR_ID_CONST___DIAGRAM_TYPE } from '__PROTOCOL_PACKAGE__';
6
+
7
+ /**
8
+ * The view type `contributes.customEditors` in `package.json` declares. VS Code
9
+ * reads the manifest statically, so the two are kept equal by hand, and a
10
+ * mismatch leaves the file with no diagram editor.
11
+ */
12
+ export const __GRAMMAR_ID_CONST___DIAGRAM_VIEW_TYPE = '__LANGUAGE_ID__.diagram';
13
+
14
+ export class __GRAMMAR__DiagramEditorProvider extends HydraniumGlspEditorProvider {
15
+ override diagramType = __GRAMMAR_ID_CONST___DIAGRAM_TYPE;
16
+ protected override readonly scriptPath = 'out/webview/__GRAMMAR_ID__-diagram.js';
17
+ protected override readonly stylePath = 'out/webview/__GRAMMAR_ID__-diagram.css';
18
+ protected override readonly title = '__GRAMMAR__ Diagram';
19
+ }
@@ -0,0 +1,24 @@
1
+ // `__NAME__: Show Model`: the active tab's file, text or diagram, read through
2
+ // the data head, and its root type shown in a notification.
3
+
4
+ import type { DataConnectionWithEvents } from '@hydranium/protocol';
5
+ import { ALL_LANGUAGES, type __NAME__Element } from '__PROTOCOL_PACKAGE__';
6
+ import { TabInputCustom, TabInputText, window } from 'vscode';
7
+
8
+ /** The command's handler, reading through `connection`. */
9
+ export function showModelCommand(connection: DataConnectionWithEvents<__NAME__Element>): () => Promise<void> {
10
+ // Opened on the first run and kept until the connection is disposed.
11
+ let session: ReturnType<typeof connection.createSession> | undefined;
12
+ return async () => {
13
+ const input = window.tabGroups.activeTabGroup.activeTab?.input;
14
+ const uri = input instanceof TabInputText || input instanceof TabInputCustom ? input.uri : undefined;
15
+ if (uri === undefined || !ALL_LANGUAGES.some(language => language.fileExtensions.some(extension => uri.path.endsWith(extension)))) {
16
+ void window.showInformationMessage('Open a __NAME__ file first.');
17
+ return;
18
+ }
19
+ session ??= connection.createSession('__PROJECT_ID__-show-model');
20
+ const root = await session.withOpenDocument({ uri: uri.toString() }, document => document.model?.root.$type);
21
+ const file = uri.path.slice(uri.path.lastIndexOf('/') + 1);
22
+ void window.showInformationMessage(`${file} holds a ${root ?? 'document with no model'}`);
23
+ };
24
+ }
@@ -0,0 +1,18 @@
1
+ // The __GRAMMAR__ diagram webview: GLSP's handshake with the extension host,
2
+ // over the diagram the client package defines. Never import this from the
3
+ // extension: its graph reaches stylesheets, which Node cannot load.
4
+
5
+ import type { ContainerConfiguration } from '@eclipse-glsp/client';
6
+ import { HydraniumGlspStarter } from '@hydranium/glsp-client-vscode/browser';
7
+ import { Container } from 'inversify';
8
+ import { initialize__GRAMMAR__DiagramContainer } from '__CLIENT_PACKAGE__';
9
+ // Last, so its rules follow GLSP's stylesheet in the bundle.
10
+ import './diagram.css';
11
+
12
+ class __GRAMMAR__DiagramStarter extends HydraniumGlspStarter {
13
+ createContainer(...containerConfiguration: ContainerConfiguration): Container {
14
+ return initialize__GRAMMAR__DiagramContainer(new Container(), ...containerConfiguration);
15
+ }
16
+ }
17
+
18
+ new __GRAMMAR__DiagramStarter();
@@ -0,0 +1,40 @@
1
+ /* The diagram webviews' size and colours, bundled after GLSP's stylesheet so
2
+ these rules win on equal specificity. */
3
+
4
+ /* A webview has no layout above the document, so each element down to the
5
+ SVG needs a height, or the diagram draws at zero height. */
6
+ html,
7
+ body {
8
+ height: 100%;
9
+ width: 100%;
10
+ margin: 0;
11
+ padding: 0;
12
+ overflow: hidden;
13
+ }
14
+
15
+ .hydranium-diagram-container,
16
+ .hydranium-diagram-container > .sprotty,
17
+ .hydranium-diagram-container > .sprotty > svg {
18
+ height: 100%;
19
+ width: 100%;
20
+ display: block;
21
+ }
22
+
23
+ /* The editor's theme, rather than GLSP's colours for a light canvas. */
24
+ .sprotty {
25
+ background: var(--vscode-editor-background);
26
+ color: var(--vscode-editor-foreground);
27
+ font-family: var(--vscode-font-family);
28
+ font-size: var(--vscode-font-size);
29
+ }
30
+
31
+ /* GLSP's stylesheet leaves shapes at the SVG defaults: a black fill, and no stroke on an edge. */
32
+ .sprotty-node {
33
+ fill: var(--vscode-editorWidget-background);
34
+ stroke: var(--vscode-focusBorder);
35
+ }
36
+
37
+ .sprotty-edge {
38
+ fill: none;
39
+ stroke: var(--vscode-editor-foreground);
40
+ }
@@ -0,0 +1,22 @@
1
+ // Starts VS Code with this extension on `../workspace`. VS Code reads a
2
+ // relative `--extensionDevelopmentPath` against its own working directory
3
+ // rather than this one, so both paths are made absolute here.
4
+
5
+ import { spawn } from 'node:child_process';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const args = [
9
+ `--extensionDevelopmentPath=${fileURLToPath(new URL('.', import.meta.url))}`,
10
+ fileURLToPath(new URL('../workspace', import.meta.url))
11
+ ];
12
+ // On Windows `code` is a `.cmd`, which only a shell runs. The shell gets one
13
+ // command line, as Node deprecates arguments beside `shell: true`, and each
14
+ // path is quoted so the shell does not split one with a space.
15
+ const child =
16
+ process.platform === 'win32'
17
+ ? spawn(['code', ...args.map(arg => `"${arg}"`)].join(' '), { stdio: 'inherit', shell: true })
18
+ : spawn('code', args, { stdio: 'inherit' });
19
+ child.on('error', error => {
20
+ console.error(`Could not start VS Code: ${error.message}. npm start needs \`code\` on the PATH.`);
21
+ process.exitCode = 1;
22
+ });
@@ -0,0 +1,21 @@
1
+ // Downloads the pinned VS Code into `.vscode-test/`, retrying, so the smoke
2
+ // test finds it there and asks the update server nothing.
3
+
4
+ import { downloadAndUnzipVSCode } from '@vscode/test-electron';
5
+ import { setTimeout } from 'node:timers/promises';
6
+ import { VSCODE_CACHE, VSCODE_VERSION } from './vscode-version.mts';
7
+
8
+ const ATTEMPTS = 3;
9
+
10
+ for (let attempt = 1; ; attempt++) {
11
+ try {
12
+ await downloadAndUnzipVSCode({ version: VSCODE_VERSION, cachePath: VSCODE_CACHE });
13
+ break;
14
+ } catch (error: unknown) {
15
+ if (attempt === ATTEMPTS) {
16
+ throw error;
17
+ }
18
+ console.warn(`Downloading VS Code ${VSCODE_VERSION} failed, attempt ${attempt} of ${ATTEMPTS}.`, error);
19
+ await setTimeout(attempt * 5_000);
20
+ }
21
+ }
@@ -0,0 +1,10 @@
1
+ import { fileURLToPath } from 'node:url';
2
+
3
+ /**
4
+ * The VS Code build the smoke test downloads and runs. Pinned rather than
5
+ * `stable`, so a VS Code release cannot fail a run with no change here.
6
+ */
7
+ export const VSCODE_VERSION = '1.141.0';
8
+
9
+ /** Where `@vscode/test-electron` keeps the download, below this package. */
10
+ export const VSCODE_CACHE = fileURLToPath(new URL('../.vscode-test', import.meta.url));