@parallelworks/ui 0.1.0 → 0.2.0

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 (102) hide show
  1. package/dist/assets/editor.worker-CuBRIwi-.js +14776 -0
  2. package/dist/assets/json.worker-8ytS02vz.js +22161 -0
  3. package/dist/assets/yaml.worker-DJC-VUcv.js +48367 -0
  4. package/dist/chunks/Indicator-fF8AVbjQ.js +158 -0
  5. package/dist/chunks/Loader-DQiZ75fa.js +25 -0
  6. package/dist/chunks/MarkdownImpl-Bq4QhrtA.js +33 -0
  7. package/dist/chunks/Monaco-CP_cL9gp.js +745 -0
  8. package/dist/chunks/Provider-C2VtPnh5.js +151 -0
  9. package/dist/chunks/SwitchToggleSmall-FcsaYSTs.js +14 -0
  10. package/dist/chunks/Table-DWuRINhu.js +855 -0
  11. package/dist/chunks/Tooltip-WgbiC_nd.js +71 -0
  12. package/dist/chunks/lib-DTgL8klR.js +191 -0
  13. package/dist/chunks/workers-Cjfjb8E7.js +119 -0
  14. package/dist/components/CollapsiblePanel.d.ts +27 -0
  15. package/dist/components/CopyToClipboard.d.ts +17 -0
  16. package/dist/components/Dropdown.d.ts +24 -0
  17. package/dist/components/Indicator.d.ts +4 -0
  18. package/dist/components/Listbox.d.ts +18 -0
  19. package/dist/components/Markdown.d.ts +7 -0
  20. package/dist/components/MarkdownImpl.d.ts +2 -0
  21. package/dist/components/MultiSelect.d.ts +19 -0
  22. package/dist/components/Overview.d.ts +12 -0
  23. package/dist/components/Provider.d.ts +145 -0
  24. package/dist/components/SectionHeader.d.ts +6 -0
  25. package/dist/components/SwitchToggle.d.ts +12 -0
  26. package/dist/components/SwitchToggleSmall.d.ts +6 -0
  27. package/dist/components/dropdownUtils.d.ts +18 -0
  28. package/dist/editor/Editor.d.ts +2 -0
  29. package/dist/editor/EditorField.d.ts +11 -0
  30. package/dist/editor/FormCodePanel.d.ts +8 -0
  31. package/dist/editor/Monaco.d.ts +31 -0
  32. package/dist/editor/index.d.ts +6 -0
  33. package/dist/editor/index.js +219 -0
  34. package/dist/editor/themes.d.ts +4 -0
  35. package/dist/editor/workers.d.ts +6 -0
  36. package/dist/form/FieldWrapper.d.ts +10 -0
  37. package/dist/form/Fields/BooleanField.d.ts +12 -0
  38. package/dist/form/Fields/BucketReadOnlyField.d.ts +7 -0
  39. package/dist/form/Fields/CheckboxGroupField.d.ts +18 -0
  40. package/dist/form/Fields/ColorField.d.ts +8 -0
  41. package/dist/form/Fields/DropdownField.d.ts +24 -0
  42. package/dist/form/Fields/DurationField.d.ts +13 -0
  43. package/dist/form/Fields/LabelField.d.ts +9 -0
  44. package/dist/form/Fields/MultiDropdownField.d.ts +18 -0
  45. package/dist/form/Fields/NumberField.d.ts +13 -0
  46. package/dist/form/Fields/ObjectField.d.ts +8 -0
  47. package/dist/form/Fields/RadioField.d.ts +9 -0
  48. package/dist/form/Fields/RangeField.d.ts +11 -0
  49. package/dist/form/Fields/ResourceOptionIcon.d.ts +11 -0
  50. package/dist/form/Fields/StepField.d.ts +3 -0
  51. package/dist/form/Fields/StringField.d.ts +17 -0
  52. package/dist/form/Fields/TextAreaField.d.ts +8 -0
  53. package/dist/form/Fields/durationValidity.d.ts +2 -0
  54. package/dist/form/Form.d.ts +105 -0
  55. package/dist/form/SpecialTypes/DropdownWrapper.d.ts +14 -0
  56. package/dist/form/SpecialTypes/FormikCustomDropdown.d.ts +53 -0
  57. package/dist/form/SpecialTypes/FormikCustomInput.d.ts +17 -0
  58. package/dist/form/SpecialTypes/FormikMultiSelectDropdown.d.ts +13 -0
  59. package/dist/form/SpecialTypes/FormikNumericInput.d.ts +9 -0
  60. package/dist/form/SpecialTypes/index.d.ts +3 -0
  61. package/dist/form/Wizard/WizardContainer.d.ts +5 -0
  62. package/dist/form/Wizard/WizardNavigation.d.ts +2 -0
  63. package/dist/form/Wizard/WizardStepContent.d.ts +8 -0
  64. package/dist/form/Wizard/WizardStepIndicator.d.ts +2 -0
  65. package/dist/form/Wizard/types.d.ts +171 -0
  66. package/dist/form/Wizard/useWizardState.d.ts +28 -0
  67. package/dist/form/Wizard/utils.d.ts +2 -0
  68. package/dist/form/fieldRegistry.d.ts +7 -0
  69. package/dist/form/index.d.ts +16 -0
  70. package/dist/form/index.js +2317 -0
  71. package/dist/form/lib.d.ts +15 -0
  72. package/dist/form/types/fieldComponentTypes.d.ts +32 -0
  73. package/dist/form/types/fieldTypes.d.ts +80 -0
  74. package/dist/form/types/workflow-schema.d.ts +1107 -0
  75. package/dist/form/useParsedOpts.d.ts +5 -0
  76. package/dist/form/usePrevious.d.ts +1 -0
  77. package/dist/form/utils/checkShowIfObject.d.ts +1 -0
  78. package/dist/form/utils/getParentValue.d.ts +1 -0
  79. package/dist/form/utils/getPlaceholder.d.ts +2 -0
  80. package/dist/form/utils/getValueUsingPath.d.ts +1 -0
  81. package/dist/form/utils/pwResolver.d.ts +18 -0
  82. package/dist/form/utils/secondaryField.d.ts +8 -0
  83. package/dist/form/utils/slurmDuration.d.ts +15 -0
  84. package/dist/icons.d.ts +1 -1
  85. package/dist/icons.js +2 -2
  86. package/dist/index.d.ts +17 -0
  87. package/dist/index.js +107 -323
  88. package/dist/logviewer/LogViewer.d.ts +18 -0
  89. package/dist/logviewer/index.d.ts +1 -0
  90. package/dist/logviewer/index.js +534 -0
  91. package/dist/logviewer/logviewer.css +70 -0
  92. package/dist/styles.css +1 -1
  93. package/dist/workflow/AnnotationBanner.d.ts +5 -0
  94. package/dist/workflow/Collapse.d.ts +6 -0
  95. package/dist/workflow/DependencyGraph.d.ts +15 -0
  96. package/dist/workflow/JobSummary.d.ts +38 -0
  97. package/dist/workflow/MatrixGroup.d.ts +41 -0
  98. package/dist/workflow/Reveal.d.ts +7 -0
  99. package/dist/workflow/TreeView.d.ts +42 -0
  100. package/dist/workflow/index.d.ts +1 -0
  101. package/dist/workflow/index.js +2330 -0
  102. package/package.json +60 -6
@@ -0,0 +1,745 @@
1
+ import { s as useWorkflowJsonResolver } from "./Provider-C2VtPnh5.js";
2
+ import { n as defineEditorThemes, r as getThemeName, t as setupMonacoWorkers } from "./workers-Cjfjb8E7.js";
3
+ import cx from "classnames";
4
+ import { jsx } from "react/jsx-runtime";
5
+ import { useEffect, useRef, useState } from "react";
6
+ import { dumpYaml } from "@parallelworks/workflow-parser";
7
+ import yaml from "js-yaml";
8
+ import * as monaco from "monaco-editor";
9
+ import { configureMonacoYaml } from "monaco-yaml";
10
+ //#region src/editor/Monaco.tsx
11
+ setupMonacoWorkers();
12
+ var activeWorkflowJsonResolver;
13
+ function readIsDark() {
14
+ const scheme = getComputedStyle(document.documentElement).colorScheme;
15
+ const dark = scheme.includes("dark");
16
+ if (dark !== scheme.includes("light")) return dark;
17
+ return window.matchMedia("(prefers-color-scheme: dark)").matches;
18
+ }
19
+ function useCssIsDark() {
20
+ const [isDark, setIsDark] = useState(readIsDark);
21
+ useEffect(() => {
22
+ const update = () => setIsDark(readIsDark());
23
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
24
+ media.addEventListener("change", update);
25
+ const observer = new MutationObserver(update);
26
+ observer.observe(document.documentElement, {
27
+ attributes: true,
28
+ attributeFilter: [
29
+ "style",
30
+ "class",
31
+ "data-theme"
32
+ ]
33
+ });
34
+ return () => {
35
+ media.removeEventListener("change", update);
36
+ observer.disconnect();
37
+ };
38
+ }, []);
39
+ return isDark;
40
+ }
41
+ var monacoWithWorkerBridge = {
42
+ ...monaco,
43
+ editor: {
44
+ ...monaco.editor,
45
+ createWebWorker: (opts) => "worker" in opts ? monaco.editor.createWebWorker(opts) : monaco.createWebWorker(opts)
46
+ }
47
+ };
48
+ var monacoYamlConfigured = false;
49
+ function ensureMonacoYamlConfigured() {
50
+ if (monacoYamlConfigured) return;
51
+ monacoYamlConfigured = true;
52
+ configureMonacoYaml(monacoWithWorkerBridge, {
53
+ enableSchemaRequest: true,
54
+ completion: true,
55
+ validate: true,
56
+ hover: true,
57
+ format: { enable: true },
58
+ schemas: [{
59
+ fileMatch: ["file:///workflow.yaml"],
60
+ uri: `${window.location.origin}/workflow.schema.json`
61
+ }]
62
+ });
63
+ }
64
+ var workflowCompletionContexts = /* @__PURE__ */ new Map();
65
+ var registeredCompletionProviders = [];
66
+ var workflowCompletionProvidersRegistered = false;
67
+ var noop = () => {};
68
+ function MonacoEditor({ className, disabled, onChange = noop, onValidate = noop, width = "100%", height = "100%", path = "file:///default", marketplaceItems = [], workflows = [], ...options }) {
69
+ const isDark = useCssIsDark();
70
+ const resolveWorkflowJson = useWorkflowJsonResolver();
71
+ const monacoRef = useRef(null);
72
+ const isSyncingValueRef = useRef(false);
73
+ useEffect(() => {
74
+ activeWorkflowJsonResolver = resolveWorkflowJson;
75
+ }, [resolveWorkflowJson]);
76
+ useEffect(() => {
77
+ if (!monacoRef.current) return;
78
+ defineEditorThemes(monaco.editor);
79
+ monaco.editor.setTheme(getThemeName(isDark, disabled));
80
+ monacoRef.current.updateOptions({ readOnly: disabled });
81
+ }, [disabled, isDark]);
82
+ useEffect(() => {
83
+ if (!monacoRef.current) return;
84
+ const model = monacoRef.current.getModel();
85
+ if (model && options.value !== void 0 && model.getValue() !== options.value) {
86
+ isSyncingValueRef.current = true;
87
+ model.setValue(options.value);
88
+ isSyncingValueRef.current = false;
89
+ }
90
+ }, [options.value]);
91
+ useEffect(() => {
92
+ return () => {
93
+ monacoRef.current?.dispose();
94
+ monacoRef.current = null;
95
+ };
96
+ }, []);
97
+ useEffect(() => {
98
+ const uri = monaco.Uri.parse(path).toString();
99
+ if (marketplaceItems.length > 0 || workflows.length > 0) workflowCompletionContexts.set(uri, {
100
+ marketplaceItems,
101
+ workflows
102
+ });
103
+ else workflowCompletionContexts.delete(uri);
104
+ return () => {
105
+ workflowCompletionContexts.delete(uri);
106
+ };
107
+ }, [
108
+ marketplaceItems,
109
+ workflows,
110
+ path
111
+ ]);
112
+ /** Strips CR (\r) — Windows line endings break bash scripts. */
113
+ const onChangeWrapper = (value) => {
114
+ value = value.replace(/\r/g, "");
115
+ onChange(value);
116
+ };
117
+ return /* @__PURE__ */ jsx("div", {
118
+ style: {
119
+ width,
120
+ height
121
+ },
122
+ className: cx("relative border rounded", className),
123
+ ref: (ref) => {
124
+ if (ref && !monacoRef.current) {
125
+ monaco.editor.getModel(monaco.Uri.parse(path))?.dispose();
126
+ defineEditorThemes(monaco.editor);
127
+ monaco.editor.setTheme(getThemeName(isDark, disabled));
128
+ const model = monaco.editor.createModel(options.value ?? "", options.language, monaco.Uri.parse(path));
129
+ const editor = monaco.editor.create(ref, {
130
+ automaticLayout: true,
131
+ model,
132
+ ...options,
133
+ readOnly: disabled || options.readOnly,
134
+ fixedOverflowWidgets: true,
135
+ suggest: {
136
+ preview: true,
137
+ showWords: false,
138
+ showInlineDetails: true
139
+ }
140
+ });
141
+ model.onDidChangeContent(() => {
142
+ if (isSyncingValueRef.current) return;
143
+ onChangeWrapper(editor.getValue());
144
+ });
145
+ model.onDidChangeDecorations(() => {
146
+ onValidate(monaco.editor.getModelMarkers({ resource: model.uri }));
147
+ });
148
+ ensureMonacoYamlConfigured();
149
+ monacoRef.current = editor;
150
+ if (workflowCompletionProvidersRegistered) return;
151
+ workflowCompletionProvidersRegistered = true;
152
+ registeredCompletionProviders.push(monaco.languages.registerCompletionItemProvider("yaml", {
153
+ triggerCharacters: [
154
+ "/",
155
+ " ",
156
+ "@"
157
+ ],
158
+ provideCompletionItems: async (model, position) => {
159
+ const context = workflowCompletionContexts.get(model.uri.toString());
160
+ if (!context) return { suggestions: [] };
161
+ const { marketplaceItems, workflows } = context;
162
+ const lineContent = model.getLineContent(position.lineNumber);
163
+ const textUntilPos = lineContent.substring(0, position.column - 1);
164
+ const defaultItem = {
165
+ kind: monaco.languages.CompletionItemKind.Value,
166
+ range: {
167
+ startLineNumber: position.lineNumber,
168
+ startColumn: position.column,
169
+ endLineNumber: position.lineNumber,
170
+ endColumn: position.column
171
+ }
172
+ };
173
+ let suggestions = [];
174
+ if (!/^\s*(?:-\s+)?uses\s*:\s*([^\n\r]*)$/.test(textUntilPos)) return { suggestions };
175
+ const marketIndex = textUntilPos.lastIndexOf("marketplace/");
176
+ const workflowIndex = textUntilPos.lastIndexOf("workflow/");
177
+ const pwIndex = textUntilPos.lastIndexOf("parallelworks/");
178
+ const githubIndex = textUntilPos.lastIndexOf("github/");
179
+ const usesText = textUntilPos.match(/^\s*(?:-\s+)?uses\s*:\s*([^\n\r]*)$/)?.[1]?.trim() ?? "";
180
+ if (marketIndex === -1 && workflowIndex === -1 && pwIndex === -1 && githubIndex === -1) {
181
+ if (usesText.trim().length > 0) return { suggestions };
182
+ return { suggestions: [
183
+ {
184
+ label: "marketplace",
185
+ sortText: "2",
186
+ insertText: "marketplace",
187
+ detail: "use a workflow from the marketplace",
188
+ ...defaultItem
189
+ },
190
+ {
191
+ label: "workflow",
192
+ sortText: "3",
193
+ insertText: "workflow",
194
+ detail: "use one of your workflows",
195
+ ...defaultItem
196
+ },
197
+ {
198
+ label: "github",
199
+ sortText: "4",
200
+ insertText: "github",
201
+ detail: "use a workflow from a public GitHub repository",
202
+ ...defaultItem
203
+ }
204
+ ] };
205
+ }
206
+ const noExtra = [
207
+ "marketplace/",
208
+ "workflow/",
209
+ "parallelworks/",
210
+ "github/"
211
+ ].includes(usesText);
212
+ if (workflowIndex !== -1 && noExtra) {
213
+ if (lineContent.substring(workflowIndex, position.column - 1).split("/").length === 2) suggestions = workflows.map((item) => {
214
+ const name = item.name ?? "";
215
+ return {
216
+ label: item.displayName && item.displayName !== name ? `${item.displayName} (${name})` : name,
217
+ insertText: name,
218
+ detail: item.description ? item.description : `${item.type ?? ""} workflow`,
219
+ ...defaultItem
220
+ };
221
+ });
222
+ } else if (marketIndex !== -1) {
223
+ const sections = lineContent.substring(marketIndex, position.column - 1).split("/");
224
+ if (sections.length === 2 && noExtra) suggestions = marketplaceItems.filter((item) => item.type === "workflow").map((item) => {
225
+ const slug = item.slug ?? "";
226
+ return {
227
+ label: item.name && item.name !== slug ? `${item.name} (${slug})` : slug,
228
+ insertText: slug,
229
+ detail: item.description ? item.description : `${item.subtype ?? ""} workflow`,
230
+ ...defaultItem
231
+ };
232
+ });
233
+ else if (sections.length === 3 && sections[2] === "") {
234
+ const mpItem = marketplaceItems.find((item) => item.slug === sections[1]);
235
+ if (mpItem && mpItem.versions) suggestions = [...mpItem.versions.map((v) => {
236
+ return {
237
+ label: v,
238
+ insertText: v,
239
+ ...defaultItem
240
+ };
241
+ }), {
242
+ label: "latest",
243
+ insertText: "latest",
244
+ ...defaultItem
245
+ }];
246
+ }
247
+ } else if (githubIndex !== -1) {
248
+ const afterGithub = usesText.replace("github/", "");
249
+ if (afterGithub === "") suggestions = [{
250
+ label: "Type a GitHub username or organization",
251
+ insertText: "",
252
+ detail: "e.g. github/myorg/",
253
+ ...defaultItem
254
+ }];
255
+ else if (!afterGithub.includes("/")) {} else {
256
+ const ghOwner = afterGithub.split("/")[0];
257
+ const rest = afterGithub.split("/").slice(1).join("/");
258
+ if (rest === "") try {
259
+ const resp = await fetch(`https://api.github.com/users/${ghOwner}/repos?per_page=100&sort=updated`);
260
+ if (resp.ok) suggestions = (await resp.json()).map((r) => ({
261
+ label: r.description ? `${r.name} — ${r.description}` : r.name,
262
+ insertText: r.name,
263
+ detail: r.description || "GitHub repository",
264
+ ...defaultItem
265
+ }));
266
+ } catch {}
267
+ else {
268
+ let ghRepo = "";
269
+ let isRefTrigger = false;
270
+ let replaceSlash = false;
271
+ const slashRefMatch = rest.match(/^([^@/]+)\/$/);
272
+ if (slashRefMatch) {
273
+ ghRepo = slashRefMatch[1];
274
+ isRefTrigger = true;
275
+ replaceSlash = true;
276
+ } else if (rest.includes("@")) {
277
+ const atRepo = rest.split("@")[0];
278
+ const refPart = rest.split("@").slice(1).join("@");
279
+ if (atRepo && refPart === "") {
280
+ ghRepo = atRepo;
281
+ isRefTrigger = true;
282
+ }
283
+ }
284
+ if (isRefTrigger && ghRepo) {
285
+ const slashToAtEdit = replaceSlash ? [{
286
+ range: {
287
+ startLineNumber: position.lineNumber,
288
+ startColumn: position.column - 1,
289
+ endLineNumber: position.lineNumber,
290
+ endColumn: position.column
291
+ },
292
+ text: "@"
293
+ }] : [];
294
+ try {
295
+ const [branchResp, tagResp, commitResp] = await Promise.all([
296
+ fetch(`https://api.github.com/repos/${ghOwner}/${ghRepo}/branches?per_page=100`),
297
+ fetch(`https://api.github.com/repos/${ghOwner}/${ghRepo}/tags?per_page=100`),
298
+ fetch(`https://api.github.com/repos/${ghOwner}/${ghRepo}/commits?per_page=10`)
299
+ ]);
300
+ if (branchResp.ok) {
301
+ const branches = await branchResp.json();
302
+ suggestions.push(...branches.map((b) => ({
303
+ label: b.name,
304
+ insertText: b.name,
305
+ detail: "branch",
306
+ additionalTextEdits: slashToAtEdit,
307
+ ...defaultItem
308
+ })));
309
+ }
310
+ if (tagResp.ok) {
311
+ const tags = await tagResp.json();
312
+ suggestions.push(...tags.map((t) => ({
313
+ label: t.name,
314
+ insertText: t.name,
315
+ detail: "tag",
316
+ additionalTextEdits: slashToAtEdit,
317
+ ...defaultItem
318
+ })));
319
+ }
320
+ if (commitResp.ok) {
321
+ const commits = await commitResp.json();
322
+ suggestions.push(...commits.map((c) => ({
323
+ label: `${c.sha.slice(0, 7)} — ${c.commit.message.split("\n")[0]}`,
324
+ insertText: c.sha,
325
+ detail: "commit",
326
+ additionalTextEdits: slashToAtEdit,
327
+ ...defaultItem
328
+ })));
329
+ }
330
+ } catch {}
331
+ }
332
+ }
333
+ }
334
+ }
335
+ return { suggestions };
336
+ }
337
+ }));
338
+ registeredCompletionProviders.push(monaco.languages.registerCompletionItemProvider("yaml", {
339
+ triggerCharacters: [" ", "\n"],
340
+ provideCompletionItems: async (model, position) => {
341
+ if (!workflowCompletionContexts.has(model.uri.toString())) return { suggestions: [] };
342
+ const lineNumber = position.lineNumber;
343
+ const lineContent = model.getLineContent(lineNumber);
344
+ const textUntilPos = lineContent.substring(0, position.column - 1);
345
+ if (/^\s*\$yaml\s*:\s*$/.test(textUntilPos)) {
346
+ for (let ln = lineNumber - 1; ln >= Math.max(lineNumber - 30, 1); ln--) {
347
+ const m = model.getLineContent(ln).trim().match(/^(?:-\s+)?uses\s*:\s*github\/([^/]+)\/([^@]+)@(.+)$/);
348
+ if (m) {
349
+ const [, owner, repo, ref] = m;
350
+ try {
351
+ const resp = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/${ref}?recursive=1`);
352
+ if (resp.ok) {
353
+ const data = await resp.json();
354
+ const defaultItem = {
355
+ kind: monaco.languages.CompletionItemKind.File,
356
+ range: {
357
+ startLineNumber: lineNumber,
358
+ startColumn: position.column,
359
+ endLineNumber: lineNumber,
360
+ endColumn: position.column
361
+ }
362
+ };
363
+ return { suggestions: (data.tree || []).filter((item) => item.type === "blob" && (item.path.endsWith(".yaml") || item.path.endsWith(".yml"))).map((f) => ({
364
+ label: f.path,
365
+ insertText: f.path,
366
+ detail: "yaml file",
367
+ ...defaultItem
368
+ })) };
369
+ }
370
+ } catch {}
371
+ break;
372
+ }
373
+ }
374
+ return { suggestions: [] };
375
+ }
376
+ if (/^\s*\$thumbnail\s*:\s*$/.test(textUntilPos)) {
377
+ for (let ln = lineNumber - 1; ln >= Math.max(lineNumber - 30, 1); ln--) {
378
+ const m = model.getLineContent(ln).trim().match(/^(?:-\s+)?uses\s*:\s*github\/([^/]+)\/([^@]+)@(.+)$/);
379
+ if (m) {
380
+ const [, owner, repo, ref] = m;
381
+ try {
382
+ const resp = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/${ref}?recursive=1`);
383
+ if (resp.ok) {
384
+ const data = await resp.json();
385
+ const defaultItem = {
386
+ kind: monaco.languages.CompletionItemKind.File,
387
+ range: {
388
+ startLineNumber: lineNumber,
389
+ startColumn: position.column,
390
+ endLineNumber: lineNumber,
391
+ endColumn: position.column
392
+ }
393
+ };
394
+ const imageExts = [
395
+ ".png",
396
+ ".jpg",
397
+ ".jpeg",
398
+ ".gif",
399
+ ".svg",
400
+ ".webp"
401
+ ];
402
+ return { suggestions: (data.tree || []).filter((item) => item.type === "blob" && imageExts.some((ext) => item.path.toLowerCase().endsWith(ext))).map((f) => ({
403
+ label: f.path,
404
+ insertText: f.path,
405
+ detail: "image file",
406
+ ...defaultItem
407
+ })) };
408
+ }
409
+ } catch {}
410
+ break;
411
+ }
412
+ }
413
+ return { suggestions: [] };
414
+ }
415
+ const isOnWithLine = /^\s*with\s*:\s*([^\n\r]*)$/.test(textUntilPos);
416
+ const isOnYamlLine = /^\s*\$(yaml|thumbnail)\s*:\s*\S/.test(textUntilPos);
417
+ let withLineNum = -1;
418
+ if (isOnWithLine) withLineNum = lineNumber;
419
+ else if (isOnYamlLine) for (let ln = lineNumber - 1; ln >= Math.max(lineNumber - 20, 1); ln--) {
420
+ const content = model.getLineContent(ln);
421
+ if (content.trim() === "") continue;
422
+ if (/^\s*with\s*:\s*([^\n\r]*)$/.test(content)) {
423
+ withLineNum = ln;
424
+ break;
425
+ }
426
+ }
427
+ else if (textUntilPos.trim() === "") for (let ln = lineNumber - 1; ln >= Math.max(lineNumber - 20, 1); ln--) {
428
+ const content = model.getLineContent(ln);
429
+ if (content.trim() === "") continue;
430
+ if (/^\s*with\s*:\s*([^\n\r]*)$/.test(content)) {
431
+ withLineNum = ln;
432
+ break;
433
+ }
434
+ }
435
+ if (withLineNum === -1) return { suggestions: [] };
436
+ if (!isOnWithLine && !isOnYamlLine && withLineNum !== -1) for (let ln = withLineNum + 1; ln < lineNumber; ln++) {
437
+ const content = model.getLineContent(ln).trim();
438
+ if (content === "") continue;
439
+ if (/^\$(yaml|thumbnail)\s*:/.test(content)) continue;
440
+ return { suggestions: [] };
441
+ }
442
+ const needsNewline = isOnWithLine || isOnYamlLine;
443
+ const needsIndent = isOnWithLine;
444
+ let usesLine = "";
445
+ let usesLineNum = -1;
446
+ for (let ln = lineNumber - 1; ln >= Math.max(lineNumber - 20, 1); ln--) {
447
+ const content = model.getLineContent(ln).trim();
448
+ if (/^(?:-\s+)?uses\s*:\s*(.+)$/.test(content)) {
449
+ usesLine = content;
450
+ usesLineNum = ln;
451
+ break;
452
+ }
453
+ }
454
+ const needsWithLine = usesLineNum !== -1 && withLineNum < usesLineNum;
455
+ let withIndent = "";
456
+ if (needsWithLine && usesLineNum !== -1) {
457
+ const indentMatch = model.getLineContent(usesLineNum).match(/^(\s*(?:-\s+)?)uses/);
458
+ if (indentMatch) withIndent = " ".repeat(indentMatch[1].length);
459
+ }
460
+ const defaultItem = {
461
+ kind: monaco.languages.CompletionItemKind.Value,
462
+ range: needsWithLine ? {
463
+ startLineNumber: lineNumber,
464
+ startColumn: 1,
465
+ endLineNumber: lineNumber,
466
+ endColumn: lineContent.length + 1
467
+ } : {
468
+ startLineNumber: lineNumber,
469
+ startColumn: position.column,
470
+ endLineNumber: lineNumber,
471
+ endColumn: position.column
472
+ }
473
+ };
474
+ if (usesLine === "") return { suggestions: [] };
475
+ const m = usesLine.match(/^(?:-\s+)?uses\s*:\s*(.+)$/);
476
+ if (!m) return { suggestions: [] };
477
+ const usesValue = m[1].trim();
478
+ if (!usesValue) return { suggestions: [] };
479
+ if (usesValue.startsWith("parallelworks/")) {
480
+ if (!needsWithLine && !isOnWithLine) return { suggestions: [] };
481
+ const actionDef = {
482
+ "parallelworks/checkout": {
483
+ label: "checkout",
484
+ required: {
485
+ repo: "https://github.com/parallelworks/interactive_session.git",
486
+ branch: "main"
487
+ }
488
+ },
489
+ "parallelworks/scheduler-agent": {
490
+ label: "scheduler-agent",
491
+ required: { "scheduler-type": "slurm" }
492
+ },
493
+ "parallelworks/wait-for-agent": {
494
+ label: "wait-for-agent",
495
+ required: { agentId: "${{ needs.JOB.steps.STEP.outputs.agentId }}" }
496
+ },
497
+ "parallelworks/cancel-jobs": {
498
+ label: "cancel-jobs",
499
+ required: {}
500
+ },
501
+ "parallelworks/update-session": {
502
+ label: "update-session",
503
+ required: { name: "session-name" }
504
+ }
505
+ }[usesValue];
506
+ if (!actionDef || Object.keys(actionDef.required).length === 0) return { suggestions: [] };
507
+ const withObj = { ...actionDef.required };
508
+ const withSuggestionYaml = prepareWithSuggestion(needsNewline, needsIndent, needsWithLine, withIndent, withObj);
509
+ return { suggestions: [{
510
+ label: actionDef.label + " inputs",
511
+ detail: "inputs for the " + actionDef.label + " action",
512
+ sortText: "1",
513
+ insertText: withSuggestionYaml,
514
+ ...defaultItem
515
+ }] };
516
+ }
517
+ if (!(usesValue.startsWith("marketplace/") || usesValue.startsWith("workflow/") || usesValue.startsWith("github/"))) return { suggestions: [] };
518
+ let inputs = {};
519
+ let usesWorkflowRef;
520
+ const isGithub = usesValue.startsWith("github/");
521
+ const hasYamlOverride = isGithub && (needsWithLine ? hasYamlNearUses(model, usesLineNum) : scanForYamlInWithBlock(model, withLineNum) !== void 0);
522
+ if (isGithub) {
523
+ const [ownerRepo, ref] = usesValue.replace("github/", "").split("@");
524
+ if (!ownerRepo || !ref) return { suggestions: [] };
525
+ const [owner, repo] = ownerRepo.split("/");
526
+ if (!owner || !repo) return { suggestions: [] };
527
+ usesWorkflowRef = [ownerRepo];
528
+ const yamlPath = (!needsWithLine ? scanForYamlInWithBlock(model, withLineNum) : void 0) || "workflow.yaml";
529
+ try {
530
+ const response = await fetch(`https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${yamlPath}`);
531
+ if (response.ok) {
532
+ const text = await response.text();
533
+ inputs = yaml.load(text)?.on?.execute?.inputs || {};
534
+ }
535
+ } catch {}
536
+ } else {
537
+ usesWorkflowRef = usesValue.split("/").slice(1);
538
+ const resolveJson = activeWorkflowJsonResolver;
539
+ if (!resolveJson) return { suggestions: [] };
540
+ try {
541
+ const data = await resolveJson(usesValue.startsWith("marketplace/") ? {
542
+ kind: "marketplace",
543
+ slug: usesWorkflowRef[0],
544
+ version: usesWorkflowRef.length > 1 ? usesWorkflowRef[1] : "latest"
545
+ } : {
546
+ kind: "workflow",
547
+ name: usesWorkflowRef[0]
548
+ });
549
+ if (!data) return { suggestions: [] };
550
+ inputs = data?.on?.execute?.inputs || {};
551
+ } catch {
552
+ return { suggestions: [] };
553
+ }
554
+ }
555
+ if (Object.keys(inputs).length === 0) {
556
+ const noInputSuggestions = [{
557
+ label: "No inputs found",
558
+ detail: usesWorkflowRef[0] + " has no inputs.",
559
+ insertText: "",
560
+ ...defaultItem
561
+ }];
562
+ if (isGithub) {
563
+ if (!hasYamlOverride) noInputSuggestions.unshift({
564
+ label: "$yaml",
565
+ detail: "Specify the yaml path in the GitHub repo (default: workflow.yaml)",
566
+ sortText: "0",
567
+ insertText: needsWithLine ? `${withIndent}with:\n${withIndent} $yaml:` : needsIndent ? "\n $yaml:" : "$yaml:",
568
+ ...defaultItem
569
+ });
570
+ noInputSuggestions.unshift({
571
+ label: "$thumbnail",
572
+ detail: "Specify a thumbnail image path or URL for this subworkflow",
573
+ sortText: "0a",
574
+ insertText: needsWithLine ? `${withIndent}with:\n${withIndent} $thumbnail:` : needsIndent ? "\n $thumbnail:" : "$thumbnail:",
575
+ ...defaultItem
576
+ });
577
+ }
578
+ return { suggestions: noInputSuggestions };
579
+ }
580
+ let groupname = usesValue.replaceAll("/", "_").replaceAll(".", "-").replaceAll("@", "_");
581
+ const fullText = model.getValue();
582
+ let groupidx = 0;
583
+ try {
584
+ const basename = groupname;
585
+ while (true) if (yaml.load(fullText)?.on?.execute?.inputs?.[groupname]) {
586
+ groupidx++;
587
+ groupname = `${basename}_${groupidx}`;
588
+ } else break;
589
+ } catch {}
590
+ const withSuggestionYaml = prepareWithSuggestion(needsNewline, needsIndent, needsWithLine, withIndent, constructWithSuggestion(inputs, groupname));
591
+ const lines = fullText.split(/\r?\n/);
592
+ let inputsLineIndex = -1;
593
+ for (let i = 0; i < lines.length; i++) if (/^[ \t]*inputs\s*:\s*(#.*)?$/.test(lines[i])) {
594
+ const inputsIndentMatch = lines[i].match(/^([ \t]*)/);
595
+ const inputsIndent = inputsIndentMatch ? inputsIndentMatch[1].length : 0;
596
+ let executeIndex = -1;
597
+ for (let j = i - 1; j >= 0; j--) {
598
+ const m = lines[j].match(/^([ \t]*)([^\s:]+)\s*:\s*(#.*)?$/);
599
+ if (!m) continue;
600
+ const indent = m[1].length;
601
+ if (m[2] === "execute" && indent < inputsIndent) {
602
+ executeIndex = j;
603
+ break;
604
+ }
605
+ }
606
+ if (executeIndex === -1) continue;
607
+ const execIndentMatch = lines[executeIndex].match(/^([ \t]*)/);
608
+ const execIndent = execIndentMatch ? execIndentMatch[1].length : 0;
609
+ let onFound = false;
610
+ for (let k = executeIndex - 1; k >= 0; k--) {
611
+ const mOn = lines[k].match(/^([ \t]*)(?:'on'|on)\s*:\s*(#.*)?$/);
612
+ if (!mOn) continue;
613
+ if (mOn[1].length < execIndent) {
614
+ onFound = true;
615
+ break;
616
+ }
617
+ }
618
+ if (onFound) {
619
+ inputsLineIndex = i;
620
+ break;
621
+ }
622
+ }
623
+ const additionalTextEdits = [];
624
+ 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";
625
+ if (inputsLineIndex !== -1) {
626
+ const insertLineNumber = inputsLineIndex + 2;
627
+ additionalTextEdits.push({
628
+ range: {
629
+ startLineNumber: insertLineNumber,
630
+ startColumn: 1,
631
+ endLineNumber: insertLineNumber,
632
+ endColumn: 1
633
+ },
634
+ text: yamlInputs
635
+ });
636
+ } else {
637
+ if (position.lineNumber === lines.length) {
638
+ const editor = monaco.editor.getEditors().find((e) => e.getModel()?.uri.toString() === model.uri.toString());
639
+ editor?.executeEdits("ensure-trailing-newline", [{
640
+ range: new monaco.Range(lines.length, lineContent.length + 1, lines.length, lineContent.length + 1),
641
+ text: "\n",
642
+ forceMoveMarkers: true
643
+ }], () => {
644
+ const sel = editor?.getSelection();
645
+ return sel ? [sel] : null;
646
+ });
647
+ }
648
+ additionalTextEdits.push({
649
+ range: {
650
+ startLineNumber: lines.length + 1,
651
+ startColumn: 1,
652
+ endLineNumber: lines.length + 1,
653
+ endColumn: 1
654
+ },
655
+ text: "\non:\n execute:\n inputs:\n" + yamlInputs
656
+ });
657
+ }
658
+ const suggestions = [{
659
+ label: usesWorkflowRef[0] + " inputs",
660
+ detail: "all input expressions and dynamic form additions",
661
+ sortText: "1",
662
+ insertText: withSuggestionYaml,
663
+ additionalTextEdits,
664
+ ...defaultItem
665
+ }, {
666
+ label: usesWorkflowRef[0] + " inputs only",
667
+ detail: "all input expressions only, no dynamic form additions",
668
+ sortText: "2",
669
+ insertText: withSuggestionYaml,
670
+ ...defaultItem
671
+ }];
672
+ if (isGithub) {
673
+ if (!hasYamlOverride) suggestions.unshift({
674
+ label: "$yaml (custom yaml path)",
675
+ detail: "Override the default workflow.yaml path in the GitHub repo",
676
+ sortText: "0",
677
+ insertText: needsWithLine ? `${withIndent}with:\n${withIndent} $yaml:` : needsIndent ? "\n $yaml:" : "$yaml:",
678
+ ...defaultItem
679
+ });
680
+ suggestions.unshift({
681
+ label: "$thumbnail (custom thumbnail)",
682
+ detail: "Specify a thumbnail image path or URL for this subworkflow",
683
+ sortText: "0a",
684
+ insertText: needsWithLine ? `${withIndent}with:\n${withIndent} $thumbnail:` : needsIndent ? "\n $thumbnail:" : "$thumbnail:",
685
+ ...defaultItem
686
+ });
687
+ }
688
+ return { suggestions };
689
+ }
690
+ }));
691
+ }
692
+ }
693
+ });
694
+ }
695
+ /** When a workflow is imported as a group, rewrites bare `inputs.X` → `inputs.<groupname>.X` so expressions still resolve. */
696
+ function rewriteInputExpressionsForGroup(yamlStr, groupname) {
697
+ return yamlStr.replace(/\$\{\{(.*?)\}\}/g, (_match, expr) => {
698
+ return "${{ " + expr.replace(/\binputs\./g, `inputs.${groupname}.`).trim() + " }}";
699
+ });
700
+ }
701
+ function constructWithSuggestion(inputs, path) {
702
+ const withSuggestion = {};
703
+ Object.keys(inputs).map((k) => {
704
+ const input = inputs[k];
705
+ if (input.type === "group" && input.items) withSuggestion[k] = constructWithSuggestion(input.items, path + "." + k);
706
+ else withSuggestion[k] = "${{ inputs." + path + "." + k + " }}";
707
+ });
708
+ return withSuggestion;
709
+ }
710
+ function prepareWithSuggestion(needsNewline, needsIndent, needsWithLine, withIndent, withSuggestion) {
711
+ let withSuggestionYaml = dumpYaml(withSuggestion);
712
+ if (needsWithLine) {
713
+ const childIndent = withIndent + " ";
714
+ withSuggestionYaml = (withIndent + "with:\n" + withSuggestionYaml).replaceAll("\n", "\n" + childIndent);
715
+ } else if (needsNewline && needsIndent) withSuggestionYaml = ("\n" + withSuggestionYaml).replaceAll("\n", "\n ");
716
+ else if (needsNewline) withSuggestionYaml = "\n" + withSuggestionYaml;
717
+ return withSuggestionYaml;
718
+ }
719
+ /** Scan lines below a `uses:` line for a `$yaml:` key, stopping at another step. */
720
+ function hasYamlNearUses(model, usesLineNumber) {
721
+ const totalLines = model.getLineCount();
722
+ for (let ln = usesLineNumber + 1; ln <= Math.min(usesLineNumber + 20, totalLines); ln++) {
723
+ const content = model.getLineContent(ln).trim();
724
+ if (content === "") continue;
725
+ if (/^-\s/.test(content)) break;
726
+ if (/^\$yaml\s*:/.test(content)) return true;
727
+ }
728
+ return false;
729
+ }
730
+ /** Scan lines below a `with:` line for a `$yaml:` key within the same indent block. */
731
+ function scanForYamlInWithBlock(model, withLineNumber) {
732
+ const withIndentMatch = model.getLineContent(withLineNumber).match(/^(\s*)/);
733
+ const withIndent = withIndentMatch ? withIndentMatch[1].length : 0;
734
+ const totalLines = model.getLineCount();
735
+ for (let ln = withLineNumber + 1; ln <= totalLines; ln++) {
736
+ const line = model.getLineContent(ln);
737
+ if (line.trim() === "") continue;
738
+ const indentMatch = line.match(/^(\s*)/);
739
+ if ((indentMatch ? indentMatch[1].length : 0) <= withIndent) break;
740
+ const yamlMatch = line.match(/^\s*\$yaml\s*:\s*(.*)$/);
741
+ if (yamlMatch) return yamlMatch[1].trim();
742
+ }
743
+ }
744
+ //#endregion
745
+ export { MonacoEditor as default };