@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.
- package/README.md +86 -6
- package/dist/ai/adapter/memoryStore.d.ts +8 -0
- package/dist/ai/adapter/memoryStore.js +152 -0
- package/dist/ai/adapter/mock/index.d.ts +2 -0
- package/dist/ai/adapter/mock/index.js +3 -0
- package/dist/ai/adapter/mock/mockAdapter.d.ts +4 -0
- package/dist/ai/adapter/mock/mockAdapter.js +81 -0
- package/dist/ai/adapter/openai/createOpenAIStream.d.ts +9 -0
- package/dist/ai/adapter/openai/createOpenAIStream.js +33 -0
- package/dist/ai/adapter/openai/errors.d.ts +3 -0
- package/dist/ai/adapter/openai/errors.js +37 -0
- package/dist/ai/adapter/openai/index.d.ts +6 -0
- package/dist/ai/adapter/openai/index.js +6 -0
- package/dist/ai/adapter/openai/openaiAdapter.d.ts +9 -0
- package/dist/ai/adapter/openai/openaiAdapter.js +54 -0
- package/dist/ai/adapter/openai/responses.d.ts +4 -0
- package/dist/ai/adapter/openai/responses.js +108 -0
- package/dist/ai/adapter/openai/sse.d.ts +24 -0
- package/dist/ai/adapter/openai/sse.js +112 -0
- package/dist/ai/adapter/openai/wire.d.ts +40 -0
- package/dist/ai/adapter/types.d.ts +131 -0
- package/dist/ai/adapter/types.js +11 -0
- package/dist/ai/agent/classifier.d.ts +2 -0
- package/dist/ai/agent/classifier.js +12 -0
- package/dist/ai/agent/diff.d.ts +35 -0
- package/dist/ai/agent/diff.js +223 -0
- package/dist/ai/agent/diffHighlight.d.ts +6 -0
- package/dist/ai/agent/diffHighlight.js +101 -0
- package/dist/ai/agent/index.d.ts +6 -0
- package/dist/ai/agent/permissions.d.ts +11 -0
- package/dist/ai/agent/permissions.js +29 -0
- package/dist/ai/agent/toolfmt.d.ts +18 -0
- package/dist/ai/agent/toolfmt.js +383 -0
- package/dist/ai/agent/transcript.d.ts +38 -0
- package/dist/ai/agent/transcript.js +127 -0
- package/dist/ai/ai.css +325 -0
- package/dist/ai/components/AllocationSelector.d.ts +4 -0
- package/dist/ai/components/AllocationSelector.js +56 -0
- package/dist/ai/components/AttachmentManager.d.ts +1 -0
- package/dist/ai/components/AttachmentManager.js +144 -0
- package/dist/ai/components/AttachmentUpload.d.ts +24 -0
- package/dist/ai/components/AttachmentUpload.js +179 -0
- package/dist/ai/components/BlockedGroupBanner.d.ts +1 -0
- package/dist/ai/components/BlockedGroupBanner.js +29 -0
- package/dist/ai/components/BranchNavigator.d.ts +8 -0
- package/dist/ai/components/BranchNavigator.js +57 -0
- package/dist/ai/components/ChatEmptyState.d.ts +3 -0
- package/dist/ai/components/ChatEmptyState.js +171 -0
- package/dist/ai/components/ChatInput.d.ts +45 -0
- package/dist/ai/components/ChatInput.js +265 -0
- package/dist/ai/components/ChatLayout.d.ts +5 -0
- package/dist/ai/components/ChatLayout.js +15 -0
- package/dist/ai/components/ChatMessage.d.ts +24 -0
- package/dist/ai/components/ChatMessage.js +338 -0
- package/dist/ai/components/ChatMessageList.d.ts +35 -0
- package/dist/ai/components/ChatMessageList.js +301 -0
- package/dist/ai/components/ChatSidebar.d.ts +5 -0
- package/dist/ai/components/ChatSidebar.js +345 -0
- package/dist/ai/components/ChatThread.d.ts +3 -0
- package/dist/ai/components/ChatThread.js +198 -0
- package/dist/ai/components/ComposerChrome.d.ts +18 -0
- package/dist/ai/components/ComposerChrome.js +50 -0
- package/dist/ai/components/ComposerFrame.d.ts +16 -0
- package/dist/ai/components/ComposerFrame.js +33 -0
- package/dist/ai/components/DragOverlay.d.ts +5 -0
- package/dist/ai/components/DragOverlay.js +20 -0
- package/dist/ai/components/ModelSelector.d.ts +6 -0
- package/dist/ai/components/ModelSelector.js +343 -0
- package/dist/ai/components/ShareDialog.d.ts +11 -0
- package/dist/ai/components/ShareDialog.js +260 -0
- package/dist/ai/components/SlashMenu.d.ts +28 -0
- package/dist/ai/components/SlashMenu.js +91 -0
- package/dist/ai/components/agent/ActivityLine.d.ts +21 -0
- package/dist/ai/components/agent/ActivityLine.js +151 -0
- package/dist/ai/components/agent/AgentMessageParts.d.ts +54 -0
- package/dist/ai/components/agent/AgentMessageParts.js +721 -0
- package/dist/ai/components/agent/PermissionPicker.d.ts +11 -0
- package/dist/ai/components/agent/PermissionPicker.js +38 -0
- package/dist/ai/components/attachmentKeys.d.ts +4 -0
- package/dist/ai/components/attachmentKeys.js +10 -0
- package/dist/ai/core/ChatProvider.d.ts +66 -0
- package/dist/ai/core/ChatProvider.js +848 -0
- package/dist/ai/core/KeyboardShortcutsProvider.d.ts +5 -0
- package/dist/ai/core/KeyboardShortcutsProvider.js +9 -0
- package/dist/ai/core/chatReducer.d.ts +114 -0
- package/dist/ai/core/chatReducer.js +223 -0
- package/dist/ai/core/config.d.ts +47 -0
- package/dist/ai/core/config.js +45 -0
- package/dist/ai/core/events.d.ts +1 -0
- package/dist/ai/core/events.js +4 -0
- package/dist/ai/core/exportConversation.d.ts +4 -0
- package/dist/ai/core/exportConversation.js +42 -0
- package/dist/ai/core/greeting.d.ts +3 -0
- package/dist/ai/core/greeting.js +38 -0
- package/dist/ai/core/parts.d.ts +11 -0
- package/dist/ai/core/parts.js +84 -0
- package/dist/ai/core/useDragDrop.d.ts +14 -0
- package/dist/ai/core/useDragDrop.js +36 -0
- package/dist/ai/core/useKeyboardShortcuts.d.ts +12 -0
- package/dist/ai/core/useKeyboardShortcuts.js +77 -0
- package/dist/ai/core/useSSEStream.d.ts +34 -0
- package/dist/ai/core/useSSEStream.js +98 -0
- package/dist/ai/index.d.ts +40 -0
- package/dist/ai/index.js +44 -0
- package/dist/ai/strings.d.ts +167 -0
- package/dist/ai/strings.js +175 -0
- package/dist/ai/types.d.ts +236 -0
- package/dist/ai/ui/Avatar.d.ts +8 -0
- package/dist/ai/ui/Avatar.js +35 -0
- package/dist/ai/ui/Dropdown.d.ts +31 -0
- package/dist/ai/ui/Dropdown.js +110 -0
- package/dist/ai/ui/Markdown.d.ts +8 -0
- package/dist/ai/ui/Markdown.js +79 -0
- package/dist/ai/ui/Notice.d.ts +21 -0
- package/dist/ai/ui/Notice.js +77 -0
- package/dist/ai/ui/buttons.d.ts +1 -0
- package/dist/ai/ui/codeBreaks.d.ts +16 -0
- package/dist/ai/ui/codeBreaks.js +36 -0
- package/dist/ai/ui/prose.d.ts +1 -0
- package/dist/ai/ui/prose.js +13 -0
- package/dist/ai/ui/sidebar.d.ts +81 -0
- package/dist/ai/ui/sidebar.js +148 -0
- package/dist/ai/utils.d.ts +4 -0
- package/dist/ai/utils.js +23 -0
- package/dist/assets/{editor.worker-CuBRIwi-.js → editor.worker-BmHUIVG4.js} +194 -163
- package/dist/assets/{json.worker-8ytS02vz.js → json.worker-D8bJ92nw.js} +384 -338
- package/dist/assets/{yaml.worker-DJC-VUcv.js → yaml.worker-C4gyGAko.js} +15657 -13823
- package/dist/components/AddMenu.js +46 -0
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/Avatar.js +46 -0
- package/dist/{chunks/BareModal-BtRiQezg.js → components/BareModal.js} +1 -1
- package/dist/components/Breadcrumbs.d.ts +1 -1
- package/dist/components/Breadcrumbs.js +83 -0
- package/dist/components/Button.js +83 -0
- package/dist/components/Callout.js +23 -0
- package/dist/components/Card.js +31 -0
- package/dist/components/CollapsiblePanel.js +77 -0
- package/dist/components/ConfirmModal.js +74 -0
- package/dist/components/CopyToClipboard.js +81 -0
- package/dist/{chunks/CreateModal-BY6DLmur.js → components/CreateModal.js} +10 -189
- package/dist/components/DescriptionList.js +42 -0
- package/dist/components/Dropdown.d.ts +1 -1
- package/dist/components/Dropdown.js +349 -0
- package/dist/components/EmptyState.d.ts +1 -1
- package/dist/components/EmptyState.js +27 -0
- package/dist/components/Filter.js +32 -0
- package/dist/components/FilterPill.js +13 -0
- package/dist/components/IconButton.js +34 -0
- package/dist/components/Indicator.d.ts +2 -2
- package/dist/{chunks/Indicator-hBSIxOPK.js → components/Indicator.js} +1 -1
- package/dist/components/Input.d.ts +1 -1
- package/dist/components/Input.js +88 -0
- package/dist/components/LetterBadge.d.ts +1 -1
- package/dist/components/LetterBadge.js +13 -0
- package/dist/components/Listbox.js +123 -0
- package/dist/{chunks/Loader-BXoXTZGc.js → components/Loader.js} +1 -1
- package/dist/components/Markdown.js +15 -0
- package/dist/components/MarkdownImpl.d.ts +1 -1
- package/dist/components/MultiSelect.js +171 -0
- package/dist/components/Overview.js +39 -0
- package/dist/components/PageHeader.js +194 -0
- package/dist/components/Pagination.js +35 -0
- package/dist/components/Provider.d.ts +16 -65
- package/dist/{chunks/Provider-Ghua9PkN.js → components/Provider.js} +54 -20
- package/dist/{chunks/RequiredMark-DfLdYbYZ.js → components/RequiredMark.js} +1 -1
- package/dist/components/SectionHeader.js +11 -0
- package/dist/components/SettingModeOption.js +30 -0
- package/dist/components/SettingRow.d.ts +1 -1
- package/dist/components/SettingRow.js +35 -0
- package/dist/components/SettingsCard.js +31 -0
- package/dist/components/SettingsGroup.d.ts +1 -1
- package/dist/components/SettingsGroup.js +17 -0
- package/dist/components/Sort.js +42 -0
- package/dist/components/StatusBadge.js +63 -0
- package/dist/components/SwitchToggle.js +59 -0
- package/dist/{chunks/SwitchToggleSmall-FcsaYSTs.js → components/SwitchToggleSmall.js} +1 -1
- package/dist/{chunks/Table-4OHjdKdO.js → components/Table.js} +7 -5
- package/dist/components/Toggle.js +20 -0
- package/dist/{chunks/Tooltip-Crxo_eU_.js → components/Tooltip.js} +6 -4
- package/dist/{chunks/StatusBadge-C1Blx_cW.js → components/dropdownUtils.js} +2 -63
- package/dist/components/focus.d.ts +2 -0
- package/dist/components/focus.js +7 -0
- package/dist/components/ghostButton.js +7 -0
- package/dist/components/inputClasses.js +4 -0
- package/dist/components/keys.d.ts +12 -0
- package/dist/components/keys.js +27 -0
- package/dist/components/relativeTime.js +32 -0
- package/dist/{chunks/useCssIsDark-DHr05dG2.js → components/useCssIsDark.js} +1 -1
- package/dist/duration.d.ts +3 -0
- package/dist/duration.js +17 -0
- package/dist/editor/Editor.js +13 -0
- package/dist/editor/EditorField.js +53 -0
- package/dist/editor/FormCodePanel.js +173 -0
- package/dist/editor/Monaco.d.ts +1 -20
- package/dist/editor/Monaco.js +112 -0
- package/dist/editor/index.d.ts +2 -1
- package/dist/editor/index.js +7 -2
- package/dist/editor/themes.js +91 -0
- package/dist/editor/workers.js +29 -0
- package/dist/editor/yaml.d.ts +5 -0
- package/dist/editor/yaml.js +15 -0
- package/dist/engine.d.ts +98 -0
- package/dist/file-explorer/FileExplorer.d.ts +11 -2
- package/dist/file-explorer/FileExplorer.js +1402 -0
- package/dist/file-explorer/FileExplorerContext.d.ts +1 -1
- package/dist/file-explorer/FileExplorerContext.js +370 -0
- package/dist/file-explorer/FilePreview/CodePreviewEditor.d.ts +1 -1
- package/dist/{chunks/CodePreviewEditor-4lx3kE-w.js → file-explorer/FilePreview/CodePreviewEditor.js} +3 -2
- package/dist/file-explorer/FilePreview/Inline.js +45 -0
- package/dist/file-explorer/FilePreview/StreamedBadge.js +16 -0
- package/dist/file-explorer/FilePreview/Viewer.js +526 -0
- package/dist/file-explorer/FilePreview/index.js +266 -0
- package/dist/file-explorer/FilePreview/parse.js +63 -0
- package/dist/file-explorer/FilePreview/previewCache.js +98 -0
- package/dist/file-explorer/FilePreview/previewType.d.ts +1 -1
- package/dist/file-explorer/FilePreview/previewType.js +151 -0
- package/dist/file-explorer/FilePreview/readCappedBytes.js +196 -0
- package/dist/file-explorer/FilePreview/useObjectBlobUrl.js +95 -0
- package/dist/file-explorer/FilePreview/useObjectText.js +109 -0
- package/dist/file-explorer/FilePreview/usePresignedUrl.js +73 -0
- package/dist/file-explorer/FileRow.d.ts +1 -1
- package/dist/file-explorer/FileRow.js +165 -0
- package/dist/file-explorer/FileRowsWindow.js +92 -0
- package/dist/file-explorer/FileTable.js +172 -0
- package/dist/file-explorer/FileTableHeaders.js +49 -0
- package/dist/file-explorer/LoadMoreRow.js +46 -0
- package/dist/file-explorer/TreeView.js +376 -0
- package/dist/file-explorer/helpers.d.ts +4 -4
- package/dist/file-explorer/helpers.js +537 -0
- package/dist/file-explorer/index.d.ts +3 -3
- package/dist/file-explorer/index.js +7 -5705
- package/dist/file-explorer/lib/batch.js +39 -0
- package/dist/file-explorer/lib/errors.js +9 -0
- package/dist/file-explorer/lib/paging.js +124 -0
- package/dist/file-explorer/lib/types.d.ts +6 -1
- package/dist/file-explorer/lib/utils.d.ts +1 -3
- package/dist/file-explorer/lib/utils.js +322 -0
- package/dist/file-explorer/toasts.js +400 -0
- package/dist/file-explorer/treeRows.js +73 -0
- package/dist/file-explorer/useNearEnd.d.ts +1 -1
- package/dist/file-explorer/useNearEnd.js +27 -0
- package/dist/form/FieldWrapper.d.ts +2 -2
- package/dist/form/FieldWrapper.js +54 -0
- package/dist/form/Fields/BooleanField.js +51 -0
- package/dist/form/Fields/CheckboxGroupField.js +72 -0
- package/dist/form/Fields/ColorField.js +33 -0
- package/dist/form/Fields/DropdownField.js +49 -0
- package/dist/form/Fields/DurationField.js +130 -0
- package/dist/form/Fields/LabelField.js +19 -0
- package/dist/form/Fields/MultiDropdownField.js +40 -0
- package/dist/form/Fields/NumberField.js +51 -0
- package/dist/form/Fields/ObjectField.js +54 -0
- package/dist/form/Fields/RadioField.js +49 -0
- package/dist/form/Fields/RangeField.js +58 -0
- package/dist/form/Fields/ResourceOptionIcon.d.ts +1 -1
- package/dist/form/Fields/ResourceOptionIcon.js +21 -0
- package/dist/form/Fields/StepField.js +6 -0
- package/dist/form/Fields/StringField.js +37 -0
- package/dist/form/Fields/TextAreaField.js +31 -0
- package/dist/form/Fields/durationValidity.js +11 -0
- package/dist/form/Form.d.ts +4 -21
- package/dist/form/Form.js +643 -0
- package/dist/form/SpecialTypes/DropdownWrapper.js +30 -0
- package/dist/form/SpecialTypes/FormikCustomDropdown.js +122 -0
- package/dist/form/SpecialTypes/FormikCustomInput.js +111 -0
- package/dist/form/SpecialTypes/FormikMultiSelectDropdown.js +31 -0
- package/dist/form/SpecialTypes/FormikNumericInput.js +60 -0
- package/dist/form/Wizard/WizardContainer.js +138 -0
- package/dist/form/Wizard/WizardNavigation.js +92 -0
- package/dist/form/Wizard/WizardStepContent.js +35 -0
- package/dist/form/Wizard/WizardStepIndicator.js +108 -0
- package/dist/form/Wizard/useWizardState.js +118 -0
- package/dist/form/Wizard/utils.js +34 -0
- package/dist/form/fieldContext.js +26 -0
- package/dist/form/fieldRegistry.js +50 -0
- package/dist/form/index.d.ts +11 -12
- package/dist/form/index.js +18 -2445
- package/dist/form/lib.d.ts +6 -1
- package/dist/{chunks/FieldWrapper-Bef01EWO.js → form/lib.js} +5 -104
- package/dist/form/types/fieldComponentTypes.d.ts +3 -2
- package/dist/form/types/fieldTypes.d.ts +6 -20
- package/dist/form/useParsedOpts.d.ts +1 -1
- package/dist/form/useParsedOpts.js +62 -0
- package/dist/form/usePrevious.js +11 -0
- package/dist/form/utils/checkShowIfObject.js +14 -0
- package/dist/form/utils/{slurmDuration.d.ts → duration.d.ts} +2 -2
- package/dist/form/utils/duration.js +69 -0
- package/dist/form/utils/getParentValue.js +23 -0
- package/dist/form/utils/getPlaceholder.js +9 -0
- package/dist/form/utils/getValueUsingPath.js +23 -0
- package/dist/form/utils/secondaryField.js +22 -0
- package/dist/graph/AnnotationBanner.js +141 -0
- package/dist/graph/Collapse.js +41 -0
- package/dist/{workflow/index.js → graph/DependencyGraph.js} +76 -1163
- package/dist/graph/JobSummary.js +104 -0
- package/dist/{workflow → graph}/MatrixGroup.d.ts +2 -2
- package/dist/graph/MatrixGroup.js +107 -0
- package/dist/graph/Reveal.js +57 -0
- package/dist/graph/TreeView.js +711 -0
- package/dist/{workflow → graph}/index.d.ts +1 -1
- package/dist/graph/index.js +2 -0
- package/dist/{workflow → graph}/types.d.ts +3 -3
- package/dist/graph/types.js +6 -0
- package/dist/icons.d.ts +20 -21
- package/dist/index.d.ts +38 -40
- package/dist/index.js +47 -599
- package/dist/list/ActionToolbar.js +184 -0
- package/dist/list/HoverCard.js +73 -0
- package/dist/list/ListPage.js +145 -0
- package/dist/list/ListPager.js +59 -0
- package/dist/list/ListTable.d.ts +1 -1
- package/dist/list/ListTable.js +163 -0
- package/dist/list/ListViewControls.d.ts +2 -2
- package/dist/list/ListViewControls.js +372 -0
- package/dist/list/RowContextMenu.js +350 -0
- package/dist/list/UserHoverCard.js +117 -0
- package/dist/list/index.d.ts +9 -9
- package/dist/list/index.js +11 -2
- package/dist/list/listSearch.js +137 -0
- package/dist/list/listView.js +210 -0
- package/dist/list/textEntry.js +10 -0
- package/dist/logviewer/LogViewer.d.ts +1 -1
- package/dist/logviewer/LogViewer.js +750 -0
- package/dist/logviewer/index.d.ts +1 -0
- package/dist/logviewer/index.js +3 -742
- package/dist/logviewer/segments.d.ts +9 -0
- package/dist/logviewer/segments.js +80 -0
- package/dist/styles/fonts.css +4 -4
- package/dist/styles/theme.css +6 -4
- package/dist/styles.css +1 -1
- package/dist/theme/index.js +2 -2
- package/package.json +38 -14
- package/LICENSE +0 -202
- package/dist/chunks/Monaco-C1POOdVv.js +0 -1046
- package/dist/chunks/PageHeader-l49p1nM9.js +0 -690
- package/dist/chunks/SwitchToggle-CHPP8uZQ.js +0 -755
- package/dist/chunks/editor-9_uVtUyy.js +0 -347
- package/dist/chunks/list-qrojZZNv.js +0 -1411
- package/dist/editor/usesGitlab.d.ts +0 -97
- package/dist/form/Fields/BucketReadOnlyField.d.ts +0 -7
- package/dist/form/types/workflow-schema.d.ts +0 -1145
- package/dist/form/utils/pwResolver.d.ts +0 -18
- package/dist/{chunks/MarkdownImpl-Bq4QhrtA.js → components/MarkdownImpl.js} +1 -1
- /package/dist/{workflow → graph}/AnnotationBanner.d.ts +0 -0
- /package/dist/{workflow → graph}/Collapse.d.ts +0 -0
- /package/dist/{workflow → graph}/DependencyGraph.d.ts +0 -0
- /package/dist/{workflow → graph}/JobSummary.d.ts +0 -0
- /package/dist/{workflow → graph}/Reveal.d.ts +0 -0
- /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 };
|