@parallelworks/ui 0.18.0 → 0.19.1

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 (349) hide show
  1. package/README.md +86 -6
  2. package/dist/ai/adapter/memoryStore.d.ts +8 -0
  3. package/dist/ai/adapter/memoryStore.js +152 -0
  4. package/dist/ai/adapter/mock/index.d.ts +2 -0
  5. package/dist/ai/adapter/mock/index.js +3 -0
  6. package/dist/ai/adapter/mock/mockAdapter.d.ts +4 -0
  7. package/dist/ai/adapter/mock/mockAdapter.js +81 -0
  8. package/dist/ai/adapter/openai/createOpenAIStream.d.ts +9 -0
  9. package/dist/ai/adapter/openai/createOpenAIStream.js +33 -0
  10. package/dist/ai/adapter/openai/errors.d.ts +3 -0
  11. package/dist/ai/adapter/openai/errors.js +37 -0
  12. package/dist/ai/adapter/openai/index.d.ts +6 -0
  13. package/dist/ai/adapter/openai/index.js +6 -0
  14. package/dist/ai/adapter/openai/openaiAdapter.d.ts +9 -0
  15. package/dist/ai/adapter/openai/openaiAdapter.js +54 -0
  16. package/dist/ai/adapter/openai/responses.d.ts +4 -0
  17. package/dist/ai/adapter/openai/responses.js +108 -0
  18. package/dist/ai/adapter/openai/sse.d.ts +24 -0
  19. package/dist/ai/adapter/openai/sse.js +112 -0
  20. package/dist/ai/adapter/openai/wire.d.ts +40 -0
  21. package/dist/ai/adapter/types.d.ts +131 -0
  22. package/dist/ai/adapter/types.js +11 -0
  23. package/dist/ai/agent/classifier.d.ts +2 -0
  24. package/dist/ai/agent/classifier.js +12 -0
  25. package/dist/ai/agent/diff.d.ts +35 -0
  26. package/dist/ai/agent/diff.js +223 -0
  27. package/dist/ai/agent/diffHighlight.d.ts +6 -0
  28. package/dist/ai/agent/diffHighlight.js +101 -0
  29. package/dist/ai/agent/index.d.ts +6 -0
  30. package/dist/ai/agent/permissions.d.ts +11 -0
  31. package/dist/ai/agent/permissions.js +29 -0
  32. package/dist/ai/agent/toolfmt.d.ts +18 -0
  33. package/dist/ai/agent/toolfmt.js +383 -0
  34. package/dist/ai/agent/transcript.d.ts +38 -0
  35. package/dist/ai/agent/transcript.js +127 -0
  36. package/dist/ai/ai.css +325 -0
  37. package/dist/ai/components/AllocationSelector.d.ts +4 -0
  38. package/dist/ai/components/AllocationSelector.js +56 -0
  39. package/dist/ai/components/AttachmentManager.d.ts +1 -0
  40. package/dist/ai/components/AttachmentManager.js +144 -0
  41. package/dist/ai/components/AttachmentUpload.d.ts +24 -0
  42. package/dist/ai/components/AttachmentUpload.js +179 -0
  43. package/dist/ai/components/BlockedGroupBanner.d.ts +1 -0
  44. package/dist/ai/components/BlockedGroupBanner.js +29 -0
  45. package/dist/ai/components/BranchNavigator.d.ts +8 -0
  46. package/dist/ai/components/BranchNavigator.js +57 -0
  47. package/dist/ai/components/ChatEmptyState.d.ts +3 -0
  48. package/dist/ai/components/ChatEmptyState.js +171 -0
  49. package/dist/ai/components/ChatInput.d.ts +45 -0
  50. package/dist/ai/components/ChatInput.js +265 -0
  51. package/dist/ai/components/ChatLayout.d.ts +5 -0
  52. package/dist/ai/components/ChatLayout.js +15 -0
  53. package/dist/ai/components/ChatMessage.d.ts +24 -0
  54. package/dist/ai/components/ChatMessage.js +338 -0
  55. package/dist/ai/components/ChatMessageList.d.ts +35 -0
  56. package/dist/ai/components/ChatMessageList.js +301 -0
  57. package/dist/ai/components/ChatSidebar.d.ts +5 -0
  58. package/dist/ai/components/ChatSidebar.js +345 -0
  59. package/dist/ai/components/ChatThread.d.ts +3 -0
  60. package/dist/ai/components/ChatThread.js +198 -0
  61. package/dist/ai/components/ComposerChrome.d.ts +18 -0
  62. package/dist/ai/components/ComposerChrome.js +50 -0
  63. package/dist/ai/components/ComposerFrame.d.ts +16 -0
  64. package/dist/ai/components/ComposerFrame.js +33 -0
  65. package/dist/ai/components/DragOverlay.d.ts +5 -0
  66. package/dist/ai/components/DragOverlay.js +20 -0
  67. package/dist/ai/components/ModelSelector.d.ts +6 -0
  68. package/dist/ai/components/ModelSelector.js +343 -0
  69. package/dist/ai/components/ShareDialog.d.ts +11 -0
  70. package/dist/ai/components/ShareDialog.js +260 -0
  71. package/dist/ai/components/SlashMenu.d.ts +28 -0
  72. package/dist/ai/components/SlashMenu.js +91 -0
  73. package/dist/ai/components/agent/ActivityLine.d.ts +21 -0
  74. package/dist/ai/components/agent/ActivityLine.js +151 -0
  75. package/dist/ai/components/agent/AgentMessageParts.d.ts +54 -0
  76. package/dist/ai/components/agent/AgentMessageParts.js +721 -0
  77. package/dist/ai/components/agent/PermissionPicker.d.ts +11 -0
  78. package/dist/ai/components/agent/PermissionPicker.js +38 -0
  79. package/dist/ai/components/attachmentKeys.d.ts +4 -0
  80. package/dist/ai/components/attachmentKeys.js +10 -0
  81. package/dist/ai/core/ChatProvider.d.ts +66 -0
  82. package/dist/ai/core/ChatProvider.js +848 -0
  83. package/dist/ai/core/KeyboardShortcutsProvider.d.ts +5 -0
  84. package/dist/ai/core/KeyboardShortcutsProvider.js +9 -0
  85. package/dist/ai/core/chatReducer.d.ts +114 -0
  86. package/dist/ai/core/chatReducer.js +223 -0
  87. package/dist/ai/core/config.d.ts +47 -0
  88. package/dist/ai/core/config.js +45 -0
  89. package/dist/ai/core/events.d.ts +1 -0
  90. package/dist/ai/core/events.js +4 -0
  91. package/dist/ai/core/exportConversation.d.ts +4 -0
  92. package/dist/ai/core/exportConversation.js +42 -0
  93. package/dist/ai/core/greeting.d.ts +3 -0
  94. package/dist/ai/core/greeting.js +38 -0
  95. package/dist/ai/core/parts.d.ts +11 -0
  96. package/dist/ai/core/parts.js +84 -0
  97. package/dist/ai/core/useDragDrop.d.ts +14 -0
  98. package/dist/ai/core/useDragDrop.js +36 -0
  99. package/dist/ai/core/useKeyboardShortcuts.d.ts +12 -0
  100. package/dist/ai/core/useKeyboardShortcuts.js +77 -0
  101. package/dist/ai/core/useSSEStream.d.ts +34 -0
  102. package/dist/ai/core/useSSEStream.js +98 -0
  103. package/dist/ai/index.d.ts +40 -0
  104. package/dist/ai/index.js +44 -0
  105. package/dist/ai/strings.d.ts +167 -0
  106. package/dist/ai/strings.js +175 -0
  107. package/dist/ai/types.d.ts +236 -0
  108. package/dist/ai/ui/Avatar.d.ts +8 -0
  109. package/dist/ai/ui/Avatar.js +35 -0
  110. package/dist/ai/ui/Dropdown.d.ts +31 -0
  111. package/dist/ai/ui/Dropdown.js +110 -0
  112. package/dist/ai/ui/Markdown.d.ts +8 -0
  113. package/dist/ai/ui/Markdown.js +79 -0
  114. package/dist/ai/ui/Notice.d.ts +21 -0
  115. package/dist/ai/ui/Notice.js +77 -0
  116. package/dist/ai/ui/buttons.d.ts +1 -0
  117. package/dist/ai/ui/codeBreaks.d.ts +16 -0
  118. package/dist/ai/ui/codeBreaks.js +36 -0
  119. package/dist/ai/ui/prose.d.ts +1 -0
  120. package/dist/ai/ui/prose.js +13 -0
  121. package/dist/ai/ui/sidebar.d.ts +81 -0
  122. package/dist/ai/ui/sidebar.js +148 -0
  123. package/dist/ai/utils.d.ts +4 -0
  124. package/dist/ai/utils.js +23 -0
  125. package/dist/assets/{editor.worker-CuBRIwi-.js → editor.worker-BmHUIVG4.js} +194 -163
  126. package/dist/assets/{json.worker-8ytS02vz.js → json.worker-D8bJ92nw.js} +384 -338
  127. package/dist/assets/{yaml.worker-DJC-VUcv.js → yaml.worker-C4gyGAko.js} +15657 -13823
  128. package/dist/components/AddMenu.js +46 -0
  129. package/dist/components/Avatar.d.ts +1 -1
  130. package/dist/components/Avatar.js +46 -0
  131. package/dist/{chunks/BareModal-BtRiQezg.js → components/BareModal.js} +1 -1
  132. package/dist/components/Breadcrumbs.d.ts +1 -1
  133. package/dist/components/Breadcrumbs.js +83 -0
  134. package/dist/components/Button.js +83 -0
  135. package/dist/components/Callout.js +23 -0
  136. package/dist/components/Card.js +31 -0
  137. package/dist/components/CollapsiblePanel.js +77 -0
  138. package/dist/components/ConfirmModal.js +74 -0
  139. package/dist/components/CopyToClipboard.js +81 -0
  140. package/dist/{chunks/CreateModal-BY6DLmur.js → components/CreateModal.js} +10 -189
  141. package/dist/components/DescriptionList.js +42 -0
  142. package/dist/components/Dropdown.d.ts +1 -1
  143. package/dist/components/Dropdown.js +349 -0
  144. package/dist/components/EmptyState.d.ts +1 -1
  145. package/dist/components/EmptyState.js +27 -0
  146. package/dist/components/Filter.js +32 -0
  147. package/dist/components/FilterPill.js +13 -0
  148. package/dist/components/IconButton.js +34 -0
  149. package/dist/components/Indicator.d.ts +2 -2
  150. package/dist/{chunks/Indicator-hBSIxOPK.js → components/Indicator.js} +1 -1
  151. package/dist/components/Input.d.ts +1 -1
  152. package/dist/components/Input.js +88 -0
  153. package/dist/components/LetterBadge.d.ts +1 -1
  154. package/dist/components/LetterBadge.js +13 -0
  155. package/dist/components/Listbox.js +123 -0
  156. package/dist/{chunks/Loader-BXoXTZGc.js → components/Loader.js} +1 -1
  157. package/dist/components/Markdown.js +15 -0
  158. package/dist/components/MarkdownImpl.d.ts +1 -1
  159. package/dist/components/MultiSelect.js +171 -0
  160. package/dist/components/Overview.js +39 -0
  161. package/dist/components/PageHeader.js +194 -0
  162. package/dist/components/Pagination.js +35 -0
  163. package/dist/components/Provider.d.ts +16 -65
  164. package/dist/{chunks/Provider-Ghua9PkN.js → components/Provider.js} +54 -20
  165. package/dist/{chunks/RequiredMark-DfLdYbYZ.js → components/RequiredMark.js} +1 -1
  166. package/dist/components/SectionHeader.js +11 -0
  167. package/dist/components/SettingModeOption.js +30 -0
  168. package/dist/components/SettingRow.d.ts +1 -1
  169. package/dist/components/SettingRow.js +35 -0
  170. package/dist/components/SettingsCard.js +31 -0
  171. package/dist/components/SettingsGroup.d.ts +1 -1
  172. package/dist/components/SettingsGroup.js +17 -0
  173. package/dist/components/Sort.js +42 -0
  174. package/dist/components/StatusBadge.js +63 -0
  175. package/dist/components/SwitchToggle.js +59 -0
  176. package/dist/{chunks/SwitchToggleSmall-FcsaYSTs.js → components/SwitchToggleSmall.js} +1 -1
  177. package/dist/{chunks/Table-4OHjdKdO.js → components/Table.js} +7 -5
  178. package/dist/components/Toggle.js +20 -0
  179. package/dist/{chunks/Tooltip-Crxo_eU_.js → components/Tooltip.js} +6 -4
  180. package/dist/{chunks/StatusBadge-C1Blx_cW.js → components/dropdownUtils.js} +2 -63
  181. package/dist/components/focus.d.ts +2 -0
  182. package/dist/components/focus.js +7 -0
  183. package/dist/components/ghostButton.js +7 -0
  184. package/dist/components/inputClasses.js +4 -0
  185. package/dist/components/keys.d.ts +12 -0
  186. package/dist/components/keys.js +27 -0
  187. package/dist/components/relativeTime.js +32 -0
  188. package/dist/{chunks/useCssIsDark-DHr05dG2.js → components/useCssIsDark.js} +1 -1
  189. package/dist/duration.d.ts +3 -0
  190. package/dist/duration.js +17 -0
  191. package/dist/editor/Editor.js +13 -0
  192. package/dist/editor/EditorField.js +53 -0
  193. package/dist/editor/FormCodePanel.js +173 -0
  194. package/dist/editor/Monaco.d.ts +1 -20
  195. package/dist/editor/Monaco.js +112 -0
  196. package/dist/editor/index.d.ts +2 -1
  197. package/dist/editor/index.js +7 -2
  198. package/dist/editor/themes.js +91 -0
  199. package/dist/editor/workers.js +29 -0
  200. package/dist/editor/yaml.d.ts +5 -0
  201. package/dist/editor/yaml.js +15 -0
  202. package/dist/engine.d.ts +98 -0
  203. package/dist/file-explorer/FileExplorer.d.ts +11 -2
  204. package/dist/file-explorer/FileExplorer.js +1402 -0
  205. package/dist/file-explorer/FileExplorerContext.d.ts +1 -1
  206. package/dist/file-explorer/FileExplorerContext.js +370 -0
  207. package/dist/file-explorer/FilePreview/CodePreviewEditor.d.ts +1 -1
  208. package/dist/{chunks/CodePreviewEditor-4lx3kE-w.js → file-explorer/FilePreview/CodePreviewEditor.js} +3 -2
  209. package/dist/file-explorer/FilePreview/Inline.js +45 -0
  210. package/dist/file-explorer/FilePreview/StreamedBadge.js +16 -0
  211. package/dist/file-explorer/FilePreview/Viewer.js +526 -0
  212. package/dist/file-explorer/FilePreview/index.js +266 -0
  213. package/dist/file-explorer/FilePreview/parse.js +63 -0
  214. package/dist/file-explorer/FilePreview/previewCache.js +98 -0
  215. package/dist/file-explorer/FilePreview/previewType.d.ts +1 -1
  216. package/dist/file-explorer/FilePreview/previewType.js +151 -0
  217. package/dist/file-explorer/FilePreview/readCappedBytes.js +196 -0
  218. package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +95 -0
  219. package/dist/file-explorer/FilePreview/useObjectText.js +109 -0
  220. package/dist/file-explorer/FilePreview/usePresignedUrl.js +73 -0
  221. package/dist/file-explorer/FileRow.d.ts +1 -1
  222. package/dist/file-explorer/FileRow.js +165 -0
  223. package/dist/file-explorer/FileRowsWindow.js +92 -0
  224. package/dist/file-explorer/FileTable.js +172 -0
  225. package/dist/file-explorer/FileTableHeaders.js +49 -0
  226. package/dist/file-explorer/LoadMoreRow.js +46 -0
  227. package/dist/file-explorer/TreeView.js +376 -0
  228. package/dist/file-explorer/helpers.d.ts +4 -4
  229. package/dist/file-explorer/helpers.js +537 -0
  230. package/dist/file-explorer/index.d.ts +3 -3
  231. package/dist/file-explorer/index.js +7 -5705
  232. package/dist/file-explorer/lib/batch.js +39 -0
  233. package/dist/file-explorer/lib/errors.js +9 -0
  234. package/dist/file-explorer/lib/paging.js +124 -0
  235. package/dist/file-explorer/lib/types.d.ts +6 -1
  236. package/dist/file-explorer/lib/utils.d.ts +1 -3
  237. package/dist/file-explorer/lib/utils.js +322 -0
  238. package/dist/file-explorer/toasts.js +400 -0
  239. package/dist/file-explorer/treeRows.js +73 -0
  240. package/dist/file-explorer/useNearEnd.d.ts +1 -1
  241. package/dist/file-explorer/useNearEnd.js +27 -0
  242. package/dist/form/FieldWrapper.d.ts +2 -2
  243. package/dist/form/FieldWrapper.js +54 -0
  244. package/dist/form/Fields/BooleanField.js +51 -0
  245. package/dist/form/Fields/CheckboxGroupField.js +72 -0
  246. package/dist/form/Fields/ColorField.js +33 -0
  247. package/dist/form/Fields/DropdownField.js +49 -0
  248. package/dist/form/Fields/DurationField.js +130 -0
  249. package/dist/form/Fields/LabelField.js +19 -0
  250. package/dist/form/Fields/MultiDropdownField.js +40 -0
  251. package/dist/form/Fields/NumberField.js +51 -0
  252. package/dist/form/Fields/ObjectField.js +54 -0
  253. package/dist/form/Fields/RadioField.js +49 -0
  254. package/dist/form/Fields/RangeField.js +58 -0
  255. package/dist/form/Fields/ResourceOptionIcon.d.ts +1 -1
  256. package/dist/form/Fields/ResourceOptionIcon.js +21 -0
  257. package/dist/form/Fields/StepField.js +6 -0
  258. package/dist/form/Fields/StringField.js +37 -0
  259. package/dist/form/Fields/TextAreaField.js +31 -0
  260. package/dist/form/Fields/durationValidity.js +11 -0
  261. package/dist/form/Form.d.ts +4 -21
  262. package/dist/form/Form.js +643 -0
  263. package/dist/form/SpecialTypes/DropdownWrapper.js +30 -0
  264. package/dist/form/SpecialTypes/FormikCustomDropdown.js +122 -0
  265. package/dist/form/SpecialTypes/FormikCustomInput.js +111 -0
  266. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.js +31 -0
  267. package/dist/form/SpecialTypes/FormikNumericInput.js +60 -0
  268. package/dist/form/Wizard/WizardContainer.js +138 -0
  269. package/dist/form/Wizard/WizardNavigation.js +92 -0
  270. package/dist/form/Wizard/WizardStepContent.js +35 -0
  271. package/dist/form/Wizard/WizardStepIndicator.js +108 -0
  272. package/dist/form/Wizard/useWizardState.js +118 -0
  273. package/dist/form/Wizard/utils.js +34 -0
  274. package/dist/form/fieldContext.js +26 -0
  275. package/dist/form/fieldRegistry.js +50 -0
  276. package/dist/form/index.d.ts +11 -12
  277. package/dist/form/index.js +18 -2445
  278. package/dist/form/lib.d.ts +6 -1
  279. package/dist/{chunks/FieldWrapper-Bef01EWO.js → form/lib.js} +5 -104
  280. package/dist/form/types/fieldComponentTypes.d.ts +3 -2
  281. package/dist/form/types/fieldTypes.d.ts +6 -20
  282. package/dist/form/useParsedOpts.d.ts +1 -1
  283. package/dist/form/useParsedOpts.js +62 -0
  284. package/dist/form/usePrevious.js +11 -0
  285. package/dist/form/utils/checkShowIfObject.js +14 -0
  286. package/dist/form/utils/{slurmDuration.d.ts → duration.d.ts} +2 -2
  287. package/dist/form/utils/duration.js +69 -0
  288. package/dist/form/utils/getParentValue.js +23 -0
  289. package/dist/form/utils/getPlaceholder.js +9 -0
  290. package/dist/form/utils/getValueUsingPath.js +23 -0
  291. package/dist/form/utils/secondaryField.js +22 -0
  292. package/dist/graph/AnnotationBanner.js +141 -0
  293. package/dist/graph/Collapse.js +41 -0
  294. package/dist/{workflow/index.js → graph/DependencyGraph.js} +76 -1163
  295. package/dist/graph/JobSummary.js +104 -0
  296. package/dist/{workflow → graph}/MatrixGroup.d.ts +2 -2
  297. package/dist/graph/MatrixGroup.js +107 -0
  298. package/dist/graph/Reveal.js +57 -0
  299. package/dist/graph/TreeView.js +711 -0
  300. package/dist/{workflow → graph}/index.d.ts +1 -1
  301. package/dist/graph/index.js +2 -0
  302. package/dist/{workflow → graph}/types.d.ts +3 -3
  303. package/dist/graph/types.js +6 -0
  304. package/dist/icons.d.ts +20 -21
  305. package/dist/index.d.ts +38 -40
  306. package/dist/index.js +47 -599
  307. package/dist/list/ActionToolbar.js +184 -0
  308. package/dist/list/HoverCard.js +73 -0
  309. package/dist/list/ListPage.js +145 -0
  310. package/dist/list/ListPager.js +59 -0
  311. package/dist/list/ListTable.d.ts +1 -1
  312. package/dist/list/ListTable.js +163 -0
  313. package/dist/list/ListViewControls.d.ts +2 -2
  314. package/dist/list/ListViewControls.js +372 -0
  315. package/dist/list/RowContextMenu.js +350 -0
  316. package/dist/list/UserHoverCard.js +117 -0
  317. package/dist/list/index.d.ts +9 -9
  318. package/dist/list/index.js +11 -2
  319. package/dist/list/listSearch.js +137 -0
  320. package/dist/list/listView.js +210 -0
  321. package/dist/list/textEntry.js +10 -0
  322. package/dist/logviewer/LogViewer.d.ts +1 -1
  323. package/dist/logviewer/LogViewer.js +750 -0
  324. package/dist/logviewer/index.d.ts +1 -0
  325. package/dist/logviewer/index.js +3 -742
  326. package/dist/logviewer/segments.d.ts +9 -0
  327. package/dist/logviewer/segments.js +80 -0
  328. package/dist/styles/fonts.css +4 -4
  329. package/dist/styles/theme.css +6 -4
  330. package/dist/styles.css +1 -1
  331. package/dist/theme/index.js +2 -2
  332. package/package.json +38 -14
  333. package/LICENSE +0 -202
  334. package/dist/chunks/Monaco-C1POOdVv.js +0 -1046
  335. package/dist/chunks/PageHeader-l49p1nM9.js +0 -690
  336. package/dist/chunks/SwitchToggle-CHPP8uZQ.js +0 -755
  337. package/dist/chunks/editor-9_uVtUyy.js +0 -347
  338. package/dist/chunks/list-qrojZZNv.js +0 -1411
  339. package/dist/editor/usesGitlab.d.ts +0 -97
  340. package/dist/form/Fields/BucketReadOnlyField.d.ts +0 -7
  341. package/dist/form/types/workflow-schema.d.ts +0 -1145
  342. package/dist/form/utils/pwResolver.d.ts +0 -18
  343. package/dist/{chunks/MarkdownImpl-Bq4QhrtA.js → components/MarkdownImpl.js} +1 -1
  344. /package/dist/{workflow → graph}/AnnotationBanner.d.ts +0 -0
  345. /package/dist/{workflow → graph}/Collapse.d.ts +0 -0
  346. /package/dist/{workflow → graph}/DependencyGraph.d.ts +0 -0
  347. /package/dist/{workflow → graph}/JobSummary.d.ts +0 -0
  348. /package/dist/{workflow → graph}/Reveal.d.ts +0 -0
  349. /package/dist/{workflow → graph}/TreeView.d.ts +0 -0
@@ -1,1046 +0,0 @@
1
- import { d as useWorkflowJsonResolver, s as useRepoSuggestions } from "./Provider-Ghua9PkN.js";
2
- import { t as useCssIsDark } from "./useCssIsDark-DHr05dG2.js";
3
- import { n as defineEditorThemes, r as getThemeName, t as setupMonacoWorkers } from "./editor-9_uVtUyy.js";
4
- import cx from "classnames";
5
- import { useEffect, useLayoutEffect, useRef } from "react";
6
- import { jsx } from "react/jsx-runtime";
7
- import { dumpYaml } from "@parallelworks/workflow-parser";
8
- import yaml from "js-yaml";
9
- import * as monaco from "monaco-editor";
10
- import { configureMonacoYaml } from "monaco-yaml";
11
- //#region src/editor/usesGitlab.ts
12
- /** Splits GROUP/PROJECT@REF; the ref is null when no @ has been typed yet. */
13
- function splitAtRef(typed) {
14
- const at = typed.indexOf("@");
15
- if (at === -1) return [typed, null];
16
- return [typed.slice(0, at), typed.slice(at + 1)];
17
- }
18
- /** What to insert for a chosen project, given what is already typed. A typed prefix is
19
- * completed like the github one; a mid-path match has to replace what is there. */
20
- function projectInsert(typed, path) {
21
- if (path.toLowerCase().startsWith(typed.toLowerCase())) return {
22
- text: path.slice(typed.length),
23
- replace: false
24
- };
25
- return {
26
- text: path,
27
- replace: true
28
- };
29
- }
30
- /** Indent of a line, or -1 when it is blank. */
31
- function indentOf(content) {
32
- if (content.trim() === "") return -1;
33
- return content.length - content.trimStart().length;
34
- }
35
- /** Every line belonging to the step a uses: is part of, in order. */
36
- function stepBody(model, usesLine, maxLines = 40) {
37
- const keyIndent = model.getLineContent(usesLine).match(/^(\s*(?:-\s+)?)/)?.[1]?.length ?? 0;
38
- const out = [];
39
- for (let ln = usesLine + 1; ln <= usesLine + maxLines; ln++) {
40
- let content;
41
- try {
42
- content = model.getLineContent(ln);
43
- } catch {
44
- break;
45
- }
46
- const indent = indentOf(content);
47
- if (indent === -1) continue;
48
- if (indent < keyIndent) break;
49
- out.push({
50
- line: ln,
51
- content
52
- });
53
- }
54
- return out;
55
- }
56
- /** The $host the step pins, or undefined. Quotes and a trailing comment are stripped,
57
- * so a pinned host is recognised however it was written. */
58
- function stepHostAbove(model, usesLine, maxLines = 40) {
59
- for (const { content } of stepBody(model, usesLine, maxLines)) {
60
- const match = content.match(/^\s*\$host\s*:\s*(.+?)\s*$/);
61
- if (match) return unquoteScalar(match[1] ?? "");
62
- }
63
- }
64
- /** A YAML scalar as written: quotes removed, a trailing comment dropped. */
65
- function unquoteScalar(raw) {
66
- let value = raw.trim();
67
- const quoted = value.match(/^(['"])(.*)\1\s*(?:#.*)?$/);
68
- if (quoted) return quoted[2] ?? "";
69
- const hash = value.indexOf(" #");
70
- if (hash !== -1) value = value.slice(0, hash);
71
- return value.trim();
72
- }
73
- /** How to add `$host` to a step. Always an edit of its own: monaco re-indents a
74
- * multi-line insertText, and the cursor has to stay after the project. */
75
- function hostCompletion(model, usesLine, host) {
76
- const none = {
77
- appendText: "",
78
- edits: []
79
- };
80
- if (!host || host === "gitlab.com") return none;
81
- const usesContent = model.getLineContent(usesLine);
82
- const keyIndent = " ".repeat(usesContent.match(/^(\s*(?:-\s+)?)uses/)?.[1]?.length ?? indentOf(usesContent));
83
- for (const { line, content } of stepBody(model, usesLine)) if (/^\s*with\s*:\s*$/.test(content)) {
84
- const withIndent = content.match(/^(\s*)/)?.[1] ?? keyIndent;
85
- return place(model, line + 1, `${withIndent} $host: ${host}`);
86
- }
87
- return place(model, usesLine + 1, `${keyIndent}with:\n${keyIndent} $host: ${host}`);
88
- }
89
- /** Whole lines that belong before `line`. With nothing there to sit above, they ride along
90
- * in the completion's own text: a second edit would share the last line's end offset with
91
- * the item insert, and monaco decides which of the two lands first. */
92
- function place(model, line, text) {
93
- if (line > model.getLineCount()) return {
94
- appendText: `\n${text}`,
95
- edits: []
96
- };
97
- return {
98
- appendText: "",
99
- edits: [lineEdit(model, line, text)]
100
- };
101
- }
102
- /** An insert of whole lines before `line`, which the completion cannot shift because it
103
- * never spans a newline. */
104
- function lineEdit(model, line, text) {
105
- return {
106
- range: {
107
- startLineNumber: line,
108
- startColumn: 1,
109
- endLineNumber: line,
110
- endColumn: 1
111
- },
112
- text: `${text}\n`
113
- };
114
- }
115
- /** A trailing slash or @ after a known project is the ref separator, the way a trailing
116
- * slash is for github/OWNER/REPO. The slash is swapped for @ when a ref is accepted. */
117
- function refTrigger(typed, knownPaths) {
118
- const at = typed.indexOf("@");
119
- if (at !== -1) return typed.slice(at + 1) === "" ? {
120
- projectPath: typed.slice(0, at),
121
- replaceSlash: false
122
- } : void 0;
123
- if (!typed.endsWith("/")) return;
124
- const projectPath = typed.slice(0, -1);
125
- return knownPaths.includes(projectPath) ? {
126
- projectPath,
127
- replaceSlash: true
128
- } : void 0;
129
- }
130
- /** What to ask the platform for when completing a project path: everything before the
131
- * first slash. Typing deeper then filters that answer instead of asking again. */
132
- function projectSearchKey(typed) {
133
- const slash = typed.indexOf("/");
134
- return slash === -1 ? typed : typed.slice(0, slash);
135
- }
136
- /** Where a `$yaml:`/`$thumbnail:` value's files come from: the nearest `uses:` above,
137
- * addressed the way the platform's file endpoint expects. A gitlab/ path carries no
138
- * host, so the step's $host supplies it. */
139
- function usesFileSource(model, fromLine, maxLines = 30) {
140
- for (let ln = fromLine - 1; ln >= Math.max(fromLine - maxLines, 1); ln--) {
141
- const uses = model.getLineContent(ln).trim().match(/^(?:-\s+)?uses\s*:\s*(\S+)\s*$/);
142
- if (!uses) continue;
143
- const [target, ref] = splitAtRef(uses[1] ?? "");
144
- const github = target.match(/^github\/([^/]+\/.+)$/);
145
- if (github) return {
146
- repo: github[1] ?? "",
147
- ref: ref ?? ""
148
- };
149
- const gitlab = target.match(/^gitlab\/(.+)$/);
150
- if (gitlab) return {
151
- repo: `https://${stepHostAbove(model, ln) ?? "gitlab.com"}/${gitlab[1] ?? ""}`,
152
- ref: ref ?? ""
153
- };
154
- return;
155
- }
156
- }
157
- var FILE_VALUE_KINDS = {
158
- $yaml: {
159
- detail: "yaml file",
160
- keep: (path) => path.endsWith(".yaml") || path.endsWith(".yml")
161
- },
162
- $thumbnail: {
163
- detail: "image file",
164
- keep: (path) => [
165
- ".png",
166
- ".jpg",
167
- ".jpeg",
168
- ".gif",
169
- ".svg",
170
- ".webp"
171
- ].some((ext) => path.toLowerCase().endsWith(ext))
172
- }
173
- };
174
- /** Which of the two file-valued keys the cursor sits on, if either. */
175
- function fileValueKey(textUntilCursor) {
176
- return textUntilCursor.match(/^\s*(\$yaml|\$thumbnail)\s*:\s*$/)?.[1] ?? void 0;
177
- }
178
- /** The files worth offering for a `$yaml:`/`$thumbnail:` value, from whichever
179
- * repository the step's `uses:` names. Empty when the line is not one of those keys,
180
- * no `uses:` is above it, or the tree cannot be read. */
181
- async function fileValueSuggestions(model, line, textUntilCursor, loadTree) {
182
- const key = fileValueKey(textUntilCursor);
183
- if (!key) return [];
184
- const source = usesFileSource(model, line);
185
- if (!source) return [];
186
- const { detail, keep } = FILE_VALUE_KINDS[key];
187
- return (await loadTree(source.repo, source.ref)).filter((entry) => entry.type === "blob" && keep(entry.path)).map((entry) => ({
188
- path: entry.path,
189
- detail
190
- }));
191
- }
192
- /** Every $-prefixed key a step already sets, mapped to its value. Nothing already
193
- * written is offered a second time, and an existing one must not suppress the rest. */
194
- function specialKeysNearUses(model, usesLineNumber) {
195
- const found = /* @__PURE__ */ new Map();
196
- const totalLines = model.getLineCount();
197
- for (let ln = usesLineNumber + 1; ln <= Math.min(usesLineNumber + 20, totalLines); ln++) {
198
- const content = model.getLineContent(ln).trim();
199
- if (content === "") continue;
200
- if (/^-\s/.test(content)) break;
201
- const match = content.match(/^(\$[A-Za-z]+)\s*:\s*(.*)$/);
202
- if (match) found.set(match[1], match[2].trim());
203
- }
204
- return found;
205
- }
206
- /** The same, for a with: block located by its own indentation. */
207
- function specialKeysInWithBlock(model, withLineNumber) {
208
- const found = /* @__PURE__ */ new Map();
209
- const withIndent = model.getLineContent(withLineNumber).match(/^(\s*)/)?.[1]?.length ?? 0;
210
- const totalLines = model.getLineCount();
211
- for (let ln = withLineNumber + 1; ln <= totalLines; ln++) {
212
- const line = model.getLineContent(ln);
213
- if (line.trim() === "") continue;
214
- if ((line.match(/^(\s*)/)?.[1]?.length ?? 0) <= withIndent) break;
215
- const match = line.match(/^\s*(\$[A-Za-z]+)\s*:\s*(.*)$/);
216
- if (match) found.set(match[1], match[2].trim());
217
- }
218
- return found;
219
- }
220
- /** Text to be inserted as a snippet, with the characters monaco's snippet parser would
221
- * otherwise read as placeholders escaped. */
222
- function snippetLiteral(text) {
223
- return text.replace(/[\\$}]/g, "\\$&");
224
- }
225
- /** How a completion inserts its text when a `$host` block has to travel with it. The
226
- * cursor stop is what keeps the caret on the uses: line rather than after the block;
227
- * without a block there is nothing to escape and it stays a plain insert. */
228
- function completionInsert(text, appendText) {
229
- if (!appendText) return {
230
- insertText: text,
231
- isSnippet: false
232
- };
233
- return {
234
- insertText: `${snippetLiteral(text)}$0${snippetLiteral(appendText)}`,
235
- isSnippet: true
236
- };
237
- }
238
- //#endregion
239
- //#region src/editor/Monaco.tsx
240
- setupMonacoWorkers();
241
- var activeWorkflowJsonResolver;
242
- var activeRepoSuggestions;
243
- var suggestionCache = /* @__PURE__ */ new Map();
244
- var CACHE_MS = 3e4;
245
- var OWNER_CACHE_MS = 5 * 6e4;
246
- var asSnippet = monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet | monaco.languages.CompletionItemInsertTextRule.KeepWhitespace;
247
- var MAX_PROJECT_SUGGESTIONS = 200;
248
- async function cached(key, fallback, load, ttl = CACHE_MS) {
249
- if (!activeRepoSuggestions) return fallback;
250
- const hit = suggestionCache.get(key);
251
- if (hit && Date.now() - hit.at < ttl) try {
252
- return await hit.value;
253
- } catch {
254
- return fallback;
255
- }
256
- if (suggestionCache.size > 100) suggestionCache.clear();
257
- const value = load();
258
- suggestionCache.set(key, {
259
- at: Date.now(),
260
- value
261
- });
262
- try {
263
- return await value;
264
- } catch {
265
- suggestionCache.delete(key);
266
- return fallback;
267
- }
268
- }
269
- /** Refs of a repository, or [] when the host bound nothing or the call fails. */
270
- async function repoRefs(repo) {
271
- return cached(`refs\u0000${repo}`, [], () => activeRepoSuggestions.refs(repo));
272
- }
273
- /** Repositories under an owner, or every one the account can reach when the owner is
274
- * empty. [] when the host bound nothing. */
275
- async function repoOwnerRepos(owner) {
276
- return cached(`repos\u0000${owner}`, [], () => activeRepoSuggestions.repos(owner), OWNER_CACHE_MS);
277
- }
278
- /** Owners a reference can name: accounts and organizations, or GitLab groups. */
279
- async function repoOwners(provider, search, host) {
280
- return cached(`owners\u0000${provider}\u0000${host ?? ""}\u0000${search}`, [], () => activeRepoSuggestions.owners(provider, search, host), OWNER_CACHE_MS);
281
- }
282
- /** Projects matching what is typed, fetched once per group rather than once per
283
- * keystroke. The platform caps its answer, so a group that fills the cap is re-asked
284
- * with the full path instead of filtering an already-truncated list. */
285
- async function matchingProjects(typed, host) {
286
- const key = projectSearchKey(typed);
287
- const byGroup = await gitlabProjects(key, host);
288
- const wanted = typed.toLowerCase();
289
- const narrowed = byGroup.filter((project) => project.path.toLowerCase().includes(wanted));
290
- if (key !== typed && byGroup.length >= MAX_PROJECT_SUGGESTIONS) return gitlabProjects(typed, host);
291
- return narrowed;
292
- }
293
- /** GitLab projects matching a partial path, across every connected server. */
294
- async function gitlabProjects(search, host) {
295
- return cached(`gitlab\u0000${host ?? ""}\u0000${search}`, [], () => activeRepoSuggestions.gitlabProjects(search, host), OWNER_CACHE_MS);
296
- }
297
- /** One file's contents, or '' on the same terms. */
298
- async function repoFile(repo, ref, path) {
299
- return cached(`file\u0000${repo}\u0000${ref}\u0000${path}`, "", () => activeRepoSuggestions.file(repo, ref, path));
300
- }
301
- /** Tree entries of a repository, or [] on the same terms. */
302
- async function repoFiles(repo, ref, path) {
303
- return cached(`files\u0000${repo}\u0000${ref}\u0000${path}`, [], () => activeRepoSuggestions.files(repo, ref, path));
304
- }
305
- var monacoWithWorkerBridge = {
306
- ...monaco,
307
- editor: {
308
- ...monaco.editor,
309
- createWebWorker: (opts) => "worker" in opts ? monaco.editor.createWebWorker(opts) : monaco.createWebWorker(opts)
310
- }
311
- };
312
- var monacoYamlConfigured = false;
313
- function ensureMonacoYamlConfigured() {
314
- if (monacoYamlConfigured) return;
315
- monacoYamlConfigured = true;
316
- configureMonacoYaml(monacoWithWorkerBridge, {
317
- enableSchemaRequest: true,
318
- completion: true,
319
- validate: true,
320
- hover: true,
321
- format: { enable: true },
322
- schemas: [{
323
- fileMatch: ["file:///workflow.yaml"],
324
- uri: `${window.location.origin}/workflow.schema.json`
325
- }]
326
- });
327
- }
328
- var workflowCompletionContexts = /* @__PURE__ */ new Map();
329
- var registeredCompletionProviders = [];
330
- var workflowCompletionProvidersRegistered = false;
331
- var isWorkflowInputs = (value) => typeof value === "object" && value !== null;
332
- function readExecuteInputs(doc) {
333
- if (!isWorkflowInputs(doc)) return {};
334
- const on = doc["on"];
335
- if (!isWorkflowInputs(on)) return {};
336
- const execute = on["execute"];
337
- if (!isWorkflowInputs(execute)) return {};
338
- const inputs = execute["inputs"];
339
- return isWorkflowInputs(inputs) ? inputs : {};
340
- }
341
- function editorText(value) {
342
- return value.replace(/\r/g, "");
343
- }
344
- var noop = () => {};
345
- function MonacoEditor({ className, disabled, onChange = noop, onValidate = noop, width = "100%", height = "100%", path = "file:///default", marketplaceItems = [], workflows = [], ...options }) {
346
- const isDark = useCssIsDark();
347
- const readOnly = disabled || options.readOnly;
348
- const resolveWorkflowJson = useWorkflowJsonResolver();
349
- const repoSuggestions = useRepoSuggestions();
350
- const monacoRef = useRef(null);
351
- const isSyncingValueRef = useRef(false);
352
- useEffect(() => {
353
- activeWorkflowJsonResolver = resolveWorkflowJson;
354
- }, [resolveWorkflowJson]);
355
- useEffect(() => {
356
- activeRepoSuggestions = repoSuggestions;
357
- }, [repoSuggestions]);
358
- useEffect(() => {
359
- if (!monacoRef.current) return;
360
- defineEditorThemes(monaco.editor);
361
- monaco.editor.setTheme(getThemeName(isDark, disabled));
362
- monacoRef.current.updateOptions(readOnly === void 0 ? {} : { readOnly });
363
- }, [
364
- disabled,
365
- isDark,
366
- readOnly
367
- ]);
368
- useLayoutEffect(() => {
369
- const model = monacoRef.current?.getModel();
370
- if (!model || options.value === void 0 || editorText(model.getValue()) === editorText(options.value)) return;
371
- isSyncingValueRef.current = true;
372
- try {
373
- model.setValue(options.value);
374
- } finally {
375
- isSyncingValueRef.current = false;
376
- }
377
- }, [options.value]);
378
- useEffect(() => {
379
- return () => {
380
- monacoRef.current?.dispose();
381
- monacoRef.current = null;
382
- };
383
- }, []);
384
- useEffect(() => {
385
- const uri = monaco.Uri.parse(path).toString();
386
- if (marketplaceItems.length > 0 || workflows.length > 0) workflowCompletionContexts.set(uri, {
387
- marketplaceItems,
388
- workflows
389
- });
390
- else workflowCompletionContexts.delete(uri);
391
- return () => {
392
- workflowCompletionContexts.delete(uri);
393
- };
394
- }, [
395
- marketplaceItems,
396
- workflows,
397
- path
398
- ]);
399
- /** Strips CR (\r) — Windows line endings break bash scripts. */
400
- const onChangeWrapper = (value) => {
401
- value = editorText(value);
402
- onChange(value);
403
- };
404
- return /* @__PURE__ */ jsx("div", {
405
- style: {
406
- width,
407
- height
408
- },
409
- className: cx("relative border rounded", className),
410
- ref: (ref) => {
411
- if (ref && !monacoRef.current) {
412
- monaco.editor.getModel(monaco.Uri.parse(path))?.dispose();
413
- defineEditorThemes(monaco.editor);
414
- monaco.editor.setTheme(getThemeName(isDark, disabled));
415
- const model = monaco.editor.createModel(options.value ?? "", options.language, monaco.Uri.parse(path));
416
- const editor = monaco.editor.create(ref, {
417
- automaticLayout: true,
418
- model,
419
- ...options,
420
- ...readOnly === void 0 ? {} : { readOnly },
421
- fixedOverflowWidgets: true,
422
- suggest: {
423
- preview: true,
424
- showWords: false,
425
- showInlineDetails: true
426
- }
427
- });
428
- model.onDidChangeContent(() => {
429
- if (isSyncingValueRef.current) return;
430
- onChangeWrapper(editor.getValue());
431
- });
432
- model.onDidChangeDecorations(() => {
433
- onValidate(monaco.editor.getModelMarkers({ resource: model.uri }));
434
- });
435
- ensureMonacoYamlConfigured();
436
- monacoRef.current = editor;
437
- if (workflowCompletionProvidersRegistered) return;
438
- workflowCompletionProvidersRegistered = true;
439
- registeredCompletionProviders.push(monaco.languages.registerCompletionItemProvider("yaml", {
440
- triggerCharacters: [
441
- "/",
442
- " ",
443
- "@"
444
- ],
445
- provideCompletionItems: async (model, position) => {
446
- const context = workflowCompletionContexts.get(model.uri.toString());
447
- if (!context) return { suggestions: [] };
448
- const { marketplaceItems, workflows } = context;
449
- const lineContent = model.getLineContent(position.lineNumber);
450
- const textUntilPos = lineContent.substring(0, position.column - 1);
451
- const defaultItem = {
452
- kind: monaco.languages.CompletionItemKind.Value,
453
- range: {
454
- startLineNumber: position.lineNumber,
455
- startColumn: position.column,
456
- endLineNumber: position.lineNumber,
457
- endColumn: position.column
458
- }
459
- };
460
- let suggestions = [];
461
- if (!/^\s*(?:-\s+)?uses\s*:\s*([^\n\r]*)$/.test(textUntilPos)) return { suggestions };
462
- const marketIndex = textUntilPos.lastIndexOf("marketplace/");
463
- const workflowIndex = textUntilPos.lastIndexOf("workflow/");
464
- const pwIndex = textUntilPos.lastIndexOf("parallelworks/");
465
- const githubIndex = textUntilPos.lastIndexOf("github/");
466
- const gitlabIndex = textUntilPos.lastIndexOf("gitlab/");
467
- const usesText = textUntilPos.match(/^\s*(?:-\s+)?uses\s*:\s*([^\n\r]*)$/)?.[1]?.trim() ?? "";
468
- if (marketIndex === -1 && workflowIndex === -1 && pwIndex === -1 && githubIndex === -1 && gitlabIndex === -1) {
469
- if (usesText.trim().length > 0) return { suggestions };
470
- return { suggestions: [
471
- {
472
- label: "marketplace",
473
- sortText: "2",
474
- insertText: "marketplace",
475
- detail: "use a workflow from the marketplace",
476
- ...defaultItem
477
- },
478
- {
479
- label: "workflow",
480
- sortText: "3",
481
- insertText: "workflow",
482
- detail: "use one of your workflows",
483
- ...defaultItem
484
- },
485
- {
486
- label: "github",
487
- sortText: "4",
488
- insertText: "github",
489
- detail: "use a workflow from a GitHub repository",
490
- ...defaultItem
491
- },
492
- {
493
- label: "gitlab",
494
- sortText: "5",
495
- insertText: "gitlab",
496
- detail: "use a workflow from a GitLab project",
497
- ...defaultItem
498
- }
499
- ] };
500
- }
501
- const noExtra = [
502
- "marketplace/",
503
- "workflow/",
504
- "parallelworks/",
505
- "github/",
506
- "gitlab/"
507
- ].includes(usesText);
508
- if (workflowIndex !== -1 && noExtra) {
509
- if (lineContent.substring(workflowIndex, position.column - 1).split("/").length === 2) suggestions = workflows.map((item) => {
510
- const name = item.name ?? "";
511
- return {
512
- label: item.displayName && item.displayName !== name ? `${item.displayName} (${name})` : name,
513
- insertText: name,
514
- detail: item.description ? item.description : `${item.type ?? ""} workflow`,
515
- ...defaultItem
516
- };
517
- });
518
- } else if (marketIndex !== -1) {
519
- const sections = lineContent.substring(marketIndex, position.column - 1).split("/");
520
- if (sections.length === 2 && noExtra) suggestions = marketplaceItems.filter((item) => item.type === "workflow").map((item) => {
521
- const slug = item.slug ?? "";
522
- return {
523
- label: item.name && item.name !== slug ? `${item.name} (${slug})` : slug,
524
- insertText: slug,
525
- detail: item.description ? item.description : `${item.subtype ?? ""} workflow`,
526
- ...defaultItem
527
- };
528
- });
529
- else if (sections.length === 3 && sections[2] === "") {
530
- const mpItem = marketplaceItems.find((item) => item.slug === sections[1]);
531
- if (mpItem && mpItem.versions) suggestions = [...mpItem.versions.map((v) => {
532
- return {
533
- label: v,
534
- insertText: v,
535
- ...defaultItem
536
- };
537
- }), {
538
- label: "latest",
539
- insertText: "latest",
540
- ...defaultItem
541
- }];
542
- }
543
- } else if (githubIndex !== -1) {
544
- const afterGithub = usesText.replace("github/", "");
545
- if (afterGithub === "") suggestions = [{
546
- label: "Type a GitHub username or organization",
547
- insertText: "",
548
- detail: "e.g. github/myorg/",
549
- sortText: "0",
550
- ...defaultItem
551
- }, ...(await repoOwners("github", "")).map((owner, index) => ({
552
- label: owner.name,
553
- filterText: owner.name,
554
- insertText: owner.name,
555
- detail: owner.kind,
556
- sortText: `1${String(index).padStart(4, "0")}`,
557
- ...defaultItem
558
- }))];
559
- else if (!afterGithub.includes("/")) {} else {
560
- const ghOwner = afterGithub.split("/")[0];
561
- const rest = afterGithub.split("/").slice(1).join("/");
562
- if (rest === "") suggestions = (await repoOwnerRepos(ghOwner ?? "")).map((r) => ({
563
- label: r.description ? `${r.name} — ${r.description}` : r.name,
564
- insertText: r.name,
565
- detail: r.description || (r.private ? "private repository" : "repository"),
566
- ...defaultItem
567
- }));
568
- else {
569
- let ghRepo = "";
570
- let isRefTrigger = false;
571
- let replaceSlash = false;
572
- const slashRefMatch = rest.match(/^([^@/]+)\/$/);
573
- if (slashRefMatch) {
574
- ghRepo = slashRefMatch[1] ?? "";
575
- isRefTrigger = true;
576
- replaceSlash = true;
577
- } else if (rest.includes("@")) {
578
- const atRepo = rest.split("@")[0];
579
- const refPart = rest.split("@").slice(1).join("@");
580
- if (atRepo && refPart === "") {
581
- ghRepo = atRepo;
582
- isRefTrigger = true;
583
- }
584
- }
585
- if (isRefTrigger && ghRepo) {
586
- const slashToAtEdit = replaceSlash ? [{
587
- range: {
588
- startLineNumber: position.lineNumber,
589
- startColumn: position.column - 1,
590
- endLineNumber: position.lineNumber,
591
- endColumn: position.column
592
- },
593
- text: "@"
594
- }] : [];
595
- const refs = await repoRefs(`${ghOwner}/${ghRepo}`);
596
- suggestions.push(...refs.map((ref, index) => ({
597
- label: ref.kind === "commit" ? ref.name.slice(0, 7) : ref.name,
598
- insertText: ref.name,
599
- detail: ref.detail || ref.kind,
600
- sortText: String(index).padStart(4, "0"),
601
- additionalTextEdits: slashToAtEdit,
602
- ...defaultItem
603
- })));
604
- }
605
- }
606
- }
607
- } else if (gitlabIndex !== -1) {
608
- const typed = usesText.replace("gitlab/", "");
609
- const pinned = stepHostAbove(model, position.lineNumber);
610
- const projects = typed === "" ? [] : await matchingProjects(typed.replace(/[@/]$/, ""), pinned);
611
- const trigger = refTrigger(typed, projects.map((project) => project.path));
612
- if (trigger) {
613
- const slashToAtEdit = trigger.replaceSlash ? [{
614
- range: {
615
- startLineNumber: position.lineNumber,
616
- startColumn: position.column - 1,
617
- endLineNumber: position.lineNumber,
618
- endColumn: position.column
619
- },
620
- text: "@"
621
- }] : [];
622
- const host = pinned ?? projects.find((project) => project.path === trigger.projectPath)?.host;
623
- if (!host) return { suggestions };
624
- const refs = await repoRefs(`https://${host}/${trigger.projectPath}`);
625
- const withHost = pinned ? {
626
- appendText: "",
627
- edits: []
628
- } : hostCompletion(model, position.lineNumber, host);
629
- suggestions = refs.map((ref, index) => {
630
- const inserted = completionInsert(ref.name, withHost.appendText);
631
- return {
632
- label: ref.kind === "commit" ? ref.name.slice(0, 7) : ref.name,
633
- insertText: inserted.insertText,
634
- ...inserted.isSnippet ? { insertTextRules: asSnippet } : {},
635
- detail: ref.detail || ref.kind,
636
- sortText: String(index).padStart(4, "0"),
637
- additionalTextEdits: [...slashToAtEdit, ...withHost.edits],
638
- ...defaultItem
639
- };
640
- });
641
- } else if (typed === "") suggestions = [{
642
- label: "Type a GitLab group or namespace",
643
- insertText: "",
644
- detail: "e.g. gitlab/mygroup/",
645
- sortText: "0",
646
- ...defaultItem
647
- }, ...(await repoOwners("gitlab", "", pinned)).map((owner, index) => {
648
- const withHost = pinned ? {
649
- appendText: "",
650
- edits: []
651
- } : hostCompletion(model, position.lineNumber, owner.host ?? "");
652
- const ownerInsert = completionInsert(owner.name, withHost.appendText);
653
- return {
654
- label: owner.name,
655
- filterText: owner.name,
656
- insertText: ownerInsert.insertText,
657
- ...ownerInsert.isSnippet ? { insertTextRules: asSnippet } : {},
658
- detail: pinned ? owner.kind : `${owner.host} — ${owner.kind}`,
659
- additionalTextEdits: withHost.edits,
660
- sortText: `1${String(index).padStart(4, "0")}`,
661
- ...defaultItem
662
- };
663
- })];
664
- else if (!typed.includes("@")) suggestions = (await matchingProjects(typed, pinned)).map((project, index) => {
665
- const insert = projectInsert(typed, project.path);
666
- const withHost = pinned ? {
667
- appendText: "",
668
- edits: []
669
- } : hostCompletion(model, position.lineNumber, project.host);
670
- const projectInserted = completionInsert(insert.text, withHost.appendText);
671
- const shown = insert.replace ? project.path : insert.text;
672
- return {
673
- label: project.description ? `${shown} — ${project.description}` : shown,
674
- filterText: project.path,
675
- insertText: projectInserted.insertText,
676
- ...projectInserted.isSnippet ? { insertTextRules: asSnippet } : {},
677
- detail: pinned ? project.description || (project.private ? "private project" : "project") : `${project.host}${project.description ? ` — ${project.description}` : ""}`,
678
- additionalTextEdits: withHost.edits,
679
- range: insert.replace ? {
680
- startLineNumber: position.lineNumber,
681
- startColumn: gitlabIndex + 7 + 1,
682
- endLineNumber: position.lineNumber,
683
- endColumn: position.column
684
- } : {
685
- startLineNumber: position.lineNumber,
686
- startColumn: position.column,
687
- endLineNumber: position.lineNumber,
688
- endColumn: position.column
689
- },
690
- sortText: `1${String(index).padStart(4, "0")}`,
691
- kind: monaco.languages.CompletionItemKind.Value
692
- };
693
- });
694
- }
695
- return { suggestions };
696
- }
697
- }));
698
- registeredCompletionProviders.push(monaco.languages.registerCompletionItemProvider("yaml", {
699
- triggerCharacters: [" ", "\n"],
700
- provideCompletionItems: async (model, position) => {
701
- if (!workflowCompletionContexts.has(model.uri.toString())) return { suggestions: [] };
702
- const lineNumber = position.lineNumber;
703
- const lineContent = model.getLineContent(lineNumber);
704
- const textUntilPos = lineContent.substring(0, position.column - 1);
705
- if (fileValueKey(textUntilPos)) return { suggestions: (await fileValueSuggestions(model, lineNumber, textUntilPos, (repo, ref) => repoFiles(repo, ref, ""))).map((file) => ({
706
- label: file.path,
707
- insertText: file.path,
708
- detail: file.detail,
709
- kind: monaco.languages.CompletionItemKind.File,
710
- range: {
711
- startLineNumber: lineNumber,
712
- startColumn: position.column,
713
- endLineNumber: lineNumber,
714
- endColumn: position.column
715
- }
716
- })) };
717
- const isOnWithLine = /^\s*with\s*:\s*([^\n\r]*)$/.test(textUntilPos);
718
- const isOnYamlLine = /^\s*\$(yaml|thumbnail)\s*:\s*\S/.test(textUntilPos);
719
- let withLineNum = -1;
720
- if (isOnWithLine) withLineNum = lineNumber;
721
- else if (isOnYamlLine) for (let ln = lineNumber - 1; ln >= Math.max(lineNumber - 20, 1); ln--) {
722
- const content = model.getLineContent(ln);
723
- if (content.trim() === "") continue;
724
- if (/^\s*with\s*:\s*([^\n\r]*)$/.test(content)) {
725
- withLineNum = ln;
726
- break;
727
- }
728
- }
729
- else if (textUntilPos.trim() === "") for (let ln = lineNumber - 1; ln >= Math.max(lineNumber - 20, 1); ln--) {
730
- const content = model.getLineContent(ln);
731
- if (content.trim() === "") continue;
732
- if (/^\s*with\s*:\s*([^\n\r]*)$/.test(content)) {
733
- withLineNum = ln;
734
- break;
735
- }
736
- }
737
- if (withLineNum === -1) return { suggestions: [] };
738
- if (!isOnWithLine && !isOnYamlLine && withLineNum !== -1) for (let ln = withLineNum + 1; ln < lineNumber; ln++) {
739
- const content = model.getLineContent(ln).trim();
740
- if (content === "") continue;
741
- if (/^\$[A-Za-z]+\s*:/.test(content)) continue;
742
- return { suggestions: [] };
743
- }
744
- const needsNewline = isOnWithLine || isOnYamlLine;
745
- const needsIndent = isOnWithLine;
746
- let usesLine = "";
747
- let usesLineNum = -1;
748
- for (let ln = lineNumber - 1; ln >= Math.max(lineNumber - 20, 1); ln--) {
749
- const content = model.getLineContent(ln).trim();
750
- if (/^(?:-\s+)?uses\s*:\s*(.+)$/.test(content)) {
751
- usesLine = content;
752
- usesLineNum = ln;
753
- break;
754
- }
755
- }
756
- const needsWithLine = usesLineNum !== -1 && withLineNum < usesLineNum;
757
- let withIndent = "";
758
- if (needsWithLine && usesLineNum !== -1) {
759
- const indentMatch = model.getLineContent(usesLineNum).match(/^(\s*(?:-\s+)?)uses/);
760
- if (indentMatch) withIndent = " ".repeat(indentMatch[1].length);
761
- }
762
- const defaultItem = {
763
- kind: monaco.languages.CompletionItemKind.Value,
764
- range: needsWithLine ? {
765
- startLineNumber: lineNumber,
766
- startColumn: 1,
767
- endLineNumber: lineNumber,
768
- endColumn: lineContent.length + 1
769
- } : {
770
- startLineNumber: lineNumber,
771
- startColumn: position.column,
772
- endLineNumber: lineNumber,
773
- endColumn: position.column
774
- }
775
- };
776
- if (usesLine === "") return { suggestions: [] };
777
- const m = usesLine.match(/^(?:-\s+)?uses\s*:\s*(.+)$/);
778
- if (!m) return { suggestions: [] };
779
- const usesValue = m[1].trim();
780
- if (!usesValue) return { suggestions: [] };
781
- if (usesValue.startsWith("parallelworks/")) {
782
- if (!needsWithLine && !isOnWithLine) return { suggestions: [] };
783
- const actionDef = {
784
- "parallelworks/checkout": {
785
- label: "checkout",
786
- required: {
787
- repo: "https://github.com/parallelworks/interactive_session.git",
788
- branch: "main"
789
- }
790
- },
791
- "parallelworks/scheduler-agent": {
792
- label: "scheduler-agent",
793
- required: { "scheduler-type": "slurm" }
794
- },
795
- "parallelworks/wait-for-agent": {
796
- label: "wait-for-agent",
797
- required: { agentId: "${{ needs.JOB.steps.STEP.outputs.agentId }}" }
798
- },
799
- "parallelworks/cancel-jobs": {
800
- label: "cancel-jobs",
801
- required: {}
802
- },
803
- "parallelworks/update-session": {
804
- label: "update-session",
805
- required: { name: "session-name" }
806
- }
807
- }[usesValue];
808
- if (!actionDef || Object.keys(actionDef.required).length === 0) return { suggestions: [] };
809
- const withObj = { ...actionDef.required };
810
- const withSuggestionYaml = prepareWithSuggestion(needsNewline, needsIndent, needsWithLine, withIndent, withObj);
811
- return { suggestions: [{
812
- label: actionDef.label + " inputs",
813
- detail: "inputs for the " + actionDef.label + " action",
814
- sortText: "1",
815
- insertText: withSuggestionYaml,
816
- ...defaultItem
817
- }] };
818
- }
819
- if (!(usesValue.startsWith("marketplace/") || usesValue.startsWith("workflow/") || usesValue.startsWith("github/") || usesValue.startsWith("gitlab/"))) return { suggestions: [] };
820
- let inputs = {};
821
- let usesWorkflowRef;
822
- const isGithub = usesValue.startsWith("github/");
823
- const isGitlab = usesValue.startsWith("gitlab/");
824
- const isRepoRef = isGithub || isGitlab;
825
- const present = needsWithLine ? specialKeysNearUses(model, usesLineNum) : specialKeysInWithBlock(model, withLineNum);
826
- const offers = (key) => isRepoRef && !present.has(key);
827
- if (isGitlab) {
828
- const [projectPath, ref] = splitAtRef(usesValue.replace("gitlab/", ""));
829
- const host = stepHostAbove(model, usesLineNum) || "gitlab.com";
830
- if (!projectPath || !ref) return { suggestions: [] };
831
- usesWorkflowRef = [projectPath];
832
- const yamlPath = (!needsWithLine ? present.get("$yaml") : void 0) || "workflow.yaml";
833
- const text = await repoFile(`https://${host}/${projectPath}`, ref, yamlPath);
834
- if (text) try {
835
- inputs = readExecuteInputs(yaml.load(text));
836
- } catch {}
837
- } else if (isGithub) {
838
- const [ownerRepo, ref] = usesValue.replace("github/", "").split("@");
839
- if (!ownerRepo || !ref) return { suggestions: [] };
840
- const [owner, repo] = ownerRepo.split("/");
841
- if (!owner || !repo) return { suggestions: [] };
842
- usesWorkflowRef = [ownerRepo];
843
- const yamlPath = (!needsWithLine ? present.get("$yaml") : void 0) || "workflow.yaml";
844
- const text = await repoFile(`${owner}/${repo}`, ref ?? "", yamlPath);
845
- if (text) try {
846
- inputs = readExecuteInputs(yaml.load(text));
847
- } catch {}
848
- } else {
849
- usesWorkflowRef = usesValue.split("/").slice(1);
850
- const resolveJson = activeWorkflowJsonResolver;
851
- if (!resolveJson) return { suggestions: [] };
852
- try {
853
- const data = await resolveJson(usesValue.startsWith("marketplace/") ? {
854
- kind: "marketplace",
855
- slug: usesWorkflowRef[0] ?? "",
856
- version: usesWorkflowRef[1] ?? "latest"
857
- } : {
858
- kind: "workflow",
859
- name: usesWorkflowRef[0] ?? ""
860
- });
861
- if (!data) return { suggestions: [] };
862
- inputs = readExecuteInputs(data);
863
- } catch {
864
- return { suggestions: [] };
865
- }
866
- }
867
- if (Object.keys(inputs).length === 0) {
868
- const noInputSuggestions = [{
869
- label: "No inputs found",
870
- detail: usesWorkflowRef[0] + " has no inputs.",
871
- insertText: "",
872
- ...defaultItem
873
- }];
874
- if (isGitlab && !present.has("$host")) noInputSuggestions.unshift({
875
- label: "$host",
876
- detail: "The registered GitLab server this project lives on (default: gitlab.com)",
877
- sortText: "0b",
878
- insertText: needsWithLine ? `${withIndent}with:\n${withIndent} $host:` : needsIndent ? "\n $host:" : "$host:",
879
- ...defaultItem
880
- });
881
- if (isRepoRef) {
882
- if (offers("$yaml")) noInputSuggestions.unshift({
883
- label: "$yaml",
884
- detail: "Specify the yaml path in the repository (default: workflow.yaml)",
885
- sortText: "0",
886
- insertText: needsWithLine ? `${withIndent}with:\n${withIndent} $yaml:` : needsIndent ? "\n $yaml:" : "$yaml:",
887
- ...defaultItem
888
- });
889
- if (offers("$thumbnail")) noInputSuggestions.unshift({
890
- label: "$thumbnail",
891
- detail: "Specify a thumbnail image path or URL for this subworkflow",
892
- sortText: "0a",
893
- insertText: needsWithLine ? `${withIndent}with:\n${withIndent} $thumbnail:` : needsIndent ? "\n $thumbnail:" : "$thumbnail:",
894
- ...defaultItem
895
- });
896
- }
897
- return { suggestions: noInputSuggestions };
898
- }
899
- let groupname = usesValue.replaceAll("/", "_").replaceAll(".", "-").replaceAll("@", "_");
900
- const fullText = model.getValue();
901
- let groupidx = 0;
902
- try {
903
- const basename = groupname;
904
- while (true) if (readExecuteInputs(yaml.load(fullText))[groupname]) {
905
- groupidx++;
906
- groupname = `${basename}_${groupidx}`;
907
- } else break;
908
- } catch {}
909
- const withSuggestionYaml = prepareWithSuggestion(needsNewline, needsIndent, needsWithLine, withIndent, constructWithSuggestion(inputs, groupname));
910
- const lines = fullText.split(/\r?\n/);
911
- let inputsLineIndex = -1;
912
- for (let i = 0; i < lines.length; i++) if (/^[ \t]*inputs\s*:\s*(#.*)?$/.test(lines[i])) {
913
- const inputsIndentMatch = lines[i].match(/^([ \t]*)/);
914
- const inputsIndent = inputsIndentMatch ? inputsIndentMatch[1].length : 0;
915
- let executeIndex = -1;
916
- for (let j = i - 1; j >= 0; j--) {
917
- const m = lines[j].match(/^([ \t]*)([^\s:]+)\s*:\s*(#.*)?$/);
918
- if (!m) continue;
919
- const indent = m[1].length;
920
- if (m[2] === "execute" && indent < inputsIndent) {
921
- executeIndex = j;
922
- break;
923
- }
924
- }
925
- if (executeIndex === -1) continue;
926
- const execIndentMatch = lines[executeIndex].match(/^([ \t]*)/);
927
- const execIndent = execIndentMatch ? execIndentMatch[1].length : 0;
928
- let onFound = false;
929
- for (let k = executeIndex - 1; k >= 0; k--) {
930
- const mOn = lines[k].match(/^([ \t]*)(?:'on'|on)\s*:\s*(#.*)?$/);
931
- if (!mOn) continue;
932
- if (mOn[1].length < execIndent) {
933
- onFound = true;
934
- break;
935
- }
936
- }
937
- if (onFound) {
938
- inputsLineIndex = i;
939
- break;
940
- }
941
- }
942
- const additionalTextEdits = [];
943
- const yamlInputs = ` ${groupname}:\nlabel: ${usesWorkflowRef[0]} inputs${groupidx === 0 ? "" : ` ${groupidx}`}\ntype: group\nitems:\n `.replaceAll("\n", "\n ") + rewriteInputExpressionsForGroup(dumpYaml(inputs), groupname).trim().replaceAll("\n", "\n ") + "\n";
944
- if (inputsLineIndex !== -1) {
945
- const insertLineNumber = inputsLineIndex + 2;
946
- additionalTextEdits.push({
947
- range: {
948
- startLineNumber: insertLineNumber,
949
- startColumn: 1,
950
- endLineNumber: insertLineNumber,
951
- endColumn: 1
952
- },
953
- text: yamlInputs
954
- });
955
- } else {
956
- if (position.lineNumber === lines.length) {
957
- const editor = monaco.editor.getEditors().find((e) => e.getModel()?.uri.toString() === model.uri.toString());
958
- editor?.executeEdits("ensure-trailing-newline", [{
959
- range: new monaco.Range(lines.length, lineContent.length + 1, lines.length, lineContent.length + 1),
960
- text: "\n",
961
- forceMoveMarkers: true
962
- }], () => {
963
- const sel = editor?.getSelection();
964
- return sel ? [sel] : null;
965
- });
966
- }
967
- additionalTextEdits.push({
968
- range: {
969
- startLineNumber: lines.length + 1,
970
- startColumn: 1,
971
- endLineNumber: lines.length + 1,
972
- endColumn: 1
973
- },
974
- text: "\non:\n execute:\n inputs:\n" + yamlInputs
975
- });
976
- }
977
- const suggestions = [{
978
- label: usesWorkflowRef[0] + " inputs",
979
- detail: "all input expressions and dynamic form additions",
980
- sortText: "1",
981
- insertText: withSuggestionYaml,
982
- additionalTextEdits,
983
- ...defaultItem
984
- }, {
985
- label: usesWorkflowRef[0] + " inputs only",
986
- detail: "all input expressions only, no dynamic form additions",
987
- sortText: "2",
988
- insertText: withSuggestionYaml,
989
- ...defaultItem
990
- }];
991
- if (isGitlab && !present.has("$host")) suggestions.unshift({
992
- label: "$host (GitLab server)",
993
- detail: "The registered GitLab server this project lives on (default: gitlab.com)",
994
- sortText: "0b",
995
- insertText: needsWithLine ? `${withIndent}with:\n${withIndent} $host:` : needsIndent ? "\n $host:" : "$host:",
996
- ...defaultItem
997
- });
998
- if (isRepoRef) {
999
- if (offers("$yaml")) suggestions.unshift({
1000
- label: "$yaml (custom yaml path)",
1001
- detail: "Override the default workflow.yaml path in the repository",
1002
- sortText: "0",
1003
- insertText: needsWithLine ? `${withIndent}with:\n${withIndent} $yaml:` : needsIndent ? "\n $yaml:" : "$yaml:",
1004
- ...defaultItem
1005
- });
1006
- if (offers("$thumbnail")) suggestions.unshift({
1007
- label: "$thumbnail (custom thumbnail)",
1008
- detail: "Specify a thumbnail image path or URL for this subworkflow",
1009
- sortText: "0a",
1010
- insertText: needsWithLine ? `${withIndent}with:\n${withIndent} $thumbnail:` : needsIndent ? "\n $thumbnail:" : "$thumbnail:",
1011
- ...defaultItem
1012
- });
1013
- }
1014
- return { suggestions };
1015
- }
1016
- }));
1017
- }
1018
- }
1019
- });
1020
- }
1021
- /** When a workflow is imported as a group, rewrites bare `inputs.X` → `inputs.<groupname>.X` so expressions still resolve. */
1022
- function rewriteInputExpressionsForGroup(yamlStr, groupname) {
1023
- return yamlStr.replace(/\$\{\{(.*?)\}\}/g, (_match, expr) => {
1024
- return "${{ " + expr.replace(/\binputs\./g, `inputs.${groupname}.`).trim() + " }}";
1025
- });
1026
- }
1027
- function constructWithSuggestion(inputs, path) {
1028
- const withSuggestion = {};
1029
- Object.keys(inputs).map((k) => {
1030
- const input = inputs[k];
1031
- if (input?.type === "group" && input.items) withSuggestion[k] = constructWithSuggestion(input.items, path + "." + k);
1032
- else withSuggestion[k] = "${{ inputs." + path + "." + k + " }}";
1033
- });
1034
- return withSuggestion;
1035
- }
1036
- function prepareWithSuggestion(needsNewline, needsIndent, needsWithLine, withIndent, withSuggestion) {
1037
- let withSuggestionYaml = dumpYaml(withSuggestion);
1038
- if (needsWithLine) {
1039
- const childIndent = withIndent + " ";
1040
- withSuggestionYaml = (withIndent + "with:\n" + withSuggestionYaml).replaceAll("\n", "\n" + childIndent);
1041
- } else if (needsNewline && needsIndent) withSuggestionYaml = ("\n" + withSuggestionYaml).replaceAll("\n", "\n ");
1042
- else if (needsNewline) withSuggestionYaml = "\n" + withSuggestionYaml;
1043
- return withSuggestionYaml;
1044
- }
1045
- //#endregion
1046
- export { MonacoEditor as default };