From e8fd36e2e7578e21aeccb094bc4526c7fa4ff70c Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 27 Nov 2025 17:21:04 +0000 Subject: [PATCH 01/39] fix(cli): support better esm mode for codebases --- ...153c43903f929ae5d62fbba12610f89c36d55.json | 2 +- ...29848b2c7131b1bbc9c3a6fe121c84138662.json} | 10 ++- backend/windmill-api/src/scripts.rs | 12 ++- backend/windmill-common/src/cache.rs | 15 ++-- cli/src/commands/script/script.ts | 81 ++++++++++--------- cli/src/utils/codebase.ts | 32 +++++--- 6 files changed, 91 insertions(+), 61 deletions(-) rename backend/.sqlx/{query-a9db7b2f435bb82acb8c5eeb7f800b28f3256491fdaa168591adc7b4b9f3327a.json => query-7bca61bdff25cc5e4181d6a738bf29848b2c7131b1bbc9c3a6fe121c84138662.json} (87%) diff --git a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json index 36ddb8ab9f..713ccb9dd3 100644 --- a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json +++ b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json @@ -15,7 +15,7 @@ ] }, "nullable": [ - true + null ] }, "hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55" diff --git a/backend/.sqlx/query-a9db7b2f435bb82acb8c5eeb7f800b28f3256491fdaa168591adc7b4b9f3327a.json b/backend/.sqlx/query-7bca61bdff25cc5e4181d6a738bf29848b2c7131b1bbc9c3a6fe121c84138662.json similarity index 87% rename from backend/.sqlx/query-a9db7b2f435bb82acb8c5eeb7f800b28f3256491fdaa168591adc7b4b9f3327a.json rename to backend/.sqlx/query-7bca61bdff25cc5e4181d6a738bf29848b2c7131b1bbc9c3a6fe121c84138662.json index c2c6738167..8ec0677716 100644 --- a/backend/.sqlx/query-a9db7b2f435bb82acb8c5eeb7f800b28f3256491fdaa168591adc7b4b9f3327a.json +++ b/backend/.sqlx/query-7bca61bdff25cc5e4181d6a738bf29848b2c7131b1bbc9c3a6fe121c84138662.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT content AS \"content!: String\",\n lock AS \"lock: String\", language AS \"language: Option\", envs AS \"envs: Vec\", schema AS \"schema: String\", schema_validation AS \"schema_validation: bool\", codebase LIKE '%.tar' as use_tar FROM script WHERE hash = $1 LIMIT 1", + "query": "SELECT content AS \"content!: String\",\n lock AS \"lock: String\", language AS \"language: Option\", envs AS \"envs: Vec\", schema AS \"schema: String\", schema_validation AS \"schema_validation: bool\", codebase LIKE '%.tar' as use_tar, codebase LIKE '%.esm%' as is_esm FROM script WHERE hash = $1 LIMIT 1", "describe": { "columns": [ { @@ -68,6 +68,11 @@ "ordinal": 6, "name": "use_tar", "type_info": "Bool" + }, + { + "ordinal": 7, + "name": "is_esm", + "type_info": "Bool" } ], "parameters": { @@ -82,8 +87,9 @@ true, true, false, + null, null ] }, - "hash": "a9db7b2f435bb82acb8c5eeb7f800b28f3256491fdaa168591adc7b4b9f3327a" + "hash": "7bca61bdff25cc5e4181d6a738bf29848b2c7131b1bbc9c3a6fe121c84138662" } diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index d5d3e654a9..01200f1674 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -409,7 +409,7 @@ async fn create_snapshot_script( if name == "script" { let ns: NewScript = Some(serde_json::from_slice(&data).map_err(to_anyhow)?).unwrap(); let is_tar = ns.codebase.as_ref().is_some_and(|x| x.ends_with(".tar")); - + let use_esm = ns.codebase.as_ref().is_some_and(|x| x.contains(".esm")); let (new_hash, ntx, hdm) = create_script_internal( ns, w_id.clone(), @@ -419,8 +419,14 @@ async fn create_snapshot_script( webhook.clone(), ) .await?; - let nh = new_hash.to_string(); - script_hash = Some(if is_tar { format!("{nh}.tar") } else { nh }); + let mut nh = new_hash.to_string(); + if use_esm { + nh = format!("{nh}.esm"); + } + if is_tar { + nh = format!("{nh}.tar"); + } + script_hash = Some(nh); tx = Some(ntx); handle_deployment_metadata = hdm; } diff --git a/backend/windmill-common/src/cache.rs b/backend/windmill-common/src/cache.rs index df0ef6842a..8fef4b5e3a 100644 --- a/backend/windmill-common/src/cache.rs +++ b/backend/windmill-common/src/cache.rs @@ -631,7 +631,8 @@ pub mod script { envs AS \"envs: Vec\", \ schema AS \"schema: String\", \ schema_validation AS \"schema_validation: bool\", \ - codebase LIKE '%.tar' as use_tar \ + codebase LIKE '%.tar' as use_tar, \ + codebase LIKE '%.esm%' as is_esm \ FROM script WHERE hash = $1 LIMIT 1", hash.0 ) @@ -647,12 +648,14 @@ pub mod script { language: r.language, envs: r.envs, codebase: if let Some(use_tar) = r.use_tar { - let sh = hash.to_string(); - if use_tar { - Some(format!("{sh}.tar")) - } else { - Some(sh) + let mut sh = hash.to_string(); + if r.is_esm.unwrap_or(false) { + sh = format!("{sh}.esm"); } + if use_tar { + sh = format!("{sh}.tar"); + } + Some(sh) } else { None }, diff --git a/cli/src/commands/script/script.ts b/cli/src/commands/script/script.ts index 1c95cb297d..de1ed3289b 100644 --- a/cli/src/commands/script/script.ts +++ b/cli/src/commands/script/script.ts @@ -142,7 +142,7 @@ export async function findResourceFile(path: string) { if (validCandidates.length > 1) { throw new Error( "Found two resource files for the same resource" + - validCandidates.join(", ") + validCandidates.join(", ") ); } if (validCandidates.length < 1) { @@ -181,11 +181,11 @@ export async function handleScriptMetadata( } export interface OutputFile { - path: string - contents: Uint8Array - hash: string + path: string; + contents: Uint8Array; + hash: string; /** "contents" as text (changes automatically with "contents") */ - readonly text: string + readonly text: string; } export async function handleFile( @@ -223,10 +223,9 @@ export async function handleFile( let outputFiles: OutputFile[] = []; if (codebase.customBundler) { log.info(`Using custom bundler ${codebase.customBundler} for ${path}`); - bundleContent = execSync( - codebase.customBundler + " " + path, - { maxBuffer: 1024 * 1024 * 50 } - ).toString(); + bundleContent = execSync(codebase.customBundler + " " + path, { + maxBuffer: 1024 * 1024 * 50, + }).toString(); log.info("Custom bundler executed for " + path); } else { const esbuild = await import("npm:esbuild"); @@ -243,7 +242,7 @@ export async function handleFile( inject: codebase.inject, define: codebase.define, loader: codebase.loader ?? { ".node": "file" }, - outdir: '/', + outdir: "/", platform: "node", packages: "bundle", target: format == "cjs" ? "node20.15.1" : "esnext", @@ -260,17 +259,18 @@ export async function handleFile( if (outputFiles.length > 1) { const archiveNpm = await import("npm:@ayonli/jsext/archive"); log.info( - `Found multiple output files for ${path}, creating a tarball... ${outputFiles.map((file) => file.path).join(", ")}` + `Found multiple output files for ${path}, creating a tarball... ${outputFiles + .map((file) => file.path) + .join(", ")}` ); forceTar = true; const startTime = performance.now(); const tarball = new archiveNpm.Tarball(); const mainPath = path.split(SEP).pop()?.split(".")[0] + ".js"; - const content = outputFiles.find((file) => file.path == "/" + mainPath)?.text ?? ''; + const content = + outputFiles.find((file) => file.path == "/" + mainPath)?.text ?? ""; log.info(`Main content: ${content.length}chars`); - tarball.append( - new File([content], "main.js", { type: "text/plain" }) - ); + tarball.append(new File([content], "main.js", { type: "text/plain" })); for (const file of outputFiles) { if (file.path == "/" + mainPath) { continue; @@ -318,20 +318,20 @@ export async function handleFile( let typed = opts?.skipScriptsMetadata ? undefined : ( - await parseMetadataFile( - remotePath, - opts - ? { - ...opts, - path, - workspaceRemote: workspace, - schemaOnly: codebase ? true : undefined, - globalDeps, - codebases - } - : undefined, - ) - )?.payload; + await parseMetadataFile( + remotePath, + opts + ? { + ...opts, + path, + workspaceRemote: workspace, + schemaOnly: codebase ? true : undefined, + globalDeps, + codebases, + } + : undefined + ) + )?.payload; const workspaceId = workspace.workspaceId; @@ -401,6 +401,7 @@ export async function handleFile( on_behalf_of_email: typed?.on_behalf_of_email, }; + // console.log(requestBodyCommon.codebase); // log.info(JSON.stringify(requestBodyCommon, null, 2)) // log.info(JSON.stringify(opts, null, 2)) if (remote) { @@ -418,19 +419,19 @@ export async function handleFile( deepEqual(typed.schema, remote.schema) && typed.tag == remote.tag && (typed.ws_error_handler_muted ?? false) == - remote.ws_error_handler_muted && + remote.ws_error_handler_muted && typed.dedicated_worker == remote.dedicated_worker && typed.cache_ttl == remote.cache_ttl && typed.concurrency_time_window_s == - remote.concurrency_time_window_s && + remote.concurrency_time_window_s && typed.concurrent_limit == remote.concurrent_limit && Boolean(typed.restart_unless_cancelled) == - Boolean(remote.restart_unless_cancelled) && + Boolean(remote.restart_unless_cancelled) && Boolean(typed.visible_to_runner_only) == - Boolean(remote.visible_to_runner_only) && + Boolean(remote.visible_to_runner_only) && Boolean(typed.no_main_func) == Boolean(remote.no_main_func) && Boolean(typed.has_preprocessor) == - Boolean(remote.has_preprocessor) && + Boolean(remote.has_preprocessor) && typed.priority == Boolean(remote.priority) && typed.timeout == remote.timeout && //@ts-ignore @@ -523,7 +524,8 @@ async function createScript( }); } catch (e: any) { throw Error( - `Script creation for ${body.path} with parent ${body.parent_hash + `Script creation for ${body.path} with parent ${ + body.parent_hash } was not successful: ${e.body ?? e.message} ` ); } @@ -549,7 +551,8 @@ async function createScript( }); if (req.status != 201) { throw Error( - `Script snapshot creation was not successful: ${req.status} - ${req.statusText + `Script snapshot creation was not successful: ${req.status} - ${ + req.statusText } - ${await req.text()} ` ); } @@ -561,8 +564,8 @@ export async function findContentFile(filePath: string) { const candidates = filePath.endsWith("script.json") ? exts.map((x) => filePath.replace(".script.json", x)) : filePath.endsWith("script.lock") - ? exts.map((x) => filePath.replace(".script.lock", x)) - : exts.map((x) => filePath.replace(".script.yaml", x)); + ? exts.map((x) => filePath.replace(".script.lock", x)) + : exts.map((x) => filePath.replace(".script.yaml", x)); const validCandidates = ( await Promise.all( @@ -581,7 +584,7 @@ export async function findContentFile(filePath: string) { if (validCandidates.length > 1) { throw new Error( "No content path given and more than one candidate found: " + - validCandidates.join(", ") + validCandidates.join(", ") ); } if (validCandidates.length < 1) { diff --git a/cli/src/utils/codebase.ts b/cli/src/utils/codebase.ts index 3391f6f795..84424f87c3 100644 --- a/cli/src/utils/codebase.ts +++ b/cli/src/utils/codebase.ts @@ -2,34 +2,46 @@ import { Codebase, SyncOptions } from "../core/conf.ts"; import { log } from "../../deps.ts"; import { digestDir } from "./utils.ts"; -export type SyncCodebase = Codebase & { getDigest: (forceTar?: boolean) => Promise }; -export function listSyncCodebases( - options: SyncOptions -): SyncCodebase[] { +export type SyncCodebase = Codebase & { + getDigest: (forceTar?: boolean) => Promise; +}; +export function listSyncCodebases(options: SyncOptions): SyncCodebase[] { const res: SyncCodebase[] = []; const nb_codebase = options?.codebases?.length ?? 0; if (nb_codebase > 0) { - log.info(`Found ${nb_codebase} codebases: ${options?.codebases?.map((c) => c.relative_path).join(", ")}`); + log.info( + `Found ${nb_codebase} codebases: ${options?.codebases + ?.map((c) => c.relative_path) + .join(", ")}` + ); } for (const codebase of options?.codebases ?? []) { let _digest: string | undefined = undefined; let alreadyPrinted = false; - const getDigest: (forceTar?: boolean) => Promise = async (forceTar?: boolean) => { - if (_digest == undefined || forceTar) { + let hasAssets = false; + const getDigest: (forceTar?: boolean) => Promise = async ( + forceTar?: boolean + ) => { + if (_digest == undefined) { _digest = await digestDir( codebase.relative_path, JSON.stringify(codebase) ); - if (forceTar || (Array.isArray(codebase.assets) && codebase.assets.length > 0)) { - _digest += ".tar"; + if (codebase.format == "esm") { + _digest += ".esm"; } if (!alreadyPrinted) { alreadyPrinted = true; log.info(`Codebase ${codebase.relative_path}, digest: ${_digest}`); } + hasAssets = + Array.isArray(codebase.assets) && codebase.assets.length > 0; + } + if (forceTar || hasAssets) { + return _digest + ".tar"; + } else { return _digest; } - return _digest; }; res.push({ ...codebase, getDigest }); } From 6886ba72d1823acd6a5e012c723f362fc87d38e6 Mon Sep 17 00:00:00 2001 From: Diego Imbert <70353967+diegoimbert@users.noreply.github.com> Date: Thu, 27 Nov 2025 18:42:13 +0100 Subject: [PATCH 02/39] fix InsertModuleButton sometimes disappearing when waiting events (#7246) --- .../graph/renderers/edges/BaseEdge.svelte | 72 +++++++++---------- 1 file changed, 35 insertions(+), 37 deletions(-) diff --git a/frontend/src/lib/components/graph/renderers/edges/BaseEdge.svelte b/frontend/src/lib/components/graph/renderers/edges/BaseEdge.svelte index 3010a94325..0818e3725c 100644 --- a/frontend/src/lib/components/graph/renderers/edges/BaseEdge.svelte +++ b/frontend/src/lib/components/graph/renderers/edges/BaseEdge.svelte @@ -84,7 +84,41 @@ class="base-edge" style="" > - {#if data?.insertable && !$useDataflow && !data?.moving && !waitingForEvents} + {#if waitingForEvents && data.flowJob && data.flowJob.type === 'QueuedJob'} +
+ +
+ . + . + . +
+
+
+ {#if data?.flowJob && data.flowJob.flow_status?.modules?.[data.flowJob.flow_status?.step]?.type === 'WaitingForEvents'} + + {:else if suspendStatus && Object.keys(suspendStatus).length > 0} +
+ {#each Object.values(suspendStatus) as suspendCount (suspendCount.job.id)} + + {/each} +
+ {/if} +
+ {:else if data?.insertable && !$useDataflow && !data?.moving}
{/if} - - {#if waitingForEvents && data.flowJob && data.flowJob.type === 'QueuedJob'} -
- -
- . - . - . -
-
-
- {#if data?.flowJob && data.flowJob.flow_status?.modules?.[data.flowJob.flow_status?.step]?.type === 'WaitingForEvents'} - - {:else if suspendStatus && Object.keys(suspendStatus).length > 0} -
- {#each Object.values(suspendStatus) as suspendCount (suspendCount.job.id)} - - {/each} -
- {/if} -
- {/if} Date: Thu, 27 Nov 2025 18:42:23 +0100 Subject: [PATCH 03/39] feat(app): Add progress bar app component (#7242) * Add progress bar app component - Create AppJobProgressBar component for displaying job progress - Register jobprogressbarcomponent in component system - Add component rendering in ComponentInner - Component accepts jobId configuration parameter - Similar to jobidlogcomponent and jobidflowstatuscomponent Co-authored-by: windmill-internal-app[bot] * feat(app): Add job progress bar to component picker Add jobprogressbarcomponent to the display component set so it appears in the component picker UI alongside other job-related components. Co-authored-by: Ruben Fiszel * Add jobprogressbarcomponent to quickStyleProperties Co-authored-by: Ruben Fiszel --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: windmill-internal-app[bot] Co-authored-by: Ruben Fiszel --- .../display/AppJobProgressBar.svelte | 129 ++++++++++++++++++ .../editor/component/ComponentInner.svelte | 10 ++ .../apps/editor/component/components.ts | 22 +++ .../components/apps/editor/component/sets.ts | 1 + .../componentsPanel/quickStyleProperties.ts | 4 + 5 files changed, 166 insertions(+) create mode 100644 frontend/src/lib/components/apps/components/display/AppJobProgressBar.svelte diff --git a/frontend/src/lib/components/apps/components/display/AppJobProgressBar.svelte b/frontend/src/lib/components/apps/components/display/AppJobProgressBar.svelte new file mode 100644 index 0000000000..bbdae00f13 --- /dev/null +++ b/frontend/src/lib/components/apps/components/display/AppJobProgressBar.svelte @@ -0,0 +1,129 @@ + + +{#each Object.keys(components['jobprogressbarcomponent'].initialData.configuration) as key (key)} + +{/each} + +{#each Object.keys(css ?? {}) as key (key)} + +{/each} + + + + + +{#if render} +
+
+ Progress +
+
+ {#if testJob} + + {:else} + No job + {/if} +
+
+{/if} diff --git a/frontend/src/lib/components/apps/editor/component/ComponentInner.svelte b/frontend/src/lib/components/apps/editor/component/ComponentInner.svelte index 40261e9260..98d9b93187 100644 --- a/frontend/src/lib/components/apps/editor/component/ComponentInner.svelte +++ b/frontend/src/lib/components/apps/editor/component/ComponentInner.svelte @@ -16,6 +16,7 @@ import AppList from '../../components/layout/AppList.svelte' import AppJobIdLogComponent from '../../components/display/AppJobIdLogComponent.svelte' import AppJobIdFlowStatus from '../../components/display/AppJobIdFlowStatus.svelte' + import AppJobProgressBar from '../../components/display/AppJobProgressBar.svelte' import AppCarouselList from '../../components/display/AppCarouselList.svelte' import AppAccordionList from '../../components/display/AppAccordionList.svelte' import AppAggridTableEe from '../../components/display/table/AppAggridTableEe.svelte' @@ -146,6 +147,7 @@ ]) const chunk5Components = new Set([ 'jobidlogcomponent', + 'jobprogressbarcomponent', 'listcomponent', 'logcomponent', 'mapcomponent', @@ -605,6 +607,14 @@ configuration={component.configuration} {render} /> + {:else if component.type === 'jobprogressbarcomponent'} + {:else if component.type === 'listcomponent'} export type JobIdLogComponent = BaseComponent<'jobidlogcomponent'> export type FlowStatusComponent = BaseComponent<'flowstatuscomponent'> export type JobIdFlowStatusComponent = BaseComponent<'jobidflowstatuscomponent'> +export type JobProgressBarComponent = BaseComponent<'jobprogressbarcomponent'> export type ImageComponent = BaseComponent<'imagecomponent'> export type InputComponent = BaseComponent<'inputcomponent'> export type SelectComponent = BaseComponent<'selectcomponent'> & @@ -345,6 +346,7 @@ export type TypedComponent = | JobIdLogComponent | FlowStatusComponent | JobIdFlowStatusComponent + | JobProgressBarComponent | TextInputComponent | QuillComponent | CodeInputComponent @@ -1276,6 +1278,26 @@ export const components = { } } }, + jobprogressbarcomponent: { + name: 'Progress Bar by Job Id', + icon: Clock, + documentationLink: `${documentationBaseUrl}/progress_bar`, + dims: '2:2-6:2' as AppComponentDimensions, + customCss: { + header: { class: '', style: '' }, + container: { class: '', style: '' } + }, + initialData: { + configuration: { + jobId: { + type: 'static', + fieldType: 'text', + value: '', + tooltip: 'Job id to display progress from' + } + } + } + }, containercomponent: { name: 'Container', icon: BoxSelect, diff --git a/frontend/src/lib/components/apps/editor/component/sets.ts b/frontend/src/lib/components/apps/editor/component/sets.ts index 53e551e51a..9b45578ae5 100644 --- a/frontend/src/lib/components/apps/editor/component/sets.ts +++ b/frontend/src/lib/components/apps/editor/component/sets.ts @@ -76,6 +76,7 @@ const display: ComponentSet = { 'chatcomponent', 'displaycomponent', 'jobidlogcomponent', + 'jobprogressbarcomponent', 'jobidflowstatuscomponent', 'jobiddisplaycomponent', 'statcomponent', diff --git a/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts b/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts index ae16941e2c..ccd2457ee5 100644 --- a/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts +++ b/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts @@ -665,6 +665,10 @@ export const quickStyleProperties: Record< header: [...containerDefaultProps, typographyGrouping], container: containerDefaultProps }, + jobprogressbarcomponent: { + header: [...containerDefaultProps, typographyGrouping], + container: containerDefaultProps + }, accordionlistcomponent: { container: containerDefaultProps }, From f88fd0e61ec5b10d3f32a9a47eb7ab991393d352 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 27 Nov 2025 18:47:07 +0100 Subject: [PATCH 04/39] chore(main): release 1.586.0 (#7239) * chore(main): release 1.586.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 16 ++++++ backend/Cargo.lock | 54 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 60 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4f2ce0b34..ef445042c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## [1.586.0](https://github.com/windmill-labs/windmill/compare/v1.585.1...v1.586.0) (2025-11-27) + + +### Features + +* add license key expiration warning on workers page ([#7225](https://github.com/windmill-labs/windmill/issues/7225)) ([d876c2c](https://github.com/windmill-labs/windmill/commit/d876c2c31c1183226e47443c5fb2f5885647d303)) +* **app:** Add progress bar app component ([#7242](https://github.com/windmill-labs/windmill/issues/7242)) ([267171f](https://github.com/windmill-labs/windmill/commit/267171f2c9b1639ade8bf717d7f50d55ec2b9767)) + + +### Bug Fixes + +* **bun:** do not add builtin to lockfiles ([e3b5975](https://github.com/windmill-labs/windmill/commit/e3b59752bd0a3f278465c783a0508c5394b58119)) +* **cli:** support better esm mode for codebases ([e8fd36e](https://github.com/windmill-labs/windmill/commit/e8fd36e2e7578e21aeccb094bc4526c7fa4ff70c)) +* **cli:** update jszip to 3.8.0 ([d22d8b7](https://github.com/windmill-labs/windmill/commit/d22d8b7af020afbf2f448047ceee0e9c7d46b3f0)) +* **frontend:** check resource type name conflict in frontend ([#7237](https://github.com/windmill-labs/windmill/issues/7237)) ([fc1a52c](https://github.com/windmill-labs/windmill/commit/fc1a52c1b3bc4f3077de862162d65f81360484b7)) + ## [1.585.1](https://github.com/windmill-labs/windmill/compare/v1.585.0...v1.585.1) (2025-11-26) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 65647da64c..41334d27f5 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15162,7 +15162,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "aws-sdk-config", @@ -15224,7 +15224,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "argon2", @@ -15345,7 +15345,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.585.1" +version = "1.586.0" dependencies = [ "base64 0.22.1", "chrono", @@ -15360,7 +15360,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.585.1" +version = "1.586.0" dependencies = [ "chrono", "lazy_static", @@ -15374,7 +15374,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "axum", @@ -15393,7 +15393,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "async-recursion", @@ -15482,7 +15482,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.585.1" +version = "1.586.0" dependencies = [ "regex", "serde", @@ -15497,7 +15497,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "bytes", @@ -15521,7 +15521,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.585.1" +version = "1.586.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15533,7 +15533,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.585.1" +version = "1.586.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -15542,7 +15542,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "lazy_static", @@ -15554,7 +15554,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "serde_json", @@ -15566,7 +15566,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "gosyn", @@ -15578,7 +15578,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "lazy_static", @@ -15590,7 +15590,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "serde_json", @@ -15602,7 +15602,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "nu-parser", @@ -15613,7 +15613,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15624,7 +15624,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15636,7 +15636,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "async-recursion", @@ -15659,7 +15659,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "lazy_static", @@ -15673,7 +15673,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15690,7 +15690,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "lazy_static", @@ -15704,7 +15704,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "lazy_static", @@ -15722,7 +15722,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "serde", @@ -15733,7 +15733,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "async-recursion", @@ -15770,7 +15770,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.585.1" +version = "1.586.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15780,7 +15780,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.585.1" +version = "1.586.0" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index dba5305701..38d478fc7a 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.585.1" +version = "1.586.0" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.585.1" +version = "1.586.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index deb0f882d3..7ee99aca7b 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.585.1 + version: 1.586.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index f94f88118b..4ad2b6d1fc 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.585.1"; +export const VERSION = "v1.586.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 9f5d05f959..df61ec8c81 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -68,7 +68,7 @@ export { // } // }); -export const VERSION = "1.585.1"; +export const VERSION = "1.586.0"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ca86dea1d7..d5360c5257 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.585.1", + "version": "1.586.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.585.1", + "version": "1.586.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 7c3340235c..e5107cb02c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.585.1", + "version": "1.586.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index cd1c2f7c31..be6c80604e 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.585.1" -wmill_pg = ">=1.585.1" +wmill = ">=1.586.0" +wmill_pg = ">=1.586.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 9b58309056..152f360497 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.585.1 + version: 1.586.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 44fe48eec6..c732c39dd3 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.585.1' + ModuleVersion = '1.586.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 1bc8342d33..1a902c9adf 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.585.1" +version = "1.586.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 309ed062d0..6d7eab219f 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.585.1" +version = "1.586.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 58579bf00d..240c6617f5 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.585.1", + "version": "1.586.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index b2a49a5f3a..64895e1b3c 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.585.1", + "version": "1.586.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 26ec7f16a6..3a14e5c9f3 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.585.1 +1.586.0 From 697ed6711d2bf844cfae76d3562542614ecba88b Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Thu, 27 Nov 2025 17:48:07 +0000 Subject: [PATCH 05/39] nits progress bar --- .../display/AppJobProgressBar.svelte | 21 ++++++++++--------- .../apps/editor/component/components.ts | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/frontend/src/lib/components/apps/components/display/AppJobProgressBar.svelte b/frontend/src/lib/components/apps/components/display/AppJobProgressBar.svelte index bbdae00f13..ecee9c1199 100644 --- a/frontend/src/lib/components/apps/components/display/AppJobProgressBar.svelte +++ b/frontend/src/lib/components/apps/components/display/AppJobProgressBar.svelte @@ -11,6 +11,7 @@ import ResolveConfig from '../helpers/ResolveConfig.svelte' import ResolveStyle from '../helpers/ResolveStyle.svelte' import InitializeComponent from '../helpers/InitializeComponent.svelte' + import FlowProgressBar from '$lib/components/flows/FlowProgressBar.svelte' interface Props { id: string @@ -100,27 +101,27 @@ bind:this={jobLoader} bind:isLoading={testIsLoading} bind:job={testJob} + bind:scriptProgress /> {#if render}
-
- Progress -
{#if testJob} - + {#if testJob.job_kind == 'flow' || testJob.job_kind == 'flowpreview'} + + {:else} + + {/if} {:else} No job {/if} diff --git a/frontend/src/lib/components/apps/editor/component/components.ts b/frontend/src/lib/components/apps/editor/component/components.ts index b39c2abd92..328743c3c7 100644 --- a/frontend/src/lib/components/apps/editor/component/components.ts +++ b/frontend/src/lib/components/apps/editor/component/components.ts @@ -1280,7 +1280,7 @@ export const components = { }, jobprogressbarcomponent: { name: 'Progress Bar by Job Id', - icon: Clock, + icon: Monitor, documentationLink: `${documentationBaseUrl}/progress_bar`, dims: '2:2-6:2' as AppComponentDimensions, customCss: { From 6a6b9c7cc96572dd20bfee7271f99ead75c5d8d7 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 28 Nov 2025 11:21:17 +0000 Subject: [PATCH 06/39] add type import for esm bundle --- backend/windmill-worker/src/bun_executor.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/windmill-worker/src/bun_executor.rs b/backend/windmill-worker/src/bun_executor.rs index 26c59b77ba..a0aacffb18 100644 --- a/backend/windmill-worker/src/bun_executor.rs +++ b/backend/windmill-worker/src/bun_executor.rs @@ -14,9 +14,9 @@ use windmill_queue::{append_logs, CanceledBy, MiniPulledJob, PrecomputedAgentInf use crate::{ common::{ - create_args_and_out_file, get_reserved_variables, parse_npm_config, read_file, - build_command_with_isolation, read_file_content, read_result, start_child_process, write_file_binary, OccupancyMetrics, - StreamNotifier, + build_command_with_isolation, create_args_and_out_file, get_reserved_variables, + parse_npm_config, read_file, read_file_content, read_result, start_child_process, + write_file_binary, OccupancyMetrics, StreamNotifier, }, handle_child::handle_child, BUNFIG_INSTALL_SCOPES, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, BUN_NO_CACHE, BUN_PATH, @@ -1095,9 +1095,14 @@ pub async fn handle_bun_job( "".to_string() }; + let codebase_import = if format == BundleFormat::Esm { + " with { type: 'js' }" + } else { + "" + }; let wrapper_content = format!( r#" -import * as Main from "{main_import}"; +import * as Main from "{main_import}{codebase_import}"; import * as fs from "fs/promises"; From 80b937249e99447797dbf50e57ffdecdb594987c Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 28 Nov 2025 12:22:19 +0000 Subject: [PATCH 07/39] nit --- backend/windmill-worker/src/bun_executor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/windmill-worker/src/bun_executor.rs b/backend/windmill-worker/src/bun_executor.rs index a0aacffb18..411bb5942a 100644 --- a/backend/windmill-worker/src/bun_executor.rs +++ b/backend/windmill-worker/src/bun_executor.rs @@ -1102,7 +1102,7 @@ pub async fn handle_bun_job( }; let wrapper_content = format!( r#" -import * as Main from "{main_import}{codebase_import}"; +import * as Main from "{main_import}"{codebase_import}; import * as fs from "fs/promises"; From e26b5c94a3b187deeacbfe262ed704a89db01068 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 28 Nov 2025 13:58:36 +0000 Subject: [PATCH 08/39] improve codebase bundle js import --- backend/windmill-worker/src/bun_executor.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/backend/windmill-worker/src/bun_executor.rs b/backend/windmill-worker/src/bun_executor.rs index 411bb5942a..d1c50b9024 100644 --- a/backend/windmill-worker/src/bun_executor.rs +++ b/backend/windmill-worker/src/bun_executor.rs @@ -612,13 +612,15 @@ struct PulledCodebase { } async fn pull_codebase(w_id: &str, id: &str, job_dir: &str) -> Result { let path = windmill_common::s3_helpers::bundle(&w_id, &id); + let CodebaseInfo { is_tar, is_esm } = id_to_codebase_info(id); + let bun_cache_path = format!( - "{}/{}", + "{}/{}.{}", windmill_common::worker::ROOT_CACHE_NOMOUNT_DIR, - path + path, + if is_tar { "tar" } else { "js" } ); - let CodebaseInfo { is_tar, is_esm } = id_to_codebase_info(id); let dst = format!( "{job_dir}/{}", if is_tar { "codebase.tar" } else { "main.js" } @@ -639,9 +641,10 @@ async fn pull_codebase(w_id: &str, id: &str, job_dir: &str) -> Result Date: Fri, 28 Nov 2025 17:00:11 +0100 Subject: [PATCH 09/39] feat: workspace dependencies (#7124) * commit raw requirements Signed-off-by: pyranota * raw requirements Signed-off-by: pyranota * implement `parse_annotation` Signed-off-by: pyranota * more progress on wdeps Signed-off-by: pyranota * more progress Signed-off-by: pyranota * fixes Signed-off-by: pyranota * more progress Signed-off-by: pyranota * fixes Signed-off-by: pyranota * cli improvements + raw deps Signed-off-by: pyranota * cleanup Signed-off-by: pyranota * fix python versions Signed-off-by: pyranota * progress Signed-off-by: pyranota * update :) Signed-off-by: pyranota * add MaybeLock Signed-off-by: pyranota * go WIP Signed-off-by: pyranota * fix python ignoring py version from requirements Signed-off-by: pyranota * optimize php Signed-off-by: pyranota * require admin to alter Signed-off-by: pyranota * fix(cli): flow generateLocks raw deps Signed-off-by: pyranota * progress in checklist Signed-off-by: pyranota * fix agent workers Signed-off-by: pyranota * nits Signed-off-by: pyranota * nits Signed-off-by: pyranota * nit: remove default features Signed-off-by: pyranota * oh-wow Signed-off-by: pyranota * remove dbg! Signed-off-by: pyranota * nits Signed-off-by: pyranota * add indexes Signed-off-by: pyranota * cleanup Signed-off-by: pyranota * nits Signed-off-by: pyranota * remove todos Signed-off-by: pyranota * fix cli Signed-off-by: pyranota * add debug flag Signed-off-by: pyranota * cli: remove noise Signed-off-by: pyranota * fix cli Signed-off-by: pyranota * remove todos Signed-off-by: pyranota * trigger deps correctly Signed-off-by: pyranota * fix frontend Signed-off-by: pyranota * fix frontend again Signed-off-by: pyranota * finally fix frontend Signed-off-by: pyranota * ee repo ref Signed-off-by: pyranota * fix all Signed-off-by: pyranota * more fixes... Signed-off-by: pyranota * remove test Signed-off-by: pyranota * Update backend-test.yml * comment out legacy test Signed-off-by: pyranota * fix ci Signed-off-by: pyranota * fix ci? Signed-off-by: pyranota * comment out thing Signed-off-by: pyranota * ignore test Signed-off-by: pyranota * ci Signed-off-by: pyranota * base fixture Signed-off-by: pyranota * fix regression Signed-off-by: pyranota * fix docs links Signed-off-by: pyranota * update min version Signed-off-by: pyranota * simplify * implement cache for get_latest Signed-off-by: pyranota * move to workspace settings Signed-off-by: pyranota * sqlx + migration Signed-off-by: pyranota * more migrations Signed-off-by: pyranota * use box pin Signed-off-by: pyranota * nit Signed-off-by: pyranota --------- Signed-off-by: pyranota Co-authored-by: Ruben Fiszel --- .github/workflows/backend-test.yml | 1 + Dockerfile | 1 + ...d7d1a2e10342bbbc7f8486df0b73f5657a493.json | 20 + ...09cad66016cc112b58eb943f038308090ec5c.json | 15 + ...3a3d74b8e8141be95cbcd63e227d13091a8dd.json | 28 - ...22be8a17f06391b882337c74c1817c99b533d.json | 24 - ...b921c308b6a047530eccd8966513ffdc722d0.json | 47 + ...1885d682de0d7c6ff3e09a9fddef8bb682708.json | 35 + ...d0751f77f9cb41055481bc06af6827d647041.json | 96 ++ ...db77654c167a254053bfd3682c7d9add30b6b.json | 55 + ...9ea2f0a41771376720fb72c6b3c1fa4972eab.json | 47 + ...153c43903f929ae5d62fbba12610f89c36d55.json | 2 +- ...aeaa8e9a3a2c64e23ab42100d48039ba422b0.json | 57 + ...f6d2c2caa94f39a0e57fe015e19fd1d0ab0ea.json | 65 + ...d5a4ee50dea4b30093df2e0ceb684201ca4b0.json | 66 - ...aeda0704770eb200bae635f1933eece90c9d6.json | 20 + ...69e09f3647a45d176e253e4fc8f7206f6a18d.json | 42 - ...e8fda531e69a880d0ebc44fb5f13ac976b67d.json | 55 + ...1badc6df20086769ffd9f9ee9e6bf1527810c.json | 29 + ...4a7b4604171d12c6a4620514c6d030086936d.json | 95 ++ ...d21896d6cb8256fe05dd5b0fecb53782956ce.json | 22 - ...a0854975ad4c3f6fe24557b87a197485dff39.json | 20 + ...9f61501068f4b027f58279e0c8508839607f4.json | 35 - ...80766003d79884d33f94a79e61f0259807dbc.json | 128 ++ ...114718468fbf75ef13c6087c2f57fbbe0b82f.json | 51 + backend/Cargo.lock | 9 + backend/Cargo.toml | 1 + backend/ee-repo-ref.txt | 2 +- .../20251106152104_raw_requirements.down.sql | 10 + .../20251106152104_raw_requirements.up.sql | 23 + .../windmill-parser-py-imports/Cargo.toml | 1 + .../windmill-parser-py-imports/src/lib.rs | 446 ++++--- .../tests/fixtures/base.sql | 99 ++ .../windmill-parser-py-imports/tests/tests.rs | 36 +- backend/src/main.rs | 6 +- .../{relative_imports.rs => debouncing.rs} | 488 +------ backend/tests/dependency_map.rs | 535 ++++++++ backend/tests/fixtures/dependency_map.sql | 38 +- backend/tests/fixtures/hub_sync_blacklist.sql | 15 + .../tests/fixtures/workspace_dependencies.sql | 33 + .../fixtures/workspace_dependencies_leafs.sql | 54 + backend/tests/python_jobs.rs | 16 +- backend/tests/workspace_dependencies.rs | 194 +++ backend/windmill-api/openapi.yaml | 248 ++++ backend/windmill-api/src/jobs.rs | 95 +- backend/windmill-api/src/lib.rs | 5 + backend/windmill-api/src/scripts.rs | 13 +- .../src/workspace_dependencies.rs | 145 ++ backend/windmill-api/src/workspaces.rs | 103 +- backend/windmill-api/src/workspaces_export.rs | 39 + backend/windmill-common/Cargo.toml | 3 + backend/windmill-common/src/error.rs | 6 + backend/windmill-common/src/flows.rs | 8 +- .../windmill-common/src/global_settings.rs | 15 + backend/windmill-common/src/jobs.rs | 2 +- backend/windmill-common/src/lib.rs | 2 +- backend/windmill-common/src/schema.rs | 10 +- backend/windmill-common/src/scripts.rs | 84 +- backend/windmill-common/src/worker.rs | 122 +- .../src/workspace_dependencies.rs | 1186 +++++++++++++++++ backend/windmill-macros/Cargo.toml | 7 + backend/windmill-macros/tests/annotations.rs | 96 ++ backend/windmill-queue/src/jobs.rs | 11 +- .../windmill-worker/src/ansible_executor.rs | 32 +- backend/windmill-worker/src/bun_executor.rs | 174 +-- backend/windmill-worker/src/common.rs | 38 +- backend/windmill-worker/src/go_executor.rs | 172 ++- backend/windmill-worker/src/lib.rs | 3 +- backend/windmill-worker/src/php_executor.rs | 41 +- .../windmill-worker/src/python_executor.rs | 64 +- .../windmill-worker/src/python_versions.rs | 99 +- .../src/scoped_dependency_map.rs | 155 ++- backend/windmill-worker/src/worker.rs | 83 +- .../windmill-worker/src/worker_lockfiles.rs | 610 ++++----- .../src/workspace_dependencies.rs | 268 ++++ cli/src/commands/dependencies/dependencies.ts | 105 ++ cli/src/commands/flow/flow.ts | 18 +- cli/src/commands/script/script.ts | 57 +- cli/src/commands/sync/pull.ts | 10 +- cli/src/commands/sync/sync.ts | 105 +- cli/src/core/conf.ts | 3 + cli/src/main.ts | 2 + cli/src/types.ts | 15 +- cli/src/utils/metadata.ts | 169 +-- cli/src/utils/script_common.ts | 21 +- cli/src/utils/utils.ts | 6 +- flake.nix | 25 +- .../DependenciesDeploymentWarning.svelte | 295 ++++ .../src/lib/components/HighlightCode.svelte | 8 +- .../src/lib/components/ScriptBuilder.svelte | 5 +- .../WorkspaceDependenciesEditor.svelte | 512 +++++++ .../WorkspaceDependenciesViewer.svelte | 100 ++ .../WorkspaceDependenciesSettings.svelte | 393 ++++++ .../(logged)/workspace_settings/+page.svelte | 13 +- 94 files changed, 6919 insertions(+), 1944 deletions(-) create mode 100644 backend/.sqlx/query-04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493.json create mode 100644 backend/.sqlx/query-05bbdf192c51cd75552674c7db209cad66016cc112b58eb943f038308090ec5c.json delete mode 100644 backend/.sqlx/query-0bcbed8d2a7ad88b809a211a8c13a3d74b8e8141be95cbcd63e227d13091a8dd.json delete mode 100644 backend/.sqlx/query-0bfd22be1d6966c61c9a5fedc2522be8a17f06391b882337c74c1817c99b533d.json create mode 100644 backend/.sqlx/query-0f58d4e7e6f3e962e8a86a2d9feb921c308b6a047530eccd8966513ffdc722d0.json create mode 100644 backend/.sqlx/query-1e285da98ac08999f0ad489f1b81885d682de0d7c6ff3e09a9fddef8bb682708.json create mode 100644 backend/.sqlx/query-1f1e477b27f38f410b7e6f436ced0751f77f9cb41055481bc06af6827d647041.json create mode 100644 backend/.sqlx/query-37409e147cf69c39dad848b117bdb77654c167a254053bfd3682c7d9add30b6b.json create mode 100644 backend/.sqlx/query-5369258383098062e454539be369ea2f0a41771376720fb72c6b3c1fa4972eab.json create mode 100644 backend/.sqlx/query-5bec60e207a5933aa301f87c0afaeaa8e9a3a2c64e23ab42100d48039ba422b0.json create mode 100644 backend/.sqlx/query-5ec262094e8ddf420b9098f2b3ef6d2c2caa94f39a0e57fe015e19fd1d0ab0ea.json delete mode 100644 backend/.sqlx/query-9c6d44ffae63b4050ef3a66cb05d5a4ee50dea4b30093df2e0ceb684201ca4b0.json create mode 100644 backend/.sqlx/query-a264bbd8dbabb03854bd25350a7aeda0704770eb200bae635f1933eece90c9d6.json delete mode 100644 backend/.sqlx/query-abc9f034e62ac224894173356aa69e09f3647a45d176e253e4fc8f7206f6a18d.json create mode 100644 backend/.sqlx/query-bdb53068a223c12c2d317635993e8fda531e69a880d0ebc44fb5f13ac976b67d.json create mode 100644 backend/.sqlx/query-c6637102979d1acaf7fb76ff8e51badc6df20086769ffd9f9ee9e6bf1527810c.json create mode 100644 backend/.sqlx/query-d22f81df67d55b2d8f1d15404194a7b4604171d12c6a4620514c6d030086936d.json delete mode 100644 backend/.sqlx/query-d814833e31b3b3657c57dde1c8cd21896d6cb8256fe05dd5b0fecb53782956ce.json create mode 100644 backend/.sqlx/query-e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39.json delete mode 100644 backend/.sqlx/query-ec3359bbc309c2b893e9f68c09c9f61501068f4b027f58279e0c8508839607f4.json create mode 100644 backend/.sqlx/query-f0e7d28be69c4b922b34b76abb780766003d79884d33f94a79e61f0259807dbc.json create mode 100644 backend/.sqlx/query-f4af0affaed3b1d30f5c6f4ddf4114718468fbf75ef13c6087c2f57fbbe0b82f.json create mode 100644 backend/migrations/20251106152104_raw_requirements.down.sql create mode 100644 backend/migrations/20251106152104_raw_requirements.up.sql rename backend/tests/{relative_imports.rs => debouncing.rs} (86%) create mode 100644 backend/tests/dependency_map.rs create mode 100644 backend/tests/fixtures/hub_sync_blacklist.sql create mode 100644 backend/tests/fixtures/workspace_dependencies.sql create mode 100644 backend/tests/fixtures/workspace_dependencies_leafs.sql create mode 100644 backend/tests/workspace_dependencies.rs create mode 100644 backend/windmill-api/src/workspace_dependencies.rs create mode 100644 backend/windmill-common/src/workspace_dependencies.rs create mode 100644 backend/windmill-worker/src/workspace_dependencies.rs create mode 100644 cli/src/commands/dependencies/dependencies.ts create mode 100644 frontend/src/lib/components/DependenciesDeploymentWarning.svelte create mode 100644 frontend/src/lib/components/WorkspaceDependenciesEditor.svelte create mode 100644 frontend/src/lib/components/WorkspaceDependenciesViewer.svelte create mode 100644 frontend/src/lib/components/workspaceSettings/WorkspaceDependenciesSettings.svelte diff --git a/.github/workflows/backend-test.yml b/.github/workflows/backend-test.yml index 940eb34b1f..b6fd9066cf 100644 --- a/.github/workflows/backend-test.yml +++ b/.github/workflows/backend-test.yml @@ -83,6 +83,7 @@ jobs: RUST_LOG: info RUST_LOG_STYLE: never CARGO_NET_GIT_FETCH_WITH_CLI: true + WMDEBUG_FORCE_V0_WORKSPACE_DEPENDENCIES: 1 run: | deno --version && bun -v && go version && python3 --version cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd .. diff --git a/Dockerfile b/Dockerfile index 7aeb2b218a..fe27c7a022 100644 --- a/Dockerfile +++ b/Dockerfile @@ -100,6 +100,7 @@ ARG POWERSHELL_VERSION=7.5.0 ARG POWERSHELL_DEB_VERSION=7.5.0-1 ARG KUBECTL_VERSION=1.28.7 ARG HELM_VERSION=3.14.3 +# NOTE: If changing, also change go version in workspace dependencies template at WorkspaceDependenciesEditor.svelte ARG GO_VERSION=1.25.0 ARG APP=/usr/src/app ARG WITH_POWERSHELL=true diff --git a/backend/.sqlx/query-04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493.json b/backend/.sqlx/query-04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493.json new file mode 100644 index 0000000000..2878e54920 --- /dev/null +++ b/backend/.sqlx/query-04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM v2_job", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "04ce5c530c80ae6f911dfe0dc9ed7d1a2e10342bbbc7f8486df0b73f5657a493" +} diff --git a/backend/.sqlx/query-05bbdf192c51cd75552674c7db209cad66016cc112b58eb943f038308090ec5c.json b/backend/.sqlx/query-05bbdf192c51cd75552674c7db209cad66016cc112b58eb943f038308090ec5c.json new file mode 100644 index 0000000000..3977f2c356 --- /dev/null +++ b/backend/.sqlx/query-05bbdf192c51cd75552674c7db209cad66016cc112b58eb943f038308090ec5c.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO workspace_dependencies (workspace_id, language, name, description, content, archived, created_at)\n SELECT $1, language, name, description, content, archived, created_at\n FROM workspace_dependencies \n WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "05bbdf192c51cd75552674c7db209cad66016cc112b58eb943f038308090ec5c" +} diff --git a/backend/.sqlx/query-0bcbed8d2a7ad88b809a211a8c13a3d74b8e8141be95cbcd63e227d13091a8dd.json b/backend/.sqlx/query-0bcbed8d2a7ad88b809a211a8c13a3d74b8e8141be95cbcd63e227d13091a8dd.json deleted file mode 100644 index fa2b39ff0b..0000000000 --- a/backend/.sqlx/query-0bcbed8d2a7ad88b809a211a8c13a3d74b8e8141be95cbcd63e227d13091a8dd.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT path, hash FROM script WHERE workspace_id = $1 AND archived = false AND deleted = false", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "path", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "hash", - "type_info": "Int8" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "0bcbed8d2a7ad88b809a211a8c13a3d74b8e8141be95cbcd63e227d13091a8dd" -} diff --git a/backend/.sqlx/query-0bfd22be1d6966c61c9a5fedc2522be8a17f06391b882337c74c1817c99b533d.json b/backend/.sqlx/query-0bfd22be1d6966c61c9a5fedc2522be8a17f06391b882337c74c1817c99b533d.json deleted file mode 100644 index 38bc687e7e..0000000000 --- a/backend/.sqlx/query-0bfd22be1d6966c61c9a5fedc2522be8a17f06391b882337c74c1817c99b533d.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT name FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "name", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text", - "Int8", - "Int8" - ] - }, - "nullable": [ - false - ] - }, - "hash": "0bfd22be1d6966c61c9a5fedc2522be8a17f06391b882337c74c1817c99b533d" -} diff --git a/backend/.sqlx/query-0f58d4e7e6f3e962e8a86a2d9feb921c308b6a047530eccd8966513ffdc722d0.json b/backend/.sqlx/query-0f58d4e7e6f3e962e8a86a2d9feb921c308b6a047530eccd8966513ffdc722d0.json new file mode 100644 index 0000000000..f7bb120f53 --- /dev/null +++ b/backend/.sqlx/query-0f58d4e7e6f3e962e8a86a2d9feb921c308b6a047530eccd8966513ffdc722d0.json @@ -0,0 +1,47 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE workspace_dependencies\n SET archived = true\n WHERE name IS NOT DISTINCT FROM $1 AND workspace_id = $2 AND archived = false AND language = $3\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb", + "ruby" + ] + } + } + } + ] + }, + "nullable": [] + }, + "hash": "0f58d4e7e6f3e962e8a86a2d9feb921c308b6a047530eccd8966513ffdc722d0" +} diff --git a/backend/.sqlx/query-1e285da98ac08999f0ad489f1b81885d682de0d7c6ff3e09a9fddef8bb682708.json b/backend/.sqlx/query-1e285da98ac08999f0ad489f1b81885d682de0d7c6ff3e09a9fddef8bb682708.json new file mode 100644 index 0000000000..ed3665b584 --- /dev/null +++ b/backend/.sqlx/query-1e285da98ac08999f0ad489f1b81885d682de0d7c6ff3e09a9fddef8bb682708.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT \n importer_path,\n importer_kind::text as \"importer_kind!\", -- sqlx thinks this is nullable somehow, so enfore with !\n array_agg(importer_node_id) as importer_node_ids\n FROM dependency_map \n WHERE workspace_id = $1 AND imported_path = $2\n GROUP BY importer_path, importer_kind\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "importer_path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "importer_kind!", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "importer_node_ids", + "type_info": "VarcharArray" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + null, + null + ] + }, + "hash": "1e285da98ac08999f0ad489f1b81885d682de0d7c6ff3e09a9fddef8bb682708" +} diff --git a/backend/.sqlx/query-1f1e477b27f38f410b7e6f436ced0751f77f9cb41055481bc06af6827d647041.json b/backend/.sqlx/query-1f1e477b27f38f410b7e6f436ced0751f77f9cb41055481bc06af6827d647041.json new file mode 100644 index 0000000000..cdcb277b24 --- /dev/null +++ b/backend/.sqlx/query-1f1e477b27f38f410b7e6f436ced0751f77f9cb41055481bc06af6827d647041.json @@ -0,0 +1,96 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT id, content, language AS \"language: ScriptLang\", name, archived, description, workspace_id, created_at\n FROM workspace_dependencies\n WHERE id = $1 AND workspace_id = $2\n LIMIT 1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "content", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "language: ScriptLang", + "type_info": { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb", + "ruby" + ] + } + } + } + }, + { + "ordinal": 3, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "archived", + "type_info": "Bool" + }, + { + "ordinal": 5, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 6, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "created_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Int8", + "Text" + ] + }, + "nullable": [ + false, + false, + false, + true, + false, + false, + false, + false + ] + }, + "hash": "1f1e477b27f38f410b7e6f436ced0751f77f9cb41055481bc06af6827d647041" +} diff --git a/backend/.sqlx/query-37409e147cf69c39dad848b117bdb77654c167a254053bfd3682c7d9add30b6b.json b/backend/.sqlx/query-37409e147cf69c39dad848b117bdb77654c167a254053bfd3682c7d9add30b6b.json new file mode 100644 index 0000000000..43d54b5a1c --- /dev/null +++ b/backend/.sqlx/query-37409e147cf69c39dad848b117bdb77654c167a254053bfd3682c7d9add30b6b.json @@ -0,0 +1,55 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE workspace_dependencies\n SET archived = true \n WHERE archived = false\n AND name IS NOT DISTINCT FROM $1\n AND workspace_id = $2\n AND language = $3\n RETURNING description\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "description", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb", + "ruby" + ] + } + } + } + ] + }, + "nullable": [ + false + ] + }, + "hash": "37409e147cf69c39dad848b117bdb77654c167a254053bfd3682c7d9add30b6b" +} diff --git a/backend/.sqlx/query-5369258383098062e454539be369ea2f0a41771376720fb72c6b3c1fa4972eab.json b/backend/.sqlx/query-5369258383098062e454539be369ea2f0a41771376720fb72c6b3c1fa4972eab.json new file mode 100644 index 0000000000..6bf181d09b --- /dev/null +++ b/backend/.sqlx/query-5369258383098062e454539be369ea2f0a41771376720fb72c6b3c1fa4972eab.json @@ -0,0 +1,47 @@ +{ + "db_name": "PostgreSQL", + "query": "\n DELETE\n FROM workspace_dependencies\n WHERE name IS NOT DISTINCT FROM $1 AND workspace_id = $2 AND language = $3\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb", + "ruby" + ] + } + } + } + ] + }, + "nullable": [] + }, + "hash": "5369258383098062e454539be369ea2f0a41771376720fb72c6b3c1fa4972eab" +} diff --git a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json index 713ccb9dd3..36ddb8ab9f 100644 --- a/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json +++ b/backend/.sqlx/query-5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55.json @@ -15,7 +15,7 @@ ] }, "nullable": [ - null + true ] }, "hash": "5a219a2532517869578c4504ff3153c43903f929ae5d62fbba12610f89c36d55" diff --git a/backend/.sqlx/query-5bec60e207a5933aa301f87c0afaeaa8e9a3a2c64e23ab42100d48039ba422b0.json b/backend/.sqlx/query-5bec60e207a5933aa301f87c0afaeaa8e9a3a2c64e23ab42100d48039ba422b0.json new file mode 100644 index 0000000000..2ce0b8015a --- /dev/null +++ b/backend/.sqlx/query-5bec60e207a5933aa301f87c0afaeaa8e9a3a2c64e23ab42100d48039ba422b0.json @@ -0,0 +1,57 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO workspace_dependencies(name, workspace_id, content, language, description)\n VALUES ($1, $2, $3, $4, $5) \n RETURNING id\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Text", + { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb", + "ruby" + ] + } + } + }, + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "5bec60e207a5933aa301f87c0afaeaa8e9a3a2c64e23ab42100d48039ba422b0" +} diff --git a/backend/.sqlx/query-5ec262094e8ddf420b9098f2b3ef6d2c2caa94f39a0e57fe015e19fd1d0ab0ea.json b/backend/.sqlx/query-5ec262094e8ddf420b9098f2b3ef6d2c2caa94f39a0e57fe015e19fd1d0ab0ea.json new file mode 100644 index 0000000000..e48f00924b --- /dev/null +++ b/backend/.sqlx/query-5ec262094e8ddf420b9098f2b3ef6d2c2caa94f39a0e57fe015e19fd1d0ab0ea.json @@ -0,0 +1,65 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT path, hash, language AS \"language: ScriptLang\" FROM script WHERE workspace_id = $1 AND archived = false AND deleted = false", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "hash", + "type_info": "Int8" + }, + { + "ordinal": 2, + "name": "language: ScriptLang", + "type_info": { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb", + "ruby" + ] + } + } + } + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "5ec262094e8ddf420b9098f2b3ef6d2c2caa94f39a0e57fe015e19fd1d0ab0ea" +} diff --git a/backend/.sqlx/query-9c6d44ffae63b4050ef3a66cb05d5a4ee50dea4b30093df2e0ceb684201ca4b0.json b/backend/.sqlx/query-9c6d44ffae63b4050ef3a66cb05d5a4ee50dea4b30093df2e0ceb684201ca4b0.json deleted file mode 100644 index 8c2c014662..0000000000 --- a/backend/.sqlx/query-9c6d44ffae63b4050ef3a66cb05d5a4ee50dea4b30093df2e0ceb684201ca4b0.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT workspace_id, name, display_name, owners, extra_perms, summary, created_by, edited_at FROM folder WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "workspace_id", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "name", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "display_name", - "type_info": "Varchar" - }, - { - "ordinal": 3, - "name": "owners", - "type_info": "VarcharArray" - }, - { - "ordinal": 4, - "name": "extra_perms", - "type_info": "Jsonb" - }, - { - "ordinal": 5, - "name": "summary", - "type_info": "Text" - }, - { - "ordinal": 6, - "name": "created_by", - "type_info": "Varchar" - }, - { - "ordinal": 7, - "name": "edited_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text", - "Int8", - "Int8" - ] - }, - "nullable": [ - false, - false, - false, - false, - false, - true, - true, - true - ] - }, - "hash": "9c6d44ffae63b4050ef3a66cb05d5a4ee50dea4b30093df2e0ceb684201ca4b0" -} diff --git a/backend/.sqlx/query-a264bbd8dbabb03854bd25350a7aeda0704770eb200bae635f1933eece90c9d6.json b/backend/.sqlx/query-a264bbd8dbabb03854bd25350a7aeda0704770eb200bae635f1933eece90c9d6.json new file mode 100644 index 0000000000..1030965135 --- /dev/null +++ b/backend/.sqlx/query-a264bbd8dbabb03854bd25350a7aeda0704770eb200bae635f1933eece90c9d6.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT EXISTS(SELECT 1 FROM app WHERE path = 'g/all/setup_app')", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "a264bbd8dbabb03854bd25350a7aeda0704770eb200bae635f1933eece90c9d6" +} diff --git a/backend/.sqlx/query-abc9f034e62ac224894173356aa69e09f3647a45d176e253e4fc8f7206f6a18d.json b/backend/.sqlx/query-abc9f034e62ac224894173356aa69e09f3647a45d176e253e4fc8f7206f6a18d.json deleted file mode 100644 index 395816d916..0000000000 --- a/backend/.sqlx/query-abc9f034e62ac224894173356aa69e09f3647a45d176e253e4fc8f7206f6a18d.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT * FROM group_ WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "workspace_id", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "name", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "summary", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "extra_perms", - "type_info": "Jsonb" - } - ], - "parameters": { - "Left": [ - "Text", - "Int8", - "Int8" - ] - }, - "nullable": [ - false, - false, - true, - false - ] - }, - "hash": "abc9f034e62ac224894173356aa69e09f3647a45d176e253e4fc8f7206f6a18d" -} diff --git a/backend/.sqlx/query-bdb53068a223c12c2d317635993e8fda531e69a880d0ebc44fb5f13ac976b67d.json b/backend/.sqlx/query-bdb53068a223c12c2d317635993e8fda531e69a880d0ebc44fb5f13ac976b67d.json new file mode 100644 index 0000000000..c835fd2b7b --- /dev/null +++ b/backend/.sqlx/query-bdb53068a223c12c2d317635993e8fda531e69a880d0ebc44fb5f13ac976b67d.json @@ -0,0 +1,55 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT id FROM workspace_dependencies\n WHERE name IS NOT DISTINCT FROM $1 AND workspace_id = $2 AND language = $3\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb", + "ruby" + ] + } + } + } + ] + }, + "nullable": [ + false + ] + }, + "hash": "bdb53068a223c12c2d317635993e8fda531e69a880d0ebc44fb5f13ac976b67d" +} diff --git a/backend/.sqlx/query-c6637102979d1acaf7fb76ff8e51badc6df20086769ffd9f9ee9e6bf1527810c.json b/backend/.sqlx/query-c6637102979d1acaf7fb76ff8e51badc6df20086769ffd9f9ee9e6bf1527810c.json new file mode 100644 index 0000000000..b24e503df9 --- /dev/null +++ b/backend/.sqlx/query-c6637102979d1acaf7fb76ff8e51badc6df20086769ffd9f9ee9e6bf1527810c.json @@ -0,0 +1,29 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT \n imported_path,\n COUNT(DISTINCT importer_path) as \"count!\"\n FROM dependency_map \n WHERE workspace_id = $1 AND imported_path = ANY($2)\n GROUP BY imported_path\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "imported_path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "count!", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text", + "TextArray" + ] + }, + "nullable": [ + false, + null + ] + }, + "hash": "c6637102979d1acaf7fb76ff8e51badc6df20086769ffd9f9ee9e6bf1527810c" +} diff --git a/backend/.sqlx/query-d22f81df67d55b2d8f1d15404194a7b4604171d12c6a4620514c6d030086936d.json b/backend/.sqlx/query-d22f81df67d55b2d8f1d15404194a7b4604171d12c6a4620514c6d030086936d.json new file mode 100644 index 0000000000..4ff6142871 --- /dev/null +++ b/backend/.sqlx/query-d22f81df67d55b2d8f1d15404194a7b4604171d12c6a4620514c6d030086936d.json @@ -0,0 +1,95 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT id, created_at, archived, name, description, workspace_id, content, language AS \"language: ScriptLang\"\n FROM workspace_dependencies\n WHERE archived = false AND workspace_id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 2, + "name": "archived", + "type_info": "Bool" + }, + { + "ordinal": 3, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 6, + "name": "content", + "type_info": "Text" + }, + { + "ordinal": 7, + "name": "language: ScriptLang", + "type_info": { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb", + "ruby" + ] + } + } + } + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + true, + false, + false, + false, + false + ] + }, + "hash": "d22f81df67d55b2d8f1d15404194a7b4604171d12c6a4620514c6d030086936d" +} diff --git a/backend/.sqlx/query-d814833e31b3b3657c57dde1c8cd21896d6cb8256fe05dd5b0fecb53782956ce.json b/backend/.sqlx/query-d814833e31b3b3657c57dde1c8cd21896d6cb8256fe05dd5b0fecb53782956ce.json deleted file mode 100644 index 50d30ab09a..0000000000 --- a/backend/.sqlx/query-d814833e31b3b3657c57dde1c8cd21896d6cb8256fe05dd5b0fecb53782956ce.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT name FROM group_ WHERE workspace_id = $1 UNION SELECT name FROM instance_group ORDER BY name desc", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "name", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - null - ] - }, - "hash": "d814833e31b3b3657c57dde1c8cd21896d6cb8256fe05dd5b0fecb53782956ce" -} diff --git a/backend/.sqlx/query-e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39.json b/backend/.sqlx/query-e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39.json new file mode 100644 index 0000000000..8e8a5c98e9 --- /dev/null +++ b/backend/.sqlx/query-e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM v2_job_queue", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "e7bc612ccdbb2532a321ed83e26a0854975ad4c3f6fe24557b87a197485dff39" +} diff --git a/backend/.sqlx/query-ec3359bbc309c2b893e9f68c09c9f61501068f4b027f58279e0c8508839607f4.json b/backend/.sqlx/query-ec3359bbc309c2b893e9f68c09c9f61501068f4b027f58279e0c8508839607f4.json deleted file mode 100644 index b2fa44659b..0000000000 --- a/backend/.sqlx/query-ec3359bbc309c2b893e9f68c09c9f61501068f4b027f58279e0c8508839607f4.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT importer_path, importer_kind::text, array_agg(importer_node_id) as importer_node_ids FROM dependency_map\n WHERE imported_path = $1\n AND workspace_id = $2\n GROUP BY importer_path, importer_kind", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "importer_path", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "importer_kind", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "importer_node_ids", - "type_info": "VarcharArray" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - false, - null, - null - ] - }, - "hash": "ec3359bbc309c2b893e9f68c09c9f61501068f4b027f58279e0c8508839607f4" -} diff --git a/backend/.sqlx/query-f0e7d28be69c4b922b34b76abb780766003d79884d33f94a79e61f0259807dbc.json b/backend/.sqlx/query-f0e7d28be69c4b922b34b76abb780766003d79884d33f94a79e61f0259807dbc.json new file mode 100644 index 0000000000..ab4421f9b4 --- /dev/null +++ b/backend/.sqlx/query-f0e7d28be69c4b922b34b76abb780766003d79884d33f94a79e61f0259807dbc.json @@ -0,0 +1,128 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT id, content, language AS \"language: ScriptLang\", name, description, archived, workspace_id, created_at\n FROM workspace_dependencies\n WHERE name IS NOT DISTINCT FROM $1 AND workspace_id = $2 AND archived = false AND language = $3\n LIMIT 1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "content", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "language: ScriptLang", + "type_info": { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb", + "ruby" + ] + } + } + } + }, + { + "ordinal": 3, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "description", + "type_info": "Text" + }, + { + "ordinal": 5, + "name": "archived", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 7, + "name": "created_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text", + "Text", + { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb", + "ruby" + ] + } + } + } + ] + }, + "nullable": [ + false, + false, + false, + true, + false, + false, + false, + false + ] + }, + "hash": "f0e7d28be69c4b922b34b76abb780766003d79884d33f94a79e61f0259807dbc" +} diff --git a/backend/.sqlx/query-f4af0affaed3b1d30f5c6f4ddf4114718468fbf75ef13c6087c2f57fbbe0b82f.json b/backend/.sqlx/query-f4af0affaed3b1d30f5c6f4ddf4114718468fbf75ef13c6087c2f57fbbe0b82f.json new file mode 100644 index 0000000000..45fea6bdda --- /dev/null +++ b/backend/.sqlx/query-f4af0affaed3b1d30f5c6f4ddf4114718468fbf75ef13c6087c2f57fbbe0b82f.json @@ -0,0 +1,51 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT language AS \"language: ScriptLang\" FROM script WHERE path = 'u/admin/hub_sync'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "language: ScriptLang", + "type_info": { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb", + "ruby" + ] + } + } + } + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "f4af0affaed3b1d30f5c6f4ddf4114718468fbf75ef13c6087c2f57fbbe0b82f" +} diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 41334d27f5..06f4a1023e 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15193,6 +15193,7 @@ dependencies = [ "reqwest 0.12.24", "rustls 0.23.35", "serde", + "serde_derive", "serde_json", "serde_yml", "sha1", @@ -15438,6 +15439,8 @@ dependencies = [ "opentelemetry-otlp", "opentelemetry-semantic-conventions", "opentelemetry_sdk", + "pep440_rs", + "phf 0.11.3", "pin-project-lite", "prometheus", "quick_cache", @@ -15450,6 +15453,7 @@ dependencies = [ "semver 1.0.27", "serde", "serde_json", + "serde_yml", "sha2 0.10.9", "size", "sqlx", @@ -15525,9 +15529,13 @@ version = "1.586.0" dependencies = [ "itertools 0.14.0", "lazy_static", + "pep440_rs", "proc-macro2", "quote", "regex", + "serde", + "serde_derive", + "serde_yml", "syn 2.0.111", ] @@ -15653,6 +15661,7 @@ dependencies = [ "serde_json", "sqlx", "toml", + "tracing", "windmill-common", "windmill-parser", ] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 38d478fc7a..fddebdfaad 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -137,6 +137,7 @@ prometheus = { workspace = true, optional = true } uuid.workspace = true gethostname.workspace = true serde_json.workspace = true +serde_derive.workspace = true serde_yml.workspace = true serde.workspace = true deno_core = { workspace = true, optional = true } diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 2a3de2d939..8dce612c3c 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -99828e4a3a27af428a45d1e6e65092ca15082b1e +88d5023df7f41bb86e948b35889f962b741c860e diff --git a/backend/migrations/20251106152104_raw_requirements.down.sql b/backend/migrations/20251106152104_raw_requirements.down.sql new file mode 100644 index 0000000000..0b900d1937 --- /dev/null +++ b/backend/migrations/20251106152104_raw_requirements.down.sql @@ -0,0 +1,10 @@ +-- Drop indexes first (though dropping table will cascade) +DROP INDEX IF EXISTS workspace_dependencies_id_workspace ; +DROP INDEX IF EXISTS workspace_dependencies_workspace_lang_name_archived_idx ; +DROP INDEX IF EXISTS workspace_dependencies_workspace_archived_idx; +DROP INDEX IF EXISTS one_non_archived_per_null_name_language_constraint; +DROP INDEX IF EXISTS one_non_archived_per_name_language_constraint; + +-- Drop table and sequence +DROP TABLE IF EXISTS workspace_dependencies; +DROP SEQUENCE IF EXISTS workspace_dependencies_id_seq CASCADE; diff --git a/backend/migrations/20251106152104_raw_requirements.up.sql b/backend/migrations/20251106152104_raw_requirements.up.sql new file mode 100644 index 0000000000..b390cce8e2 --- /dev/null +++ b/backend/migrations/20251106152104_raw_requirements.up.sql @@ -0,0 +1,23 @@ +CREATE SEQUENCE IF NOT EXISTS workspace_dependencies_id_seq; + +CREATE TABLE IF NOT EXISTS workspace_dependencies( + id BIGINT DEFAULT nextval('workspace_dependencies_id_seq') PRIMARY KEY, + name VARCHAR(255), -- If NULL - it's global + content TEXT NOT NULL, + language SCRIPT_LANG NOT NULL, + description text NOT NULL DEFAULT '', + archived BOOLEAN NOT NULL DEFAULT false, + workspace_id character varying(50) NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now() +); + +-- Make any query that tries to create non-linear history fail +CREATE UNIQUE INDEX IF NOT EXISTS one_non_archived_per_name_language_constraint ON workspace_dependencies(name, language, workspace_id) WHERE archived = false AND name IS NOT NULL; +CREATE UNIQUE INDEX IF NOT EXISTS one_non_archived_per_null_name_language_constraint ON workspace_dependencies(language, workspace_id) WHERE archived = false AND name IS NULL; + +-- Performance indexes for common query patterns +-- For the list query (filtering by workspace_id and archived) +CREATE INDEX IF NOT EXISTS workspace_dependencies_workspace_archived_idx ON workspace_dependencies(workspace_id, archived) WHERE archived = false; + +CREATE INDEX IF NOT EXISTS workspace_dependencies_workspace_lang_name_archived_idx ON workspace_dependencies(workspace_id, language, name, archived); +CREATE INDEX IF NOT EXISTS workspace_dependencies_id_workspace ON workspace_dependencies(id, workspace_id); diff --git a/backend/parsers/windmill-parser-py-imports/Cargo.toml b/backend/parsers/windmill-parser-py-imports/Cargo.toml index abd363b42b..deea68c2ef 100644 --- a/backend/parsers/windmill-parser-py-imports/Cargo.toml +++ b/backend/parsers/windmill-parser-py-imports/Cargo.toml @@ -30,3 +30,4 @@ async-recursion.workspace = true toml.workspace = true serde.workspace = true pep440_rs.workspace = true +tracing.workspace = true diff --git a/backend/parsers/windmill-parser-py-imports/src/lib.rs b/backend/parsers/windmill-parser-py-imports/src/lib.rs index b024ba6244..06762dbc9e 100644 --- a/backend/parsers/windmill-parser-py-imports/src/lib.rs +++ b/backend/parsers/windmill-parser-py-imports/src/lib.rs @@ -27,7 +27,11 @@ use rustpython_parser::{ use sqlx::{Pool, Postgres}; use windmill_common::{ error::{self, to_anyhow}, - worker::PythonAnnotations, + worker::{ + split_python_requirements, try_parse_locked_python_version_from_requirements, + PythonAnnotations, + }, + workspace_dependencies::{RawWorkspaceDependencies, WorkspaceDependenciesPrefetched}, }; fn replace_import(x: String) -> String { @@ -170,7 +174,6 @@ fn parse_code_for_imports(code: &str, path: &str) -> error::Result> // This is needed because we've split off the real main function above let code_with_fake_main = format!("{}\n\ndef main(): pass", code); - let ast = Suite::parse(&code_with_fake_main, "main.py").map_err(|e| { error::Error::ExecutionErr(format!("Error parsing code for imports: {}", e.to_string())) })?; @@ -259,6 +262,8 @@ pub async fn parse_python_imports( path: &str, db: &Pool, version_specifiers: &mut Vec, + locked_v: &mut Option, + raw_workspace_dependencies_o: &Option, ) -> error::Result<(Vec, Option)> { let mut compile_error_hint: Option = None; let mut imports = parse_python_imports_inner( @@ -268,8 +273,9 @@ pub async fn parse_python_imports( db, &mut vec![], version_specifiers, - // &mut version_specifier.and_then(|_| Some(path.to_owned())), - &mut None + &mut None, + locked_v, + raw_workspace_dependencies_o, ) .await? .into_values() @@ -323,11 +329,22 @@ async fn parse_python_imports_inner( already_visited: &mut Vec, version_specifiers: &mut Vec, path_where_annotated_pyv: &mut Option, + locked_v: &mut Option, + raw_workspace_dependencies_o: &Option, ) -> error::Result> { + tracing::debug!("Parsing python imports for path: {}", path); let PythonAnnotations { py310, py311, py312, py313, .. } = PythonAnnotations::parse(&code); + tracing::debug!( + "Found python annotations - py310: {}, py311: {}, py312: {}, py313: {}", + py310, + py311, + py312, + py313 + ); let mut push_version_specifiers = |perform, unparsed: String| -> error::Result<()> { if perform { + tracing::debug!("Adding version specifier: {}", unparsed); pep440_rs::VersionSpecifiers::from_str(unparsed.as_str()) .ok() .map(|vs| version_specifiers.extend(vs.to_vec())); @@ -341,10 +358,9 @@ async fn parse_python_imports_inner( for x in code.lines() { if x.starts_with("# py:") || x.starts_with("#py:") { - push_version_specifiers( - true, - x.replace('#', "").replace("py:", "").trim().to_owned(), - )?; + let version_spec = x.replace('#', "").replace("py:", "").trim().to_owned(); + tracing::debug!("Found inline python version specifier: {}", version_spec); + push_version_specifiers(true, version_spec)?; } else if !x.starts_with('#') { break; } @@ -369,12 +385,40 @@ async fn parse_python_imports_inner( // dependencies: Vec, // } - let find_requirements = code.lines().find_position(|x| { - x.starts_with("#requirements:") - || x.starts_with("# requirements:") - || x.starts_with("# /// script") - }); + let mut final_imports = HashMap::new(); + tracing::debug!("Extracting workspace dependencies for workspace: {}", w_id); + let wdp = WorkspaceDependenciesPrefetched::extract( + code, + windmill_common::scripts::ScriptLang::Python3, + w_id, + raw_workspace_dependencies_o, + path, + db.into(), + ) + .await?; + + if let Some(c) = wdp.get_python()? { + *locked_v = extract_nimports_from_content(&c, &mut final_imports); + } + + if wdp.is_manual() { + tracing::debug!( + "Workspace dependencies mode is Manual, returning {} imports", + final_imports.len() + ); + return Ok(final_imports); + } + + let find_requirements = code + .lines() + .find_position(|x| x.starts_with("# /// script")); + tracing::debug!( + "Looking for script metadata block, found: {}", + find_requirements.is_some() + ); + if let Some((pos, item)) = find_requirements { + tracing::debug!("Found script metadata block at position {}: {}", pos, item); let mut requirements = HashMap::new(); if item.starts_with("# /// script") { let mut incorrect = false; @@ -384,7 +428,7 @@ async fn parse_python_imports_inner( .map_while(|x| { incorrect = !x.starts_with('#'); if incorrect || x.starts_with("# ///") { - None + Option::None } else { x.get(1..) } @@ -392,9 +436,11 @@ async fn parse_python_imports_inner( .join("\n") .parse::() .map_err(to_anyhow)?; + tracing::debug!("Parsed script metadata: {:?}", metadata); { if let Some(v) = metadata.get("requires-python").and_then(|v| v.as_str()) { + tracing::debug!("Found requires-python in metadata: {}", v); push_version_specifiers(true, v.to_owned())?; } }; @@ -403,9 +449,15 @@ async fn parse_python_imports_inner( .get("dependencies") .and_then(|dependencies| dependencies.as_array()) .inspect(|list| { + tracing::debug!("Found {} dependencies in script metadata", list.len()); for dependency_v in list.into_iter() { let requirement = dependency_v.as_str().unwrap_or("ERROR").to_owned(); let key = extract_pkg_name(&requirement); + tracing::debug!( + "Adding dependency from metadata: {} (key: {})", + requirement, + key + ); requirements.insert( key.clone(), NImportResolved::Pin { @@ -418,217 +470,199 @@ async fn parse_python_imports_inner( ); } }); - } else { - code.lines() - .skip(pos + 1) - .map_while(|x| { - RE.captures(x).and_then(|x| { - x.get(1).map(|m| { - let requirement = m.as_str().to_string(); - let key = extract_pkg_name(&requirement); - requirements.insert( - key.clone(), - NImportResolved::Pin { - pins: vec![ImportPin { - pkg: requirement.clone(), - path: Default::default(), - }], - key, - }, - ); - }) - }) - }) - .collect_vec(); - } - Ok(requirements) - } else { - let find_extra_requirements = code.lines().find_position(|x| { - x.starts_with("#extra_requirements:") || x.starts_with("# extra_requirements:") - }); - let mut imports: HashMap = HashMap::new(); - if let Some((pos, _)) = find_extra_requirements { - code.lines() - .skip(pos + 1) - .map_while(|x| { - RE.captures(x).and_then(|x| { - x.get(1).map(|m| { - let requirement = m.as_str().to_string(); - let key = extract_pkg_name(&requirement); - imports.insert( - key.clone(), - NImportResolved::Pin { - pins: vec![ImportPin { - pkg: requirement, - path: Default::default(), - }], - key, - }, - ); - }) - }) - }) - .collect_vec(); } + tracing::debug!( + "Returning {} requirements from script metadata", + requirements.len() + ); + return Ok(requirements); + } + // Will get unsorted vector of imports found in current script + let mut nimports = parse_code_for_imports(code, path)?; + tracing::debug!("Found {} imports in code", nimports.len()); - // Will get unsorted vector of imports found in current script - let mut nimports = parse_code_for_imports(code, path)?; + // It is important to note, that sorting is important and will always result in this pattern: + // 1. All Repins go first + // 2. All Pins go second + // 3. All Auto go third + // 4. All relative imports go the last + // + // This way we make sure all repins are resolved before (re)pins inside imported relative scripts. + nimports.sort(); + tracing::debug!("Processing imports in sorted order"); - // It is important to note, that sorting is important and will always result in this pattern: - // 1. All Repins go first - // 2. All Pins go second - // 3. All Auto go third - // 4. All relative imports go the last - // - // This way we make sure all repins are resolved before (re)pins inside imported relative scripts. - nimports.sort(); - - for n in nimports.into_iter() { - let mut nested = match n { - NImport::Relative(rpath) => { - let code = sqlx::query_scalar!( - r#" + for n in nimports.into_iter() { + let mut nested = match n { + NImport::Relative(rpath) => { + let code = sqlx::query_scalar!( + r#" SELECT content FROM script WHERE path = $1 AND workspace_id = $2 AND archived = false ORDER BY created_at DESC LIMIT 1 "#, - &rpath, - w_id - ) - .fetch_optional(db) - .await? - .unwrap_or_else(|| "".to_string()); + &rpath, + w_id + ) + .fetch_optional(db) + .await? + .unwrap_or_else(|| "".to_string()); - if already_visited.contains(&rpath) { - vec![] + if already_visited.contains(&rpath) { + vec![] + } else { + already_visited.push(rpath.clone()); + // Because the algo goes depth first, this function will never return relative import + // This why we can safely assume later, that there is no relative imports + parse_python_imports_inner( + &code, + w_id, + &rpath, + db, + already_visited, + version_specifiers, + path_where_annotated_pyv, + locked_v, + raw_workspace_dependencies_o, + ) + .await? + .into_values() + .collect_vec() + } + } + NImport::Repin { pin, key } => vec![NImportResolved::Repin { pin, key }], + NImport::Pin { pins, key } => vec![NImportResolved::Pin { pins, key }], + NImport::Auto { pkg, key } => vec![NImportResolved::Auto { pkg, key }], + }; + + // Nested should also be sorted for the same reason + nested.sort(); + tracing::debug!("Processing {} nested imports", nested.len()); + + // At this point there should be no NImport::Relative in `nested` + for imp in nested { + let key = match imp.clone() { + NImportResolved::Pin { key, .. } => key, + NImportResolved::Repin { key, .. } => key, + NImportResolved::Auto { key, pkg } => key.unwrap_or(pkg), + }; + tracing::debug!("Resolving import with key: {}", key); + // Handled cases: + // + // 1. + // Error: Imported windmill scripts have different pins + // + // auto + // ├── pin:2 + // └── pin:1 + // + // Fix 1: + // + // auto + // ├── pin:1 + // └── pin:1 + // + // Fix 2: + // + // repin:1 + // ├── pin:2 + // └── pin:1 + // + // 2. + // Error: Imported windmill scripts have different pins + // + // pin:2 + // └── pin:1 + // + // Fix 1: + // + // auto + // └── pin:1 + // + // Fix 2: + // + // repin:2 + // └── pin:1 + // + // 3. repins allowed to be repinned again + // + // repin:2 + // └── repin:1 + // + match imp.clone() { + NImportResolved::Repin { .. } => { + if let Some(existing_import) = final_imports.get(&key) { + match existing_import { + // replace + p if matches!( + p, + NImportResolved::Pin { .. } | NImportResolved::Auto { .. } + ) => + { + final_imports.insert(key, imp); + } + // do nothing (older repins have greater precedence) + NImportResolved::Repin { .. } => {} + // Should not be possible + _ => { + return Err(anyhow::anyhow!( + "Internal error: cannot resolve requirement pins", + ) + .into()); + } + } } else { - already_visited.push(rpath.clone()); - // Because the algo goes depth first, this function will never return relative import - // This why we can safely assume later, that there is no relative imports - parse_python_imports_inner( - &code, - w_id, - &rpath, - db, - already_visited, - version_specifiers, - path_where_annotated_pyv, - ) - .await? - .into_values() - .collect_vec() + final_imports.insert(key, imp.clone()); } } - NImport::Repin { pin, key } => vec![NImportResolved::Repin { pin, key }], - NImport::Pin { pins, key } => vec![NImportResolved::Pin { pins, key }], - NImport::Auto { pkg, key } => vec![NImportResolved::Auto { pkg, key }], - }; - - // Nested should also be sorted for the same reason - nested.sort(); - - // At this point there should be no NImport::Relative in `nested` - for imp in nested { - let key = match imp.clone() { - NImportResolved::Pin { key, .. } => key, - NImportResolved::Repin { key, .. } => key, - NImportResolved::Auto { key, pkg } => key.unwrap_or(pkg), - }; - // Handled cases: - // - // 1. - // Error: Imported windmill scripts have different pins - // - // auto - // ├── pin:2 - // └── pin:1 - // - // Fix 1: - // - // auto - // ├── pin:1 - // └── pin:1 - // - // Fix 2: - // - // repin:1 - // ├── pin:2 - // └── pin:1 - // - // 2. - // Error: Imported windmill scripts have different pins - // - // pin:2 - // └── pin:1 - // - // Fix 1: - // - // auto - // └── pin:1 - // - // Fix 2: - // - // repin:2 - // └── pin:1 - // - // 3. repins allowed to be repinned again - // - // repin:2 - // └── repin:1 - // - match imp.clone() { - NImportResolved::Repin { .. } => { - if let Some(existing_import) = imports.get(&key) { - match existing_import { - // replace - p if matches!( - p, - NImportResolved::Pin { .. } | NImportResolved::Auto { .. } - ) => - { - imports.insert(key, imp); - } - // do nothing (older repins have greater precedence) - NImportResolved::Repin { .. } => {} - // Should not be possible - _ => { - return Err(anyhow::anyhow!( - "Internal error: cannot resolve requirement pins", - ) - .into()); - } + NImportResolved::Pin { pins: new_pins, .. } => { + if let Some(existing_import) = final_imports.get_mut(&key) { + match existing_import { + // Check if pin is the same version, if same, do nothing, if not error + NImportResolved::Pin { pins: existing_pins, .. } => { + existing_pins.extend(new_pins) } - } else { - imports.insert(key, imp.clone()); - } - } - NImportResolved::Pin { pins: new_pins, .. } => { - if let Some(existing_import) = imports.get_mut(&key) { - match existing_import { - // Check if pin is the same version, if same, do nothing, if not error - NImportResolved::Pin { pins: existing_pins, .. } => { - existing_pins.extend(new_pins) - } - // do nothing - NImportResolved::Repin { .. } => {} - // Replace with new pin - NImportResolved::Auto { .. } => { - imports.insert(key, imp); - } + // do nothing + NImportResolved::Repin { .. } => {} + // Replace with new pin + NImportResolved::Auto { .. } => { + final_imports.insert(key, imp); } - } else { - imports.insert(key, imp.clone()); } + } else { + final_imports.insert(key, imp.clone()); } - NImportResolved::Auto { .. } => { - if !imports.contains_key(&key) { - imports.insert(key, imp); - } + } + NImportResolved::Auto { .. } => { + if !final_imports.contains_key(&key) { + final_imports.insert(key, imp); } } } } - Ok(imports) } + tracing::debug!( + "Finished processing imports, returning {} final imports", + final_imports.len() + ); + Ok(final_imports) +} + +fn extract_nimports_from_content( + content: &str, + hm: &mut HashMap, +) -> Option { + let lines = split_python_requirements(content); + let locked_version = try_parse_locked_python_version_from_requirements(&lines); + for requirement in lines { + let key = extract_pkg_name(&requirement); + hm.insert( + key.clone(), + NImportResolved::Pin { + pins: vec![ImportPin { pkg: requirement, path: Default::default() }], + key, + }, + ); + } + locked_version } const STDIMPORTS: [&str; 303] = [ diff --git a/backend/parsers/windmill-parser-py-imports/tests/fixtures/base.sql b/backend/parsers/windmill-parser-py-imports/tests/fixtures/base.sql index 590ce1bd5f..4b3df78534 100644 --- a/backend/parsers/windmill-parser-py-imports/tests/fixtures/base.sql +++ b/backend/parsers/windmill-parser-py-imports/tests/fixtures/base.sql @@ -2581,3 +2581,102 @@ import innerdifffolder '', '', 'f/foobar/bar', -28028598712388159, 'python3', ''); + + + +-- +-- Name: workspace_dependencies_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres +-- + +CREATE SEQUENCE public.workspace_dependencies_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.workspace_dependencies_id_seq OWNER TO postgres; + +-- +-- Name: workspace_dependencies_id_seq; Type: SEQUENCE SET; Schema: public; Owner: postgres +-- + +SELECT pg_catalog.setval('public.workspace_dependencies_id_seq', 7, true); + + +-- +-- Name: SEQUENCE workspace_dependencies_id_seq; Type: ACL; Schema: public; Owner: postgres +-- + +GRANT ALL ON SEQUENCE public.workspace_dependencies_id_seq TO windmill_user; +GRANT ALL ON SEQUENCE public.workspace_dependencies_id_seq TO windmill_admin; + + +-- +-- Name: workspace_dependencies; Type: TABLE; Schema: public; Owner: postgres +-- + +CREATE TABLE public.workspace_dependencies ( + id bigint DEFAULT nextval('public.workspace_dependencies_id_seq'::regclass) NOT NULL, + name character varying(255), + content text NOT NULL, + language public.script_lang NOT NULL, + description text DEFAULT ''::text NOT NULL, + archived boolean DEFAULT false NOT NULL, + workspace_id character varying(50) NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL +); + + +ALTER TABLE public.workspace_dependencies OWNER TO postgres; + +-- +-- Name: workspace_dependencies workspace_dependencies_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.workspace_dependencies + ADD CONSTRAINT workspace_dependencies_pkey PRIMARY KEY (id); + + +-- +-- Name: one_non_archived_per_name_language_constraint; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE UNIQUE INDEX one_non_archived_per_name_language_constraint ON public.workspace_dependencies USING btree (name, language, workspace_id) WHERE ((archived = false) AND (name IS NOT NULL)); + + +-- +-- Name: one_non_archived_per_null_name_language_constraint; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE UNIQUE INDEX one_non_archived_per_null_name_language_constraint ON public.workspace_dependencies USING btree (language, workspace_id) WHERE ((archived = false) AND (name IS NULL)); + + +-- +-- Name: workspace_dependencies_workspace_archived_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX workspace_dependencies_workspace_archived_idx ON public.workspace_dependencies USING btree (workspace_id, archived) WHERE (archived = false); + + +-- +-- Name: workspace_dependencies_workspace_lang_name_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX workspace_dependencies_workspace_lang_name_idx ON public.workspace_dependencies USING btree (workspace_id, language, name); + + +-- +-- Name: TABLE workspace_dependencies; Type: ACL; Schema: public; Owner: postgres +-- + +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE public.workspace_dependencies TO windmill_user; +GRANT SELECT,INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,UPDATE ON TABLE public.workspace_dependencies TO windmill_admin; + + +-- +-- PostgreSQL database dump complete +-- + + diff --git a/backend/parsers/windmill-parser-py-imports/tests/tests.rs b/backend/parsers/windmill-parser-py-imports/tests/tests.rs index b7fd2c0737..e61b4cc7db 100644 --- a/backend/parsers/windmill-parser-py-imports/tests/tests.rs +++ b/backend/parsers/windmill-parser-py-imports/tests/tests.rs @@ -18,8 +18,16 @@ def main(): pass "; - let (r, ..) = - parse_python_imports(code, "test-workspace", "f/foo/bar", &db, &mut vec![]).await?; + let (r, ..) = parse_python_imports( + code, + "test-workspace", + "f/foo/bar", + &db, + &mut vec![], + &mut None, + &None, + ) + .await?; // println!("{}", serde_json::to_string(&r)?); assert_eq!( r, @@ -51,8 +59,16 @@ def main(): pass "; - let (r, ..) = - parse_python_imports(code, "test-workspace", "f/foo/bar", &db, &mut vec![]).await?; + let (r, ..) = parse_python_imports( + code, + "test-workspace", + "f/foo/bar", + &db, + &mut vec![], + &mut None, + &None, + ) + .await?; println!("{}", serde_json::to_string(&r)?); assert_eq!(r, vec!["burkina=0.4", "nigeria"]); @@ -74,8 +90,16 @@ def main(): "; - let (r, ..) = - parse_python_imports(code, "test-workspace", "f/foo/bar", &db, &mut vec![]).await?; + let (r, ..) = parse_python_imports( + code, + "test-workspace", + "f/foo/bar", + &db, + &mut vec![], + &mut None, + &None, + ) + .await?; println!("{}", serde_json::to_string(&r)?); assert_eq!( r, diff --git a/backend/src/main.rs b/backend/src/main.rs index a619572f50..c4a5ca805c 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -271,8 +271,8 @@ async fn cache_hub_scripts(file_path: Option) -> anyhow::Result<()> { let job_id = Uuid::new_v4(); let job_dir = format!("{}/cache_init/{}", TMP_DIR, job_id); create_dir_all(&job_dir)?; - if let Some(lockfile) = res.lockfile { - let _ = windmill_worker::prepare_job_dir(&lockfile, &job_dir).await?; + if let Some(lock) = res.lockfile { + let _ = windmill_worker::prepare_job_dir(&lock, &job_dir).await?; let envs = windmill_worker::get_common_bun_proc_envs(None).await; let _ = windmill_worker::install_bun_lockfile( &mut 0, @@ -292,7 +292,7 @@ async fn cache_hub_scripts(file_path: Option) -> anyhow::Result<()> { if let Err(e) = windmill_worker::prebundle_bun_script( &res.content, - Some(&lockfile), + &lock, &path, &job_id, "admins", diff --git a/backend/tests/relative_imports.rs b/backend/tests/debouncing.rs similarity index 86% rename from backend/tests/relative_imports.rs rename to backend/tests/debouncing.rs index 052f5a3743..72fdc8a203 100644 --- a/backend/tests/relative_imports.rs +++ b/backend/tests/debouncing.rs @@ -1,7 +1,9 @@ -// TODO: move all related logic here (if anything left anywhere in codebase) mod common; + +#[cfg(feature = "test_job_debouncing")] use windmill_api_client::types::NewScript; +#[cfg(feature = "test_job_debouncing")] fn quick_ns( content: &str, language: windmill_api_client::types::ScriptLang, @@ -43,490 +45,6 @@ fn quick_ns( } } -mod dependency_map { - use super::quick_ns; - use sqlx::{Pool, Postgres}; - use tokio_stream::StreamExt; - - use crate::common::{in_test_worker, init_client, listen_for_completed_jobs, ApiServer}; - - async fn init(db: Pool) -> (windmill_api_client::Client, u16, ApiServer) { - init_client(db).await - } - - async fn _clear_dmap(db: &Pool) { - sqlx::query!("DELETE FROM dependency_map WHERE workspace_id = 'test-workspace'") - .execute(db) - .await - .unwrap(); - } - - /// Corrects map according to provided replacements. - /// Only changes importer_path and/or id - /// Does not affect imported_path nor kind! - fn corrected_dmap(replacements: Vec<(&str, &str)>) -> Vec<(String, String, String, String)> { - CORRECT_DMAP - .clone() - .into_iter() - .map(|e| { - let mut r = ( - e.0.to_owned(), - e.1.to_owned(), - e.2.to_owned(), - e.3.to_owned(), - ); - for (from, to) in &replacements { - r = ( - r.0.replace(from, to), - r.1, // Kind should be immutable - r.2, // Imported path should be immutable - // We do not modify script contents in test, so we can assume scripts always import the same path - // Modification of kind or imported path considered to be incorrect. - r.3.replace(from, to), - ); - } - r - }) - .collect() - } - - async fn assert_dmap( - db: &Pool, - importer: Option, - expected: Vec<( - impl Into, - impl Into, - impl Into, - impl Into, - )>, - ) { - let dmap = sqlx::query_as::<_, (String, String, String, String)>( - "SELECT importer_path, importer_kind::text, imported_path, importer_node_id FROM dependency_map WHERE workspace_id = 'test-workspace' AND ($1::text IS NULL OR importer_path = $1::text)", - ) - .bind(importer) - .fetch_all(db) - .await - .unwrap(); - - assert_eq!( - dmap, - expected - .into_iter() - .map(|(f, s, t, fo)| (f.into(), s.into(), t.into(), fo.into())) - .collect::>() - ); - } - - lazy_static::lazy_static! { - pub static ref CORRECT_DMAP: Vec<(&'static str, &'static str, &'static str, &'static str)> = vec![ - ("f/rel/branch", "script", "f/rel/leaf_1", ""), - ("f/rel/root_script", "script", "f/rel/branch", ""), - ("f/rel/root_script", "script", "f/rel/leaf_1", ""), - ("f/rel/root_script", "script", "f/rel/leaf_2", ""), - ("f/rel/root_app", "app", "f/rel/leaf_2", "dontpressmeplz"), - ("f/rel/root_flow", "flow", "f/rel/leaf_2", "failure"), - ("f/rel/root_flow", "flow", "f/rel/branch", "nstep1"), - ("f/rel/root_flow", "flow", "f/rel/leaf_1", "nstep1"), - ("f/rel/root_flow", "flow", "f/rel/leaf_2", "nstep1"), - ("f/rel/root_flow", "flow", "f/rel/leaf_2", "nstep2_2"), - ("f/rel/root_flow", "flow", "f/rel/branch", "nstep4_1"), - ("f/rel/root_flow", "flow", "f/rel/branch", "nstep5_1"), - ("f/rel/root_flow", "flow", "f/rel/leaf_1", "nstep5_1"), - ("f/rel/root_flow", "flow", "f/rel/leaf_2", "nstep5_1"), - ("f/rel/root_flow", "flow", "f/rel/branch", "preprocessor"), - ("f/rel/root_flow", "flow", "f/rel/leaf_1", "preprocessor"), - ("f/rel/root_flow", "flow", "f/rel/leaf_2", "preprocessor"), - ("f/rel/root_app", "app", "f/rel/branch", "pressmeplz"), - ("f/rel/root_app", "app", "f/rel/leaf_1", "pressmeplz"), - ("f/rel/root_app", "app", "f/rel/leaf_2", "pressmeplz"), - ("f/rel/root_flow", "flow", "f/rel/leaf_2", "qtool1"), - ("f/rel/root_app", "app", "f/rel/branch", "youcanpressme")]; - } - - // TODO: - // Test that checks that we can run rebuild_dmap multiple times in tests. - - #[cfg(feature = "python")] - #[sqlx::test(fixtures("base", "dependency_map"))] - async fn relative_imports_test_rebuild_correctness(db: Pool) -> anyhow::Result<()> { - let (client, _port, _s) = init(db.clone()).await; - assert_dmap(&db, None, CORRECT_DMAP.clone()).await; - // rebuild map - assert!(super::common::rebuild_dmap(&client).await); - assert_dmap(&db, None, CORRECT_DMAP.clone()).await; - Ok(()) - } - - #[cfg(feature = "python")] - #[sqlx::test(fixtures("base", "dependency_map"))] - async fn relative_imports_test_rebuild_lock(db: Pool) -> anyhow::Result<()> { - let (client, _port, _s) = init(db.clone()).await; - - // Spawn first rebuild - let handle = { - let client = client.clone(); - tokio::spawn(async move { super::common::rebuild_dmap(&client).await }) - }; - - // Immidiately spawn another - let res = client - .client() - .post(format!( - "{}/w/test-workspace/workspaces/rebuild_dependency_map", - client.baseurl() - )) - .send() - .await - .unwrap() - .text() - .await - .unwrap(); - - // Should tell us there is already rebuilt in progress - // Or if it is too fast we will be able to trigger it second time - assert!(&res == "There is already one task pending, try again later." || &res == "Success"); - - assert!(handle.await.unwrap()); - Ok(()) - } - - // If you deploy from cli and you use raw requirements you don't want the script be included in dmap - // Otherwise script will be overwritten once any relative import is updated - #[cfg(feature = "python")] - #[sqlx::test(fixtures("base", "dependency_map"))] - async fn relative_imports_test_with_requirements_txt(db: Pool) -> anyhow::Result<()> { - let (client, _port, _s) = init(db.clone()).await; - - client - .create_script( - "test-workspace", - &quick_ns( - " -from f.rel.branch import main as br; -from f.rel.leaf_1 import main as lf_1; -from f.rel.leaf_2 import main as lf_2; - -def main(): - return [br(), lf_1(), lf_2]; - ", - windmill_api_client::types::ScriptLang::Python3, - "f/rel/root_script", - Some("# from requirements.txt".to_string()), - Some("000000000005165B".into()), - ), - ) - .await - .unwrap(); - - assert_dmap( - &db, - Some("f/rel/root_script".into()), - vec![ - ("f/rel/root_script", "script", "f/rel/branch", ""), - ("f/rel/root_script", "script", "f/rel/leaf_1", ""), - ("f/rel/root_script", "script", "f/rel/leaf_2", ""), - ], - ) - .await; - - tokio::time::sleep(std::time::Duration::from_secs(13)).await; - - assert_dmap( - &db, - Some("f/rel/root_script".into()), - Vec::<(String, String, String, String)>::new(), - ) - .await; - - Ok(()) - } - - #[cfg(feature = "python")] - #[sqlx::test(fixtures("base", "dependency_map"))] - async fn relative_imports_test_without_requirements_txt( - db: Pool, - ) -> anyhow::Result<()> { - let (client, _port, _s) = init(db.clone()).await; - - client - .create_script( - "test-workspace", - &quick_ns( - " -from f.rel.branch import main as br; -from f.rel.leaf_1 import main as lf_1; -from f.rel.leaf_2 import main as lf_2; - -def main(): - return [br(), lf_1(), lf_2]; - ", - windmill_api_client::types::ScriptLang::Python3, - "f/rel/root_script", - // We still want to pass lock to it. - Some("# py311".to_string()), - Some("000000000005165B".into()), - ), - ) - .await - .unwrap(); - assert_dmap(&db, None, CORRECT_DMAP.clone()).await; - // tokio::time::sleep(std::time::Duration::from_secs(13)).await; - assert_dmap(&db, None, CORRECT_DMAP.clone()).await; - Ok(()) - } - // Consider simple one. Only referenced directly. No deep connections - #[cfg(feature = "python")] - #[sqlx::test(fixtures("base", "dependency_map"))] - async fn relative_imports_test_rename_leaf_2(db: Pool) -> anyhow::Result<()> { - let (client, port, _s) = init(db.clone()).await; - client - .create_script( - "test-workspace", - &quick_ns( - " -def main(): - return 'leaf3'; - ", - windmill_api_client::types::ScriptLang::Python3, - "f/rel/leaf_2_renamed", - None, - Some("0000000000051659".into()), - ), - ) - .await - .unwrap(); - - let mut completed = listen_for_completed_jobs(&db).await; - in_test_worker(&db, completed.next(), port).await; - - // Changing leafs should not change dependency map - assert_dmap(&db, None, CORRECT_DMAP.clone()).await; - Ok(()) - } - - // Consider hard one. Referenced deeply and exists in double references. - #[cfg(feature = "python")] - #[sqlx::test(fixtures("base", "dependency_map"))] - async fn relative_imports_test_rename_leaf_1(db: Pool) -> anyhow::Result<()> { - let (client, port, _s) = init(db.clone()).await; - client - .create_script( - "test-workspace", - &quick_ns( - " -def main(): - return 'leaf1'; - ", - windmill_api_client::types::ScriptLang::Python3, - "f/rel/leaf_1_renamed", - None, - Some("0000000000051658".into()), - ), - ) - .await - .unwrap(); - - let mut completed = listen_for_completed_jobs(&db).await; - in_test_worker(&db, completed.next(), port).await; - - // Changing leafs should not change dependency map - assert_dmap(&db, None, CORRECT_DMAP.clone()).await; - Ok(()) - } - - #[cfg(feature = "python")] - #[sqlx::test(fixtures("base", "dependency_map"))] - async fn relative_imports_test_rename_branch(db: Pool) -> anyhow::Result<()> { - let (client, port, _s) = init(db.clone()).await; - client - .create_script( - "test-workspace", - &quick_ns( - " -from f.rel.leaf_1 import main as lf_1; - -def main(): - return lf_1(); - ", - windmill_api_client::types::ScriptLang::Python3, - "f/rel/branch_renamed", - None, - Some("000000000005165A".into()), - ), - ) - .await - .unwrap(); - - let mut completed = listen_for_completed_jobs(&db).await; - in_test_worker(&db, completed.next(), port).await; - - // Changing branches SHOULD change dependency map - // Though it should only change branch item in dmap when it is importer. - // All entries when branch is imported should not change. - let mut corrected_dmap = CORRECT_DMAP.clone(); - // Corresponds to importer path of branch entry - corrected_dmap[0].0 = "f/rel/branch_renamed"; - assert_dmap(&db, None, corrected_dmap).await; - Ok(()) - } - - #[cfg(feature = "python")] - #[sqlx::test(fixtures("base", "dependency_map"))] - async fn relative_imports_test_rename_primary_script(db: Pool) -> anyhow::Result<()> { - let (client, port, _s) = init(db.clone()).await; - - client - .create_script( - "test-workspace", - &quick_ns( - " -from f.rel.branch import main as br; -from f.rel.leaf_1 import main as lf_1; -from f.rel.leaf_2 import main as lf_2; - -def main(): - return [br(), lf_1(), lf_2]; - ", - windmill_api_client::types::ScriptLang::Python3, - "f/rel/root_script_renamed", - None, - Some("000000000005165B".into()), - ), - ) - .await - .unwrap(); - - let corrected_dmap = corrected_dmap(vec![("root_script", "root_script_renamed")]); - let mut completed = listen_for_completed_jobs(&db).await; - in_test_worker(&db, completed.next(), port).await; - assert_dmap(&db, None, corrected_dmap.clone()).await; - Ok(()) - } - - #[cfg(feature = "python")] - #[sqlx::test(fixtures("base", "dependency_map"))] - async fn relative_imports_test_rename_primary_flow(db: Pool) -> anyhow::Result<()> { - use windmill_common::{cache::flow::fetch_version, flows::NewFlow, worker::to_raw_value}; - - let (client, port, _s) = init(db.clone()).await; - let flow = fetch_version(&db, 1443253234253454).await.unwrap(); - let res = client - .client() - .post(format!( - "{}/w/test-workspace/flows/update/{}", - client.baseurl(), - "f/rel/root_flow" // encode_path() - )) - .json(&NewFlow { - path: "f/rel/root_flow_renamed".into(), - summary: "".into(), - description: None, - value: to_raw_value( - &serde_json::from_str::( - &serde_json::to_string(flow.value()) - .unwrap() - .replace("nstep1", "Foxes") - .replace("nstep2_2", "like") - .replace("nstep_4_1", "Emeralds"), - ) - .unwrap(), - ), - schema: None, - draft_only: None, - tag: None, - dedicated_worker: None, - timeout: None, - deployment_message: None, - visible_to_runner_only: None, - on_behalf_of_email: None, - ws_error_handler_muted: None, - }) - .send() - .await - .unwrap(); - - assert_eq!(res.text().await.unwrap(), "f/rel/root_flow_renamed"); - - let mut completed = listen_for_completed_jobs(&db).await; - in_test_worker(&db, completed.next(), port).await; - - assert_dmap( - &db, - None, - corrected_dmap(vec![ - ("f/rel/root_flow", "f/rel/root_flow_renamed"), - ("nstep1", "Foxes"), - ("nstep2_2", "like"), - ("nstep_4_1", "Emeralds"), - ]), - ) - .await; - Ok(()) - } - - #[cfg(feature = "python")] - #[sqlx::test(fixtures("base", "dependency_map"))] - async fn relative_imports_test_rename_primary_app(db: Pool) -> anyhow::Result<()> { - let (client, port, _s) = init(db.clone()).await; - - let app_value: String = - sqlx::query_scalar!("SELECT value::text FROM app_version WHERE id = 0 AND app_id = 2") - .fetch_one(&db) - .await - .unwrap() - .unwrap(); - - // TODO: There is: - // 1. update app - // 2. create app - // 3. update app raw - // Ideally all of them should be handled - let res = client - .client() - .post(format!( - "{}/w/test-workspace/apps/update/{}", - client.baseurl(), - "f/rel/root_app" // encode_path() - )) - .json(&windmill_api::EditApp { - path: Some("f/rel/root_app_renamed".into()), - summary: None, - value: serde_json::from_str( - &app_value - .replace("dontpressmeplz", "Apps") - .replace("youcanpressme", "Work"), - ) - .unwrap(), - policy: None, - deployment_message: None, - custom_path: None, - }) - .send() - .await - .unwrap(); - - assert_eq!( - res.text().await.unwrap(), - "app f/rel/root_app updated (npath: \"f/rel/root_app_renamed\")" - ); - - let mut completed = listen_for_completed_jobs(&db).await; - in_test_worker(&db, completed.next(), port).await; - - assert_dmap( - &db, - None, - corrected_dmap(vec![ - ("f/rel/root_app", "f/rel/root_app_renamed"), - ("dontpressmeplz", "Apps"), - ("youcanpressme", "Work"), - ]), - ) - .await; - Ok(()) - } -} - #[cfg(feature = "test_job_debouncing")] mod dependency_job_debouncing { async fn trigger_djob_for( diff --git a/backend/tests/dependency_map.rs b/backend/tests/dependency_map.rs new file mode 100644 index 0000000000..d217b710d6 --- /dev/null +++ b/backend/tests/dependency_map.rs @@ -0,0 +1,535 @@ +use sqlx::{Pool, Postgres}; +use tokio_stream::StreamExt; + +use windmill_api_client::types::NewScript; +mod common; +use common::{in_test_worker, init_client, listen_for_completed_jobs, ApiServer}; + +mod dependency_map { + use super::*; + fn quick_ns( + content: &str, + language: windmill_api_client::types::ScriptLang, + path: &str, + lock: Option, + parent_hash: Option, + ) -> NewScript { + NewScript { + content: content.into(), + language, + lock, + parent_hash, + path: path.into(), + concurrent_limit: None, + concurrency_time_window_s: None, + cache_ttl: None, + dedicated_worker: None, + description: "".to_string(), + draft_only: None, + envs: vec![], + is_template: None, + kind: None, + summary: "".to_string(), + tag: None, + schema: std::collections::HashMap::new(), + ws_error_handler_muted: Some(false), + priority: None, + delete_after_use: None, + timeout: None, + restart_unless_cancelled: None, + deployment_message: None, + concurrency_key: None, + visible_to_runner_only: None, + no_main_func: None, + codebase: None, + has_preprocessor: None, + on_behalf_of_email: None, + assets: vec![], + } + } + async fn init(db: Pool) -> (windmill_api_client::Client, u16, ApiServer) { + init_client(db).await + } + + async fn _clear_dmap(db: &Pool) { + sqlx::query!("DELETE FROM dependency_map WHERE workspace_id = 'test-workspace'") + .execute(db) + .await + .unwrap(); + } + + /// Corrects map according to provided replacements. + /// Only changes importer_path and/or id + /// Does not affect imported_path nor kind! + fn corrected_dmap(replacements: Vec<(&str, &str)>) -> Vec<(String, String, String, String)> { + CORRECT_DMAP + .clone() + .into_iter() + .map(|e| { + let mut r = ( + e.0.to_owned(), + e.1.to_owned(), + e.2.to_owned(), + e.3.to_owned(), + ); + for (from, to) in &replacements { + r = ( + r.0.replace(from, to), + r.1, // Kind should be immutable + r.2, // Imported path should be immutable + // We do not modify script contents in test, so we can assume scripts always import the same path + // Modification of kind or imported path considered to be incorrect. + r.3.replace(from, to), + ); + } + r + }) + .collect() + } + + async fn assert_dmap( + db: &Pool, + importer: Option, + expected: Vec<( + impl Into, + impl Into, + impl Into, + impl Into, + )>, + ) { + let mut dmap = sqlx::query_as::<_, (String, String, String, String)>( + "SELECT importer_path, importer_kind::text, imported_path, importer_node_id FROM dependency_map WHERE workspace_id = 'test-workspace' AND ($1::text IS NULL OR importer_path = $1::text)", + ) + .bind(importer) + .fetch_all(db) + .await + .unwrap(); + + let mut expected = expected + .into_iter() + .map(|(f, s, t, fo)| (f.into(), s.into(), t.into(), fo.into())) + .collect::>(); + + dmap.sort(); + expected.sort(); + + assert_eq!(dmap, expected); + } + + lazy_static::lazy_static! { + pub static ref CORRECT_DMAP: Vec<(&'static str, &'static str, &'static str, &'static str)> = vec![ + ("f/rel/root_script", "script", "dependencies/test.requirements.in", ""), + ("f/rel/root_flow", "flow", "dependencies/test.requirements.in", "nstep1"), + ("f/rel/root_app", "app", "dependencies/test.requirements.in", "dontpressmeplz"), + ("f/rel/branch", "script", "f/rel/leaf_1", ""), + ("f/rel/root_script", "script", "f/rel/branch", ""), + ("f/rel/root_script", "script", "f/rel/leaf_1", ""), + ("f/rel/root_script", "script", "f/rel/leaf_2", ""), + ("f/rel/root_app", "app", "f/rel/leaf_2", "dontpressmeplz"), + ("f/rel/root_flow", "flow", "f/rel/leaf_2", "failure"), + ("f/rel/root_flow", "flow", "f/rel/branch", "nstep1"), + ("f/rel/root_flow", "flow", "f/rel/leaf_1", "nstep1"), + ("f/rel/root_flow", "flow", "f/rel/leaf_2", "nstep1"), + ("f/rel/root_flow", "flow", "f/rel/leaf_2", "nstep2_2"), + ("f/rel/root_flow", "flow", "f/rel/branch", "nstep4_1"), + ("f/rel/root_flow", "flow", "f/rel/branch", "nstep5_1"), + ("f/rel/root_flow", "flow", "f/rel/leaf_1", "nstep5_1"), + ("f/rel/root_flow", "flow", "f/rel/leaf_2", "nstep5_1"), + ("f/rel/root_flow", "flow", "f/rel/branch", "preprocessor"), + ("f/rel/root_flow", "flow", "f/rel/leaf_1", "preprocessor"), + ("f/rel/root_flow", "flow", "f/rel/leaf_2", "preprocessor"), + ("f/rel/root_app", "app", "f/rel/branch", "pressmeplz"), + ("f/rel/root_app", "app", "f/rel/leaf_1", "pressmeplz"), + ("f/rel/root_app", "app", "f/rel/leaf_2", "pressmeplz"), + ("f/rel/root_flow", "flow", "f/rel/leaf_2", "qtool1"), + ("f/rel/root_app", "app", "f/rel/branch", "youcanpressme"), + + // Default + ("f/rel/leaf_1", "script", "dependencies/requirements.in", ""), + ("f/rel/leaf_2", "script", "dependencies/requirements.in", ""), + ("f/rel/branch", "script", "dependencies/requirements.in", ""), + ("f/rel/root_flow", "flow", "dependencies/requirements.in", "failure"), + ("f/rel/root_flow", "flow", "dependencies/package.json", "nstep2_1"), + ("f/rel/root_flow", "flow", "dependencies/package.json", "nstep3_2"), + ("f/rel/root_flow", "flow", "dependencies/requirements.in", "nstep2_2"), + ("f/rel/root_flow", "flow", "dependencies/requirements.in", "nstep3_1"), + ("f/rel/root_flow", "flow", "dependencies/requirements.in", "nstep4_1"), + ("f/rel/root_flow", "flow", "dependencies/requirements.in", "nstep5_1"), + ("f/rel/root_flow", "flow", "dependencies/requirements.in", "preprocessor"), + ("f/rel/root_app", "app", "dependencies/requirements.in", "pressmeplz"), + ("f/rel/root_flow", "flow", "dependencies/requirements.in", "qtool1"), + ("f/rel/root_app", "app", "dependencies/requirements.in", "youcanpressme") + ]; + } + // TODO: + // Test that checks that we can run rebuild_dmap multiple times in tests. + + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "dependency_map"))] + async fn relative_imports_test_rebuild_correctness(db: Pool) -> anyhow::Result<()> { + let (client, _port, _s) = init(db.clone()).await; + assert_dmap(&db, None, CORRECT_DMAP.clone()).await; + // rebuild map + assert!(common::rebuild_dmap(&client).await); + assert_dmap(&db, None, CORRECT_DMAP.clone()).await; + Ok(()) + } + + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "dependency_map"))] + async fn relative_imports_test_rebuild_lock(db: Pool) -> anyhow::Result<()> { + let (client, _port, _s) = init(db.clone()).await; + + // Spawn first rebuild + let handle = { + let client = client.clone(); + tokio::spawn(async move { common::rebuild_dmap(&client).await }) + }; + + // Immidiately spawn another + let res = client + .client() + .post(format!( + "{}/w/test-workspace/workspaces/rebuild_dependency_map", + client.baseurl() + )) + .send() + .await + .unwrap() + .text() + .await + .unwrap(); + + // Should tell us there is already rebuilt in progress + // Or if it is too fast we will be able to trigger it second time + assert!(&res == "There is already one task pending, try again later." || &res == "Success"); + + assert!(handle.await.unwrap()); + Ok(()) + } + + // If you deploy from cli and you use raw requirements you don't want the script be included in dmap + // Otherwise script will be overwritten once any relative import is updated + // #[cfg(feature = "python")] + // #[sqlx::test(fixtures("base", "dependency_map"))] + // async fn relative_imports_test_with_legacy(db: Pool) -> anyhow::Result<()> { + // let (client, _port, _s) = init(db.clone()).await; + + // client + // .create_script( + // "test-workspace", + // &quick_ns( + // " + // from f.rel.branch import main as br; + // from f.rel.leaf_1 import main as lf_1; + // from f.rel.leaf_2 import main as lf_2; + + // def main(): + // return [br(), lf_1(), lf_2]; + // ", + // windmill_api_client::types::ScriptLang::Python3, + // "f/rel/root_script", + // Some("# from requirements.txt".to_string()), + // Some("000000000005165B".into()), + // ), + // ) + // .await + // .unwrap(); + + // assert_dmap( + // &db, + // Some("f/rel/root_script".into()), + // vec![ + // ("f/rel/root_script", "script", "f/rel/branch", ""), + // ("f/rel/root_script", "script", "f/rel/leaf_1", ""), + // ("f/rel/root_script", "script", "f/rel/leaf_2", ""), + // ], + // ) + // .await; + + // tokio::time::sleep(std::time::Duration::from_secs(13)).await; + + // assert_dmap( + // &db, + // Some("f/rel/root_script".into()), + // vec![ + // ("f/rel/root_script", "script", "f/rel/branch", ""), + // ("f/rel/root_script", "script", "f/rel/leaf_1", ""), + // ("f/rel/root_script", "script", "f/rel/leaf_2", ""), + // ], + // ) + // .await; + + // Ok(()) + // } + + // Consider simple one. Only referenced directly. No deep connections + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "dependency_map"))] + async fn relative_imports_test_rename_leaf_2(db: Pool) -> anyhow::Result<()> { + let (client, port, _s) = init(db.clone()).await; + client + .create_script( + "test-workspace", + &quick_ns( + " +def main(): + return 'leaf3'; + ", + windmill_api_client::types::ScriptLang::Python3, + "f/rel/leaf_2_renamed", + None, + Some("0000000000051659".into()), + ), + ) + .await + .unwrap(); + + let mut completed = listen_for_completed_jobs(&db).await; + in_test_worker(&db, completed.next(), port).await; + + // Changing leafs should not change dependency map + assert_dmap( + &db, + None, + corrected_dmap(vec![("f/rel/leaf_2", "f/rel/leaf_2_renamed")]), + ) + .await; + Ok(()) + } + + // Consider hard one. Referenced deeply and exists in double references. + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "dependency_map"))] + async fn relative_imports_test_rename_leaf_1(db: Pool) -> anyhow::Result<()> { + let (client, port, _s) = init(db.clone()).await; + client + .create_script( + "test-workspace", + &quick_ns( + " +def main(): + return 'leaf1'; + ", + windmill_api_client::types::ScriptLang::Python3, + "f/rel/leaf_1_renamed", + None, + Some("0000000000051658".into()), + ), + ) + .await + .unwrap(); + + let mut completed = listen_for_completed_jobs(&db).await; + in_test_worker(&db, completed.next(), port).await; + + // Changing leafs should not change dependency map + assert_dmap( + &db, + None, + corrected_dmap(vec![("f/rel/leaf_1", "f/rel/leaf_1_renamed")]), + ) + .await; + Ok(()) + } + + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "dependency_map"))] + async fn relative_imports_test_rename_branch(db: Pool) -> anyhow::Result<()> { + let (client, port, _s) = init(db.clone()).await; + client + .create_script( + "test-workspace", + &quick_ns( + " +from f.rel.leaf_1 import main as lf_1; + +def main(): + return lf_1(); + ", + windmill_api_client::types::ScriptLang::Python3, + "f/rel/branch_renamed", + None, + Some("000000000005165A".into()), + ), + ) + .await + .unwrap(); + + let mut completed = listen_for_completed_jobs(&db).await; + in_test_worker(&db, completed.next(), port).await; + + // Changing branches SHOULD change dependency map + // Though it should only change branch item in dmap when it is importer. + // All entries when branch is imported should not change. + let corrected_dmap = CORRECT_DMAP + .clone() + .iter_mut() + .map(|el| { + if el.0 == "f/rel/branch" { + el.0 = "f/rel/branch_renamed"; + } + *el + }) + .collect::>(); + assert_dmap(&db, None, corrected_dmap).await; + Ok(()) + } + + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "dependency_map"))] + async fn relative_imports_test_rename_primary_script(db: Pool) -> anyhow::Result<()> { + let (client, port, _s) = init(db.clone()).await; + + client + .create_script( + "test-workspace", + &quick_ns( + " +# requirements: test +from f.rel.branch import main as br; +from f.rel.leaf_1 import main as lf_1; +from f.rel.leaf_2 import main as lf_2; + +def main(): + return [br(), lf_1(), lf_2]; + ", + windmill_api_client::types::ScriptLang::Python3, + "f/rel/root_script_renamed", + None, + Some("000000000005165B".into()), + ), + ) + .await + .unwrap(); + + let corrected_dmap = corrected_dmap(vec![("root_script", "root_script_renamed")]); + let mut completed = listen_for_completed_jobs(&db).await; + in_test_worker(&db, completed.next(), port).await; + assert_dmap(&db, None, corrected_dmap.clone()).await; + Ok(()) + } + + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "dependency_map"))] + async fn relative_imports_test_rename_primary_flow(db: Pool) -> anyhow::Result<()> { + use windmill_common::{cache::flow::fetch_version, flows::NewFlow, worker::to_raw_value}; + + let (client, port, _s) = init(db.clone()).await; + let flow = fetch_version(&db, 1443253234253454).await.unwrap(); + let res = client + .client() + .post(format!( + "{}/w/test-workspace/flows/update/{}", + client.baseurl(), + "f/rel/root_flow" // encode_path() + )) + .json(&NewFlow { + path: "f/rel/root_flow_renamed".into(), + summary: "".into(), + description: None, + value: to_raw_value( + &serde_json::from_str::( + &serde_json::to_string(flow.value()) + .unwrap() + .replace("nstep1", "Foxes") + .replace("nstep2_2", "like") + .replace("nstep_4_1", "Emeralds"), + ) + .unwrap(), + ), + schema: None, + draft_only: None, + tag: None, + dedicated_worker: None, + timeout: None, + deployment_message: None, + visible_to_runner_only: None, + on_behalf_of_email: None, + ws_error_handler_muted: None, + }) + .send() + .await + .unwrap(); + + assert_eq!(res.text().await.unwrap(), "f/rel/root_flow_renamed"); + + let mut completed = listen_for_completed_jobs(&db).await; + in_test_worker(&db, completed.next(), port).await; + + assert_dmap( + &db, + None, + corrected_dmap(vec![ + ("f/rel/root_flow", "f/rel/root_flow_renamed"), + ("nstep1", "Foxes"), + ("nstep2_2", "like"), + ("nstep_4_1", "Emeralds"), + ]), + ) + .await; + Ok(()) + } + + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "dependency_map"))] + async fn relative_imports_test_rename_primary_app(db: Pool) -> anyhow::Result<()> { + let (client, port, _s) = init(db.clone()).await; + + let app_value: String = + sqlx::query_scalar!("SELECT value::text FROM app_version WHERE id = 0 AND app_id = 2") + .fetch_one(&db) + .await + .unwrap() + .unwrap(); + + // TODO: There is: + // 1. update app + // 2. create app + // 3. update app raw + // Ideally all of them should be handled + let res = client + .client() + .post(format!( + "{}/w/test-workspace/apps/update/{}", + client.baseurl(), + "f/rel/root_app" // encode_path() + )) + .json(&windmill_api::EditApp { + path: Some("f/rel/root_app_renamed".into()), + summary: None, + value: serde_json::from_str( + &app_value + .replace("dontpressmeplz", "Apps") + .replace("youcanpressme", "Work"), + ) + .unwrap(), + policy: None, + deployment_message: None, + custom_path: None, + }) + .send() + .await + .unwrap(); + + assert_eq!( + res.text().await.unwrap(), + "app f/rel/root_app updated (npath: \"f/rel/root_app_renamed\")" + ); + + let mut completed = listen_for_completed_jobs(&db).await; + in_test_worker(&db, completed.next(), port).await; + + assert_dmap( + &db, + None, + corrected_dmap(vec![ + ("f/rel/root_app", "f/rel/root_app_renamed"), + ("dontpressmeplz", "Apps"), + ("youcanpressme", "Work"), + ]), + ) + .await; + Ok(()) + } +} diff --git a/backend/tests/fixtures/dependency_map.sql b/backend/tests/fixtures/dependency_map.sql index c2caf110be..0b8c797155 100644 --- a/backend/tests/fixtures/dependency_map.sql +++ b/backend/tests/fixtures/dependency_map.sql @@ -43,6 +43,7 @@ INSERT INTO public.script(workspace_id, created_by, content, schema, summary, de 'test-workspace', 'test-user', ' +# requirements: test from f.rel.branch import main as br; from f.rel.leaf_1 import main as lf_1; from f.rel.leaf_2 import main as lf_2; @@ -63,7 +64,7 @@ INSERT INTO public.flow(workspace_id, summary, description, path, versions, sche 'f/rel/root_flow', '{1443253234253454}', '{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object","order":[]}', -$tag${"modules":[{"id":"nstep1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"from f.rel.branch import main as br;\nfrom f.rel.leaf_1 import main as lf_1;\nfrom ..leaf_1 import main as lf_12;\nfrom ...rel.leaf_2 import main as lf_2;\n\ndef check():\n return [br(), lf_1(), lf_2(), lf_12()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}},{"id":"nstep2","value":{"type":"branchall","branches":[{"expr":"","modules":[{"id":"nstep2_1","value":{"lock":"{\n \"dependencies\": {}\n}\n//bun.lock\n","type":"rawscript","assets":[],"content":"// import * as wmill from \"windmill-client\"\n\nexport async function main(x: string) {\n return x\n}\n","language":"bun","input_transforms":{"x":{"type":"static","value":""}}}}],"parallel":true,"skip_failure":false},{"expr":"false","modules":[{"id":"nstep2_2","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"from f.rel.leaf_2 import main as lf_2;\n\ndef check():\n return [lf_2()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"summary":"","parallel":true,"skip_failure":false}],"parallel":true},"summary":""},{"id":"nstep3","value":{"type":"branchone","default":[{"id":"nstep3_2","value":{"lock":"{\n \"dependencies\": {}\n}\n//bun.lock\n","type":"rawscript","assets":[],"content":"// import * as wmill from \"windmill-client\"\n\nexport async function main(x: string) {\n return x\n}\n","language":"bun","input_transforms":{"x":{"type":"static","value":""}}}}],"branches":[{"expr":"false","modules":[{"id":"nstep3_1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"def main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"summary":"","parallel":true,"skip_failure":true}]},"summary":""},{"id":"nstep4","value":{"type":"whileloopflow","modules":[{"id":"nstep4_1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"from f.rel.branch import main as br;\n\ndef check():\n return [br()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"skip_failures":false}},{"id":"nstep5","value":{"type":"forloopflow","modules":[{"id":"nstep5_1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"from f.rel.branch import main as br;\nfrom f.rel.leaf_1 import main as lf_1;\nfrom ..leaf_1 import main as lf_12;\nfrom ...rel.leaf_2 import main as lf_2;\n\ndef check():\n return [br(), lf_1(), lf_2(), lf_12()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"iterator":{"expr":"['dynamic or static array']","type":"javascript"},"parallel":false,"skip_failures":true}},{"id":"nstep_ai","value":{"type":"aiagent","tools":[{"id":"qtool1","value":{"type":"rawscript","content":"from f.rel.leaf_2 import main as lf_2;\n\ndef check():\n return [lf_2()];\n\ndef main(x):\n return x\n","language":"python3","input_transforms":{"x":{"type":"static"}}},"summary":"tool"}],"input_transforms":{"image":{"type":"static"},"provider":{"type":"static","value":{"kind":"openai"}},"output_type":{"type":"static","value":"text"},"temperature":{"type":"static"},"user_message":{"type":"static","value":""},"output_schema":{"type":"static"},"system_prompt":{"type":"static","value":""},"max_completion_tokens":{"type":"static"}}},"continue_on_error":false}],"failure_module":{"id":"failure","value":{"type":"rawscript","content":"from f.rel.leaf_2 import main as lf_2;\n\ndef check():\n return [lf_2()];\n\nimport os\n\ndef main(message: str, name: str, step_id: str):\n flow_id = os.environ.get(\"WM_ROOT_FLOW_JOB_ID\")\n print(\"message\", message)\n print(\"name\", name)\n print(\"step_id\", step_id)\n return { \"message\": message, \"flow_id\": flow_id, \"step_id\": step_id, \"recover\": False }","language":"python3","input_transforms":{"name":{"expr":"error.name","type":"javascript"},"message":{"expr":"error.message","type":"javascript"},"step_id":{"expr":"error.step_id","type":"javascript"}}}},"preprocessor_module":{"id":"preprocessor","value":{"type":"rawscript","content":"from f.rel.branch import main as br;\nfrom f.rel.leaf_1 import main as lf_1;\nfrom ..leaf_1 import main as lf_12;\nfrom ...rel.leaf_2 import main as lf_2;\n\ndef check():\n return [br(), lf_1(), lf_2(), lf_12()];\n\ndef preprocessor(event):\n return {\n # return the args to be passed to the runnable\n }\n","language":"python3","input_transforms":{"event":{"type":"static"}}}}}$tag$, +$tag${"modules":[{"id":"nstep1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"#requirements: test\nfrom f.rel.branch import main as br;\nfrom f.rel.leaf_1 import main as lf_1;\nfrom ..leaf_1 import main as lf_12;\nfrom ...rel.leaf_2 import main as lf_2;\n\ndef check():\n return [br(), lf_1(), lf_2(), lf_12()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}},{"id":"nstep2","value":{"type":"branchall","branches":[{"expr":"","modules":[{"id":"nstep2_1","value":{"lock":"{\n \"dependencies\": {}\n}\n//bun.lock\n","type":"rawscript","assets":[],"content":"// import * as wmill from \"windmill-client\"\n\nexport async function main(x: string) {\n return x\n}\n","language":"bun","input_transforms":{"x":{"type":"static","value":""}}}}],"parallel":true,"skip_failure":false},{"expr":"false","modules":[{"id":"nstep2_2","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"from f.rel.leaf_2 import main as lf_2;\n\ndef check():\n return [lf_2()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"summary":"","parallel":true,"skip_failure":false}],"parallel":true},"summary":""},{"id":"nstep3","value":{"type":"branchone","default":[{"id":"nstep3_2","value":{"lock":"{\n \"dependencies\": {}\n}\n//bun.lock\n","type":"rawscript","assets":[],"content":"// import * as wmill from \"windmill-client\"\n\nexport async function main(x: string) {\n return x\n}\n","language":"bun","input_transforms":{"x":{"type":"static","value":""}}}}],"branches":[{"expr":"false","modules":[{"id":"nstep3_1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"def main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"summary":"","parallel":true,"skip_failure":true}]},"summary":""},{"id":"nstep4","value":{"type":"whileloopflow","modules":[{"id":"nstep4_1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"from f.rel.branch import main as br;\n\ndef check():\n return [br()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"skip_failures":false}},{"id":"nstep5","value":{"type":"forloopflow","modules":[{"id":"nstep5_1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"from f.rel.branch import main as br;\nfrom f.rel.leaf_1 import main as lf_1;\nfrom ..leaf_1 import main as lf_12;\nfrom ...rel.leaf_2 import main as lf_2;\n\ndef check():\n return [br(), lf_1(), lf_2(), lf_12()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"iterator":{"expr":"['dynamic or static array']","type":"javascript"},"parallel":false,"skip_failures":true}},{"id":"nstep_ai","value":{"type":"aiagent","tools":[{"id":"qtool1","value":{"type":"rawscript","content":"from f.rel.leaf_2 import main as lf_2;\n\ndef check():\n return [lf_2()];\n\ndef main(x):\n return x\n","language":"python3","input_transforms":{"x":{"type":"static"}}},"summary":"tool"}],"input_transforms":{"image":{"type":"static"},"provider":{"type":"static","value":{"kind":"openai"}},"output_type":{"type":"static","value":"text"},"temperature":{"type":"static"},"user_message":{"type":"static","value":""},"output_schema":{"type":"static"},"system_prompt":{"type":"static","value":""},"max_completion_tokens":{"type":"static"}}},"continue_on_error":false}],"failure_module":{"id":"failure","value":{"type":"rawscript","content":"from f.rel.leaf_2 import main as lf_2;\n\ndef check():\n return [lf_2()];\n\nimport os\n\ndef main(message: str, name: str, step_id: str):\n flow_id = os.environ.get(\"WM_ROOT_FLOW_JOB_ID\")\n print(\"message\", message)\n print(\"name\", name)\n print(\"step_id\", step_id)\n return { \"message\": message, \"flow_id\": flow_id, \"step_id\": step_id, \"recover\": False }","language":"python3","input_transforms":{"name":{"expr":"error.name","type":"javascript"},"message":{"expr":"error.message","type":"javascript"},"step_id":{"expr":"error.step_id","type":"javascript"}}}},"preprocessor_module":{"id":"preprocessor","value":{"type":"rawscript","content":"from f.rel.branch import main as br;\nfrom f.rel.leaf_1 import main as lf_1;\nfrom ..leaf_1 import main as lf_12;\nfrom ...rel.leaf_2 import main as lf_2;\n\ndef check():\n return [br(), lf_1(), lf_2(), lf_12()];\n\ndef preprocessor(event):\n return {\n # return the args to be passed to the runnable\n }\n","language":"python3","input_transforms":{"event":{"type":"static"}}}}}$tag$, 'system' ); @@ -72,7 +73,7 @@ INSERT INTO public.flow_version(id, workspace_id, path, schema, value, created_b 'test-workspace', 'f/rel/root_flow', '{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object","order":[]}', -$tag${"modules":[{"id":"nstep1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"from f.rel.branch import main as br;\nfrom f.rel.leaf_1 import main as lf_1;\nfrom ..leaf_1 import main as lf_12;\nfrom ...rel.leaf_2 import main as lf_2;\n\ndef check():\n return [br(), lf_1(), lf_2(), lf_12()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}},{"id":"nstep2","value":{"type":"branchall","branches":[{"expr":"","modules":[{"id":"nstep2_1","value":{"lock":"{\n \"dependencies\": {}\n}\n//bun.lock\n","type":"rawscript","assets":[],"content":"// import * as wmill from \"windmill-client\"\n\nexport async function main(x: string) {\n return x\n}\n","language":"bun","input_transforms":{"x":{"type":"static","value":""}}}}],"parallel":true,"skip_failure":false},{"expr":"false","modules":[{"id":"nstep2_2","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"from f.rel.leaf_2 import main as lf_2;\n\ndef check():\n return [lf_2()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"summary":"","parallel":true,"skip_failure":false}],"parallel":true},"summary":""},{"id":"nstep3","value":{"type":"branchone","default":[{"id":"nstep3_2","value":{"lock":"{\n \"dependencies\": {}\n}\n//bun.lock\n","type":"rawscript","assets":[],"content":"// import * as wmill from \"windmill-client\"\n\nexport async function main(x: string) {\n return x\n}\n","language":"bun","input_transforms":{"x":{"type":"static","value":""}}}}],"branches":[{"expr":"false","modules":[{"id":"nstep3_1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"def main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"summary":"","parallel":true,"skip_failure":true}]},"summary":""},{"id":"nstep4","value":{"type":"whileloopflow","modules":[{"id":"nstep4_1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"from f.rel.branch import main as br;\n\ndef check():\n return [br()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"skip_failures":false}},{"id":"nstep5","value":{"type":"forloopflow","modules":[{"id":"nstep5_1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"from f.rel.branch import main as br;\nfrom f.rel.leaf_1 import main as lf_1;\nfrom ..leaf_1 import main as lf_12;\nfrom ...rel.leaf_2 import main as lf_2;\n\ndef check():\n return [br(), lf_1(), lf_2(), lf_12()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"iterator":{"expr":"['dynamic or static array']","type":"javascript"},"parallel":false,"skip_failures":true}},{"id":"nstep_ai","value":{"type":"aiagent","tools":[{"id":"qtool1","value":{"type":"rawscript","content":"from f.rel.leaf_2 import main as lf_2;\n\ndef check():\n return [lf_2()];\n\ndef main(x):\n return x\n","language":"python3","input_transforms":{"x":{"type":"static"}}},"summary":"tool"}],"input_transforms":{"image":{"type":"static"},"provider":{"type":"static","value":{"kind":"openai"}},"output_type":{"type":"static","value":"text"},"temperature":{"type":"static"},"user_message":{"type":"static","value":""},"output_schema":{"type":"static"},"system_prompt":{"type":"static","value":""},"max_completion_tokens":{"type":"static"}}},"continue_on_error":false}],"failure_module":{"id":"failure","value":{"type":"rawscript","content":"from f.rel.leaf_2 import main as lf_2;\n\ndef check():\n return [lf_2()];\n\nimport os\n\ndef main(message: str, name: str, step_id: str):\n flow_id = os.environ.get(\"WM_ROOT_FLOW_JOB_ID\")\n print(\"message\", message)\n print(\"name\", name)\n print(\"step_id\", step_id)\n return { \"message\": message, \"flow_id\": flow_id, \"step_id\": step_id, \"recover\": False }","language":"python3","input_transforms":{"name":{"expr":"error.name","type":"javascript"},"message":{"expr":"error.message","type":"javascript"},"step_id":{"expr":"error.step_id","type":"javascript"}}}},"preprocessor_module":{"id":"preprocessor","value":{"type":"rawscript","content":"from f.rel.branch import main as br;\nfrom f.rel.leaf_1 import main as lf_1;\nfrom ..leaf_1 import main as lf_12;\nfrom ...rel.leaf_2 import main as lf_2;\n\ndef check():\n return [br(), lf_1(), lf_2(), lf_12()];\n\ndef preprocessor(event):\n return {\n # return the args to be passed to the runnable\n }\n","language":"python3","input_transforms":{"event":{"type":"static"}}}}}$tag$, +$tag${"modules":[{"id":"nstep1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"#requirements: test\nfrom f.rel.branch import main as br;\nfrom f.rel.leaf_1 import main as lf_1;\nfrom ..leaf_1 import main as lf_12;\nfrom ...rel.leaf_2 import main as lf_2;\n\ndef check():\n return [br(), lf_1(), lf_2(), lf_12()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}},{"id":"nstep2","value":{"type":"branchall","branches":[{"expr":"","modules":[{"id":"nstep2_1","value":{"lock":"{\n \"dependencies\": {}\n}\n//bun.lock\n","type":"rawscript","assets":[],"content":"// import * as wmill from \"windmill-client\"\n\nexport async function main(x: string) {\n return x\n}\n","language":"bun","input_transforms":{"x":{"type":"static","value":""}}}}],"parallel":true,"skip_failure":false},{"expr":"false","modules":[{"id":"nstep2_2","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"from f.rel.leaf_2 import main as lf_2;\n\ndef check():\n return [lf_2()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"summary":"","parallel":true,"skip_failure":false}],"parallel":true},"summary":""},{"id":"nstep3","value":{"type":"branchone","default":[{"id":"nstep3_2","value":{"lock":"{\n \"dependencies\": {}\n}\n//bun.lock\n","type":"rawscript","assets":[],"content":"// import * as wmill from \"windmill-client\"\n\nexport async function main(x: string) {\n return x\n}\n","language":"bun","input_transforms":{"x":{"type":"static","value":""}}}}],"branches":[{"expr":"false","modules":[{"id":"nstep3_1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"def main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"summary":"","parallel":true,"skip_failure":true}]},"summary":""},{"id":"nstep4","value":{"type":"whileloopflow","modules":[{"id":"nstep4_1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"from f.rel.branch import main as br;\n\ndef check():\n return [br()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"skip_failures":false}},{"id":"nstep5","value":{"type":"forloopflow","modules":[{"id":"nstep5_1","value":{"lock":"# py: 3.11\n","type":"rawscript","assets":[],"content":"from f.rel.branch import main as br;\nfrom f.rel.leaf_1 import main as lf_1;\nfrom ..leaf_1 import main as lf_12;\nfrom ...rel.leaf_2 import main as lf_2;\n\ndef check():\n return [br(), lf_1(), lf_2(), lf_12()];\n\ndef main(x: str):\n return x","language":"python3","input_transforms":{"x":{"type":"static","value":""}}}}],"iterator":{"expr":"['dynamic or static array']","type":"javascript"},"parallel":false,"skip_failures":true}},{"id":"nstep_ai","value":{"type":"aiagent","tools":[{"id":"qtool1","value":{"type":"rawscript","content":"from f.rel.leaf_2 import main as lf_2;\n\ndef check():\n return [lf_2()];\n\ndef main(x):\n return x\n","language":"python3","input_transforms":{"x":{"type":"static"}}},"summary":"tool"}],"input_transforms":{"image":{"type":"static"},"provider":{"type":"static","value":{"kind":"openai"}},"output_type":{"type":"static","value":"text"},"temperature":{"type":"static"},"user_message":{"type":"static","value":""},"output_schema":{"type":"static"},"system_prompt":{"type":"static","value":""},"max_completion_tokens":{"type":"static"}}},"continue_on_error":false}],"failure_module":{"id":"failure","value":{"type":"rawscript","content":"from f.rel.leaf_2 import main as lf_2;\n\ndef check():\n return [lf_2()];\n\nimport os\n\ndef main(message: str, name: str, step_id: str):\n flow_id = os.environ.get(\"WM_ROOT_FLOW_JOB_ID\")\n print(\"message\", message)\n print(\"name\", name)\n print(\"step_id\", step_id)\n return { \"message\": message, \"flow_id\": flow_id, \"step_id\": step_id, \"recover\": False }","language":"python3","input_transforms":{"name":{"expr":"error.name","type":"javascript"},"message":{"expr":"error.message","type":"javascript"},"step_id":{"expr":"error.step_id","type":"javascript"}}}},"preprocessor_module":{"id":"preprocessor","value":{"type":"rawscript","content":"from f.rel.branch import main as br;\nfrom f.rel.leaf_1 import main as lf_1;\nfrom ..leaf_1 import main as lf_12;\nfrom ...rel.leaf_2 import main as lf_2;\n\ndef check():\n return [br(), lf_1(), lf_2(), lf_12()];\n\ndef preprocessor(event):\n return {\n # return the args to be passed to the runnable\n }\n","language":"python3","input_transforms":{"event":{"type":"static"}}}}}$tag$, 'system' ); @@ -87,19 +88,35 @@ INSERT INTO public.app(id, workspace_id, path, versions, policy) VALUES ( INSERT INTO public.app_version(id, app_id, value, created_by) VALUES ( 0, 2, -$tag${"grid":[{"3":{"h":2,"w":6,"x":0,"y":0,"fixed":true,"fullHeight":false},"12":{"h":2,"w":12,"x":0,"y":0,"fixed":true,"fullHeight":false},"id":"topbar","data":{"id":"topbar","type":"containercomponent","customCss":{"container":{"class":"!p-0","style":""}},"configuration":{},"numberOfSubgrids":1}},{"3":{"h":8,"w":2,"x":0,"y":2,"fixed":false,"fullHeight":false},"12":{"h":2,"w":6,"x":0,"y":2,"fixed":false,"fullHeight":false},"id":"a","data":{"id":"a","type":"containercomponent","customCss":{"container":{"class":"","style":""}},"configuration":{},"numberOfSubgrids":1}},{"3":{"h":1,"w":1,"x":2,"y":2,"fixed":false,"fullHeight":false},"12":{"h":1,"w":2,"x":6,"y":2,"fixed":false,"fullHeight":false},"id":"dontpressmeplz","data":{"id":"dontpressmeplz","type":"buttoncomponent","customCss":{"button":{"class":"","style":""},"container":{"class":"","style":""}},"recomputeIds":[],"configuration":{"size":{"type":"static","value":"xs"},"color":{"type":"static","value":"blue"},"label":{"type":"static","value":"Press me"},"onError":{"type":"oneOf","selected":"errorOverlay","configuration":{"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"setTab":{"setTab":{"type":"static","value":[]}},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"errorOverlay":{},"sendErrorToast":{"message":{"type":"static","value":"An error occured"},"appendError":{"type":"static","value":true}}}},"disabled":{"type":"static","value":false},"afterIcon":{"type":"static"},"onSuccess":{"type":"oneOf","selected":"none","configuration":{"none":{},"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"setTab":{"setTab":{"type":"static","value":[]}},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"openModal":{"modalId":{"type":"static","value":""}},"sendToast":{"message":{"type":"static","value":""}},"clearFiles":{"id":{"type":"static","value":""}},"closeModal":{"modalId":{"type":"static","value":""}}}},"beforeIcon":{"type":"static"},"fillContainer":{"type":"static","value":false},"triggerOnAppLoad":{"type":"static","value":false},"confirmationModal":{"type":"oneOf","selected":"none","configuration":{"none":{},"confirmationModal":{"title":{"type":"static","value":"Title"},"description":{"type":"static","value":"Are you sure?"},"confirmationText":{"type":"static","value":"Confirm"}}}}},"componentInput":{"type":"runnable","fields":{"x":{"type":"static","value":null,"fieldType":"string"}},"runnable":{"name":"Inline Script","type":"runnableByName","inlineScript":{"path":"u/admin@windmill.dev/newapp/Inline_Script","schema":{"type":"object","$schema":"https://json-schema.org/draft/2020-12/schema","required":["x"],"properties":{"x":{"default":null,"description":"","originalType":"string","type":"string"}}},"content":"from f.rel.leaf_2 import main as lf_2;\n\ndef check():\n return [lf_2()];\n \ndef main(x: str):\n return x","language":"python3"}},"fieldType":"any","autoRefresh":false,"recomputeOnInputChanged":false},"verticalAlignment":"center","horizontalAlignment":"center"}},{"3":{"h":1,"w":1,"x":2,"y":3,"fixed":false,"fullHeight":false},"12":{"h":1,"w":2,"x":8,"y":2,"fixed":false,"fullHeight":false},"id":"d","data":{"id":"d","type":"checkboxcomponent","customCss":{"text":{"class":"","style":""},"container":{"class":"","style":""}},"recomputeIds":[],"configuration":{"label":{"type":"static","value":"Label"},"disabled":{"type":"static","value":false},"defaultValue":{"type":"static","value":false}},"verticalAlignment":"center","horizontalAlignment":"center"}},{"3":{"h":1,"w":1,"x":2,"y":4,"fixed":false,"fullHeight":false},"12":{"h":1,"w":2,"x":6,"y":3,"fixed":false,"fullHeight":false},"id":"youcanpressme","data":{"id":"youcanpressme","type":"buttoncomponent","customCss":{"button":{"class":"","style":""},"container":{"class":"","style":""}},"recomputeIds":[],"configuration":{"size":{"type":"static","value":"xs"},"color":{"type":"static","value":"blue"},"label":{"type":"static","value":"Press me"},"onError":{"type":"oneOf","selected":"errorOverlay","configuration":{"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"setTab":{"setTab":{"type":"static","value":[]}},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"errorOverlay":{},"sendErrorToast":{"message":{"type":"static","value":"An error occured"},"appendError":{"type":"static","value":true}}}},"disabled":{"type":"static","value":false},"afterIcon":{"type":"static"},"onSuccess":{"type":"oneOf","selected":"none","configuration":{"none":{},"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"setTab":{"setTab":{"type":"static","value":[]}},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"openModal":{"modalId":{"type":"static","value":""}},"sendToast":{"message":{"type":"static","value":""}},"clearFiles":{"id":{"type":"static","value":""}},"closeModal":{"modalId":{"type":"static","value":""}}}},"beforeIcon":{"type":"static"},"fillContainer":{"type":"static","value":false},"triggerOnAppLoad":{"type":"static","value":false},"confirmationModal":{"type":"oneOf","selected":"none","configuration":{"none":{},"confirmationModal":{"title":{"type":"static","value":"Title"},"description":{"type":"static","value":"Are you sure?"},"confirmationText":{"type":"static","value":"Confirm"}}}}},"componentInput":{"type":"runnable","fields":{"x":{"type":"static","value":null,"fieldType":"string"}},"runnable":{"name":"Inline Script","type":"runnableByName","inlineScript":{"path":"u/admin/easy_to_use_app/Inline_Script","schema":{"type":"object","$schema":"https://json-schema.org/draft/2020-12/schema","required":["x"],"properties":{"x":{"default":null,"description":"","originalType":"string","type":"string"}}},"content":"from f.rel.branch import main as br;\n\ndef check():\n return [br()];\n\ndef main(x: str):\n return x","language":"python3"}},"fieldType":"any","autoRefresh":false,"recomputeOnInputChanged":false},"verticalAlignment":"center","horizontalAlignment":"center"}}],"theme":{"path":"f/app_themes/theme_0","type":"path"},"subgrids":{"a-0":[{"3":{"h":1,"w":1,"x":0,"y":0,"fixed":false,"fullHeight":false},"12":{"h":2,"w":5,"x":0,"y":0,"fixed":false,"fullHeight":false},"id":"pressmeplz","data":{"id":"pressmeplz","type":"buttoncomponent","customCss":{"button":{"class":"","style":""},"container":{"class":"","style":""}},"recomputeIds":[],"configuration":{"size":{"type":"static","value":"xs"},"color":{"type":"static","value":"blue"},"label":{"type":"static","value":"Press me"},"onError":{"type":"oneOf","selected":"errorOverlay","configuration":{"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"setTab":{"setTab":{"type":"static","value":[]}},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"errorOverlay":{},"sendErrorToast":{"message":{"type":"static","value":"An error occured"},"appendError":{"type":"static","value":true}}}},"disabled":{"type":"static","value":false},"afterIcon":{"type":"static"},"onSuccess":{"type":"oneOf","selected":"none","configuration":{"none":{},"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"setTab":{"setTab":{"type":"static","value":[]}},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"openModal":{"modalId":{"type":"static","value":""}},"sendToast":{"message":{"type":"static","value":""}},"clearFiles":{"id":{"type":"static","value":""}},"closeModal":{"modalId":{"type":"static","value":""}}}},"beforeIcon":{"type":"static"},"fillContainer":{"type":"static","value":false},"triggerOnAppLoad":{"type":"static","value":false},"confirmationModal":{"type":"oneOf","selected":"none","configuration":{"none":{},"confirmationModal":{"title":{"type":"static","value":"Title"},"description":{"type":"static","value":"Are you sure?"},"confirmationText":{"type":"static","value":"Confirm"}}}}},"componentInput":{"type":"runnable","fields":{"x":{"type":"static","value":null,"fieldType":"string"}},"runnable":{"name":"Inline Script","type":"runnableByName","inlineScript":{"path":"u/admin@windmill.dev/newapp/Inline_Script","schema":{"type":"object","$schema":"https://json-schema.org/draft/2020-12/schema","required":["x"],"properties":{"x":{"default":null,"description":"","originalType":"string","type":"string"}}},"content":"from f.rel.branch import main as br;\nfrom f.rel.leaf_1 import main as lf_1;\nfrom ..leaf_1 import main as lf_12;\nfrom ...rel.leaf_2 import main as lf_2;\n\ndef check():\n return [br(), lf_1(), lf_2(), lf_12()];\n\n\ndef main(x: str):\n return x","language":"python3"}},"fieldType":"any","autoRefresh":false,"recomputeOnInputChanged":false},"verticalAlignment":"center","horizontalAlignment":"center"}}],"topbar-0":[{"3":{"h":1,"w":6,"x":0,"y":0,"fixed":false,"fullHeight":false},"12":{"h":1,"w":6,"x":0,"y":0,"fixed":false,"fullHeight":false},"id":"title","data":{"id":"title","type":"textcomponent","customCss":{"text":{"class":"text-xl font-semibold whitespace-nowrap truncate","style":""},"container":{"class":"","style":""}},"configuration":{"style":{"type":"static","value":"Body"},"tooltip":{"expr":"`Author: ${ctx.author}`","type":"evalv2","value":"","fieldType":"text","connections":[{"id":"author","componentId":"ctx"}]},"copyButton":{"type":"static","value":false},"disableNoText":{"type":"static","value":true,"fieldType":"boolean"}},"componentInput":{"eval":"${ctx.summary}","type":"templatev2","fieldType":"template","connections":[{"id":"summary","componentId":"ctx"}]},"verticalAlignment":"center","horizontalAlignment":"left"}},{"3":{"h":1,"w":3,"x":0,"y":1,"fixed":false,"fullHeight":false},"12":{"h":1,"w":6,"x":6,"y":0,"fixed":false,"fullHeight":false},"id":"recomputeall","data":{"id":"recomputeall","type":"recomputeallcomponent","customCss":{"container":{"class":"","style":""}},"menuItems":[],"configuration":{"defaultRefreshInterval":{"type":"static","value":"0"}},"verticalAlignment":"center","horizontalAlignment":"right"}}]},"fullscreen":false,"norefreshbar":false,"hideLegacyTopBar":true,"hiddenInlineScripts":[],"unusedInlineScripts":[],"mobileViewOnSmallerScreens":false}$tag$, +$tag${"grid":[{"3":{"h":2,"w":6,"x":0,"y":0,"fixed":true,"fullHeight":false},"12":{"h":2,"w":12,"x":0,"y":0,"fixed":true,"fullHeight":false},"id":"topbar","data":{"id":"topbar","type":"containercomponent","customCss":{"container":{"class":"!p-0","style":""}},"configuration":{},"numberOfSubgrids":1}},{"3":{"h":8,"w":2,"x":0,"y":2,"fixed":false,"fullHeight":false},"12":{"h":2,"w":6,"x":0,"y":2,"fixed":false,"fullHeight":false},"id":"a","data":{"id":"a","type":"containercomponent","customCss":{"container":{"class":"","style":""}},"configuration":{},"numberOfSubgrids":1}},{"3":{"h":1,"w":1,"x":2,"y":2,"fixed":false,"fullHeight":false},"12":{"h":1,"w":2,"x":6,"y":2,"fixed":false,"fullHeight":false},"id":"dontpressmeplz","data":{"id":"dontpressmeplz","type":"buttoncomponent","customCss":{"button":{"class":"","style":""},"container":{"class":"","style":""}},"recomputeIds":[],"configuration":{"size":{"type":"static","value":"xs"},"color":{"type":"static","value":"blue"},"label":{"type":"static","value":"Press me"},"onError":{"type":"oneOf","selected":"errorOverlay","configuration":{"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"setTab":{"setTab":{"type":"static","value":[]}},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"errorOverlay":{},"sendErrorToast":{"message":{"type":"static","value":"An error occured"},"appendError":{"type":"static","value":true}}}},"disabled":{"type":"static","value":false},"afterIcon":{"type":"static"},"onSuccess":{"type":"oneOf","selected":"none","configuration":{"none":{},"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"setTab":{"setTab":{"type":"static","value":[]}},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"openModal":{"modalId":{"type":"static","value":""}},"sendToast":{"message":{"type":"static","value":""}},"clearFiles":{"id":{"type":"static","value":""}},"closeModal":{"modalId":{"type":"static","value":""}}}},"beforeIcon":{"type":"static"},"fillContainer":{"type":"static","value":false},"triggerOnAppLoad":{"type":"static","value":false},"confirmationModal":{"type":"oneOf","selected":"none","configuration":{"none":{},"confirmationModal":{"title":{"type":"static","value":"Title"},"description":{"type":"static","value":"Are you sure?"},"confirmationText":{"type":"static","value":"Confirm"}}}}},"componentInput":{"type":"runnable","fields":{"x":{"type":"static","value":null,"fieldType":"string"}},"runnable":{"name":"Inline Script","type":"runnableByName","inlineScript":{"path":"u/admin@windmill.dev/newapp/Inline_Script","schema":{"type":"object","$schema":"https://json-schema.org/draft/2020-12/schema","required":["x"],"properties":{"x":{"default":null,"description":"","originalType":"string","type":"string"}}},"content":"#requirements: test\nfrom f.rel.leaf_2 import main as lf_2;\n\ndef check():\n return [lf_2()];\n \ndef main(x: str):\n return x","language":"python3"}},"fieldType":"any","autoRefresh":false,"recomputeOnInputChanged":false},"verticalAlignment":"center","horizontalAlignment":"center"}},{"3":{"h":1,"w":1,"x":2,"y":3,"fixed":false,"fullHeight":false},"12":{"h":1,"w":2,"x":8,"y":2,"fixed":false,"fullHeight":false},"id":"d","data":{"id":"d","type":"checkboxcomponent","customCss":{"text":{"class":"","style":""},"container":{"class":"","style":""}},"recomputeIds":[],"configuration":{"label":{"type":"static","value":"Label"},"disabled":{"type":"static","value":false},"defaultValue":{"type":"static","value":false}},"verticalAlignment":"center","horizontalAlignment":"center"}},{"3":{"h":1,"w":1,"x":2,"y":4,"fixed":false,"fullHeight":false},"12":{"h":1,"w":2,"x":6,"y":3,"fixed":false,"fullHeight":false},"id":"youcanpressme","data":{"id":"youcanpressme","type":"buttoncomponent","customCss":{"button":{"class":"","style":""},"container":{"class":"","style":""}},"recomputeIds":[],"configuration":{"size":{"type":"static","value":"xs"},"color":{"type":"static","value":"blue"},"label":{"type":"static","value":"Press me"},"onError":{"type":"oneOf","selected":"errorOverlay","configuration":{"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"setTab":{"setTab":{"type":"static","value":[]}},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"errorOverlay":{},"sendErrorToast":{"message":{"type":"static","value":"An error occured"},"appendError":{"type":"static","value":true}}}},"disabled":{"type":"static","value":false},"afterIcon":{"type":"static"},"onSuccess":{"type":"oneOf","selected":"none","configuration":{"none":{},"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"setTab":{"setTab":{"type":"static","value":[]}},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"openModal":{"modalId":{"type":"static","value":""}},"sendToast":{"message":{"type":"static","value":""}},"clearFiles":{"id":{"type":"static","value":""}},"closeModal":{"modalId":{"type":"static","value":""}}}},"beforeIcon":{"type":"static"},"fillContainer":{"type":"static","value":false},"triggerOnAppLoad":{"type":"static","value":false},"confirmationModal":{"type":"oneOf","selected":"none","configuration":{"none":{},"confirmationModal":{"title":{"type":"static","value":"Title"},"description":{"type":"static","value":"Are you sure?"},"confirmationText":{"type":"static","value":"Confirm"}}}}},"componentInput":{"type":"runnable","fields":{"x":{"type":"static","value":null,"fieldType":"string"}},"runnable":{"name":"Inline Script","type":"runnableByName","inlineScript":{"path":"u/admin/easy_to_use_app/Inline_Script","schema":{"type":"object","$schema":"https://json-schema.org/draft/2020-12/schema","required":["x"],"properties":{"x":{"default":null,"description":"","originalType":"string","type":"string"}}},"content":"from f.rel.branch import main as br;\n\ndef check():\n return [br()];\n\ndef main(x: str):\n return x","language":"python3"}},"fieldType":"any","autoRefresh":false,"recomputeOnInputChanged":false},"verticalAlignment":"center","horizontalAlignment":"center"}}],"theme":{"path":"f/app_themes/theme_0","type":"path"},"subgrids":{"a-0":[{"3":{"h":1,"w":1,"x":0,"y":0,"fixed":false,"fullHeight":false},"12":{"h":2,"w":5,"x":0,"y":0,"fixed":false,"fullHeight":false},"id":"pressmeplz","data":{"id":"pressmeplz","type":"buttoncomponent","customCss":{"button":{"class":"","style":""},"container":{"class":"","style":""}},"recomputeIds":[],"configuration":{"size":{"type":"static","value":"xs"},"color":{"type":"static","value":"blue"},"label":{"type":"static","value":"Press me"},"onError":{"type":"oneOf","selected":"errorOverlay","configuration":{"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"setTab":{"setTab":{"type":"static","value":[]}},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"errorOverlay":{},"sendErrorToast":{"message":{"type":"static","value":"An error occured"},"appendError":{"type":"static","value":true}}}},"disabled":{"type":"static","value":false},"afterIcon":{"type":"static"},"onSuccess":{"type":"oneOf","selected":"none","configuration":{"none":{},"open":{"id":{"type":"static","value":""}},"close":{"id":{"type":"static","value":""}},"setTab":{"setTab":{"type":"static","value":[]}},"gotoUrl":{"url":{"type":"static","value":""},"newTab":{"type":"static","value":true}},"openModal":{"modalId":{"type":"static","value":""}},"sendToast":{"message":{"type":"static","value":""}},"clearFiles":{"id":{"type":"static","value":""}},"closeModal":{"modalId":{"type":"static","value":""}}}},"beforeIcon":{"type":"static"},"fillContainer":{"type":"static","value":false},"triggerOnAppLoad":{"type":"static","value":false},"confirmationModal":{"type":"oneOf","selected":"none","configuration":{"none":{},"confirmationModal":{"title":{"type":"static","value":"Title"},"description":{"type":"static","value":"Are you sure?"},"confirmationText":{"type":"static","value":"Confirm"}}}}},"componentInput":{"type":"runnable","fields":{"x":{"type":"static","value":null,"fieldType":"string"}},"runnable":{"name":"Inline Script","type":"runnableByName","inlineScript":{"path":"u/admin@windmill.dev/newapp/Inline_Script","schema":{"type":"object","$schema":"https://json-schema.org/draft/2020-12/schema","required":["x"],"properties":{"x":{"default":null,"description":"","originalType":"string","type":"string"}}},"content":"from f.rel.branch import main as br;\nfrom f.rel.leaf_1 import main as lf_1;\nfrom ..leaf_1 import main as lf_12;\nfrom ...rel.leaf_2 import main as lf_2;\n\ndef check():\n return [br(), lf_1(), lf_2(), lf_12()];\n\n\ndef main(x: str):\n return x","language":"python3"}},"fieldType":"any","autoRefresh":false,"recomputeOnInputChanged":false},"verticalAlignment":"center","horizontalAlignment":"center"}}],"topbar-0":[{"3":{"h":1,"w":6,"x":0,"y":0,"fixed":false,"fullHeight":false},"12":{"h":1,"w":6,"x":0,"y":0,"fixed":false,"fullHeight":false},"id":"title","data":{"id":"title","type":"textcomponent","customCss":{"text":{"class":"text-xl font-semibold whitespace-nowrap truncate","style":""},"container":{"class":"","style":""}},"configuration":{"style":{"type":"static","value":"Body"},"tooltip":{"expr":"`Author: ${ctx.author}`","type":"evalv2","value":"","fieldType":"text","connections":[{"id":"author","componentId":"ctx"}]},"copyButton":{"type":"static","value":false},"disableNoText":{"type":"static","value":true,"fieldType":"boolean"}},"componentInput":{"eval":"${ctx.summary}","type":"templatev2","fieldType":"template","connections":[{"id":"summary","componentId":"ctx"}]},"verticalAlignment":"center","horizontalAlignment":"left"}},{"3":{"h":1,"w":3,"x":0,"y":1,"fixed":false,"fullHeight":false},"12":{"h":1,"w":6,"x":6,"y":0,"fixed":false,"fullHeight":false},"id":"recomputeall","data":{"id":"recomputeall","type":"recomputeallcomponent","customCss":{"container":{"class":"","style":""}},"menuItems":[],"configuration":{"defaultRefreshInterval":{"type":"static","value":"0"}},"verticalAlignment":"center","horizontalAlignment":"right"}}]},"fullscreen":false,"norefreshbar":false,"hideLegacyTopBar":true,"hiddenInlineScripts":[],"unusedInlineScripts":[],"mobileViewOnSmallerScreens":false}$tag$, 'system' ); +INSERT INTO public.workspace_dependencies(name, content, language, workspace_id) VALUES ( +'test', +'', +'python3', +'test-workspace' +); + +INSERT INTO public.workspace_dependencies(content, language, workspace_id) VALUES ( +'', +'python3', +'test-workspace' +); + -- Prebuild dependency_map -- It would be done by Windmill, but this one is static. INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/branch', 'script', 'f/rel/leaf_1', ''); INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_script', 'script', 'f/rel/branch', ''); INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_script', 'script', 'f/rel/leaf_1', ''); INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_script', 'script', 'f/rel/leaf_2', ''); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_script', 'script', 'dependencies/test.requirements.in', ''); INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_app', 'app', 'f/rel/leaf_2', 'dontpressmeplz'); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_app', 'app', 'dependencies/test.requirements.in', 'dontpressmeplz'); INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'f/rel/leaf_2', 'failure'); INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'f/rel/branch', 'nstep1'); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'dependencies/test.requirements.in', 'nstep1'); INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'f/rel/leaf_1', 'nstep1'); INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'f/rel/leaf_2', 'nstep1'); INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'f/rel/leaf_2', 'nstep2_2'); @@ -116,3 +133,18 @@ INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'f/rel/leaf_2', 'qtool1'); INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_app', 'app', 'f/rel/branch', 'youcanpressme'); +-- Default +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/leaf_1', 'script', 'dependencies/requirements.in', ''); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/leaf_2', 'script', 'dependencies/requirements.in', ''); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/branch', 'script', 'dependencies/requirements.in', ''); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'dependencies/requirements.in', 'failure'); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'dependencies/package.json', 'nstep2_1'); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'dependencies/package.json', 'nstep3_2'); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'dependencies/requirements.in', 'nstep2_2'); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'dependencies/requirements.in', 'nstep3_1'); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'dependencies/requirements.in', 'nstep4_1'); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'dependencies/requirements.in', 'nstep5_1'); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'dependencies/requirements.in', 'preprocessor'); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_app', 'app', 'dependencies/requirements.in', 'pressmeplz'); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_flow', 'flow', 'dependencies/requirements.in', 'qtool1'); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/rel/root_app', 'app', 'dependencies/requirements.in', 'youcanpressme'); diff --git a/backend/tests/fixtures/hub_sync_blacklist.sql b/backend/tests/fixtures/hub_sync_blacklist.sql new file mode 100644 index 0000000000..b3c59b2edb --- /dev/null +++ b/backend/tests/fixtures/hub_sync_blacklist.sql @@ -0,0 +1,15 @@ +-- Fixture for testing hub_sync blacklist from workspace dependencies +-- hub_sync and apps are already built-in via migrations + +-- Insert a simple Bun script that should be affected by workspace dependencies +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'admins', +'test-user', +' +export async function main() { + return "Simple bun script"; +}', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'Simple bun script', +'', +'u/admin/simple_bun', 700001, 'bun', ''); diff --git a/backend/tests/fixtures/workspace_dependencies.sql b/backend/tests/fixtures/workspace_dependencies.sql new file mode 100644 index 0000000000..9491f62e7c --- /dev/null +++ b/backend/tests/fixtures/workspace_dependencies.sql @@ -0,0 +1,33 @@ +-- NOTE: Applied after workspace_dependencies_leafs.sql +-- Python script that uses relative imports +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +' +from f.leafs.python import main as python_leaf + +def main(): + return {"python_import": python_leaf()}', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/python_importer', 500005, 'python3', ''); + +-- TypeScript script that uses relative imports +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +' +import { main as tsLeaf } from "./leafs/ts"; + +export async function main() { + return { ts_import: await tsLeaf() }; +}', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/ts_importer', 500006, 'nativets', ''); + +-- Dependency map entries +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/python_importer', 'script', 'f/leafs/python', ''); +INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ('test-workspace', 'f/ts_importer', 'script', 'f/leafs/ts', ''); diff --git a/backend/tests/fixtures/workspace_dependencies_leafs.sql b/backend/tests/fixtures/workspace_dependencies_leafs.sql new file mode 100644 index 0000000000..3896cb4b00 --- /dev/null +++ b/backend/tests/fixtures/workspace_dependencies_leafs.sql @@ -0,0 +1,54 @@ +-- Basic leaf scripts in different languages +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +' +export async function main() { + return "TypeScript leaf"; +}', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/leafs/ts', 500001, 'nativets', ''); + +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +' +package main + +import "fmt" + +func main() { + fmt.Println("Go leaf") +}', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/leafs/go', 500002, 'go', ''); + +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +' +def main(): + return "Python leaf"', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/leafs/python', 500003, 'python3', ''); + +INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES ( +'test-workspace', +'test-user', +' +', +'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}', +'', +'', +'f/leafs/php', 500004, 'php', ''); + diff --git a/backend/tests/python_jobs.rs b/backend/tests/python_jobs.rs index afa662f582..549e052b65 100644 --- a/backend/tests/python_jobs.rs +++ b/backend/tests/python_jobs.rs @@ -25,7 +25,7 @@ def main(): &db, content, ScriptLang::Python3, - vec!["# py: 3.11.11", "tiny==0.1.3"], + vec!["# workspace-dependencies-mode: manual\n# py: 3.11.11","tiny==0.1.3"], ) .await?; Ok(()) @@ -55,7 +55,11 @@ def main(): &db, content, ScriptLang::Python3, - vec!["# py: 3.11.11", "bottle==0.13.2", "tiny==0.1.2"], + vec![ + "# workspace-dependencies-mode: extra\n# py: 3.11.11", + "bottle==0.13.2", + "tiny==0.1.2", + ], ) .await?; } @@ -79,7 +83,11 @@ def main(): &db, content, ScriptLang::Python3, - vec!["# py: 3.11.11", "simplejson==3.20.1", "tiny==0.1.3"], + vec![ + "# workspace-dependencies-mode: extra\n# py: 3.11.11", + "simplejson==3.20.1", + "tiny==0.1.3", + ], ) .await?; Ok(()) @@ -108,7 +116,7 @@ def main(): content, ScriptLang::Python3, vec![ - "# py: 3.11.11", + "# workspace-dependencies-mode: extra\n# py: 3.11.11", "bottle==0.13.2", "microdot==2.2.0", "simplejson==3.19.3", diff --git a/backend/tests/workspace_dependencies.rs b/backend/tests/workspace_dependencies.rs new file mode 100644 index 0000000000..6875520125 --- /dev/null +++ b/backend/tests/workspace_dependencies.rs @@ -0,0 +1,194 @@ +mod common; +mod workspace_dependencies { + + use crate::common::in_test_worker; + use crate::common::init_client; + use crate::common::listen_for_completed_jobs; + use sqlx::{Pool, Postgres}; + use tokio_stream::StreamExt; + use windmill_common::scripts::ScriptLang; + use windmill_worker::workspace_dependencies::NewWorkspaceDependencies; + mod deps { + pub const REQUIREMENTS_IN: &'static str = "tiny==0.1.3"; + // pub const GO_MOD: &'static str = r##" + // module example.com/project + + // go 1.20 + + // require github.com/gin-gonic/gin v1.8.1 + // "##; + + pub const PACKAGE_JSON: &'static str = r##" + { + "name": "example-project", + "version": "1.0.0", + "dependencies": { + "express": "^4.17.1" + } + } + "##; + + pub const COMPOSER_JSON: &'static str = r##" + { + "name": "example/project", + "require": { + "monolog/monolog": "^2.3" + } + } + "##; + } + + #[cfg(feature = "python")] + #[sqlx::test(fixtures("base", "workspace_dependencies_leafs"))] + #[ignore] + async fn basic_manual_named(db: Pool) -> anyhow::Result<()> { + let ((_client, port, _s), db, mut completed) = ( + init_client(db.clone()).await, + &db, + listen_for_completed_jobs(&db).await, + ); + + for (idx, (l, c)) in [ + (ScriptLang::Python3, deps::REQUIREMENTS_IN), + (ScriptLang::Bun, deps::PACKAGE_JSON), + (ScriptLang::Php, deps::COMPOSER_JSON), + // (ScriptLang::Go, deps::GO_MOD), + ] + .iter() + .enumerate() + { + let id = NewWorkspaceDependencies { + workspace_id: "test-workspace".into(), + language: *l, + content: (*c).into(), + name: Some("test".to_owned()), + description: None, + } + .create("", "", "", db) + .await + .unwrap(); + + assert_eq!(idx + 1, id as usize); + } + + // Wait for 4 jobs. + // Creating those dependencies will trigger redeployment of all scripts in workspace_dependencies_leafs.sql + in_test_worker( + db, + async { + completed.next().await; + completed.next().await; + completed.next().await; + // completed.next().await; + }, + port, + ) + .await; + + // Verify all scripts have correct locks + // let mut langs = vec![]; + // for r in sqlx::query!( + // r#"SELECT language AS "language: ScriptLang",lock FROM script WHERE archived = false"# + // ) + // .fetch_all(db) + // .await + // .unwrap() + // { + // match r.language { + // ScriptLang::Python3 => assert_eq!("", &r.lock.unwrap()), + // ScriptLang::Go => todo!(), + // ScriptLang::Bun => todo!(), + // ScriptLang::Bunnative => todo!(), + // ScriptLang::Php => todo!(), + // _ => panic!("Unsupported language"), + // } + + // langs.push(r.language); + // } + + // langs.sort(); + // // Just tiny additional verification for peace of mind. + // assert_eq!(langs.as_slice(), &[]); + + Ok(()) + } + + #[sqlx::test(fixtures("base", "hub_sync_blacklist"))] + async fn hub_sync_blacklist_from_workspace_deps(db: Pool) -> anyhow::Result<()> { + let ((_client, port, _s), db, mut completed) = ( + init_client(db.clone()).await, + &db, + listen_for_completed_jobs(&db).await, + ); + + // Verify built-in fixtures exist + // Check that the setup_app exists + let app_exists = + sqlx::query_scalar!("SELECT EXISTS(SELECT 1 FROM app WHERE path = 'g/all/setup_app')") + .fetch_one(db) + .await + .unwrap(); + assert!(app_exists.unwrap(), "Expected g/all/setup_app to exist"); + + // Check that hub_sync script exists and is a Bun script + let hub_sync_lang = sqlx::query_scalar!( + r#"SELECT language AS "language: ScriptLang" FROM script WHERE path = 'u/admin/hub_sync'"# + ) + .fetch_one(db) + .await + .unwrap(); + assert_eq!( + hub_sync_lang, + ScriptLang::Bun, + "Expected hub_sync to be a Bun script" + ); + + // Create unnamed (default) workspace dependencies for Bun + let _id = NewWorkspaceDependencies { + workspace_id: "admins".into(), + language: ScriptLang::Bun, + content: deps::PACKAGE_JSON.into(), + name: None, // No name = default workspace dependencies + description: None, + } + .create("", "", "", db) + .await + .unwrap(); + + // Wait for exactly 1 job (only u/admin/simple_bun, not hub_sync) + let job_id = in_test_worker(db, async { completed.next().await }, port) + .await + .expect("Expected one job to complete"); + + // Query the job's runnable_path + let runnable_path = + sqlx::query_scalar!("SELECT runnable_path FROM v2_job WHERE id = $1", job_id) + .fetch_one(db) + .await + .unwrap(); + + assert_eq!( + runnable_path, + Some("u/admin/simple_bun".to_string()), + "Expected job runnable_path to be 'u/admin/simple_bun' (hub_sync should be blacklisted)" + ); + + // Assert total job count is 1 + let job_count = sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job") + .fetch_one(db) + .await + .unwrap(); + + assert_eq!(job_count, Some(1), "Expected exactly one job total"); + + // Assert v2_job_queue is empty + let queue_count = sqlx::query_scalar!("SELECT COUNT(*) FROM v2_job_queue") + .fetch_one(db) + .await + .unwrap(); + + assert_eq!(queue_count, Some(0), "Expected job queue to be empty"); + + Ok(()) + } +} diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 7ee99aca7b..43c9a40053 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -2249,6 +2249,56 @@ paths: text/plain: schema: type: string + /w/{workspace}/workspaces/get_dependents/{imported_path}: + get: + summary: get dependents of an imported path + operationId: getDependents + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - name: imported_path + in: path + required: true + schema: + type: string + description: The imported path to get dependents for + responses: + "200": + description: list of dependents + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/DependencyDependent" + + /w/{workspace}/workspaces/get_dependents_amounts: + post: + summary: get dependents amounts for multiple imported paths + operationId: getDependentsAmounts + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: List of imported paths to get dependents counts for + required: true + content: + application/json: + schema: + type: array + items: + type: string + responses: + "200": + description: list of dependents amounts + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/DependentsAmount" /w/{workspace}/workspaces/get_dependency_map: get: @@ -5400,6 +5450,126 @@ paths: schema: type: boolean + /w/{workspace}/workspace_dependencies/create: + post: + summary: create workspace dependencies + operationId: createWorkspaceDependencies + tags: + - workspace_dependencies + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: New workspace dependencies + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/NewWorkspaceDependencies" + + responses: + "201": + description: workspace dependencies created + content: + text/plain: + schema: + type: string + + /w/{workspace}/workspace_dependencies/archive/{language}: + post: + summary: archive workspace dependencies (require admin) + operationId: archiveWorkspaceDependencies + tags: + - workspace_dependencies + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - name: language + in: path + required: true + schema: + $ref: "#/components/schemas/ScriptLang" + - name: name + in: query + required: false + schema: + type: string + + responses: + "200": + description: result + content: + application/json: + schema: {} + + /w/{workspace}/workspace_dependencies/delete/{language}: + post: + summary: delete workspace dependencies (require admin) + operationId: deleteWorkspaceDependencies + tags: + - workspace_dependencies + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - name: language + in: path + required: true + schema: + $ref: "#/components/schemas/ScriptLang" + - name: name + in: query + required: false + schema: + type: string + + responses: + "200": + description: result + content: + application/json: + schema: {} + + /w/{workspace}/workspace_dependencies/list: + get: + summary: list all workspace dependencies + operationId: listWorkspaceDependencies + tags: + - workspace_dependencies + parameters: + - $ref: "#/components/parameters/WorkspaceId" + responses: + "200": + description: All workspace dependencies + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/WorkspaceDependencies" + + /w/{workspace}/workspace_dependencies/get_latest/{language}: + get: + summary: get latest workspace dependencies by language and name + operationId: getLatestWorkspaceDependencies + tags: + - workspace_dependencies + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - name: language + in: path + required: true + schema: + $ref: "#/components/schemas/ScriptLang" + - name: name + in: query + required: false + schema: + type: string + responses: + "200": + description: Latest workspace dependencies + content: + application/json: + schema: + $ref: "#/components/schemas/WorkspaceDependencies" + /w/{workspace}/scripts/archive/p/{path}: post: summary: archive script by path @@ -15759,6 +15929,52 @@ components: items: $ref: "#/components/schemas/Alert" + WorkspaceDependencies: + type: object + properties: + id: + type: integer + archived: + type: boolean + name: + type: string + description: + type: string + content: + type: string + language: + $ref: "#/components/schemas/ScriptLang" + workspace_id: + type: string + created_at: + type: string + format: date-time + required: + - workspace_id + - language + - created_at + - content + - id + - archived + + NewWorkspaceDependencies: + type: object + properties: + workspace_id: + type: string + language: + $ref: "#/components/schemas/ScriptLang" + name: + type: string + description: + type: string + content: + type: string + required: + - workspace_id + - language + - content + Script: type: object properties: @@ -18957,6 +19173,38 @@ components: type: string nullable: true + DependencyDependent: + type: object + properties: + importer_path: + type: string + importer_kind: + type: string + enum: + - script + - flow + - app + importer_node_ids: + type: array + items: + type: string + nullable: true + required: + - importer_path + - importer_kind + + DependentsAmount: + type: object + properties: + imported_path: + type: string + count: + type: integer + format: int64 + required: + - imported_path + - count + WorkspaceInvite: type: object properties: diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 86e16a3bb0..584ddad54c 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -25,6 +25,7 @@ use std::str::FromStr; use std::time::Instant; use tokio::io::AsyncReadExt; use tower::ServiceBuilder; +use url::Url; #[cfg(all(feature = "enterprise", feature = "smtp"))] use windmill_common::auth::is_super_admin_email; use windmill_common::auth::TOKEN_PREFIX_LEN; @@ -39,6 +40,9 @@ use windmill_common::jobs::{ use windmill_common::s3_helpers::{upload_artifact_to_store, BundleFormat}; use windmill_common::utils::{RunnableKind, WarnAfterExt}; use windmill_common::worker::{Connection, CLOUD_HOSTED, TMP_DIR}; +use windmill_common::workspace_dependencies::{ + RawWorkspaceDependencies, MIN_VERSION_WORKSPACE_DEPENDENCIES, +}; use windmill_common::DYNAMIC_INPUT_CACHE; #[cfg(all(feature = "enterprise", feature = "smtp"))] use windmill_common::{email_oss::send_email_html, server::load_smtp_config}; @@ -6116,14 +6120,17 @@ async fn run_bundle_preview_script( Ok((StatusCode::CREATED, job_id.unwrap().to_string())) } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] pub struct RunDependenciesRequest { pub raw_scripts: Vec, pub entrypoint: String, + #[serde(default)] + pub raw_workspace_dependencies: Option, + #[serde(default)] pub raw_deps: Option, } -#[derive(Deserialize, Clone)] +#[derive(Deserialize, Clone, Debug)] pub struct RawScriptForDependencies { pub script_path: String, pub raw_code: Option, @@ -6148,41 +6155,49 @@ async fn run_dependencies_job( )); } + if req.raw_deps.is_some() { + return Err(error::Error::MigrationNeeded { + feature: "cli is outdated".into(), + version: MIN_VERSION_WORKSPACE_DEPENDENCIES.to_owned(), + guide_url: Url::from_str( + "https://www.windmill.dev/docs/core_concepts/workspace_dependencies/migration", + )?, + }); + } + + // Check if workers support workspace dependencies feature + if req.raw_workspace_dependencies.is_some() { + windmill_common::workspace_dependencies::min_version_supports_v0_workspace_dependencies() + .await?; + } + if req.raw_scripts.len() != 1 || req.raw_scripts[0].script_path != req.entrypoint { return Err(error::Error::internal_err( "For now only a single raw script can be passed to this endpoint, and the entrypoint should be set to the script path".to_string(), )); } - let raw_script = req.raw_scripts[0].clone(); - let script_path = raw_script.script_path; - let ehm = HashMap::new(); - let raw_code = raw_script.raw_code.unwrap_or_else(|| "".to_string()); - let language = raw_script.language; - let (args, raw_code) = if let Some(deps) = req.raw_deps { - let mut hm = HashMap::new(); - hm.insert( - "raw_deps".to_string(), - JsonRawValue::from_string("true".to_string()).unwrap(), - ); - if language == ScriptLang::Bun { - let annotation = windmill_common::worker::TypeScriptAnnotations::parse(&raw_code); - hm.insert( - "npm_mode".to_string(), - JsonRawValue::from_string(annotation.npm.to_string()).unwrap(), - ); - } - (PushArgs { extra: Some(hm), args: &ehm }, deps) - } else { - (PushArgs::from(&ehm), raw_code) - }; + let RawScriptForDependencies { + // unwrap + script_path, + raw_code, + language, + } = req.raw_scripts[0].clone(); + + let mut hm = HashMap::new(); + req.raw_workspace_dependencies + .map(|v| hm.insert("raw_workspace_dependencies".to_owned(), to_raw_value(&v))); let (uuid, tx) = push( &db, PushIsolationLevel::IsolatedRoot(db.clone()), &w_id, - JobPayload::RawScriptDependencies { script_path, content: raw_code, language }, - args, + JobPayload::RawScriptDependencies { + script_path, + content: raw_code.unwrap_or_default(), + language, + }, + PushArgs { extra: Some(hm), args: &HashMap::new() }, authed.display_username(), &authed.email, username_to_permissioned_as(&authed.username), @@ -6218,6 +6233,9 @@ async fn run_dependencies_job( pub struct RunFlowDependenciesRequest { pub path: String, pub flow_value: FlowValue, + #[serde(default)] + pub raw_workspace_dependencies: Option, + #[serde(default)] pub raw_deps: Option>, } @@ -6239,13 +6257,28 @@ async fn run_flow_dependencies_job( )); } - // Create args HashMap with skip_flow_update and raw_deps if present + if req.raw_deps.is_some() { + return Err(error::Error::MigrationNeeded { + feature: "cli is outdated".into(), + version: MIN_VERSION_WORKSPACE_DEPENDENCIES.to_owned(), + guide_url: Url::from_str( + "https://www.windmill.dev/docs/core_concepts/workspace_dependencies/migration", + )?, + }); + } + + // Check if workers support workspace dependencies feature + if req.raw_workspace_dependencies.is_some() { + windmill_common::workspace_dependencies::min_version_supports_v0_workspace_dependencies() + .await?; + } + + // Create args HashMap with skip_flow_update and raw_workspace_dependencies if present let mut args_map = HashMap::from([("skip_flow_update".to_string(), to_raw_value(&true))]); - // Add raw_deps to args if present - if let Some(ref raw_deps) = req.raw_deps { - args_map.insert("raw_deps".to_string(), to_raw_value(raw_deps)); - } + // Add raw_workspace_dependencies to args if present + req.raw_workspace_dependencies + .map(|v| args_map.insert("raw_workspace_dependencies".to_string(), to_raw_value(&v))); let (uuid, tx) = push( &db, diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index 96bf0ed085..ac39df0ee2 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -110,6 +110,7 @@ mod openapi; #[cfg(all(feature = "private", feature = "parquet"))] pub mod s3_proxy_ee; mod s3_proxy_oss; +mod workspace_dependencies; mod approvals; #[cfg(all(feature = "enterprise", feature = "private"))] @@ -455,6 +456,10 @@ pub async fn run_server( .nest("/drafts", drafts::workspaced_service()) .nest("/favorites", favorite::workspaced_service()) .nest("/flows", flows::workspaced_service()) + .nest( + "/workspace_dependencies", + workspace_dependencies::workspaced_service(), + ) .nest( "/flow_conversations", flow_conversations::workspaced_service(), diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index 01200f1674..7cbcf8cc03 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -41,11 +41,11 @@ use windmill_audit::ActionKind; use windmill_worker::{process_relative_imports, scoped_dependency_map::ScopedDependencyMap}; use windmill_common::{ - assets::{AssetUsageKind, AssetWithAltAccessType, clear_asset_usage, insert_asset_usage}, + assets::{clear_asset_usage, insert_asset_usage, AssetUsageKind, AssetWithAltAccessType}, error::to_anyhow, s3_helpers::upload_artifact_to_store, scripts::hash_script, - utils::{WarnAfterExt, paginate_without_limits}, + utils::{paginate_without_limits, WarnAfterExt}, worker::{CLOUD_HOSTED, MIN_VERSION_SUPPORTS_DEBOUNCING}, }; @@ -60,9 +60,7 @@ use windmill_common::{ ScriptHistory, ScriptHistoryUpdate, ScriptKind, ScriptLang, ScriptWithStarred, }, users::username_to_permissioned_as, - utils::{ - not_found_if_none, query_elems_from_hub, require_admin, Pagination, StripPath, - }, + utils::{not_found_if_none, query_elems_from_hub, require_admin, Pagination, StripPath}, worker::to_raw_value, HUB_BASE_URL, }; @@ -1045,14 +1043,10 @@ async fn create_script_internal<'c>( let permissioned_as2 = permissioned_as.clone(); let script_path2 = script_path.clone(); let parent_path = p_path_opt.clone(); - let lock = ns.lock.clone(); let deployment_message = ns.deployment_message.clone(); let content = ns.content.clone(); let language = ns.language.clone(); tokio::spawn(async move { - // TODO: I don't think we want this. We might want to send dependency job. But skip any calculations if lock is already present. - // It will allow us to make code more consistent and predictable. - // wait for 10 seconds to make sure the script is deployed and that the CLI sync that pushed it (f one) is complete tokio::time::sleep(std::time::Duration::from_secs(10)).await; if let Err(e) = process_relative_imports( @@ -1068,7 +1062,6 @@ async fn create_script_internal<'c>( &authed2.email, &authed2.username, &permissioned_as2, - lock, ) .await { diff --git a/backend/windmill-api/src/workspace_dependencies.rs b/backend/windmill-api/src/workspace_dependencies.rs new file mode 100644 index 0000000000..bb511518d2 --- /dev/null +++ b/backend/windmill-api/src/workspace_dependencies.rs @@ -0,0 +1,145 @@ +use axum::{ + extract::{Path, Query}, + routing::{get, post}, + Extension, Json, Router, +}; +use http::StatusCode; +use serde::Deserialize; +use windmill_common::{ + error::{self, JsonResult}, + scripts::ScriptLang, + users::username_to_permissioned_as, + utils::require_admin, + workspace_dependencies::WorkspaceDependencies, + DB, +}; +use windmill_worker::{ + scoped_dependency_map, trigger_dependents_to_recompute_dependencies, + workspace_dependencies::NewWorkspaceDependencies, +}; + +use crate::db::ApiAuthed; + +pub fn workspaced_service() -> Router { + Router::new() + .route("/create", post(create)) + .route("/list", get(list)) + .route("/archive/:language", post(archive)) + .route("/get_latest/:language", get(get_latest)) + .route("/delete/:language", post(delete)) +} + +#[axum::debug_handler] +async fn create( + authed: ApiAuthed, + // Extension(user_db): Extension, + Extension(db): Extension, + Json(nwd): Json, +) -> error::Result<(StatusCode, String)> { + tracing::info!(workspace_id = %nwd.workspace_id, name = ?nwd.name, language = ?nwd.language, "create workspace dependencies"); + require_admin(authed.is_admin, &authed.username)?; + Ok(( + StatusCode::CREATED, + format!( + "{}", + nwd.create( + &authed.email, + &authed.username, + &username_to_permissioned_as(&authed.username), + &db + ) + .await? + ), + )) +} + +#[axum::debug_handler] +async fn list( + // Extension(user_db): Extension, + Extension(db): Extension, + Path(w_id): Path, +) -> JsonResult> { + tracing::info!(workspace_id = %w_id, "list workspace dependencies"); + Ok(Json(WorkspaceDependencies::list(&w_id, &db).await?)) +} + +#[derive(Deserialize)] +pub(super) struct NameQuery { + name: Option, +} + +#[axum::debug_handler] +pub(super) async fn get_latest( + Extension(db): Extension, + Path((w_id, language)): Path<(String, ScriptLang)>, + Query(params): Query, +) -> JsonResult> { + tracing::info!(workspace_id = %w_id, language = ?language, name = ?params.name, "get latest workspace dependencies"); + Ok(Json( + WorkspaceDependencies::get_latest(params.name, language, &w_id, db.into()).await?, + )) +} + +#[axum::debug_handler] +async fn archive( + authed: ApiAuthed, + // Extension(user_db): Extension, + Extension(db): Extension, + Path((w_id, language)): Path<(String, ScriptLang)>, + Query(params): Query, +) -> error::Result<()> { + tracing::info!(workspace_id = %w_id, language = ?language, name = ?params.name, "archive workspace dependencies"); + require_admin(authed.is_admin, &authed.username)?; + let db = &db; + WorkspaceDependencies::archive(params.name.clone(), language, &w_id, db).await?; + + trigger_dependents_to_recompute_dependencies( + &w_id, + scoped_dependency_map::ScopedDependencyMap::get_dependents( + WorkspaceDependencies::to_path(¶ms.name, language)?.as_str(), + &w_id, + db, + ) + .await?, + None, + None, + &authed.email, + &authed.username, + &username_to_permissioned_as(&authed.username), + db, + vec![], + ) + .await +} + +#[axum::debug_handler] +async fn delete( + authed: ApiAuthed, + // Extension(user_db): Extension, + Extension(db): Extension, + Path((w_id, language)): Path<(String, ScriptLang)>, + Query(params): Query, +) -> error::Result<()> { + tracing::info!(workspace_id = %w_id, language = ?language, name = ?params.name, "delete workspace dependencies"); + require_admin(authed.is_admin, &authed.username)?; + let db = &db; + WorkspaceDependencies::delete(params.name.clone(), language, &w_id, db).await?; + + trigger_dependents_to_recompute_dependencies( + &w_id, + scoped_dependency_map::ScopedDependencyMap::get_dependents( + WorkspaceDependencies::to_path(¶ms.name, language)?.as_str(), + &w_id, + db, + ) + .await?, + None, + None, + &authed.email, + &authed.username, + &username_to_permissioned_as(&authed.username), + db, + vec![], + ) + .await +} diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 9fbba5cc48..effe576d55 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -51,7 +51,9 @@ use windmill_common::{ utils::{paginate, rd_string, require_admin, Pagination}, }; use windmill_git_sync::{handle_deployment_metadata, handle_fork_branch_creation, DeployedObject}; -use windmill_worker::scoped_dependency_map::{DependencyMap, ScopedDependencyMap}; +use windmill_worker::scoped_dependency_map::{ + DependencyDependent, DependencyMap, ScopedDependencyMap, +}; #[cfg(feature = "enterprise")] use windmill_common::utils::require_admin_or_devops; @@ -82,6 +84,8 @@ pub fn workspaced_service() -> Router { .route("/delete_invite", post(delete_invite)) .route("/rebuild_dependency_map", post(rebuild_dependency_map)) .route("/get_dependency_map", get(get_dependency_map)) + .route("/get_dependents/*imported_path", get(get_dependents)) + .route("/get_dependents_amounts", post(get_dependents_amounts)) .route("/get_settings", get(get_settings)) .route("/get_deploy_to", get(get_deploy_to)) .route("/edit_slack_command", post(edit_slack_command)) @@ -2553,8 +2557,11 @@ async fn clone_workspace_data( clone_raw_apps(tx, source_workspace_id, target_workspace_id).await?; // Clone workspace runnable dependencies and dependency map - clone_workspace_dependencies(tx, source_workspace_id, target_workspace_id).await?; + clone_workspace_runnable_dependencies(tx, source_workspace_id, target_workspace_id).await?; + // TODO: Enable when git sync is implemented for workspace dependencies. + // // Clone workspace dependencies + // clone_workspace_dependencies(tx, source_workspace_id, target_workspace_id).await?; Ok(()) } @@ -3017,7 +3024,7 @@ async fn clone_raw_apps( Ok(()) } -async fn clone_workspace_dependencies( +async fn clone_workspace_runnable_dependencies( tx: &mut Transaction<'_, Postgres>, source_workspace_id: &str, target_workspace_id: &str, @@ -3049,6 +3056,27 @@ async fn clone_workspace_dependencies( Ok(()) } +#[allow(dead_code)] +async fn clone_workspace_dependencies( + tx: &mut Transaction<'_, Postgres>, + source_workspace_id: &str, + target_workspace_id: &str, +) -> Result<()> { + // Clone workspace_runnable_dependencies + sqlx::query!( + "INSERT INTO workspace_dependencies (workspace_id, language, name, description, content, archived, created_at) + SELECT $1, language, name, description, content, archived, created_at + FROM workspace_dependencies + WHERE workspace_id = $2", + target_workspace_id, + source_workspace_id + ) + .execute(&mut **tx) + .await?; + + Ok(()) +} + async fn deprecated_create_workspace_fork(_authed: ApiAuthed) -> Result { return Err(Error::BadRequest("This API endpoint has been relocated. Your Windmill CLI version is outdated and needs to be updated.".to_string())); } @@ -3583,6 +3611,75 @@ async fn rebuild_dependency_map( ScopedDependencyMap::rebuild_map(&w_id, &db).await } +#[axum::debug_handler] +async fn get_dependents( + Extension(db): Extension, + Path((w_id, imported_path)): Path<(String, String)>, + _authed: ApiAuthed, +) -> JsonResult> { + tracing::debug!( + workspace_id = %w_id, + imported_path = %imported_path, + "API: Getting dependents for imported path" + ); + + let dependents = ScopedDependencyMap::get_dependents(&imported_path, &w_id, &db).await?; + + tracing::debug!( + workspace_id = %w_id, + imported_path = %imported_path, + dependents_count = dependents.len(), + "API: Found dependents: {:?}", + dependents + ); + + Ok(Json(dependents)) +} + +#[derive(Serialize, Debug)] +struct DependentsAmount { + imported_path: String, + count: i64, +} + +#[axum::debug_handler] +async fn get_dependents_amounts( + Extension(db): Extension, + Path(w_id): Path, + Json(imported_paths): Json>, +) -> JsonResult> { + tracing::debug!( + workspace_id = %w_id, + imported_paths = ?imported_paths, + "API: Getting dependents amounts for imported paths" + ); + + let results = sqlx::query_as!( + DependentsAmount, + r#" + SELECT + imported_path, + COUNT(DISTINCT importer_path) as "count!" + FROM dependency_map + WHERE workspace_id = $1 AND imported_path = ANY($2) + GROUP BY imported_path + "#, + w_id, + &imported_paths + ) + .fetch_all(&db) + .await?; + + tracing::debug!( + workspace_id = %w_id, + results_count = results.len(), + "API: Found dependents amounts: {:?}", + results + ); + + Ok(Json(results)) +} + #[derive(Deserialize)] struct ChangeWorkspaceName { new_name: String, diff --git a/backend/windmill-api/src/workspaces_export.rs b/backend/windmill-api/src/workspaces_export.rs index 74732c9f22..3af2f5d44a 100644 --- a/backend/windmill-api/src/workspaces_export.rs +++ b/backend/windmill-api/src/workspaces_export.rs @@ -44,6 +44,7 @@ use axum::{ use http::HeaderName; use itertools::Itertools; +use windmill_common::utils::require_admin; use windmill_common::variables::decrypt; use windmill_common::{ db::UserDB, @@ -52,6 +53,7 @@ use windmill_common::{ schedule::Schedule, scripts::{Schema, Script, ScriptLang}, variables::{build_crypt, ExportableListableVariable}, + workspace_dependencies::WorkspaceDependencies, }; use hyper::header; @@ -177,6 +179,7 @@ pub(crate) struct ArchiveQueryParams { include_groups: Option, include_settings: Option, include_key: Option, + include_workspace_dependencies: Option, default_ts: Option, } @@ -309,11 +312,20 @@ pub(crate) async fn tarball_workspace( include_groups, include_settings, include_key, + include_workspace_dependencies, default_ts, }): Query, ) -> Result<([(HeaderName, String); 2], impl IntoResponse)> { // require_admin(authed.is_admin, &authed.username)?; + tracing::info!( + "tarball_workspace called for workspace {}: include_workspace_dependencies={:?}, skip_variables={:?}, skip_resources={:?}", + w_id, + include_workspace_dependencies, + skip_variables, + skip_resources + ); + let mut tx = user_db.begin(&authed).await?; let tmp_dir = TempDir::new_in("/tmp/windmill/")?; @@ -545,6 +557,33 @@ pub(crate) async fn tarball_workspace( } } + if include_workspace_dependencies.unwrap_or(false) + && require_admin(authed.is_admin, &authed.username).is_ok() + { + tracing::info!("Including workspace dependencies in tarball export"); + let workspace_dependencies = WorkspaceDependencies::list(&w_id, &db).await?; + tracing::info!( + "Found {} workspace dependencies", + workspace_dependencies.len() + ); + for dep in workspace_dependencies { + // let dep_str = &to_string_without_metadata(&dep, false, None).unwrap(); + let filename = WorkspaceDependencies::to_path(&dep.name, dep.language)?; + tracing::info!( + "Adding workspace dependency: name={:?}, language={:?}, filename={}", + dep.name, + dep.language, + filename + ); + archive.write_to_archive(&dep.content, &filename).await?; + } + } else { + tracing::info!( + "Skipping workspace dependencies: include_workspace_dependencies={:?}", + include_workspace_dependencies + ); + } + if include_schedules.unwrap_or(false) { let schedules = sqlx::query_as::<_, Schedule>( "SELECT * FROM schedule diff --git a/backend/windmill-common/Cargo.toml b/backend/windmill-common/Cargo.toml index c0a52c6677..0ba041e1ec 100644 --- a/backend/windmill-common/Cargo.toml +++ b/backend/windmill-common/Cargo.toml @@ -33,6 +33,7 @@ thiserror.workspace = true anyhow.workspace = true serde.workspace = true serde_json.workspace = true +serde_yml.workspace = true chrono.workspace = true chrono-tz.workspace = true hex.workspace = true @@ -68,6 +69,7 @@ aws-credential-types.workspace = true aws-smithy-types.workspace = true base64.workspace = true bitflags.workspace = true +phf.workspace = true aws-smithy-types-convert = { workspace = true, optional = true } aws-sdk-rds = { workspace = true, optional = true } @@ -91,6 +93,7 @@ strum_macros.workspace = true url.workspace = true urlencoding.workspace = true async-recursion.workspace = true +pep440_rs.workspace = true semver.workspace = true croner = "2.2.0" diff --git a/backend/windmill-common/src/error.rs b/backend/windmill-common/src/error.rs index 8048b59586..1e051f064e 100644 --- a/backend/windmill-common/src/error.rs +++ b/backend/windmill-common/src/error.rs @@ -86,6 +86,12 @@ pub enum Error { Generic(StatusCode, String), #[error("{feature} is unavailable due to some workers being behind. Do not use the feature or make sure all workers run at least {min_version}")] WorkersAreBehind { feature: String, min_version: String }, + #[error( + "Breaking change was introduced in v{version} ({feature}). Follow this migration guide: {guide_url}" + )] + MigrationNeeded { version: String, feature: String, guide_url: url::Url }, + #[error("{0} is unavailable. It is possible for this worker to be behind.")] + FeatureUnavailable(String), } impl Error { diff --git a/backend/windmill-common/src/flows.rs b/backend/windmill-common/src/flows.rs index 104f99b301..9134da3c41 100644 --- a/backend/windmill-common/src/flows.rs +++ b/backend/windmill-common/src/flows.rs @@ -246,7 +246,13 @@ impl FlowValue { } } } - BranchOne { branches, .. } | BranchAll { branches, .. } => { + BranchOne { default, branches, .. } => { + Self::traverse_leafs(default.iter().collect(), cb)?; + for branch in branches { + Self::traverse_leafs(branch.modules.iter().collect(), cb)?; + } + } + BranchAll { branches, .. } => { for branch in branches { Self::traverse_leafs(branch.modules.iter().collect(), cb)?; } diff --git a/backend/windmill-common/src/global_settings.rs b/backend/windmill-common/src/global_settings.rs index 13be9ff02f..e6beb964df 100644 --- a/backend/windmill-common/src/global_settings.rs +++ b/backend/windmill-common/src/global_settings.rs @@ -133,3 +133,18 @@ pub async fn load_value_from_global_settings( .map(|x| x.value); Ok(r) } + +pub async fn set_value_in_global_settings( + db: &Pool, + setting_name: &str, + value: serde_json::Value, +) -> error::Result<()> { + sqlx::query!( + "INSERT INTO global_settings (name, value) VALUES ($1, $2) ON CONFLICT (name) DO UPDATE SET value = EXCLUDED.value, updated_at = now()", + setting_name, + value + ) + .execute(db) + .await?; + Ok(()) +} diff --git a/backend/windmill-common/src/jobs.rs b/backend/windmill-common/src/jobs.rs index f32b3447c8..d4589dd5fd 100644 --- a/backend/windmill-common/src/jobs.rs +++ b/backend/windmill-common/src/jobs.rs @@ -412,7 +412,7 @@ pub enum JobPayload { /// Dependency Job, exposed with API. Requirements can be predefined RawScriptDependencies { script_path: String, - /// Will reflect raw requirements content (e.g. requirements.txt) + /// Will reflect raw requirements content (e.g. requirements.in) content: String, language: ScriptLang, }, diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index 04e59cfac9..1f3dbd414b 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -55,7 +55,7 @@ pub mod job_metrics; pub mod job_s3_helpers_ee; #[cfg(feature = "parquet")] pub mod job_s3_helpers_oss; -pub mod lockfiles; +pub mod workspace_dependencies; #[cfg(feature = "private")] pub mod git_sync_ee; diff --git a/backend/windmill-common/src/schema.rs b/backend/windmill-common/src/schema.rs index d16b86d507..6c5d3cedd2 100644 --- a/backend/windmill-common/src/schema.rs +++ b/backend/windmill-common/src/schema.rs @@ -345,15 +345,7 @@ fn find_annotation(comm_lit: &str, annotation: &str, code: &str) -> bool { pub fn should_validate_schema(code: &str, lang: &ScriptLang) -> bool { let annotation = "schema_validation"; - use ScriptLang::*; - let comment = match lang { - Nativets | Bun | Bunnative | Deno | Php | CSharp | Java => "//", - Python3 | Go | Bash | Powershell | Graphql | Ansible | Nu | Ruby => "#", - Postgresql | Mysql | Bigquery | Snowflake | Mssql | OracleDB | DuckDb => "--", - Rust => "//!", - // for related places search: ADD_NEW_LANG - }; - find_annotation(comment, annotation, code) + find_annotation(&lang.as_comment_lit(), annotation, code) } #[derive(Serialize, Deserialize, Debug, Clone)] diff --git a/backend/windmill-common/src/scripts.rs b/backend/windmill-common/src/scripts.rs index a5ba95f93b..ca34b5eb5e 100644 --- a/backend/windmill-common/src/scripts.rs +++ b/backend/windmill-common/src/scripts.rs @@ -17,6 +17,7 @@ use crate::{ assets::AssetWithAltAccessType, error::{to_anyhow, Error}, utils::http_get_from_hub, + workspace_dependencies::WorkspaceDependenciesAnnotatedRefs, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, PRIVATE_HUB_MIN_VERSION, }; @@ -25,12 +26,26 @@ use anyhow::Context; use backon::ConstantBuilder; use backon::{BackoffBuilder, Retryable}; use itertools::Itertools; +use regex::Regex; use serde::de::Error as _; use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serialize}; use crate::utils::StripPath; -#[derive(Serialize, Deserialize, Debug, PartialEq, Copy, Clone, Hash, Eq, sqlx::Type, Default)] +#[derive( + Serialize, + Deserialize, + Debug, + PartialEq, + Copy, + Clone, + Hash, + Eq, + sqlx::Type, + Default, + Ord, + PartialOrd, +)] #[sqlx(type_name = "SCRIPT_LANG", rename_all = "lowercase")] #[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))] pub enum ScriptLang { @@ -90,6 +105,71 @@ impl ScriptLang { // for related places search: ADD_NEW_LANG } } + + pub fn as_dependencies_filename(&self) -> Option { + use ScriptLang::*; + Some( + match self { + Bun | Bunnative => "package.json", + Python3 => "requirements.in", + // Go => "go.mod", + Php => "composer.json", + _ => return None, + } + .to_owned(), + ) + } + + pub fn as_comment_lit(&self) -> String { + use ScriptLang::*; + match self { + Nativets | Bun | Bunnative | Deno | Go | Php | CSharp | Java => "//", + Python3 | Bash | Powershell | Graphql | Ansible | Nu | Ruby => "#", + Postgresql | Mysql | Bigquery | Snowflake | Mssql | OracleDB | DuckDb => "--", + Rust => "//!", + // for related places search: ADD_NEW_LANG + } + .to_owned() + } + + pub fn extract_workspace_dependencies_annotated_refs( + &self, + code: &str, + runnable_path: &str, + ) -> Option> { + use ScriptLang::*; + lazy_static::lazy_static! { + static ref RE_PYTHON: Regex = Regex::new(r"^\#\s?(\S+)\s*$").unwrap(); + } + match self { + // TODO: Maybe use regex + Bun | Bunnative => WorkspaceDependenciesAnnotatedRefs::parse( + "//", + "package_json", + code, + None, + runnable_path, + ), + Python3 => WorkspaceDependenciesAnnotatedRefs::parse( + "#", + "requirements", + code, + Some(&RE_PYTHON), + runnable_path, + ), + Go => { + WorkspaceDependenciesAnnotatedRefs::parse("//", "go_mod", code, None, runnable_path) + } + Php => WorkspaceDependenciesAnnotatedRefs::parse( + "//", + "composer_json", + code, + None, + runnable_path, + ), + _ => return None, + } + } } impl FromStr for ScriptLang { @@ -739,12 +819,14 @@ pub struct ClonedScript { pub old_script: NewScript, pub new_hash: i64, } +// TODO: What if dependency job fails, there is script with NULL in the lock pub async fn clone_script<'c>( base_hash: ScriptHash, w_id: &str, deployment_message: Option, tx: &mut sqlx::Transaction<'c, sqlx::Postgres>, ) -> crate::error::Result { + // TODO:! let s = sqlx::query_as::<_, Script>( "SELECT * FROM script WHERE hash = $1 AND workspace_id = $2 AND archived = false FOR UPDATE", ) diff --git a/backend/windmill-common/src/worker.rs b/backend/windmill-common/src/worker.rs index 2918d40b0c..76c04cbe42 100644 --- a/backend/windmill-common/src/worker.rs +++ b/backend/windmill-common/src/worker.rs @@ -264,6 +264,10 @@ lazy_static::lazy_static! { .unwrap_or(false); pub static ref MIN_VERSION: Arc> = Arc::new(RwLock::new(Version::new(0, 0, 0))); + /// Global flag indicating if all workers support workspace dependencies feature (>= 1.583.0) + /// This flag is updated during worker initialization by checking the minimum version across all workers + /// When false, creation of workspace dependencies is forbidden and extraction of external workspace dependencies will error + pub static ref MIN_VERSION_SUPPORTS_V0_WORKSPACE_DEPENDENCIES: Arc> = Arc::new(RwLock::new(false)); /// Global flag indicating if all workers support the debouncing feature (>= 1.566.0) /// Debouncing consolidates multiple dependency job requests within a time window to avoid redundant work /// This flag is updated during worker initialization by checking the minimum version across all workers @@ -505,7 +509,10 @@ pub const ROOT_CACHE_DIR: &str = concatcp!(TMP_DIR, "/cache/"); pub fn write_file(dir: &str, path: &str, content: &str) -> error::Result { let path = format!("{}/{}", dir, path); - let mut file = File::create(&path)?; + let mut file = File::create(&path).map_err(|e| { + tracing::error!("Failed to create file at {path}: {:?}", &e); + e + })?; file.write_all(content.as_bytes())?; file.flush()?; Ok(file) @@ -1264,6 +1271,10 @@ pub async fn update_min_version(conn: &Connection) -> bool { tracing::info!("Minimal worker version: {min_version}"); } + // Workspace dependencies feature requires minimum version across all workers + *MIN_VERSION_SUPPORTS_V0_WORKSPACE_DEPENDENCIES.write().await = min_version + >= Version::parse(crate::workspace_dependencies::MIN_VERSION_WORKSPACE_DEPENDENCIES) + .unwrap(); // Debouncing feature requires minimum version 1.566.0 across all workers // This ensures all workers can handle debounce keys and stale data accumulation *MIN_VERSION_SUPPORTS_DEBOUNCING.write().await = min_version >= Version::new(1, 566, 0); @@ -1949,6 +1960,115 @@ pub fn to_raw_value_owned(result: serde_json::Value) -> Box { .unwrap_or_else(|_| RawValue::from_string("{}".to_string()).unwrap()) } +pub fn split_python_requirements>(requirements: T) -> Vec { + requirements + .as_ref() + .lines() + .filter(|x| !x.trim_start().starts_with("--") && !x.trim().is_empty()) + .map(String::from) + .collect() +} + +#[derive(Eq, PartialEq, Clone, Copy, Default, Debug)] +#[repr(u32)] +pub enum PyVAlias { + Py310 = 10, + #[default] + Py311, + Py312, + Py313, +} + +impl Into for PyVAlias { + fn into(self) -> pep440_rs::Version { + pep440_rs::Version::new([self.major() as u64, self as u64]) + } +} + +impl Into for PyVAlias { + fn into(self) -> u32 { + self.major() * 100 + self as u32 + } +} + +impl PyVAlias { + pub fn all>() -> Vec { + use PyVAlias::*; + vec![Py310.into(), Py311.into(), Py312.into(), Py313.into()] + } + // Get MAJOR part of alias. (semver: MAJOR.MINOR.PATCH) + fn major(&self) -> u32 { + use PyVAlias::*; + match self { + Py310 | Py311 | Py312 | Py313 => 3, + // Py400 | Py401 => 4 + } + } + + /// Converts numeric format to alias + /// Example: + /// 310u32 (in) -> PyVAlias::Py310 (out) + pub fn try_from_v1(numeric: T) -> Option { + use PyVAlias::*; + match numeric.to_string().as_str() { + "310" => Some(Py310), + "311" => Some(Py311), + "312" => Some(Py312), + "313" => Some(Py313), + _ => None, + } + } +} + +/// Parse lockfile for assigned python version. +/// If not found returns None +pub fn try_parse_locked_python_version_from_requirements>( + requirements_lines: &[S], +) -> Option { + let parse_version = |s: &str| -> Option { + // Possible inputs: + // V2: + // # py: 3.11.0 or #py:3.11.0 or #py: 3.11.0 + // + // V1: + // # py311 or #py311 + let version_unparsed = s + .to_owned() + // Remove whitespaces. That leaves us with: + // V2: #py:3.11.0 + // V1: #py311 + // + // Remove # + // V2: py:3.11.0 + // V1: py311 + // + // Remove : + // V2: py3.11.0 + // V1: py311 + .replace([' ', '#', ':'], "") + // Remove "py" + // V2: 3.11.0 + // V1: 311 + .replace("py", ""); + + // We will support reading V1 syntax, but it will be overwritten next deploy + PyVAlias::try_from_v1(&version_unparsed) + .map(PyVAlias::into) + .or(pep440_rs::Version::from_str(&version_unparsed) + .ok() + .map(pep440_rs::Version::into)) + }; + + requirements_lines + .iter() + .find(|s| { + let s = s.as_ref(); + s.starts_with("#py") || s.starts_with("# py") + }) + .map(S::as_ref) + .and_then(parse_version) +} + #[cfg(test)] mod tests { use super::*; diff --git a/backend/windmill-common/src/workspace_dependencies.rs b/backend/windmill-common/src/workspace_dependencies.rs new file mode 100644 index 0000000000..e93f6686f7 --- /dev/null +++ b/backend/windmill-common/src/workspace_dependencies.rs @@ -0,0 +1,1186 @@ +use itertools::Itertools; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use sqlx::PgExecutor; +use std::time::{Duration, Instant}; + +use crate::{error, scripts::ScriptLang, utils::calculate_hash, worker::Connection}; +use phf::phf_set; + +pub static BLACKLIST: phf::Set<&'static str> = phf_set! { + "u/admin/hub_sync", + "g/all/setup_app/app", + "g/all/setup_app" +}; + +lazy_static::lazy_static! { + static ref WMDEBUG_FORCE_V0_WORKSPACE_DEPENDENCIES: bool = std::env::var("WMDEBUG_FORCE_V0_WORKSPACE_DEPENDENCIES").is_ok(); + + /// Simple in-memory cache for workspace dependencies get_latest with 10-second timeout. + /// Cache key: (workspace_id, language, name) + /// Cache value: (Option, cached_at timestamp) + static ref WORKSPACE_DEPENDENCIES_CACHE: quick_cache::sync::Cache<(String, ScriptLang, Option), (Option, Instant)> = quick_cache::sync::Cache::new(1000); +} + +/// Cache timeout for workspace dependencies +const CACHE_TIMEOUT: Duration = Duration::from_secs(10); + +/// Minimum Windmill version required for workspace dependencies feature +pub const MIN_VERSION_WORKSPACE_DEPENDENCIES: &str = "1.587.0"; + +pub async fn min_version_supports_v0_workspace_dependencies() -> error::Result<()> { + // Check if workers support workspace dependencies feature + if !*WMDEBUG_FORCE_V0_WORKSPACE_DEPENDENCIES + && !*crate::worker::MIN_VERSION_SUPPORTS_V0_WORKSPACE_DEPENDENCIES + .read() + .await + { + tracing::warn!( + "Workspace dependencies feature will be disabled because not all workers support it (minimum version {} required)", + MIN_VERSION_WORKSPACE_DEPENDENCIES + ); + return Err(error::Error::WorkersAreBehind { + feature: "Workspace dependencies".to_string(), + min_version: MIN_VERSION_WORKSPACE_DEPENDENCIES.to_string(), + }); + } else { + Ok(()) + } +} + +pub type RawWorkspaceDependencies = std::collections::HashMap; + +/// Removes workspace dependencies annotation comments from lock files. +/// This is used when passing locks to resolver expect no comments (looking at you, json). +/// IMPORTANT: lock is expected to start with annotations +pub fn clean_lock_from_annotations(lock: &str, language: ScriptLang) -> String { + let mat = format!("{} workspace-dependencies", language.as_comment_lit()); + lock.lines().filter(|l| !l.starts_with(&mat)).collect() +} + +pub fn get_raw_workspace_dependencies( + raw_workspace_dependencies_o: &Option, + name: Option, + language: ScriptLang, + workspace_id: String, +) -> Option { + raw_workspace_dependencies_o + .as_ref() + .zip(WorkspaceDependencies::to_path(&name, language).ok()) + .and_then(|(hm, path)| hm.get(&path)) + .map(|raw_content| WorkspaceDependencies { + name, + workspace_id, + created_at: chrono::Utc::now(), + language, + content: raw_content.to_owned(), + ..Default::default() + }) +} + +fn map_err(e: String) -> error::Error { + error::Error::FeatureUnavailable(e) +} +#[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize, Default)] +pub struct WorkspaceDependencies { + /// Global id (across all workspaces) + id: i64, + archived: bool, + /// If not set becomes default for given language + pub name: Option, + pub description: Option, + pub workspace_id: String, + pub created_at: chrono::DateTime, + pub language: ScriptLang, + pub content: String, +} + +impl WorkspaceDependencies { + pub fn hash(&self) -> String { + // non-raw workspace dependencies will start with index 1. + // so if we see index 0, it is either default or default and raw deps + // if so we will use it's content as baseline + if self.id == 0 { + calculate_hash(&self.content) + } else { + self.id.to_string() + } + } + /// Marks workspace dependencies as archived. + pub async fn archive<'c>( + name: Option, + language: ScriptLang, + workspace_id: &str, + e: impl PgExecutor<'c>, + ) -> error::Result<()> { + if language.as_dependencies_filename().is_none() { + return Ok(()); + } + + sqlx::query!( + " + UPDATE workspace_dependencies + SET archived = true + WHERE name IS NOT DISTINCT FROM $1 AND workspace_id = $2 AND archived = false AND language = $3 + ", + name, + workspace_id, + language as ScriptLang + ) + .execute(e) + .await?; + Ok(()) + } + + /// Permanently deletes workspace dependencies from the database. + pub async fn delete<'c>( + name: Option, + language: ScriptLang, + workspace_id: &str, + e: impl PgExecutor<'c>, + ) -> error::Result<()> { + if language.as_dependencies_filename().is_none() { + return Ok(()); + } + + sqlx::query!( + " + DELETE + FROM workspace_dependencies + WHERE name IS NOT DISTINCT FROM $1 AND workspace_id = $2 AND language = $3 + ", + name, + workspace_id, + language as ScriptLang + ) + .execute(e) + .await?; + Ok(()) + } + + /// Lists all active workspace dependencies for a workspace. + pub async fn list<'c>(workspace_id: &str, e: impl PgExecutor<'c>) -> error::Result> { + sqlx::query_as!( + Self, + r##" + SELECT id, created_at, archived, name, description, workspace_id, content, language AS "language: ScriptLang" + FROM workspace_dependencies + WHERE archived = false AND workspace_id = $1 + "##, + workspace_id, + ) + .fetch_all(e) + .await + .map_err(error::Error::from) + } + + /// Gets the latest version of workspace dependencies by name and language. + pub async fn get_latest( + name: Option, + language: ScriptLang, + workspace_id: &str, + conn: Connection, + ) -> error::Result> { + if language.as_dependencies_filename().is_none() { + return Ok(None); + } + + let cache_key = (workspace_id.to_string(), language, name.clone()); + + // Check if cached value is still valid + if let Some((cached_value, cached_at)) = WORKSPACE_DEPENDENCIES_CACHE.get(&cache_key) { + if cached_at.elapsed() < CACHE_TIMEOUT { + return Ok(cached_value); + } + // Expired, remove it + WORKSPACE_DEPENDENCIES_CACHE.remove(&cache_key); + } + + // Fetch and cache + let fetch = Box::pin(async { + match &conn { + Connection::Sql(db) => sqlx::query_as!( + Self, + r#" + SELECT id, content, language AS "language: ScriptLang", name, description, archived, workspace_id, created_at + FROM workspace_dependencies + WHERE name IS NOT DISTINCT FROM $1 AND workspace_id = $2 AND archived = false AND language = $3 + LIMIT 1 + "#, + name, + workspace_id, + language as ScriptLang + ) + .fetch_optional(db) + .await + .map_err(error::Error::from), + + Connection::Http(http_client) => http_client + .get::>(&format!( + "/api/w/{workspace_id}/agent_workers/workspace_dependencies/get_latest/{}{}", + language.as_str(), + if let Some(ref name_val) = name { + format!("?name={name_val}") + } else { + "".to_owned() + } + )) + .await + .map_err(error::Error::from), + } + }); + + let (workspace_dependencies_o, ..) = WORKSPACE_DEPENDENCIES_CACHE + .get_or_insert_async(&cache_key, async { + Ok::<_, error::Error>((fetch.await?, Instant::now())) + }) + .await?; + + Ok(workspace_dependencies_o) + } + + /// Gets workspace dependencies by their unique ID. + pub async fn get<'c>( + id: i64, + workspace_id: &str, + e: impl PgExecutor<'c>, + ) -> error::Result> { + sqlx::query_as!( + Self, + r#" + SELECT id, content, language AS "language: ScriptLang", name, archived, description, workspace_id, created_at + FROM workspace_dependencies + WHERE id = $1 AND workspace_id = $2 + LIMIT 1 + "#, + id, + workspace_id + ) + .fetch_optional(e) + .await + .map_err(error::Error::from) + } + + /// Gets the version history for workspace dependencies. + pub async fn get_history<'c>( + name: Option, + language: ScriptLang, + workspace_id: &str, + e: impl PgExecutor<'c>, + ) -> error::Result> { + if language.as_dependencies_filename().is_none() { + return Ok(vec![]); + } + sqlx::query_scalar!( + r#" + SELECT id FROM workspace_dependencies + WHERE name IS NOT DISTINCT FROM $1 AND workspace_id = $2 AND language = $3 + "#, + name, + workspace_id, + language as ScriptLang + ) + .fetch_all(e) + .await + .map_err(error::Error::from) + } + + pub fn to_path(name: &Option, language: ScriptLang) -> error::Result { + let requirements_filename = + language + .as_dependencies_filename() + .ok_or(error::Error::BadConfig(format!( + "workspace dependencies are not supported for: {}", + language.as_str() + )))?; + + Ok(if let Some(name) = name { + format!("dependencies/{name}.{requirements_filename}") + } else { + format!("dependencies/{requirements_filename}") + }) + } +} + +#[derive(Debug, Clone)] +pub struct WorkspaceDependenciesPrefetched { + language: ScriptLang, + runnable_path: String, + #[allow(dead_code)] + workspace_id: String, + internal: WorkspaceDependenciesPrefetchedInternal, +} + +#[derive(Debug, Clone)] +enum WorkspaceDependenciesPrefetchedInternal { + Explicit(WorkspaceDependenciesAnnotatedRefs), + Implicit { workspace_dependencies: WorkspaceDependencies, mode: Mode }, + None, +} + +impl WorkspaceDependenciesPrefetched { + pub async fn extract<'c>( + code: &str, + language: ScriptLang, + workspace_id: &str, + raw_workspace_dependencies_o: &Option, + runnable_path: &str, + conn: Connection, + ) -> error::Result { + use WorkspaceDependenciesPrefetchedInternal::*; + + tracing::debug!(workspace_id, ?language, "extracting workspace dependencies"); + + Box::pin(async { + let r = if let Some(wdar) = + language.extract_workspace_dependencies_annotated_refs(code, runnable_path) + { + tracing::debug!(workspace_id, ?language, "found explicit annotations"); + + let expanded = wdar + .expand(language, workspace_id, raw_workspace_dependencies_o, conn) + .await?; + + Explicit(expanded) + // First try in raw dependencies + } else if let Some(workspace_dependencies) = get_raw_workspace_dependencies( + raw_workspace_dependencies_o, + Option::None, + language, + workspace_id.to_owned(), + ) { + tracing::debug!( + workspace_id, + ?language, + dep_id = workspace_dependencies.id, + "using implicit raw" + ); + + // Hardcode to manual for now. + Implicit { workspace_dependencies, mode: Mode::manual } + } else if let Some(workspace_dependencies) = + // If not found, fetch from db + WorkspaceDependencies::get_latest( + Option::None, + language, + workspace_id, + conn, + ) + .await? + { + tracing::debug!( + workspace_id, + ?language, + dep_id = workspace_dependencies.id, + "using implicit default" + ); + + // Hardcode to manual for now. + Implicit { workspace_dependencies, mode: Mode::manual } + } else { + tracing::debug!(workspace_id, ?language, "no dependencies found"); + + None + }; + + // Crucial part. It will drop all blacklisted runnables + WorkspaceDependenciesPrefetched { + internal: r, + language, + runnable_path: runnable_path.to_owned(), + workspace_id: workspace_id.to_owned(), + } + .preprocess() + .await + }) + .await + } + + pub fn get_python(&self) -> error::Result> { + use WorkspaceDependenciesPrefetchedInternal::*; + Ok(match &self.internal { + Explicit(wdar @ WorkspaceDependenciesAnnotatedRefs { inline, external, .. }) => { + tracing::debug!( + "Processing explicit workspace dependencies with inline: {}, external count: {}", + inline.is_some(), + external.len() + ); + + wdar.assert_inline_or_external_or_none().map_err(map_err)?; + wdar.assert_external_less_than(2).map_err(map_err)?; + wdar.assert_no_extra_mode_for_external().map_err(map_err)?; + external + .get(0) + .map(|wd| wd.content.clone()) + .or(inline.to_owned()) + } + Implicit { workspace_dependencies, .. } => { + self.internal.assert_no_extra_mode().map_err(map_err)?; + Some(workspace_dependencies.content.to_owned()) + } + None => Option::None, + }) + } + + pub fn get_bun(&self) -> error::Result> { + use WorkspaceDependenciesPrefetchedInternal::*; + self.internal.assert_no_extra_mode().map_err(map_err)?; + Ok(match &self.internal { + Explicit(wdar @ WorkspaceDependenciesAnnotatedRefs { external, .. }) => { + wdar.assert_no_inline().map_err(map_err)?; + wdar.assert_external_less_than(2).map_err(map_err)?; + external + .get(0) + .map(|wd| wd.content.clone()) + .or(Some("".to_owned())) + } + Implicit { workspace_dependencies, .. } => Some(workspace_dependencies.content.clone()), + None => Option::None, + }) + } + + pub fn get_go(&self) -> error::Result> { + // NOTE: go is disabled for now: + // https://discord.com/channels/930051556043276338/1031563866641018910/1443541229349634189 + + self.internal + .assert_no_workspace_dependencies() + .map_err(map_err)?; + Ok(None) + // use WorkspaceDependenciesPrefetchedInternal::*; + // self.internal.assert_no_manual_mode().map_err(map_err)?; + // Ok(match &self.internal { + // Explicit(wdar @ WorkspaceDependenciesAnnotatedRefs { external, .. }) => { + // wdar.assert_no_inline().map_err(map_err)?; + // wdar.assert_external_less_than(2).map_err(map_err)?; + // external.get(0).map(|wd| dbg!(wd.content.clone())).or(Some( + // " + // module mymod + // go 1.25 + // require () + // " + // .to_owned(), + // )) + // } + // Implicit { workspace_dependencies, .. } => Some(workspace_dependencies.content.clone()), + // None => Option::None, + // } + // .map(|go_mod_content| { + // if let Some(module) = go_mod_content + // .lines() + // .find(|l| l.trim_start().starts_with("module ")) + // { + // go_mod_content.replace(module, "module mymod") + // } else { + // format!("module mymod\n{go_mod_content}") + // } + // })) + } + + pub fn get_php(&self) -> error::Result> { + use WorkspaceDependenciesPrefetchedInternal::*; + self.internal.assert_no_extra_mode().map_err(map_err)?; + Ok(match &self.internal { + Explicit(wdar @ WorkspaceDependenciesAnnotatedRefs { external, .. }) => { + wdar.assert_no_inline().map_err(map_err)?; + wdar.assert_external_less_than(2).map_err(map_err)?; + external + .get(0) + .map(|wd| wd.content.clone()) + .or(Some(r#"{"require": {}}"#.to_owned())) + } + Implicit { workspace_dependencies, .. } => Some(workspace_dependencies.content.clone()), + None => Option::None, + }) + } + + /// Is the runnable permitted to have external references + pub fn is_external_references_permitted(runnable_path: &str) -> bool { + !BLACKLIST.contains(runnable_path) && !runnable_path.starts_with("hub/") + } + + async fn preprocess(mut self) -> error::Result { + // NOTE: we should error if it is not compatible. User should either update workers or do not use incompatible feature. + // Check if compatible with legacy and if not check that all workers run compatible versions. + if let Err(feature) = self.check_legacy_compat() { + min_version_supports_v0_workspace_dependencies() + .await + .map_err(|_| { + // NOTE: this error is flakey, sometimes it will error, somethimes it will just ignore. + error::Error::WorkersAreBehind { + feature, + min_version: MIN_VERSION_WORKSPACE_DEPENDENCIES.to_owned(), + } + })?; + } + + // NOTE: if you update or add new language, add compatibility checks here. + // for example if you were to update lang do: + // if let Err(incompatible_e) = self.check_v0_python_compat() { + // min_version_supports_v1_workspace_dependencies_python() + // ... + // } + // + // Where `check_v0_python_compat` describes all features of previous python + + if !Self::is_external_references_permitted(&self.runnable_path) { + self.remove_external_references(); + } + Ok(self) + } + + fn check_legacy_compat(&self) -> Result<(), String> { + use ScriptLang::*; + use WorkspaceDependenciesPrefetchedInternal::*; + match (self.language, &self.internal) { + // These languages except for python had none of this functionality + (Php | Bun | Bunnative | Go, wdp) => wdp.assert_no_workspace_dependencies()?, + + // Python, had #(extra_)requirements: + // but it had no external requirements. + // that's why we check if it is using only inline syntax + (Python3, Explicit(wdar)) => wdar.assert_no_external()?, + (Python3, wdp) => wdp.assert_no_implicit()?, + + _ => return Err(format!("language is unsupported")), + } + Ok(()) + } + + // TODO: + // pub fn check_v0_compat(&self) -> bool {} + // pub fn check_v1_compat(&self) -> bool {} + // pub fn check_v1_python_compat + + fn remove_external_references(&mut self) { + use WorkspaceDependenciesPrefetchedInternal::*; + match self.internal { + Explicit(WorkspaceDependenciesAnnotatedRefs { ref mut external, .. }) + if !external.is_empty() => + { + external.clear(); + } + // Implicit is an external reference to the default. So we just replace it to none + ref mut wdp @ Implicit { .. } => drop(std::mem::replace(wdp, None)), + // Return early not to show warning + _ => return, + } + tracing::warn!( + self.runnable_path, + "skipping external workspace dependencies for runnable" + ); + } + + pub async fn to_lock_header(&self) -> Option { + use WorkspaceDependenciesPrefetchedInternal::*; + + if min_version_supports_v0_workspace_dependencies() + .await + .is_err() + { + return Option::None; + } + + let mut header = vec![]; + let prepend_mode = |mode| { + format!( + "{} workspace-dependencies-mode: {}", + self.language.as_comment_lit(), + mode + ) + }; + + let insert_line = |hash, name: Option| { + format!( + "{} workspace-dependencies: {}:{}", + self.language.as_comment_lit(), + name.unwrap_or("default".to_owned()), + hash + ) + }; + match &self.internal { + Explicit(workspace_dependencies_annotated_refs) => { + header.push(prepend_mode(workspace_dependencies_annotated_refs.mode)); + for wd in &workspace_dependencies_annotated_refs.external { + header.push(insert_line(wd.hash(), wd.name.clone())); + } + } + Implicit { workspace_dependencies: wd, mode } => { + header.push(prepend_mode(*mode)); + header.push(insert_line(wd.hash(), Option::None)); + } + None => return Option::None, + } + Some(header.join("\n")) + } + + pub fn is_manual(&self) -> bool { + self.internal.get_mode() == Some(Mode::manual) + } +} + +#[derive(Debug, Clone)] +pub struct WorkspaceDependenciesAnnotatedRefs { + /// ```python + /// # requirements: + /// # rich==x.y.z << + /// # pandas==x.y.z << + /// ``` + pub inline: Option, + /// ```python + /// # requirements: default, base, prod + /// ^^^^^^^ ^^^^ ^^^^ + /// ``` + /// + /// Can either be a [[String]] or [[WorkspaceDependencies]] + /// + /// The workflow is following: + /// 1. You create Self with <[[String]]> - this will fetch a minimal amount of info (just the name). + /// 2. You [[Self::expand]] to replace all external names with <[[WorkspaceDependencies]]> + pub external: Vec, + pub mode: Mode, +} + +/// `# extra_requirements:` - Extra +/// `# requirements:` - Manual +#[allow(non_camel_case_types)] +#[derive(PartialEq, Eq, strum_macros::Display, Clone, Copy, Debug)] +pub enum Mode { + manual, + extra, +} + +impl WorkspaceDependenciesPrefetchedInternal { + fn get_mode(&self) -> Option { + use WorkspaceDependenciesPrefetchedInternal::*; + match &self { + Explicit(WorkspaceDependenciesAnnotatedRefs { mode, .. }) | Implicit { mode, .. } => { + Some(*mode) + } + None => Option::None, + } + } + + fn assert_no_implicit(&self) -> Result<(), String> { + if matches!(self, Self::Implicit { .. }) { + Err(format!("'default workspace dependencies'")) + } else { + Ok(()) + } + } + + fn assert_no_workspace_dependencies(&self) -> Result<(), String> { + if !matches!(self, Self::None { .. }) { + Err(format!("'workspace dependencies'")) + } else { + Ok(()) + } + } + + #[allow(dead_code)] + fn assert_no_explicit(&self) -> Result<(), String> { + if matches!(self, Self::Explicit { .. }) { + Err(format!("'external workspace dependencies'")) + } else { + Ok(()) + } + } + + fn assert_no_extra_mode(&self) -> Result<(), String> { + if self.get_mode() == Some(Mode::extra) { + Err(format!("'workspace dependencies in extra mode'")) + } else { + Ok(()) + } + } + + #[allow(dead_code)] + fn assert_no_manual_mode(&self) -> Result<(), String> { + if self.get_mode() == Some(Mode::manual) { + Err(format!("'workspace dependencies in manual mode'")) + } else { + Ok(()) + } + } +} + +impl WorkspaceDependenciesAnnotatedRefs { + fn assert_no_inline(&self) -> Result<(), String> { + if self.inline.is_none() { + Ok(()) + } else { + Err(format!("'inline workspace dependencies'")) + } + } + + fn assert_no_extra_mode_for_external(&self) -> Result<(), String> { + if self.mode == Mode::extra && !self.external.is_empty() { + Err(format!("'external workspace dependencies in extra mode'")) + } else { + Ok(()) + } + } + + #[allow(dead_code)] + fn assert_no_extra_mode_for_inline(&self) -> Result<(), String> { + if self.mode == Mode::extra && self.inline.is_some() { + Err(format!("'inline workspace dependencies in extra mode'")) + } else { + Ok(()) + } + } + + fn assert_no_external(&self) -> Result<(), String> { + self.assert_external_less_than(1) + .map_err(|_e| format!("'external workspace dependencies'")) + } + + fn assert_inline_or_external_or_none(&self) -> Result<(), String> { + if self.inline.is_some() && !self.external.is_empty() { + Err(format!( + "'inline and externally referenced workspace dependencies at the same time'" + )) + } else { + Ok(()) + } + } + fn assert_external_less_than(&self, amount: usize) -> Result<(), String> { + if self.external.len() < amount { + Ok(()) + } else { + Err(format!( + "'multiple external workspace dependencies referenced'", + )) + } + } +} + +impl WorkspaceDependenciesAnnotatedRefs { + pub(super) async fn expand( + self, + language: ScriptLang, + workspace_id: &str, + raw_workspace_dependencies_o: &Option, + conn: Connection, + ) -> error::Result> { + let mut res = WorkspaceDependenciesAnnotatedRefs { + inline: self.inline, + external: vec![], + mode: self.mode, + }; + + for name in self.external { + // "default" maps to unnamed workspace dependencies. + let name = if name == "default" { None } else { Some(name) }; + // First try in raw dependencies + if let Some(wd) = get_raw_workspace_dependencies( + raw_workspace_dependencies_o, + name.clone(), + language, + workspace_id.to_owned(), + ) { + res.external.push(wd); + + // If not found, fetch from db + } else if let Some(wd) = WorkspaceDependencies::get_latest( + name.clone(), + language, + workspace_id, + conn.clone(), + ) + .await? + { + res.external.push(wd.clone()); + } else { + tracing::warn!( + workspace_id, + ?language, + dependency_name = name, + "workspace dependencies not found" + ); + } + } + Ok(res) + } + // TODO: Maybe implemented by our Annotations macro + // TODO: Add sep config ':' or '='? + pub fn parse( + comment: &str, + keyword: &str, + code: &str, + validity_re_o: Option<&Regex>, + runnable_path: &str, + ) -> Option { + let (extra_deps, manual_deps) = (format!("extra_{keyword}:"), format!("{keyword}:")); + + let Some((pos, mat)) = code.lines().find_position(|l| { + l.starts_with(&comment) && (l.contains(&extra_deps) || l.contains(&manual_deps)) + }) else { + return None; + }; + let mut lines_it = code.lines().skip(pos); + + let mode = if mat.contains(&extra_deps) { + Mode::extra + } else { + Mode::manual + }; + + let external = { + let next_line = lines_it.next(); + if !WorkspaceDependenciesPrefetched::is_external_references_permitted(runnable_path) { + tracing::warn!( + runnable_path, + "skipping external workspace dependencies for runnable" + ); + + Default::default() + // return Err(error::Error::BadConfig(format!( + // "{runnable_path} should not include external Workspace Dependencies" + // ))); + } else { + next_line + .map(|s| { + match mode { + Mode::manual => s.replace(&manual_deps, ""), + Mode::extra => s.replace(&extra_deps, ""), + } + .replace(comment, "") + }) + .map(|unparsed| { + unparsed + .split(',') + // TODO: do we want to sort it? + .map(str::trim) + .filter(|s| !s.is_empty()) + // .map(FromName::from_name) + .map(str::to_owned) + .collect_vec() + }) + .unwrap_or_default() + } + }; + + let inline_deps = lines_it + .map_while(|l| { + match validity_re_o { + Some(re) => re.captures(l).and_then(|c| c.get(1).map(|m| m.as_str())), + None => { + if !l.starts_with(comment) { + None + } else { + // Skip comment + // If it fails (None) iteration is just finished. + l.get(comment.len()..) + } + } + } + }) + .join("\n"); + + let inline = if inline_deps.trim().is_empty() { + None + } else { + Some(inline_deps) + }; + + Some(WorkspaceDependenciesAnnotatedRefs { + inline, // TODO: Parse + external, + mode, + }) + } +} + +#[cfg(test)] +mod workspace_dependencies_tests { + use super::*; + + #[test] + fn test_parse_annotation_python_requirements_manual_mode() { + let code = r#" +# requirements: default, base +#requests==2.31.0 +#pandas>=1.5.0 + +def main(): + pass +"#; + + let result = WorkspaceDependenciesAnnotatedRefs::::parse( + "#", + "requirements", + code, + None, + "", + ) + .unwrap(); + assert!(matches!(result.mode, Mode::manual)); + assert_eq!( + result.external, + vec!["default".to_owned(), "base".to_owned()] + ); + assert_eq!( + result.inline.as_ref().unwrap(), + "requests==2.31.0\npandas>=1.5.0" + ); + } + + #[test] + fn test_parse_annotation_python_extra_requirements_mode() { + let code = r#" +# extra_requirements: utils +#numpy>=1.24.0 + +def main(): + pass +"#; + + let result = WorkspaceDependenciesAnnotatedRefs::::parse( + "#", + "requirements", + code, + None, + "", + ) + .unwrap(); + assert!(matches!(result.mode, Mode::extra)); + assert_eq!(result.external, vec!["utils".to_owned()]); + assert_eq!(result.inline.as_ref().unwrap(), "numpy>=1.24.0"); + } + + #[test] + fn test_parse_annotation_typescript_requirements() { + let code = r#" +// requirements: utils, base +//{ +// "dependencies": { +// "axios": "^1.6.0" +// } +//} + +export function main() {} +"#; + + let result = WorkspaceDependenciesAnnotatedRefs::::parse( + "//", + "requirements", + code, + None, + "", + ) + .unwrap(); + assert!(matches!(result.mode, Mode::manual)); + assert_eq!(result.external, vec!["utils".to_owned(), "base".to_owned()]); + let expected_inline = r#"{ + "dependencies": { + "axios": "^1.6.0" + } +}"#; + assert_eq!(result.inline.as_ref().unwrap(), expected_inline); + } + + #[test] + fn test_parse_annotation_with_spacing_variations() { + let code = r#" +#requirements: no_space +def main(): + pass +"#; + + let result = WorkspaceDependenciesAnnotatedRefs::::parse( + "#", + "requirements", + code, + None, + "", + ) + .unwrap(); + assert!(matches!(result.mode, Mode::manual)); + assert_eq!(result.external, vec!["no_space".to_owned()]); + assert!(result.inline.is_none()); + } + + #[test] + fn test_parse_annotation_with_spacing_variations_spaced() { + let code = r#" +# requirements: with_space +def main(): + pass +"#; + + let result = WorkspaceDependenciesAnnotatedRefs::::parse( + "#", + "requirements", + code, + None, + "", + ) + .unwrap(); + assert!(matches!(result.mode, Mode::manual)); + assert_eq!(result.external, vec!["with_space".to_owned()]); + assert!(result.inline.is_none()); + } + + #[test] + fn test_parse_annotation_go_style() { + let code = r#" +// go_mod: base, +//github.com/gin-gonic/gin v1.9.1 + +package main +func main() {} +"#; + + let result = + WorkspaceDependenciesAnnotatedRefs::::parse("//", "go_mod", code, None, "") + .unwrap(); + assert!(matches!(result.mode, Mode::manual)); + assert_eq!(result.external, vec!["base".to_owned()]); + assert_eq!( + result.inline.as_ref().unwrap(), + "github.com/gin-gonic/gin v1.9.1" + ); + } + + #[test] + fn test_parse_annotation_no_inline_deps() { + let code = r#" +# requirements: default + +def main(): + pass +"#; + + let result = WorkspaceDependenciesAnnotatedRefs::::parse( + "#", + "requirements", + code, + None, + "", + ) + .unwrap(); + assert!(matches!(result.mode, Mode::manual)); + assert_eq!(result.external, vec!["default".to_owned()]); + assert!(result.inline.is_none()); + } + + #[test] + fn test_parse_annotation_inline_only() { + let code = r#" +# requirements: +#requests==2.31.0 +# pandas>=1.5.0 + +def main(): + pass +"#; + + let result = WorkspaceDependenciesAnnotatedRefs::::parse( + "#", + "requirements", + code, + None, + "", + ) + .unwrap(); + assert!(matches!(result.mode, Mode::manual)); + assert!(result.external.is_empty()); + assert_eq!( + result.inline.as_ref().unwrap(), + "requests==2.31.0\n pandas>=1.5.0" + ); + } + + #[test] + fn test_parse_annotation_no_match() { + let code = r#" +def main(): + pass +"#; + + let result = WorkspaceDependenciesAnnotatedRefs::::parse( + "#", + "requirements", + code, + None, + "", + ); + assert!(result.is_none()); + } + + #[test] + fn test_parse_annotation_php_style() { + let code = r#" +::parse( + "//", + "requirements", + code, + None, + "", + ) + .unwrap(); + assert!(matches!(result.mode, Mode::manual)); + assert_eq!(result.external, vec!["composer".to_owned()]); + let expected_inline = r#"{ + "require": { + "guzzlehttp/guzzle": "^7.0" + } +}"#; + assert_eq!(result.inline.as_ref().unwrap(), expected_inline); + } + + #[test] + fn test_parse_annotation_just_requirements_colon() { + let code = r#" +#requirements: + +def main(): + pass +"#; + + let result = WorkspaceDependenciesAnnotatedRefs::::parse( + "#", + "requirements", + code, + None, + "", + ) + .unwrap(); + assert!(matches!(result.mode, Mode::manual)); + assert!(result.external.is_empty()); + assert!(result.inline.is_none()); + } + #[test] + fn test_parse_annotation_blacklisted() { + let code = r#" +#requirements: hello, world + +def main(): + pass +"#; + + let result = WorkspaceDependenciesAnnotatedRefs::::parse( + "#", + "requirements", + code, + None, + "u/admin/hub_sync", + ) + .unwrap(); + assert!(matches!(result.mode, Mode::manual)); + assert!(result.external.is_empty()); + assert!(result.inline.is_none()); + } +} diff --git a/backend/windmill-macros/Cargo.toml b/backend/windmill-macros/Cargo.toml index 100b4678d3..5d7d12cc6a 100644 --- a/backend/windmill-macros/Cargo.toml +++ b/backend/windmill-macros/Cargo.toml @@ -11,6 +11,9 @@ proc-macro = true proc-macro2.workspace = true quote.workspace = true syn.workspace = true +serde.workspace = true +serde_yml.workspace = true +serde_derive.workspace = true # Dependencies for tests [dev-dependencies] @@ -18,3 +21,7 @@ syn.workspace = true lazy_static.workspace = true itertools.workspace = true regex.workspace = true +serde.workspace = true +serde_yml.workspace = true +serde_derive.workspace = true +pep440_rs.workspace = true diff --git a/backend/windmill-macros/tests/annotations.rs b/backend/windmill-macros/tests/annotations.rs index d906b72887..395643364d 100644 --- a/backend/windmill-macros/tests/annotations.rs +++ b/backend/windmill-macros/tests/annotations.rs @@ -3,6 +3,7 @@ mod annotations_tests { extern crate windmill_macros; use itertools::Itertools; + // use pep440_rs::Version; use windmill_macros::annotations; // Previous implementation. @@ -166,4 +167,99 @@ mod annotations_tests { assert_eq!(expected, Annotations::parse(cont)); } } + + // // #[derive(serde_derive::Serialize, serde_derive::Deserialize, Eq, PartialEq)] + // // #[annotations("#")] + // // pub struct SerAnnotations { + // // pub ann1: bool, + // // pub stt: String, + // // } + + // #[test] + // fn non_bool_1() { + // let cont = r#"#ann1, stt: "hey""#; + // // non-bool take entire line, so you can't have one normal and than parsed. + + // let a = SerAnnotations { ann1: false, stt: "".to_owned() }; + // assert_eq!(a, SerAnnotations::parse(cont)); + // } + + // #[test] + // fn non_bool_2() { + // let cont = "#ann1, \n#stt: hey"; + // let a = SerAnnotations { ann1: true, stt: "hey".to_owned() }; + // assert_eq!(a, SerAnnotations::parse(cont)); + // } + + // #[test] + // fn non_bool_different_idents() { + // #[derive(serde_derive::Serialize, serde_derive::Deserialize, Eq, PartialEq)] + // #[annotations("#")] + // pub struct A { + // pub s: String, + // } + // assert_eq!(A { s: "hey".to_owned() }, A::parse("#s:hey")); + // assert_eq!(A { s: "hey".to_owned() }, A::parse("#s : hey")); + // assert_eq!(A { s: "hey".to_owned() }, A::parse("#s :hey")); + // assert_eq!(A { s: "hey".to_owned() }, A::parse("#s : hey ")); + // assert_eq!(A { s: "hey".to_owned() }, A::parse("# s : hey ")); + // assert_eq!(A { s: "".to_owned() }, A::parse(" # s : hey ")); + // } + // #[test] + // fn non_bool_unparseable_last() { + // #[derive(serde_derive::Serialize, serde_derive::Deserialize, Eq, PartialEq)] + // #[annotations("#")] + // pub struct A { + // pub v: i32, + // } + // let cont = "#v: 1\n#v: non int"; + // let a = A { + // v: 1, // Should still be first, second unparsable one should have no affect on existing values + // }; + // assert_eq!(a, A::parse(cont)); + // } + + // #[test] + // fn non_bool_different_types() { + // #[derive(serde_derive::Serialize, serde_derive::Deserialize, Eq, PartialEq)] + // #[annotations("#")] + // pub struct A { + // pub s: String, + // pub i: i32, + // pub o: Option, + // pub a: Vec, + // pub v: Option, // Custom deser + // pub e: E, + // } + + // #[derive( + // serde_derive::Serialize, serde_derive::Deserialize, Eq, PartialEq, Clone, Default, Debug, + // )] + // pub enum E { + // #[default] + // One, + // Two(String), + // Three, + // } + // assert_eq!( + // A { + // s: "foo".to_owned(), + // i: 33, + // o: None, + // a: vec![1, 2, 3], + // v: Some(Version::new([1, 0, 0])), + // e: E::Three + // }, + // A::parse( + // "# + // #s: foo + // #i: 33 + // #o: + // #a: [1, 2, 3] + // #v: 1.0.0 + // #e: Three + // " + // ) + // ); + // } } diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index cc29b94f1b..25d10784bb 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -38,7 +38,6 @@ use windmill_common::add_time; use windmill_common::auth::JobPerms; #[cfg(feature = "benchmark")] use windmill_common::bench::BenchmarkIter; -use windmill_common::lockfiles::is_generated_from_raw_requirements; use windmill_common::jobs::{JobTriggerKind, EMAIL_ERROR_HANDLER_USER_EMAIL}; use windmill_common::utils::{configure_client, now_from_db}; use windmill_common::worker::{Connection, MIN_VERSION_SUPPORTS_DEBOUNCING, SCRIPT_TOKEN_EXPIRY}; @@ -2659,13 +2658,6 @@ impl PulledJobResult { ) .await?; - if is_generated_from_raw_requirements(&Some(cloned_script.old_script.language), &cloned_script.old_script.lock.map(|v| v.to_string())) { - return Err(Error::BadRequest(format!( - "Script at path {} is generated from raw requirements, not overriding", - pulled_job.runnable_path() - ))); - } - cloned_script.new_hash } JobKind::FlowDependencies => { @@ -3632,7 +3624,7 @@ pub enum PushIsolationLevel<'c> { } impl<'c> PushIsolationLevel<'c> { - async fn into_tx(self) -> error::Result> { + pub async fn into_tx(self) -> error::Result> { match self { PushIsolationLevel::Isolated(db, authed) => Ok((db.begin(&authed).await?).into()), PushIsolationLevel::IsolatedRoot(db) => Ok(db.begin().await?), @@ -5867,3 +5859,4 @@ pub async fn get_same_worker_job( )) }) } + diff --git a/backend/windmill-worker/src/ansible_executor.rs b/backend/windmill-worker/src/ansible_executor.rs index 9aed0e18ad..3da3fa619d 100644 --- a/backend/windmill-worker/src/ansible_executor.rs +++ b/backend/windmill-worker/src/ansible_executor.rs @@ -13,10 +13,10 @@ use tokio::process::Command; use uuid::Uuid; use windmill_common::{ error, - git_sync_oss::{prepend_token_to_github_url}, + git_sync_oss::prepend_token_to_github_url, worker::{ - is_allowed_file_location, to_raw_value, write_file, write_file_at_user_defined_location, - Connection, WORKER_CONFIG, + is_allowed_file_location, split_python_requirements, to_raw_value, write_file, + write_file_at_user_defined_location, Connection, PyVAlias, WORKER_CONFIG, }, }; use windmill_queue::MiniPulledJob; @@ -34,8 +34,8 @@ use crate::{ }, handle_child::handle_child, python_executor::{create_dependencies_dir, handle_python_reqs, uv_pip_compile}, - DISABLE_NSJAIL, DISABLE_NUSER, GIT_PATH, HOME_ENV, NSJAIL_PATH, PATH_ENV, - PROXY_ENVS, PY_INSTALL_DIR, PyVAlias, TZ_ENV, + DISABLE_NSJAIL, DISABLE_NUSER, GIT_PATH, HOME_ENV, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, + PY_INSTALL_DIR, TZ_ENV, }; use windmill_common::client::AuthedClient; @@ -399,7 +399,7 @@ async fn handle_ansible_python_deps( if requirements.len() > 0 { let mut venv_path = handle_python_reqs( - crate::python_executor::split_requirements(requirements), + split_python_requirements(requirements), job_id, w_id, mem_peak, @@ -958,7 +958,11 @@ pub async fn handle_ansible_job( #[cfg(feature = "enterprise")] if is_github_app { if let Connection::Sql(db) = conn { - let token = windmill_common::git_sync_oss::get_github_app_token_internal(db, &client.token).await?; + let token = windmill_common::git_sync_oss::get_github_app_token_internal( + db, + &client.token, + ) + .await?; secret_url = prepend_token_to_github_url(&secret_url, &token)?; } else { return Err(windmill_common::error::Error::BadRequest("Github App authentication is currently unavailable for agent workers. Contact the windmill team to request this feature".to_string())); @@ -970,8 +974,12 @@ pub async fn handle_ansible_job( let target_path = "delegate_git_repository".to_string(); - let repo = - GitRepo { url: secret_url, commit: delegated_git_repo.commit.clone(), branch, target_path }; + let repo = GitRepo { + url: secret_url, + commit: delegated_git_repo.commit.clone(), + branch, + target_path, + }; append_logs( &job.id, &job.workspace_id, @@ -1241,10 +1249,8 @@ fi start_child_process(nsjail_cmd, NSJAIL_PATH.as_str(), false).await? } else { let ansible_args: Vec<&str> = cmd_args.iter().map(|s| s.as_ref()).collect(); - let mut ansible_cmd = build_command_with_isolation( - ANSIBLE_PLAYBOOK_PATH.as_str(), - &ansible_args, - ); + let mut ansible_cmd = + build_command_with_isolation(ANSIBLE_PLAYBOOK_PATH.as_str(), &ansible_args); ansible_cmd .current_dir(job_dir) .env_clear() diff --git a/backend/windmill-worker/src/bun_executor.rs b/backend/windmill-worker/src/bun_executor.rs index d1c50b9024..e76974d319 100644 --- a/backend/windmill-worker/src/bun_executor.rs +++ b/backend/windmill-worker/src/bun_executor.rs @@ -16,7 +16,7 @@ use crate::{ common::{ build_command_with_isolation, create_args_and_out_file, get_reserved_variables, parse_npm_config, read_file, read_file_content, read_result, start_child_process, - write_file_binary, OccupancyMetrics, StreamNotifier, + write_file_binary, MaybeLock, OccupancyMetrics, StreamNotifier, }, handle_child::handle_child, BUNFIG_INSTALL_SCOPES, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, BUN_NO_CACHE, BUN_PATH, @@ -27,6 +27,7 @@ use windmill_common::{ client::AuthedClient, s3_helpers::BundleFormat, scripts::{id_to_codebase_info, CodebaseInfo}, + workspace_dependencies::WorkspaceDependenciesPrefetched, }; #[cfg(windows)] @@ -40,7 +41,7 @@ use windmill_common::{ error::{self, Result}, get_latest_hash_for_path, scripts::ScriptLang, - worker::{exists_in_cache, save_cache, to_raw_value, write_file, Connection, DISABLE_BUNDLING}, + worker::{exists_in_cache, save_cache, write_file, Connection, DISABLE_BUNDLING}, DB, }; @@ -101,7 +102,7 @@ pub async fn gen_bun_lockfile( base_internal_url: &str, worker_name: &str, export_pkg: bool, - raw_deps: Option, + workspace_dependencies: &WorkspaceDependenciesPrefetched, npm_mode: bool, occupancy_metrics: &mut Option<&mut OccupancyMetrics>, ) -> Result> { @@ -109,11 +110,11 @@ pub async fn gen_bun_lockfile( let mut empty_deps = false; - if let Some(raw_deps) = raw_deps.as_ref() { + if let Some(package_json_content) = workspace_dependencies.get_bun()? { gen_bunfig(job_dir).await?; - write_file(job_dir, "package.json", raw_deps.as_str())?; + write_file(job_dir, "package.json", package_json_content.as_str())?; } else { - let _ = write_file( + write_file( &job_dir, "build.js", &format!( @@ -205,16 +206,7 @@ pub async fn gen_bun_lockfile( let mut file = File::open(format!("{job_dir}/package.json")).await?; let mut buf = String::default(); file.read_to_string(&mut buf).await?; - if raw_deps.is_some() { - let mut json_map: HashMap> = serde_json::from_str(&buf)?; - json_map.insert( - "generatedFromPackageJson".to_string(), - to_raw_value(&"true".to_string()), - ); - content = serde_json::to_string_pretty(&json_map)?; - } else { - content = buf; - } + content = buf; } if !npm_mode { #[cfg(any(target_os = "linux", target_os = "macos"))] @@ -703,7 +695,7 @@ fn extract_saved_codebase( pub async fn prebundle_bun_script( inner_content: &str, - lockfile: Option<&String>, + lock: &str, script_path: &str, job_id: &Uuid, w_id: &str, @@ -715,7 +707,7 @@ pub async fn prebundle_bun_script( occupancy_metrics: &mut Option<&mut OccupancyMetrics>, ) -> Result<()> { let (local_path, remote_path) = - compute_bundle_local_and_remote_path(inner_content, lockfile, script_path, db, w_id).await; + compute_bundle_local_and_remote_path(inner_content, lock, script_path, db, w_id).await; if exists_in_cache(&local_path, &remote_path).await { return Ok(()); } @@ -779,16 +771,12 @@ async fn get_script_import_updated_at(db: &DB, w_id: &str, script_path: &str) -> pub async fn compute_bundle_local_and_remote_path( inner_content: &str, - requirements_o: Option<&String>, + lock: &str, script_path: &str, db: Option<&DB>, w_id: &str, ) -> (String, String) { - let mut input_src = format!( - "{}{}", - inner_content, - requirements_o.as_ref().map(|x| x.as_str()).unwrap_or("") - ); + let mut input_src = format!("{inner_content}{lock}",); if let Some(db) = db { let relative_imports = crate::worker_lockfiles::extract_relative_imports( @@ -844,7 +832,7 @@ async fn write_lock(splitted_lockb_2: &str, job_dir: &str, is_binary: bool) -> R #[tracing::instrument(level = "trace", skip_all)] pub async fn handle_bun_job( - requirements_o: Option<&String>, + maybe_lock: MaybeLock, codebase: Option<&String>, mem_peak: &mut i32, canceled_by: &mut Option, @@ -865,16 +853,15 @@ pub async fn handle_bun_job( ) -> error::Result> { let mut annotation = windmill_common::worker::TypeScriptAnnotations::parse(inner_content); - let (mut has_bundle_cache, cache_logs, local_path, remote_path) = if requirements_o.is_some() - && !annotation.nobundling - && !*DISABLE_BUNDLING - && codebase.is_none() - { + let (mut has_bundle_cache, cache_logs, local_path, remote_path) = if let (Some(lock), true) = ( + maybe_lock.get_lock(), + !annotation.nobundling && !*DISABLE_BUNDLING && codebase.is_none(), + ) { let (local_path, remote_path) = match conn { Connection::Sql(db) => { compute_bundle_local_and_remote_path( inner_content, - requirements_o, + lock, job.runnable_path(), Some(db), &job.workspace_id, @@ -940,59 +927,70 @@ pub async fn handle_bun_job( if pulled_codebase.is_esm { format = BundleFormat::Esm; } - } else if let Some(reqs) = requirements_o.as_ref() { - let (pkg, lock, empty, is_binary) = split_lockfile(reqs); - - if lock.is_none() && !annotation.npm { - return Err(error::Error::ExecutionErr( - format!("Invalid requirements, expected to find //bun.lock{} split pattern in reqs. Found: |{reqs}|", if is_binary {"b"} else {""}) - )); - } - - let _ = write_file(job_dir, "package.json", pkg)?; - let lock = if annotation.npm { "" } else { lock.unwrap() }; - if !empty { - if !annotation.npm { - let _ = write_lock(lock, job_dir, is_binary).await?; - } - - install_bun_lockfile( - mem_peak, - canceled_by, - &job.id, - &job.workspace_id, - Some(conn), - job_dir, - worker_name, - common_bun_proc_envs.clone(), - annotation.npm, - &mut Some(occupancy_metrics), - ) - .await?; - } } else { - // if !*DISABLE_NSJAIL || !empty_trusted_deps || has_custom_config_registry { - let logs1 = "\n\n--- BUN INSTALL ---\n".to_string(); - append_logs(&job.id, &job.workspace_id, logs1, conn).await; - let _ = gen_bun_lockfile( - mem_peak, - canceled_by, - &job.id, - &job.workspace_id, - Some(conn), - &client.token, - job.runnable_path(), - job_dir, - base_internal_url, - worker_name, - false, - None, - annotation.npm, - &mut Some(occupancy_metrics), - ) - .await?; + match &maybe_lock { + MaybeLock::Resolved { lock } => { + let (package_json, bun_lock, empty, is_binary) = split_lockfile(lock); - // } + if bun_lock.is_none() && !annotation.npm { + return Err(error::Error::ExecutionErr( + format!("Invalid requirements, expected to find //bun.lock{} split pattern in reqs. Found: |{lock}|", if is_binary {"b"} else {""}) + )); + } + + write_file(job_dir, "package.json", package_json)?; + + let bun_lock = if annotation.npm { + "" + } else { + bun_lock.unwrap() + }; + + if !empty { + if !annotation.npm { + write_lock(bun_lock, job_dir, is_binary).await?; + } + + install_bun_lockfile( + mem_peak, + canceled_by, + &job.id, + &job.workspace_id, + Some(conn), + job_dir, + worker_name, + common_bun_proc_envs.clone(), + annotation.npm, + &mut Some(occupancy_metrics), + ) + .await?; + } + } + MaybeLock::Unresolved { ref workspace_dependencies } => { + // if !*DISABLE_NSJAIL || !empty_trusted_deps || has_custom_config_registry { + let logs1 = "\n\n--- BUN INSTALL ---\n".to_string(); + append_logs(&job.id, &job.workspace_id, logs1, conn).await; + gen_bun_lockfile( + mem_peak, + canceled_by, + &job.id, + &job.workspace_id, + Some(conn), + &client.token, + job.runnable_path(), + job_dir, + base_internal_url, + worker_name, + false, + workspace_dependencies, + annotation.npm, + &mut Some(occupancy_metrics), + ) + .await?; + + // } + } + } } if codebase.is_some() && format == BundleFormat::Cjs { @@ -1184,7 +1182,7 @@ try {{ && !annotation.nobundling && !*DISABLE_BUNDLING && !codebase.is_some() - && (requirements_o.is_some() || annotation.native); + && (maybe_lock.get_lock().is_some() || annotation.native); let write_loader_f = async { if build_cache { @@ -1689,7 +1687,15 @@ pub async fn start_worker( base_internal_url, worker_name, false, - None, + &WorkspaceDependenciesPrefetched::extract( + inner_content, + ScriptLang::Bun, + w_id, + &None, + &script_path, + db.into(), + ) + .await?, annotation.npm, &mut None, ) diff --git a/backend/windmill-worker/src/common.rs b/backend/windmill-worker/src/common.rs index 2d995ea9bb..4856e030f7 100644 --- a/backend/windmill-worker/src/common.rs +++ b/backend/windmill-worker/src/common.rs @@ -24,6 +24,7 @@ use windmill_common::worker::{ to_raw_value, update_ping_for_failed_init_script_query, write_file, Connection, Ping, PingType, CLOUD_HOSTED, ROOT_CACHE_DIR, WORKER_CONFIG, }; +use windmill_common::workspace_dependencies::WorkspaceDependenciesPrefetched; use windmill_common::{ cache::{Cache, RawData}, error::{self, Error}, @@ -629,10 +630,7 @@ lazy_static! { static ref DISABLE_PROCESS_GROUP: bool = std::env::var("DISABLE_PROCESS_GROUP").is_ok(); } -pub fn build_command_with_isolation( - program: &str, - args: &[&str], -) -> Command { +pub fn build_command_with_isolation(program: &str, args: &[&str]) -> Command { use tokio::process::Command; if *crate::ENABLE_UNSHARE_PID { @@ -1371,3 +1369,35 @@ pub fn s3_mode_args_to_worker_data( workspace_id: job.workspace_id.clone(), } } + +#[derive(Debug)] +pub enum MaybeLock { + /// Deployed Scripts + Resolved { lock: String }, + /// Previews + Unresolved { workspace_dependencies: WorkspaceDependenciesPrefetched }, +} + +impl MaybeLock { + pub fn map_unresolved(&self, mut f: F) -> Option + where + Self: Sized, + F: FnMut(&WorkspaceDependenciesPrefetched) -> B, + { + self.get_workspace_dependencies().map(|wd| f(wd)) + } + + pub fn get_workspace_dependencies(&self) -> Option<&WorkspaceDependenciesPrefetched> { + match self { + MaybeLock::Resolved { .. } => None, + MaybeLock::Unresolved { ref workspace_dependencies } => Some(workspace_dependencies), + } + } + + pub fn get_lock(&self) -> Option<&String> { + match self { + MaybeLock::Resolved { ref lock } => Some(lock), + MaybeLock::Unresolved { .. } => None, + } + } +} diff --git a/backend/windmill-worker/src/go_executor.rs b/backend/windmill-worker/src/go_executor.rs index bf3957c430..d99adff7e3 100644 --- a/backend/windmill-worker/src/go_executor.rs +++ b/backend/windmill-worker/src/go_executor.rs @@ -1,4 +1,4 @@ -use crate::PROXY_ENVS; +use crate::{common::MaybeLock, PROXY_ENVS}; use std::{collections::HashMap, fs::DirBuilder, process::Stdio}; use itertools::Itertools; @@ -19,8 +19,8 @@ use windmill_queue::{append_logs, CanceledBy, MiniPulledJob}; use crate::{ common::{ - build_command_with_isolation, capitalize, create_args_and_out_file, get_reserved_variables, read_result, - start_child_process, OccupancyMetrics, + build_command_with_isolation, capitalize, create_args_and_out_file, get_reserved_variables, + read_result, start_child_process, OccupancyMetrics, }, handle_child::handle_child, DISABLE_NSJAIL, DISABLE_NUSER, GOPRIVATE, GOPROXY, GO_BIN_CACHE_DIR, GO_CACHE_DIR, HOME_ENV, @@ -91,12 +91,12 @@ pub async fn handle_go_job( parent_runnable_path: Option, inner_content: &str, job_dir: &str, - requirements_o: Option<&String>, shared_mount: &str, base_internal_url: &str, worker_name: &str, envs: HashMap, occupation_metrics: &mut OccupancyMetrics, + maybe_lock: MaybeLock, ) -> Result, Error> { //go does not like executing modules at temp root let job_dir = &format!("{job_dir}/go"); @@ -105,14 +105,7 @@ pub async fn handle_go_job( .create(&job_dir) .expect("could not create go job dir"); - let hash = calculate_hash(&format!( - "{}{}v2", - inner_content, - requirements_o - .as_ref() - .map(|x| x.to_string()) - .unwrap_or_default() - )); + let hash = calculate_hash(&format!("{}{:?}v2", inner_content, &maybe_lock)); let bin_path = format!("{}/{hash}", GO_BIN_CACHE_DIR); let remote_path = format!("{GO_OBJECT_STORE_PREFIX}{hash}"); let (cache, cache_logs) = @@ -120,8 +113,8 @@ pub async fn handle_go_job( let (skip_go_mod, skip_tidy) = if cache { (true, true) - } else if let Some(requirements) = requirements_o { - gen_go_mod(inner_content, job_dir, &requirements).await? + } else if let Some(lock) = maybe_lock.get_lock() { + gen_go_mod(inner_content, job_dir, &lock).await? } else { (false, false) }; @@ -133,6 +126,7 @@ pub async fn handle_go_job( install_go_dependencies( &job.id, inner_content, + maybe_lock, mem_peak, canceled_by, job_dir, @@ -140,7 +134,6 @@ pub async fn handle_go_job( true, skip_go_mod, skip_tidy, - false, worker_name, &job.workspace_id, occupation_metrics, @@ -430,20 +423,16 @@ func Run(req Req) (interface{{}}, error){{ read_result(job_dir, handle_result.result_stream).await } -async fn gen_go_mod( - inner_content: &str, - job_dir: &str, - requirements: &str, -) -> error::Result<(bool, bool)> { +async fn gen_go_mod(inner_content: &str, job_dir: &str, lock: &str) -> error::Result<(bool, bool)> { gen_go_mymod(inner_content, job_dir).await?; - let md = requirements.split_once(GO_REQ_SPLITTER); + let md = lock.split_once(GO_REQ_SPLITTER); if let Some((req, sum)) = md { write_file(job_dir, "go.mod", &req)?; write_file(job_dir, "go.sum", &sum)?; Ok((true, true)) } else { - write_file(job_dir, "go.mod", &requirements)?; + write_file(job_dir, "go.mod", &lock)?; Ok((true, false)) } } @@ -454,92 +443,99 @@ use std::io::prelude::*; pub async fn install_go_dependencies( job_id: &Uuid, code: &str, + maybe_lock: MaybeLock, mem_peak: &mut i32, canceled_by: &mut Option, job_dir: &str, conn: &Connection, non_dep_job: bool, + // NOTE: this is impossible for skip_go_mod be `false` and maybe_lock be `Resolved`. + // TODO: make it comptime gurantee skip_go_mod: bool, has_sum: bool, - raw_deps: bool, worker_name: &str, w_id: &str, occupation_metrics: &mut OccupancyMetrics, ) -> error::Result { let anns = GoAnnotations::parse(code); - if raw_deps { - let go_mod = - if let Some(module) = code.lines().find(|l| l.trim_start().starts_with("module ")) { - code.replace(module, "module mymod") + + let hash_input = match maybe_lock { + MaybeLock::Resolved { ref lock } => lock.clone(), + MaybeLock::Unresolved { ref workspace_dependencies } => { + // NOTE: This will always be none, go workspace dependencies are disabled for now. + // read more on discord (internal): + // https://discord.com/channels/930051556043276338/1031563866641018910/1443541229349634189 + if let Some(go_mod) = workspace_dependencies.get_go()? { + if !skip_go_mod { + gen_go_mymod(code, job_dir).await?; + fs::write(format!("{job_dir}/go.mod"), &go_mod).await?; + } + go_mod } else { - format!("module mymod\n{code}") - }; - fs::write(format!("{job_dir}/go.mod"), go_mod).await?; - } - if !raw_deps && !skip_go_mod { - gen_go_mymod(code, job_dir).await?; - let mut child_cmd = Command::new(GO_PATH.as_str()); - child_cmd - .current_dir(job_dir) - .env_clear() - .args(vec!["mod", "init", "mymod"]) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); + if !skip_go_mod { + gen_go_mymod(code, job_dir).await?; + let mut child_cmd = Command::new(GO_PATH.as_str()); + child_cmd + .current_dir(job_dir) + .env_clear() + .args(vec!["mod", "init", "mymod"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); - #[cfg(windows)] - child_cmd.env("GOPATH", windows_gopath()); - #[cfg(unix)] - child_cmd.env("GOPATH", GO_CACHE_DIR); + #[cfg(windows)] + child_cmd.env("GOPATH", windows_gopath()); + #[cfg(unix)] + child_cmd.env("GOPATH", GO_CACHE_DIR); - #[cfg(windows)] - set_windows_env_vars(&mut child_cmd); - let child_process = start_child_process(child_cmd, GO_PATH.as_str(), false).await?; + #[cfg(windows)] + set_windows_env_vars(&mut child_cmd); + let child_process = + start_child_process(child_cmd, GO_PATH.as_str(), false).await?; - handle_child( - job_id, - conn, - mem_peak, - canceled_by, - child_process, - false, - worker_name, - w_id, - "go init", - None, - false, - &mut Some(occupation_metrics), - None, - None, - ) - .await?; + handle_child( + job_id, + conn, + mem_peak, + canceled_by, + child_process, + false, + worker_name, + w_id, + "go init", + None, + false, + &mut Some(occupation_metrics), + None, + None, + ) + .await?; - for x in REQUIRE_PARSE.captures_iter(code) { - let mut file = OpenOptions::new() - .write(true) - .append(true) - .open(format!("{job_dir}/go.mod")) - .unwrap(); + for x in REQUIRE_PARSE.captures_iter(code) { + let mut file = OpenOptions::new() + .write(true) + .append(true) + .open(format!("{job_dir}/go.mod")) + .unwrap(); - writeln!(file, "require {}\n", &x[1])?; + writeln!(file, "require {}\n", &x[1])?; + } + } + if !has_sum { + calculate_hash(parse_go_imports(&code)?.iter().join("\n").as_str()) + } else { + "".to_owned() + } + } } - } - - let mut new_lockfile = false; - - let hash = if raw_deps { - calculate_hash(code) - } else if !has_sum { - calculate_hash(parse_go_imports(&code)?.iter().join("\n").as_str()) - } else { - "".to_string() }; + let hash = format!( "go{}-{}", if anns.go1_22_compat { "1.22" } else { "" }, - hash + calculate_hash(&hash_input) ); - let mut skip_tidy = has_sum; + let (mut new_lockfile, mut skip_tidy) = (false, has_sum); if !has_sum { if let Some(db) = conn.as_sql() { @@ -561,15 +557,7 @@ pub async fn install_go_dependencies( } } - let mod_command = if skip_tidy || - // If there is go.mod provided we want to use `download` only. - // Unlike `tidy` it does not modify local go.mod - raw_deps - { - "download" - } else { - "tidy" - }; + let mod_command = if skip_tidy { "download" } else { "tidy" }; let mut child_cmd = Command::new(GO_PATH.as_str()); child_cmd .current_dir(job_dir) diff --git a/backend/windmill-worker/src/lib.rs b/backend/windmill-worker/src/lib.rs index c5db591c42..6c9663d2d2 100644 --- a/backend/windmill-worker/src/lib.rs +++ b/backend/windmill-worker/src/lib.rs @@ -70,6 +70,7 @@ mod worker; mod worker_flow; mod worker_lockfiles; mod worker_utils; +pub mod workspace_dependencies; pub use worker::*; pub use worker_lockfiles::{ @@ -85,4 +86,4 @@ pub use bun_executor::{ pub use deno_executor::generate_deno_lock; #[cfg(feature = "python")] -pub use python_versions::{PyV, PyVAlias}; +pub use python_versions::PyV; diff --git a/backend/windmill-worker/src/php_executor.rs b/backend/windmill-worker/src/php_executor.rs index 4ead742f63..dd6f3b202e 100644 --- a/backend/windmill-worker/src/php_executor.rs +++ b/backend/windmill-worker/src/php_executor.rs @@ -7,7 +7,9 @@ use tokio::{fs::File, io::AsyncReadExt, process::Command}; use uuid::Uuid; use windmill_common::{ error::{self, to_anyhow, Result}, + scripts::ScriptLang, worker::{write_file, Connection}, + workspace_dependencies::clean_lock_from_annotations, }; use windmill_queue::MiniPulledJob; @@ -17,11 +19,10 @@ use windmill_queue::{append_logs, CanceledBy}; use crate::{ common::{ build_command_with_isolation, check_executor_binary_exists, create_args_and_out_file, - get_reserved_variables, read_result, start_child_process, OccupancyMetrics, + get_reserved_variables, read_result, start_child_process, MaybeLock, OccupancyMetrics, }, handle_child::handle_child, - COMPOSER_CACHE_DIR, COMPOSER_PATH, DISABLE_NSJAIL, DISABLE_NUSER, - NSJAIL_PATH, PHP_PATH, + COMPOSER_CACHE_DIR, COMPOSER_PATH, DISABLE_NSJAIL, DISABLE_NUSER, NSJAIL_PATH, PHP_PATH, }; use windmill_common::client::AuthedClient; @@ -135,9 +136,23 @@ $args->{arg_name} = new {rt_name}($args->{arg_name});" ) } +fn split_reqs_and_lock(content: &String) -> error::Result<(Option, Option)> { + let splitted = content.split(COMPOSER_LOCK_SPLIT).collect_vec(); + if splitted.len() != 2 { + return Err(error::Error::ExecutionErr(format!( + "Invalid requirements, expected to find LOCK split pattern in reqs. Found: |{content}|" + ))); + } + + Ok(( + Some(clean_lock_from_annotations(splitted[0], ScriptLang::Php)), + Some(splitted[1].to_string()), + )) +} + #[tracing::instrument(level = "trace", skip_all)] pub async fn handle_php_job( - requirements_o: Option<&String>, + maybe_lock: MaybeLock, mem_peak: &mut i32, canceled_by: &mut Option, job: &MiniPulledJob, @@ -154,16 +169,14 @@ pub async fn handle_php_job( ) -> error::Result> { check_executor_binary_exists("php", PHP_PATH.as_str(), "php")?; - let (composer_json, composer_lock) = match requirements_o { - Some(reqs_and_lock) if !reqs_and_lock.is_empty() => { - let splitted = reqs_and_lock.split(COMPOSER_LOCK_SPLIT).collect_vec(); - if splitted.len() != 2 { - return Err(error::Error::ExecutionErr( - format!("Invalid requirements, expected to find LOCK split pattern in reqs. Found: |{reqs_and_lock}|") - )); - } - (Some(splitted[0].to_string()), Some(splitted[1].to_string())) - } + let (composer_json, composer_lock) = match &maybe_lock { + MaybeLock::Resolved { lock } if !lock.is_empty() => split_reqs_and_lock(lock)?, + MaybeLock::Unresolved { workspace_dependencies } => ( + workspace_dependencies + .get_php()? + .or(parse_php_imports(inner_content)?), + None, + ), _ => (parse_php_imports(inner_content)?, None), }; diff --git a/backend/windmill-worker/src/python_executor.rs b/backend/windmill-worker/src/python_executor.rs index b417014d85..3f64641813 100644 --- a/backend/windmill-worker/src/python_executor.rs +++ b/backend/windmill-worker/src/python_executor.rs @@ -30,7 +30,8 @@ use windmill_common::{ }, utils::calculate_hash, worker::{ - copy_dir_recursively, pad_string, write_file, Connection, PythonAnnotations, WORKER_CONFIG, + copy_dir_recursively, pad_string, split_python_requirements, write_file, Connection, + PyVAlias, PythonAnnotations, WORKER_CONFIG, }, }; @@ -122,13 +123,13 @@ use windmill_common::s3_helpers::OBJECT_STORE_SETTINGS; use crate::{ common::{ - build_command_with_isolation, create_args_and_out_file, get_reserved_variables, read_file, read_result, - start_child_process, OccupancyMetrics, StreamNotifier, + build_command_with_isolation, create_args_and_out_file, get_reserved_variables, read_file, + read_result, start_child_process, OccupancyMetrics, StreamNotifier, }, handle_child::handle_child, worker_utils::ping_job_status, - PyV, PyVAlias, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, - PIP_EXTRA_INDEX_URL, PIP_INDEX_URL, PROXY_ENVS, PY_INSTALL_DIR, TZ_ENV, UV_CACHE_DIR, + PyV, DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NSJAIL_PATH, PATH_ENV, PIP_EXTRA_INDEX_URL, + PIP_INDEX_URL, PROXY_ENVS, PY_INSTALL_DIR, TZ_ENV, UV_CACHE_DIR, }; use windmill_common::client::AuthedClient; @@ -566,7 +567,7 @@ pub async fn handle_python_job( canceled_by, &mut Some(occupancy_metrics), precomputed_agent_info, - annotations, + annotations.clone(), ) .await?; @@ -838,10 +839,7 @@ mount {{ } else { let args = vec!["-u", "-m", "wrapper"]; - let mut python_cmd = build_command_with_isolation( - &python_path, - &args, - ); + let mut python_cmd = build_command_with_isolation(&python_path, &args); python_cmd .current_dir(job_dir) .env_clear() @@ -1177,36 +1175,43 @@ async fn handle_python_deps( let (pyv, resolved_lines) = match requirements_o { // Deployed Some(r) => { - let rl = split_requirements(r); + let rl = split_python_requirements(r); (PyV::parse_from_requirements(&rl), rl) } // Preview None => { let (v, requirements_lines, error_hint) = match conn { Connection::Sql(db) => { - let mut version_specifiers = vec![]; + let (mut version_specifiers, mut locked_v) = (vec![], None); let (r, h) = Box::pin(windmill_parser_py_imports::parse_python_imports( inner_content, w_id, script_path, db, &mut version_specifiers, + &mut locked_v, + &None, )) .await?; - let v = PyV::resolve( - version_specifiers, - job_id, - w_id, - annotations.py_select_latest, - Some(conn.clone()), - None, - None, - ) - .await?; + let v = if let Some(v) = locked_v { + v.into() + } else { + PyV::resolve( + version_specifiers, + job_id, + w_id, + annotations.py_select_latest, + Some(conn.clone()), + None, + None, + ) + .await? + }; (v, r, h) } + Connection::Http(_) => match precomputed_agent_info { Some(PrecomputedAgentInfo::Python { requirements, @@ -1237,7 +1242,7 @@ Returned from server: py_version - {:?}, py_version_v2 - {:?} } }; - let r = split_requirements(requirements.unwrap_or_default()); + let r = split_python_requirements(requirements.unwrap_or_default()); let h = None; (v, r, h) @@ -2113,15 +2118,6 @@ pub async fn handle_python_reqs( }; } -pub fn split_requirements>(requirements: T) -> Vec { - requirements - .as_ref() - .lines() - .filter(|x| !x.trim_start().starts_with("--") && !x.trim().is_empty()) - .map(String::from) - .collect() -} - // Returns code snippet that needs to be injected into wrapper to post-process results or leave unprocessed fn get_result_postprocessor<'a>(skip: bool) -> &'a str { if skip { @@ -2157,7 +2153,7 @@ pub async fn start_worker( killpill_rx: tokio::sync::broadcast::Receiver<()>, client: windmill_common::client::AuthedClient, ) -> error::Result<()> { - use crate::{PyV, PyVAlias}; + use crate::PyV; tracing::info!("script path: {}", script_path); let mut mem_peak: i32 = 0; @@ -2317,7 +2313,7 @@ for line in sys.stdin: proc_envs.insert("BASE_URL".to_string(), base_internal_url.to_string()); let py_version = if let Some(requirements) = requirements_o { - PyV::parse_from_requirements(&split_requirements(requirements.as_str())) + PyV::parse_from_requirements(&split_python_requirements(requirements.as_str())) } else { tracing::warn!(workspace_id = %w_id, "lockfile is empty for dedicated worker, thus python version cannot be inferred. Fallback to 3.11"); PyVAlias::Py311.into() diff --git a/backend/windmill-worker/src/python_versions.rs b/backend/windmill-worker/src/python_versions.rs index b4b39a0c46..9cf74e9e0e 100644 --- a/backend/windmill-worker/src/python_versions.rs +++ b/backend/windmill-worker/src/python_versions.rs @@ -12,8 +12,7 @@ use tokio::{fs::DirBuilder, process::Command, sync::RwLock}; use uuid::Uuid; use windmill_common::{ error::{self, Error}, - lockfiles::LOCKFILE_GENERATED_FROM_REQUIREMENTS_TXT, - worker::Connection, + worker::{try_parse_locked_python_version_from_requirements, Connection, PyVAlias}, }; use anyhow::{anyhow, bail}; @@ -26,28 +25,6 @@ use crate::{ HOME_ENV, INSTANCE_PYTHON_VERSION, PATH_ENV, PROXY_ENVS, PY_INSTALL_DIR, WIN_ENVS, }; -#[derive(Eq, PartialEq, Clone, Copy, Default, Debug)] -#[repr(u32)] -pub enum PyVAlias { - Py310 = 10, - #[default] - Py311, - Py312, - Py313, -} - -impl Into for PyVAlias { - fn into(self) -> pep440_rs::Version { - pep440_rs::Version::new([self.major() as u64, self as u64]) - } -} - -impl Into for PyVAlias { - fn into(self) -> u32 { - self.major() * 100 + self as u32 - } -} - impl From for PyVAlias { fn from(value: PyV) -> Self { match value.release() { @@ -66,34 +43,6 @@ impl From for PyVAlias { Self::default() } } -impl PyVAlias { - fn all>() -> Vec { - use PyVAlias::*; - vec![Py310.into(), Py311.into(), Py312.into(), Py313.into()] - } - // Get MAJOR part of alias. (semver: MAJOR.MINOR.PATCH) - fn major(&self) -> u32 { - use PyVAlias::*; - match self { - Py310 | Py311 | Py312 | Py313 => 3, - // Py400 | Py401 => 4 - } - } - - /// Converts numeric format to alias - /// Example: - /// 310u32 (in) -> PyVAlias::Py310 (out) - pub(crate) fn try_from_v1(numeric: T) -> Option { - use PyVAlias::*; - match numeric.to_string().as_str() { - "310" => Some(Py310), - "311" => Some(Py311), - "312" => Some(Py312), - "313" => Some(Py313), - _ => None, - } - } -} // To change latest stable version: // 1. Change placeholder in instanceSettings.ts @@ -431,51 +380,7 @@ impl PyV { /// Parse lockfile for assigned python version. /// If not found returns None pub fn try_parse_from_requirements>(requirements_lines: &[S]) -> Option { - let parse_version = |s: &str| -> Option { - // Possible inputs: - // V2: - // # py: 3.11.0 or #py:3.11.0 or #py: 3.11.0 - // - // V1: - // # py311 or #py311 - let version_unparsed = s - .to_owned() - // Remove whitespaces. That leaves us with: - // V2: #py:3.11.0 - // V1: #py311 - // - // Remove # - // V2: py:3.11.0 - // V1: py311 - // - // Remove : - // V2: py3.11.0 - // V1: py311 - .replace([' ', '#', ':'], "") - // Remove "py" - // V2: 3.11.0 - // V1: 311 - .replace("py", ""); - - // We will support reading V1 syntax, but it will be overwritten next deploy - PyVAlias::try_from_v1(&version_unparsed) - .map(PyVAlias::into) - .or(pep440_rs::Version::from_str(&version_unparsed) - .ok() - .map(pep440_rs::Version::into)) - }; - let index = if requirements_lines.get(0).map_or(false, |line| { - line.as_ref() - .starts_with(LOCKFILE_GENERATED_FROM_REQUIREMENTS_TXT) - }) { - 1 - } else { - 0 - }; - requirements_lines - .get(index) - .map(S::as_ref) - .and_then(parse_version) + try_parse_locked_python_version_from_requirements(requirements_lines).map(PyV::from) } pub async fn get_python( diff --git a/backend/windmill-worker/src/scoped_dependency_map.rs b/backend/windmill-worker/src/scoped_dependency_map.rs index 072fedc8a6..9e9884e538 100644 --- a/backend/windmill-worker/src/scoped_dependency_map.rs +++ b/backend/windmill-worker/src/scoped_dependency_map.rs @@ -1,22 +1,24 @@ use serde::Serialize; +use sqlx::PgExecutor; use tokio::sync::RwLock; use windmill_common::{ apps::traverse_app_inline_scripts, cache, error::{Error, Result}, flows::{FlowModuleValue, FlowValue}, + scripts::ScriptLang, }; use std::collections::HashSet; -use crate::worker_lockfiles::extract_relative_imports; -use windmill_common::lockfiles::is_generated_from_raw_requirements; +use crate::worker_lockfiles::extract_referenced_paths; // TODO: To be removed in future versions lazy_static::lazy_static! { pub static ref WMDEBUG_NO_DMAP_DISSOLVE: bool = std::env::var("WMDEBUG_NO_DMAP_DISSOLVE").is_ok(); } +// TODO: Rename to DependencyRelation #[derive(Serialize)] pub struct DependencyMap { pub workspace_id: Option, @@ -26,9 +28,17 @@ pub struct DependencyMap { pub importer_node_id: Option, } +#[derive(Debug, Clone, Serialize, sqlx::FromRow)] +pub struct DependencyDependent { + pub importer_path: String, + pub importer_kind: String, + pub importer_node_ids: Option>, +} + #[derive(Debug)] pub struct ScopedDependencyMap { - dmap: HashSet<(String, String)>, + /// (importer_node_id, imported_path) + to_delete: HashSet<(String, String)>, w_id: String, importer_path: String, importer_kind: String, @@ -72,7 +82,7 @@ RETURNING importer_node_id, imported_path .fetch_all(executor) .await?; Ok(Self { - dmap: HashSet::from_iter(dmap.into_iter()), + to_delete: HashSet::from_iter(dmap.into_iter()), w_id: w_id.to_owned(), importer_path: importer_path.to_owned(), importer_kind: importer_kind.to_owned(), @@ -104,7 +114,7 @@ SELECT importer_node_id, imported_path .await?; Ok(Self { - dmap: HashSet::from_iter(dmap.into_iter()), + to_delete: HashSet::from_iter(dmap.into_iter()), w_id: w_id.to_owned(), importer_path: importer_path.to_owned(), importer_kind: importer_kind.to_owned(), @@ -115,22 +125,23 @@ SELECT importer_node_id, imported_path /// Remove matching entries pub(crate) async fn patch<'c>( &mut self, - relative_imports: Option>, + referenced_paths: Option>, node_id: String, // Flow Step/Node ID mut tx: sqlx::Transaction<'c, sqlx::Postgres>, ) -> Result> { - self.patch_tx_ref(relative_imports, &node_id, &mut tx) + self.patch_tx_ref(referenced_paths, &node_id, &mut tx) .await?; Ok(tx) } pub(crate) async fn patch_tx_ref<'c>( &mut self, - relative_imports: Option>, + // NOTE: Referenced_paths should include all of the paths. + referenced_paths: Option>, node_id: &str, // Flow Step/Node ID tx: &mut sqlx::Transaction<'c, sqlx::Postgres>, ) -> Result<()> { - let Some(mut relative_imports) = relative_imports else { + let Some(mut referenced_paths) = referenced_paths else { tracing::info!("relative imports are not found for: importer - {}, importer_node_id - {}, importer_kind - {}", &self.importer_path, &node_id, @@ -149,9 +160,9 @@ SELECT importer_node_id, imported_path // After all `reduce`'s called ScopedDependencyMap has only extra/orphan imports // these are going to be clean up by calling [dissolve] // NOTE: `retain` iterates over vec and remove the ones whose closures returned false. - relative_imports.retain(|imported_path| { + referenced_paths.retain(|imported_path| { !self - .dmap + .to_delete // As dmap is HashSet, removing is O(1) operation // thus making entire process very efficient // NOTE: `remove` returns true if item was removed and false if wasn't. @@ -159,15 +170,15 @@ SELECT importer_node_id, imported_path }); // As mentioned above, usually this will always be empty. - if !relative_imports.is_empty() { + if !referenced_paths.is_empty() { tracing::info!("adding missing entries to dependency_map: importer_node_id - {}, importer_kind - {}, new_imported_paths - {:?}", &node_id, &self.importer_kind, - &relative_imports, + &referenced_paths, ); } - for import in relative_imports { + for import in referenced_paths { sqlx::query!( "INSERT INTO dependency_map (workspace_id, importer_path, importer_kind, imported_path, importer_node_id) VALUES ($1, $2, $3::text::IMPORTER_KIND, $4, $5) ON CONFLICT DO NOTHING", @@ -200,7 +211,7 @@ SELECT importer_node_id, imported_path tracing::info!("dissolving dependency_map: {:?}", &self); // We _could_ shove it into single query, but this query is rarely called AND let's keep it simple for redability. - for (importer_node_id, imported_path) in self.dmap.into_iter() { + for (importer_node_id, imported_path) in self.to_delete.into_iter() { tracing::info!("cleaning orphan entry from dependency_map: importer_kind - {}, imported_path - {}, importer_node_id - {}", &self.importer_kind, &imported_path, @@ -275,14 +286,14 @@ SELECT importer_node_id, imported_path tx } - /// Run if you want to rebuild maps on specific workspace. - /// Potentially takes much time - pub async fn rebuild_map(w_id: &str, db: &sqlx::Pool) -> Result { - async fn inner<'c>(w_id: &str, db: &sqlx::Pool) -> Result { - // Scripts - tracing::info!(workspace_id = w_id, "Rebuilding dependency map for scripts"); - for r in sqlx::query!( - "SELECT path, hash FROM script WHERE workspace_id = $1 AND archived = false AND deleted = false", + pub(crate) async fn rebuild_map_unchecked<'c>( + w_id: &str, + db: &sqlx::Pool, + ) -> Result { + // Scripts + tracing::info!(workspace_id = w_id, "Rebuilding dependency map for scripts"); + for r in sqlx::query!( + r#"SELECT path, hash, language AS "language: ScriptLang" FROM script WHERE workspace_id = $1 AND archived = false AND deleted = false"#, w_id ) .fetch_all(db) @@ -292,29 +303,24 @@ SELECT importer_node_id, imported_path let mut dmap = ScopedDependencyMap::fetch(w_id, &r.path, "script", db).await?; let mut tx = db.begin().await?; - if is_generated_from_raw_requirements(&smd.language, &sd.lock) { - // if the lock file is generated from a package.json/requirements.txt, we need to clear the dependency map - // because we do not want to have dependencies be recomputed automatically. Empty relative imports passed - // to update_script_dependency_map will clear the dependency map. - } else { - tx = dmap - .patch( - extract_relative_imports(&sd.code, &r.path, &smd.language), - "".into(), - tx, - ) - .await?; - } + tx = dmap + .patch( + extract_referenced_paths(&sd.code, &r.path, smd.language), + "".into(), + tx, + ) + .await?; + if !*WMDEBUG_NO_DMAP_DISSOLVE { dmap.dissolve(tx).await.commit().await?; } tracing::info!(workspace_id = w_id, "Rebuilt for script {}", &r.path); } - // Fetch only top level versions and paths - // It is not fetching value - tracing::info!(workspace_id = w_id, "Rebuilding dependency map for flows"); - for r in sqlx::query!("SELECT path, versions[array_upper(versions, 1)] as version FROM flow WHERE workspace_id = $1 AND archived = false", w_id).fetch_all(db).await? { + // Fetch only top level versions and paths + // It is not fetching value + tracing::info!(workspace_id = w_id, "Rebuilding dependency map for flows"); + for r in sqlx::query!("SELECT path, versions[array_upper(versions, 1)] as version FROM flow WHERE workspace_id = $1 AND archived = false", w_id).fetch_all(db).await? { if let Some(version) = r.version { // To reduce stress on db try to fetch from cache // Since our flow versions are immutable it is safe to assume if we have cache for specific version/id it is up to date. @@ -337,17 +343,15 @@ SELECT importer_node_id, imported_path FlowValue::traverse_leafs(modules_to_check, &mut |fmv, id| { match fmv { // Since we fetched from flow_version it is safe to assume all inline scripts are in form of RawScript. - FlowModuleValue::RawScript { content, language, lock ,.. } => { - if !is_generated_from_raw_requirements(&Some(*language), lock) { - to_process.push(( - extract_relative_imports( - content, - &(r.path.clone() + "/flow"), - &Some(language.clone()), - ), - id.clone(), - )); - } + FlowModuleValue::RawScript { content, language, .. } => { + to_process.push(( + extract_referenced_paths( + content, + &(r.path.clone() + "/flow"), + Some(*language), + ), + id.clone(), + )); } // But just in case we will also handle other cases. FlowModuleValue::FlowScript { .. } => { @@ -359,8 +363,8 @@ SELECT importer_node_id, imported_path Ok(()) })?; - for (ri, id) in to_process { - tx = dmap.patch(ri, id, tx).await?; + for (rp, id) in to_process { + tx = dmap.patch(rp, id, tx).await?; } if !*WMDEBUG_NO_DMAP_DISSOLVE { @@ -374,9 +378,9 @@ SELECT importer_node_id, imported_path } } - // Apps - tracing::info!(workspace_id = w_id, "Rebuilding dependency map for apps"); - for r in sqlx::query!("SELECT path, versions[array_upper(versions, 1)] as version FROM app WHERE workspace_id = $1", w_id).fetch_all(db).await? { + // Apps + tracing::info!(workspace_id = w_id, "Rebuilding dependency map for apps"); + for r in sqlx::query!("SELECT path, versions[array_upper(versions, 1)] as version FROM app WHERE workspace_id = $1", w_id).fetch_all(db).await? { if let Some(version) = r.version { // TODO: Use cache when implemented. let value = sqlx::query_scalar!( @@ -391,10 +395,10 @@ SELECT importer_node_id, imported_path let mut to_process = vec![]; traverse_app_inline_scripts(&value, None, &mut |ais, id| { to_process.push(( - extract_relative_imports( + extract_referenced_paths( &ais.content, &(r.path.clone() + "/app"), - &ais.language, + ais.language, ), id, )); @@ -417,9 +421,11 @@ SELECT importer_node_id, imported_path } } - Ok("Success".into()) - } - + Ok("Success".into()) + } + /// Run if you want to rebuild maps on specific workspace. + /// Potentially takes much time + pub async fn rebuild_map(w_id: &str, db: &sqlx::Pool) -> Result { lazy_static::lazy_static! { pub static ref LOCKED: RwLock = RwLock::new(false); } @@ -438,9 +444,34 @@ SELECT importer_node_id, imported_path } *LOCKED.write().await = true; - let r = inner(w_id, db).await; + let r = Self::rebuild_map_unchecked(w_id, db).await; *LOCKED.write().await = false; r } } + + /// Get dependents of any imported path - returns scripts/flows/apps that depend on it + pub async fn get_dependents<'c>( + imported_path: &str, + workspace_id: &str, + e: impl PgExecutor<'c>, + ) -> Result> { + sqlx::query_as!( + DependencyDependent, + r#" + SELECT + importer_path, + importer_kind::text as "importer_kind!", -- sqlx thinks this is nullable somehow, so enfore with ! + array_agg(importer_node_id) as importer_node_ids + FROM dependency_map + WHERE workspace_id = $1 AND imported_path = $2 + GROUP BY importer_path, importer_kind + "#, + workspace_id, + imported_path + ) + .fetch_all(e) + .await + .map_err(Error::from) + } } diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 2919243dbc..9dd8e89219 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -20,6 +20,8 @@ use windmill_common::scripts::is_special_codebase_hash; use windmill_common::utils::report_critical_error; use windmill_common::utils::retrieve_common_worker_prefix; use windmill_common::worker::error_to_value; +use windmill_common::workspace_dependencies::RawWorkspaceDependencies; +use windmill_common::workspace_dependencies::WorkspaceDependenciesPrefetched; use windmill_common::{ agent_workers::DECODED_AGENT_TOKEN, apps::AppScriptId, @@ -109,6 +111,7 @@ use tokio::{ use rand::Rng; use crate::ai_executor::handle_ai_agent_job; +use crate::common::MaybeLock; use crate::common::StreamNotifier; use crate::{ agent_workers::{queue_init_job, queue_periodic_job}, @@ -153,10 +156,9 @@ use crate::ruby_executor::{handle_ruby_job, JobHandlerInput as JobHandlerInputRu use crate::php_executor::handle_php_job; #[cfg(feature = "python")] -use crate::{ - python_executor::handle_python_job, - python_versions::{PyV, PyVAlias}, -}; +use crate::{python_executor::handle_python_job, python_versions::PyV}; +#[cfg(feature = "python")] +use windmill_common::worker::PyVAlias; #[cfg(feature = "python")] use crate::ansible_executor::handle_ansible_job; @@ -2857,6 +2859,16 @@ pub async fn handle_queued_job( let mut column_order: Option> = None; let mut new_args: Option>> = None; let mut has_stream = false; + + let raw_workspace_dependencies_o = if job.kind.is_dependency() { + job.args + .as_ref() + .and_then(|x| x.get("raw_workspace_dependencies")) + .map(|v| v.get()) + .and_then(|v| serde_json::from_str::(v).ok()) + } else { + None + }; // Box::pin all async branches to prevent large match enum on stack let result = match job.kind { JobKind::Dependencies => match conn { @@ -2873,6 +2885,7 @@ pub async fn handle_queued_job( base_internal_url, &client.token, occupancy_metrics, + raw_workspace_dependencies_o, )) .await } @@ -2896,6 +2909,7 @@ pub async fn handle_queued_job( base_internal_url, &client.token, occupancy_metrics, + raw_workspace_dependencies_o, )) .await } @@ -2917,6 +2931,7 @@ pub async fn handle_queued_job( base_internal_url, &client.token, occupancy_metrics, + raw_workspace_dependencies_o, )) .await .map(|()| serde_json::from_str("{}").unwrap()), @@ -3614,14 +3629,32 @@ mount {{ let envs = build_envs(envs.as_ref())?; + let Some(language) = language else { + return Err(Error::ExecutionErr( + "Require language to be not null".to_string(), + ))?; + }; + + let maybe_lock = if let Some(lock) = lock.clone() { + MaybeLock::Resolved { lock } + } else { + MaybeLock::Unresolved { + workspace_dependencies: WorkspaceDependenciesPrefetched::extract( + code, + language, + &job.workspace_id, + // TODO: implement + &None, + job.runnable_path(), + conn.clone(), + ) + .await?, + } + }; + // Box::pin all language handlers to prevent large match enum on stack let result: error::Result> = match language { - None => { - return Err(Error::ExecutionErr( - "Require language to be not null".to_string(), - ))?; - } - Some(ScriptLang::Python3) => { + ScriptLang::Python3 => { #[cfg(not(feature = "python"))] return Err(Error::internal_err( "Python requires the python feature to be enabled".to_string(), @@ -3650,7 +3683,7 @@ mount {{ )) .await } - Some(ScriptLang::Deno) => { + ScriptLang::Deno => { Box::pin(handle_deno_job( lock.as_ref(), mem_peak, @@ -3670,9 +3703,9 @@ mount {{ )) .await } - Some(ScriptLang::Bun) | Some(ScriptLang::Bunnative) => { + ScriptLang::Bun | ScriptLang::Bunnative => { Box::pin(handle_bun_job( - lock.as_ref(), + maybe_lock, codebase.as_ref(), mem_peak, canceled_by, @@ -3693,7 +3726,7 @@ mount {{ )) .await } - Some(ScriptLang::Go) => { + ScriptLang::Go => { Box::pin(handle_go_job( mem_peak, canceled_by, @@ -3703,16 +3736,16 @@ mount {{ parent_runnable_path, &code, job_dir, - lock.as_ref(), &shared_mount, base_internal_url, worker_name, envs, occupancy_metrics, + maybe_lock, )) .await } - Some(ScriptLang::Bash) => { + ScriptLang::Bash => { Box::pin(handle_bash_job( mem_peak, canceled_by, @@ -3731,7 +3764,7 @@ mount {{ )) .await } - Some(ScriptLang::Powershell) => { + ScriptLang::Powershell => { Box::pin(handle_powershell_job( mem_peak, canceled_by, @@ -3749,7 +3782,7 @@ mount {{ )) .await } - Some(ScriptLang::Php) => { + ScriptLang::Php => { #[cfg(not(feature = "php"))] return Err(Error::internal_err( "PHP requires the php feature to be enabled".to_string(), @@ -3757,7 +3790,7 @@ mount {{ #[cfg(feature = "php")] Box::pin(handle_php_job( - lock.as_ref(), + maybe_lock, mem_peak, canceled_by, job, @@ -3774,7 +3807,7 @@ mount {{ )) .await } - Some(ScriptLang::Rust) => { + ScriptLang::Rust => { #[cfg(not(feature = "rust"))] return Err(Error::internal_err( "Rust requires the rust feature to be enabled".to_string(), @@ -3799,7 +3832,7 @@ mount {{ )) .await } - Some(ScriptLang::Ansible) => { + ScriptLang::Ansible => { #[cfg(not(feature = "python"))] return Err(Error::internal_err( "Ansible requires the python feature to be enabled".to_string(), @@ -3825,7 +3858,7 @@ mount {{ )) .await } - Some(ScriptLang::CSharp) => { + ScriptLang::CSharp => { Box::pin(handle_csharp_job( mem_peak, canceled_by, @@ -3844,7 +3877,7 @@ mount {{ )) .await } - Some(ScriptLang::Nu) => { + ScriptLang::Nu => { #[cfg(not(feature = "nu"))] return Err( anyhow::anyhow!("Nu is not available because the feature is not enabled").into(), @@ -3869,7 +3902,7 @@ mount {{ })) .await } - Some(ScriptLang::Java) => { + ScriptLang::Java => { #[cfg(not(feature = "java"))] return Err(anyhow::anyhow!( "Java is not available because the feature is not enabled" @@ -3895,7 +3928,7 @@ mount {{ })) .await } - Some(ScriptLang::Ruby) => { + ScriptLang::Ruby => { #[cfg(not(feature = "ruby"))] return Err(anyhow::anyhow!( "Ruby is not available because the feature is not enabled" diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index 4ec0fa648e..b3b6333f05 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -5,7 +5,7 @@ use std::path::{Component, Path, PathBuf}; #[cfg(feature = "python")] use crate::ansible_executor::{get_git_repos_lock, AnsibleDependencyLocks}; -use crate::scoped_dependency_map::ScopedDependencyMap; +use crate::scoped_dependency_map::{DependencyDependent, ScopedDependencyMap}; use async_recursion::async_recursion; use chrono::{Duration, Utc}; use itertools::Itertools; @@ -21,12 +21,14 @@ use windmill_common::error::Error; use windmill_common::error::Result; use windmill_common::flows::{FlowModule, FlowModuleValue, FlowNodeId}; use windmill_common::jobs::JobPayload; -use windmill_common::lockfiles::is_generated_from_raw_requirements; use windmill_common::scripts::ScriptHash; use windmill_common::utils::WarnAfterExt; #[cfg(feature = "python")] use windmill_common::worker::PythonAnnotations; use windmill_common::worker::{to_raw_value, to_raw_value_owned, write_file, Connection}; +use windmill_common::workspace_dependencies::{ + RawWorkspaceDependencies, WorkspaceDependencies, WorkspaceDependenciesPrefetched, +}; #[cfg(feature = "python")] use windmill_parser_yaml::AnsibleRequirements; @@ -55,7 +57,7 @@ lazy_static::lazy_static! { ); } -use crate::common::OccupancyMetrics; +use crate::common::{MaybeLock, OccupancyMetrics}; use crate::csharp_executor::generate_nuget_lockfile; #[cfg(feature = "java")] @@ -67,9 +69,7 @@ use crate::ruby_executor; #[cfg(feature = "php")] use crate::php_executor::{composer_install, parse_php_imports}; #[cfg(feature = "python")] -use crate::python_executor::{ - create_dependencies_dir, handle_python_reqs, split_requirements, uv_pip_compile, -}; +use crate::python_executor::{create_dependencies_dir, handle_python_reqs, uv_pip_compile}; #[cfg(feature = "rust")] use crate::rust_executor::generate_cargo_lockfile; use crate::{ @@ -137,6 +137,43 @@ pub fn extract_relative_imports( } } +pub fn extract_referenced_paths( + raw_code: &str, + script_path: &str, + language: Option, +) -> Option> { + let mut referenced_paths = vec![]; + if let Some(wk_deps_refs) = language + .and_then(|l| l.extract_workspace_dependencies_annotated_refs(raw_code, script_path)) + .map(|r| r.external) + { + let l = language.expect("should be some"); + for wk_deps_ref in wk_deps_refs { + if let Some(path) = WorkspaceDependencies::to_path(&Some(wk_deps_ref), l).ok() { + referenced_paths.push(path); + }; + } + } else if let (Some(l), true /* Only if it is not blacklisted */) = ( + language, + WorkspaceDependenciesPrefetched::is_external_references_permitted(script_path), + ) { + // we assume all runnables without annotated dependencies reference default dependencies file. + WorkspaceDependencies::to_path(&None, l) + .ok() + .inspect(|p| referenced_paths.push(p.to_owned())); + } + + if let Some(relative_imports) = extract_relative_imports(raw_code, script_path, &language) { + referenced_paths.extend(relative_imports); + } + + if referenced_paths.is_empty() { + None + } else { + Some(referenced_paths) + } +} + #[tracing::instrument(level = "trace", skip_all)] pub async fn handle_dependency_job( job: &MiniPulledJob, @@ -150,6 +187,7 @@ pub async fn handle_dependency_job( base_internal_url: &str, token: &str, occupancy_metrics: &mut OccupancyMetrics, + raw_workspace_dependencies_o: Option, ) -> error::Result> { // Processing a dependency job - these jobs handle lockfile generation and dependency updates // for scripts, flows, and apps when their dependencies or imported scripts change @@ -158,33 +196,6 @@ pub async fn handle_dependency_job( job.runnable_path() ); let script_path = job.runnable_path(); - let raw_deps = job - .args - .as_ref() - .map(|x| { - x.get("raw_deps") - .is_some_and(|y| y.to_string().as_str() == "true") - }) - .unwrap_or(false); - - let npm_mode = if job - .script_lang - .as_ref() - .map(|v| v == &ScriptLang::Bun) - .unwrap_or(false) - { - Some( - job.args - .as_ref() - .map(|x| { - x.get("npm_mode") - .is_some_and(|y| y.to_string().as_str() == "true") - }) - .unwrap_or(false), - ) - } else { - None - }; // `JobKind::Dependencies` job store either: // - A saved script `hash` in the `script_hash` column. @@ -239,9 +250,8 @@ pub async fn handle_dependency_job( base_internal_url, token, script_path, - raw_deps, - npm_mode, occupancy_metrics, + &raw_workspace_dependencies_o, ) .await; @@ -307,7 +317,6 @@ pub async fn handle_dependency_job( &job.permissioned_as_email, &job.created_by, &job.permissioned_as, - None, ) .await?; @@ -360,45 +369,33 @@ pub async fn process_relative_imports( permissioned_as_email: &str, created_by: &str, permissioned_as: &str, - lock: Option, ) -> error::Result<()> { // TODO: Should be moved into handle_dependency_job body to be more consistent with how flows and apps are handled { - let relative_imports = extract_relative_imports(&code, script_path, script_lang); - if let Some(relative_imports) = relative_imports { - let mut tx = db.begin().await?; - let mut dependency_map = ScopedDependencyMap::fetch_maybe_rearranged( - &w_id, - script_path, - "script", - &parent_path, - db, + let mut tx = db.begin().await?; + let mut dependency_map = ScopedDependencyMap::fetch_maybe_rearranged( + &w_id, + script_path, + "script", + &parent_path, + db, + ) + .await?; + + tx = dependency_map + .patch( + extract_referenced_paths(&code, script_path, *script_lang), + // Ideally should be None, but due to current implementation will use empty string to represent None. + "".into(), + tx, ) .await?; - if is_generated_from_raw_requirements(script_lang, &lock) { - // if the lock file is generated from a package.json/requirements.txt, we need to clear the dependency map - // because we do not want to have dependencies be recomputed automatically. Empty relative imports passed - // to update_script_dependency_map will clear the dependency map. - // TODO: Rework the logic for synchronized raw requirements PR. - // For now we will just do nothing and let dissolve clear every item related to this script. - } else { - tx = dependency_map - .patch( - Some(relative_imports), - // Ideally should be None, but due to current implementation will use empty string to represent None. - "".into(), - tx, - ) - .await?; - } - // If felt into first branch which did not call .patch(, this operation will clean dependency_map for this script. - dependency_map.dissolve(tx).await.commit().await?; - } + dependency_map.dissolve(tx).await.commit().await?; } { - let already_visited = args + let mut already_visited = args .map(|x| { x.get("already_visited") .map(|v| serde_json::from_str::>(v.get()).ok()) @@ -407,13 +404,30 @@ pub async fn process_relative_imports( .flatten() .unwrap_or_default(); + // TODO: There is a race-condition. + // This can be old version. + + // Check lines of code below, you will find that we get the latest version of the script/app/flow + + // However the latest version does not necessarily mean that it is finalized. + // Instead we assume that this would be the version we would base on. + + // So the script_importers might be behind. Thus some information like nodes_to_relock might be lost. + let importers = crate::scoped_dependency_map::ScopedDependencyMap::get_dependents( + script_path, + w_id, + db, + ) + .await?; + + already_visited.push(script_path.to_string()); // But currently we will do this extra db call for every script regardless of whether they have relative imports or not // Script might have no relative imports but still be referenced by someone else. match timeout( core::time::Duration::from_secs(60), Box::pin(trigger_dependents_to_recompute_dependencies( w_id, - script_path, + importers, deployment_message, parent_path, permissioned_as_email, @@ -441,44 +455,24 @@ pub async fn process_relative_imports( pub async fn trigger_dependents_to_recompute_dependencies( w_id: &str, - script_path: &str, + importers: Vec, + // imported_path: &str, deployment_message: Option, parent_path: Option, email: &str, created_by: &str, permissioned_as: &str, db: &sqlx::Pool, - mut already_visited: Vec, + already_visited: Vec, ) -> error::Result<()> { - // TODO: There is a race-condition. - // This can be old version. - // - // Check lines of code below, you will find that we get the latest version of the script/app/flow - // - // However the latest version does not necessarily mean that it is finalized. - // Instead we assume that this would be the version we would base on. - // - // So the script_importers might be behind. Thus some information like nodes_to_relock might be lost. - let script_importers = sqlx::query!( - "SELECT importer_path, importer_kind::text, array_agg(importer_node_id) as importer_node_ids FROM dependency_map - WHERE imported_path = $1 - AND workspace_id = $2 - GROUP BY importer_path, importer_kind", - script_path, - w_id - ) - .fetch_all(db) - .await?; - tracing::debug!( - "Triggering dependents to recompute dependencies for: {}", - &script_path + "Triggering dependents to recompute dependencies: {}", + importers.iter().map(|dd| &dd.importer_path).join(",") ); - - already_visited.push(script_path.to_string()); - for s in script_importers.iter() { - tracing::trace!("Processing dependency: {:?}", &s); - if already_visited.contains(&s.importer_path) { + for DependencyDependent { importer_path, importer_kind, importer_node_ids } in importers.iter() + { + tracing::trace!("Processing dependency: {:?}", importer_path); + if already_visited.contains(importer_path) { tracing::trace!("Skipping already visited dependency"); continue; } @@ -517,33 +511,33 @@ pub async fn trigger_dependents_to_recompute_dependencies( // After our transaction commits, any pending push/pull requests can proceed with // their debounce logic. let debounce_job_id_o = - windmill_common::jobs::lock_debounce_key(w_id, &s.importer_path, &mut tx).await?; + windmill_common::jobs::lock_debounce_key(w_id, &importer_path, &mut tx).await?; tracing::debug!( debounce_job_id = ?debounce_job_id_o, - importer_path = %s.importer_path, + importer_path = %importer_path, "Retrieved debounce job ID (if exists)" ); - let kind = s.importer_kind.clone().unwrap_or_default(); - let job_payload = if kind == "script" { + let job_payload = match importer_kind.as_str() { // TODO: Make it query only non-archived - match sqlx::query_scalar!( + // Scripts + "script" => match sqlx::query_scalar!( "SELECT hash FROM script WHERE path = $1 AND workspace_id = $2 AND deleted = false ORDER BY created_at DESC LIMIT 1", - s.importer_path.clone(), + importer_path, w_id ) .fetch_optional(&mut *tx) .await? { Some(hash) => { - tracing::debug!("newest hash for {} is: {hash}", &s.importer_path); + tracing::debug!("newest hash for {} is: {hash}", importer_path); let info = windmill_common::get_script_info_for_hash(None, db, w_id, hash).await?; JobPayload::Dependencies { - path: s.importer_path.clone(), + path: importer_path.clone(), hash: ScriptHash(hash), language: info.language, dedicated_worker: info.dedicated_worker, @@ -551,7 +545,7 @@ pub async fn trigger_dependents_to_recompute_dependencies( } None => { ScopedDependencyMap::clear_map_for_item( - &s.importer_path, + importer_path, w_id, "script", tx, @@ -562,86 +556,80 @@ pub async fn trigger_dependents_to_recompute_dependencies( .await?; continue; } - } - } else if kind == "flow" { - tracing::debug!("Handling flow dependency update for: {}", s.importer_path); + }, - args.insert( - "nodes_to_relock".to_string(), - to_raw_value(&s.importer_node_ids), - ); - - match sqlx::query_scalar!( + // Flows + "flow" => match sqlx::query_scalar!( "SELECT id FROM flow_version WHERE path = $1 AND workspace_id = $2 ORDER BY created_at DESC LIMIT 1", - s.importer_path.clone(), - w_id - ) - .fetch_optional(&mut *tx) - .await? - { - Some(version) => JobPayload::FlowDependencies { - path: s.importer_path.clone(), - version, - dedicated_worker: None, - }, - None => { - ScopedDependencyMap::clear_map_for_item( - &s.importer_path, - w_id, - "flow", - tx, - &None, - ) - .await - .commit() - .await?; - continue; - } - } - } else if kind == "app" && !*WMDEBUG_NO_NEW_APP_VERSION_ON_DJ { - tracing::debug!("Handling flow dependency update for: {}", s.importer_path); - - args.insert( - "components_to_relock".to_string(), - // TODO: unsafe. Importer Node Ids are not checked. They can simply be array of empty strings! - to_raw_value(&s.importer_node_ids), - ); - - match sqlx::query_scalar!( - "SELECT id FROM app_version WHERE app_id = (SELECT id FROM app WHERE path = $1 AND workspace_id = $2) ORDER BY created_at DESC LIMIT 1", - s.importer_path.clone(), + importer_path, w_id ) .fetch_optional(&mut *tx) .await? { Some(version) => { - JobPayload::AppDependencies { path: s.importer_path.clone(), version } + tracing::debug!("Handling flow dependency update for: {}", importer_path); + + args.insert( + "nodes_to_relock".to_string(), + to_raw_value(&importer_node_ids), + ); + + JobPayload::FlowDependencies { + path: importer_path.clone(), + version, + dedicated_worker: None, + } } None => { - ScopedDependencyMap::clear_map_for_item( - &s.importer_path, - w_id, - "app", - tx, - &None, - ) - .await - .commit() - .await?; + ScopedDependencyMap::clear_map_for_item(importer_path, w_id, "flow", tx, &None) + .await + .commit() + .await?; continue; } + }, + + // Apps + "app" => match sqlx::query_scalar!( + "SELECT id FROM app_version WHERE app_id = (SELECT id FROM app WHERE path = $1 AND workspace_id = $2) ORDER BY created_at DESC LIMIT 1", + importer_path, + w_id + ) + .fetch_optional(&mut *tx) + .await? + { + Some(version) => { + tracing::debug!("Handling app dependency update for: {}", importer_path); + + args.insert( + "components_to_relock".to_string(), + // TODO: unsafe. Importer Node Ids are not checked. They can simply be array of empty strings! + to_raw_value(importer_node_ids), + ); + + JobPayload::AppDependencies { path: importer_path.clone(), version } + } + None => { + ScopedDependencyMap::clear_map_for_item(importer_path, w_id, "app", tx, &None) + .await + .commit() + .await?; + continue; + } + }, + + _ => { + tracing::error!( + "unexpected importer kind: {kind:?} for path {path}", + kind = importer_kind, + path = importer_path + ); + continue; } - } else { - tracing::error!( - "unexpected importer kind: {kind} for path {path}", - kind = kind, - path = s.importer_path - ); - continue; }; - tracing::debug!("Pushing dependency job for: {}", s.importer_path); + tracing::debug!("Pushing dependency job for: {}", importer_path); let (job_uuid, new_tx) = windmill_queue::push( db, PushIsolationLevel::Transaction(tx), @@ -677,7 +665,7 @@ pub async fn trigger_dependents_to_recompute_dependencies( tracing::info!( "pushed dependency job due to common python path: {job_uuid} for path {path}", - path = s.importer_path, + path = importer_path, ); new_tx.commit().await?; } @@ -696,6 +684,7 @@ pub async fn handle_flow_dependency_job( base_internal_url: &str, token: &str, occupancy_metrics: &mut OccupancyMetrics, + raw_workspace_dependencies_o: Option, ) -> error::Result> { tracing::debug!("Processing flow dependency job"); tracing::trace!("Job details: {:?}", &job); @@ -820,6 +809,7 @@ pub async fn handle_flow_dependency_job( skip_flow_update, &raw_deps, &mut dependency_map, + &raw_workspace_dependencies_o, ) .await?; @@ -1031,6 +1021,7 @@ async fn lock_flow_value<'c>( skip_flow_update: bool, raw_deps: &Option>, dependency_map: &mut ScopedDependencyMap, + raw_workspace_dependencies_o: &Option, ) -> Result<( FlowValue, sqlx::Transaction<'c, sqlx::Postgres>, @@ -1059,6 +1050,7 @@ async fn lock_flow_value<'c>( skip_flow_update, &raw_deps, dependency_map, + &raw_workspace_dependencies_o, ) .await?; @@ -1088,6 +1080,7 @@ async fn lock_flow_value<'c>( skip_flow_update, &raw_deps, dependency_map, + &raw_workspace_dependencies_o, ) .await?; @@ -1123,6 +1116,7 @@ async fn lock_flow_value<'c>( skip_flow_update, &raw_deps, dependency_map, + raw_workspace_dependencies_o, ) .await?; @@ -1159,6 +1153,7 @@ async fn lock_modules<'c>( skip_flow_update: bool, raw_deps: &Option>, dependency_map: &mut ScopedDependencyMap, // (modules to replace old seq (even unmmodified ones), new transaction, modified ids) ) + raw_workspace_dependencies_o: &Option, ) -> Result<( Vec, sqlx::Transaction<'c, sqlx::Postgres>, @@ -1214,6 +1209,7 @@ async fn lock_modules<'c>( skip_flow_update, &raw_deps, dependency_map, + &raw_workspace_dependencies_o, )) .await?; e.value = FlowModuleValue::ForloopFlow { @@ -1251,6 +1247,7 @@ async fn lock_modules<'c>( skip_flow_update, &raw_deps, dependency_map, + raw_workspace_dependencies_o, )) .await?; nmodified_ids.extend(inner_modified_ids); @@ -1280,6 +1277,7 @@ async fn lock_modules<'c>( skip_flow_update, &raw_deps, dependency_map, + raw_workspace_dependencies_o, )) .await?; e.value = FlowModuleValue::WhileloopFlow { @@ -1314,6 +1312,7 @@ async fn lock_modules<'c>( skip_flow_update, &raw_deps, dependency_map, + raw_workspace_dependencies_o, )) .await?; nmodified_ids.extend(inner_modified_ids); @@ -1342,6 +1341,7 @@ async fn lock_modules<'c>( skip_flow_update, &raw_deps, dependency_map, + raw_workspace_dependencies_o, )) .await?; errors.extend(ninner_errors); @@ -1410,6 +1410,7 @@ async fn lock_modules<'c>( skip_flow_update, &raw_deps, dependency_map, + raw_workspace_dependencies_o, )) .await?; @@ -1442,23 +1443,16 @@ async fn lock_modules<'c>( .await?; } - let get_imports = || { + let get_references = || { let dep_path = path.clone().unwrap_or_else(|| job_path.to_string()); - extract_relative_imports( - &content, - &format!("{dep_path}/flow"), - &Some(language.clone()), - ) + extract_referenced_paths(&content, &format!("{dep_path}/flow"), Some(language)) }; if let Some(locks_to_reload) = locks_to_reload { if !locks_to_reload.contains(&e.id) { - if !is_generated_from_raw_requirements(&Some(language), &lock) { - let relative_imports = get_imports(); - tx = dependency_map - .patch(relative_imports.clone(), e.id.clone(), tx) - .await?; - } + tx = dependency_map + .patch(get_references(), e.id.clone(), tx) + .await?; new_flow_modules.push(e); continue; } @@ -1466,12 +1460,9 @@ async fn lock_modules<'c>( if lock.as_ref().is_some_and(|x| !x.trim().is_empty()) { let skip_creating_new_lock = skip_creating_new_lock(&language, &content); if skip_creating_new_lock { - if !is_generated_from_raw_requirements(&Some(language), &lock) { - let relative_imports = get_imports(); - tx = dependency_map - .patch(relative_imports.clone(), e.id.clone(), tx) - .await?; - } + tx = dependency_map + .patch(get_references(), e.id.clone(), tx) + .await?; new_flow_modules.push(e); continue; @@ -1512,16 +1503,15 @@ async fn lock_modules<'c>( "{}/flow", &path.clone().unwrap_or_else(|| job_path.to_string()) ), - raw_deps, - None, occupancy_metrics, + raw_workspace_dependencies_o, ) .await; // let lock = match new_lock { Ok(new_lock) => { if !raw_deps && !skip_flow_update { - let relative_imports = get_imports(); + let relative_imports = get_references(); tx = dependency_map .patch(relative_imports.clone(), e.id.clone(), tx) .await?; @@ -1926,6 +1916,7 @@ async fn lock_modules_app( // Represents the closest container id container_id: Option, dependency_map: &mut ScopedDependencyMap, + raw_workspace_dependencies_o: &Option, ) -> Result { match value { Value::Object(mut m) => { @@ -1960,10 +1951,10 @@ async fn lock_modules_app( .to_string(); let mut logs = "".to_string(); - let relative_imports = extract_relative_imports( + let referenced_paths = extract_referenced_paths( &content, &format!("{job_path}/app"), - &Some(language.clone()), + Some(language), ); if let Some((l, id)) = locks_to_reload @@ -1981,7 +1972,7 @@ async fn lock_modules_app( if !l.contains(id) { dependency_map .patch( - relative_imports.clone(), + referenced_paths.clone(), container_id.unwrap_or_default(), db.begin().await?, ) @@ -1997,7 +1988,7 @@ async fn lock_modules_app( if skip_creating_new_lock(&language, &content) { dependency_map .patch( - relative_imports.clone(), + referenced_paths.clone(), container_id.unwrap_or_default(), db.begin().await?, ) @@ -2026,9 +2017,9 @@ async fn lock_modules_app( base_internal_url, token, &format!("{}/app", job.runnable_path()), - false, - None, occupancy_metrics, + // TODO: + &None, ) .await; match new_lock { @@ -2037,7 +2028,7 @@ async fn lock_modules_app( dependency_map .patch( - relative_imports.clone(), + referenced_paths.clone(), container_id.unwrap_or_default(), db.begin().await?, ) @@ -2104,6 +2095,7 @@ async fn lock_modules_app( .map(str::to_owned) .or(container_id.clone()), dependency_map, + raw_workspace_dependencies_o, ) .await?, ); @@ -2130,6 +2122,7 @@ async fn lock_modules_app( locks_to_reload, container_id.clone(), dependency_map, + raw_workspace_dependencies_o, ) .await?, ); @@ -2151,6 +2144,7 @@ pub async fn handle_app_dependency_job( base_internal_url: &str, token: &str, occupancy_metrics: &mut OccupancyMetrics, + raw_workspace_dependencies_o: Option, ) -> error::Result<()> { let job_path = job.runnable_path.clone().ok_or_else(|| { error::Error::internal_err( @@ -2222,6 +2216,7 @@ pub async fn handle_app_dependency_job( &components_to_relock, None, &mut dependency_map, + &raw_workspace_dependencies_o, ) .await?; @@ -2422,7 +2417,7 @@ async fn python_dep( py_version: crate::PyV, annotations: PythonAnnotations, ) -> std::result::Result { - use crate::python_executor::split_requirements; + use windmill_common::worker::{split_python_requirements, PyVAlias}; create_dependencies_dir(job_dir).await; @@ -2443,8 +2438,7 @@ async fn python_dep( // install the dependencies to pre-fill the cache if let Ok(req) = req.as_ref() { let r = handle_python_reqs( - split_requirements(req), - // req.split("\n").filter(|x| !x.starts_with("--")).collect(), + split_python_requirements(req), job_id, w_id, mem_peak, @@ -2455,7 +2449,7 @@ async fn python_dep( worker_dir, occupancy_metrics, // final_version, - crate::PyVAlias::default().into(), + PyVAlias::default().into(), ) .await; @@ -2599,11 +2593,20 @@ async fn capture_dependency_job( base_internal_url: &str, token: &str, script_path: &str, - raw_deps: bool, - npm_mode: Option, occupancy_metrics: &mut OccupancyMetrics, + raw_workspace_dependencies_o: &Option, ) -> error::Result { - match job_language { + let workspace_dependencies = WorkspaceDependenciesPrefetched::extract( + job_raw_code, + *job_language, + w_id, + raw_workspace_dependencies_o, + script_path, + db.into(), + ) + .await?; + + let lock = match job_language { ScriptLang::Python3 => { #[cfg(not(feature = "python"))] return Err(Error::internal_err( @@ -2611,49 +2614,41 @@ async fn capture_dependency_job( )); #[cfg(feature = "python")] { - // Manually assigned version from requirements.txt - // let assigned_py_version; - let (reqs, py_version) = if raw_deps { - // `wmill script generate-metadata` - // should also respect annotated pyversion - // can be annotated in script itself - // or in requirements.txt if present + let annotations = PythonAnnotations::parse(job_raw_code); - ( - job_raw_code.to_owned(), - match crate::PyV::try_parse_from_requirements(&split_requirements( - job_raw_code, - )) { - Some(pyv) => pyv, - None => crate::PyV::gravitational_version(job_id, w_id, None).await, - }, + let (pyv, reqs) = { + let (mut version_specifiers, mut locked_v) = (vec![], None); + let reqs = windmill_parser_py_imports::parse_python_imports( + job_raw_code, + &w_id, + script_path, + &db, + &mut version_specifiers, + &mut locked_v, + raw_workspace_dependencies_o, ) - } else { - let mut version_specifiers = vec![]; - let PythonAnnotations { py_select_latest, .. } = - PythonAnnotations::parse(job_raw_code); - ( - windmill_parser_py_imports::parse_python_imports( - job_raw_code, - &w_id, - script_path, - &db, - &mut version_specifiers, - ) - .await? - .0 - .join("\n"), + .await? + .0 + .join("\n"); + + // Resolve python version + // It is based on version specifiers + let pyv = if let Some(v) = locked_v { + v.into() + } else { crate::PyV::resolve( version_specifiers, job_id, w_id, - py_select_latest, + annotations.py_select_latest, Some(db.clone().into()), None, None, ) - .await?, - ) + .await? + }; + + (pyv, reqs) }; python_dep( @@ -2667,21 +2662,10 @@ async fn capture_dependency_job( w_id, worker_dir, &mut Some(occupancy_metrics), - py_version, - PythonAnnotations::parse(job_raw_code), + pyv, + annotations, ) - .await - .map(|res| { - if raw_deps { - format!( - "{}\n{}", - windmill_common::lockfiles::LOCKFILE_GENERATED_FROM_REQUIREMENTS_TXT, - res - ) - } else { - res - } - }) + .await? } } ScriptLang::Ansible => { @@ -2692,11 +2676,6 @@ async fn capture_dependency_job( #[cfg(feature = "python")] { - if raw_deps { - return Err(Error::ExecutionErr( - "Raw dependencies not supported for ansible".to_string(), - )); - } let (_logs, reqs, _) = windmill_parser_yaml::parse_ansible_reqs(job_raw_code)?; ansible_dep( @@ -2713,13 +2692,14 @@ async fn capture_dependency_job( token, base_internal_url, ) - .await + .await? } } ScriptLang::Go => { install_go_dependencies( job_id, job_raw_code, + MaybeLock::Unresolved { workspace_dependencies: workspace_dependencies.clone() }, mem_peak, canceled_by, job_dir, @@ -2727,19 +2707,13 @@ async fn capture_dependency_job( false, false, false, - raw_deps, worker_name, w_id, occupancy_metrics, ) - .await + .await? } ScriptLang::Deno => { - if raw_deps { - return Err(Error::ExecutionErr( - "Raw dependencies not supported for deno".to_string(), - )); - } generate_deno_lock( job_id, job_raw_code, @@ -2752,16 +2726,12 @@ async fn capture_dependency_job( base_internal_url, &mut Some(occupancy_metrics), ) - .await + .await? } ScriptLang::Bun | ScriptLang::Bunnative => { - let npm_mode = npm_mode.unwrap_or_else(|| { - windmill_common::worker::TypeScriptAnnotations::parse(job_raw_code).npm - }); - if !raw_deps { - let _ = write_file(job_dir, "main.ts", job_raw_code)?; - } - let req = gen_bun_lockfile( + // TODO: move inside gen_bun_lockfile + write_file(job_dir, "main.ts", job_raw_code)?; + if let Some(lock) = gen_bun_lockfile( mem_peak, canceled_by, job_id, @@ -2773,19 +2743,15 @@ async fn capture_dependency_job( base_internal_url, worker_name, true, - if raw_deps { - Some(job_raw_code.to_string()) - } else { - None - }, - npm_mode, + &workspace_dependencies, + windmill_common::worker::TypeScriptAnnotations::parse(job_raw_code).npm, &mut Some(occupancy_metrics), ) - .await?; - if req.is_some() && !raw_deps { + .await? + { crate::bun_executor::prebundle_bun_script( job_raw_code, - req.as_ref(), + &lock, script_path, job_id, w_id, @@ -2797,8 +2763,11 @@ async fn capture_dependency_job( &mut Some(occupancy_metrics), ) .await?; + + lock + } else { + Default::default() } - Ok(req.unwrap_or_else(String::new)) } ScriptLang::Php => { #[cfg(not(feature = "php"))] @@ -2808,19 +2777,15 @@ async fn capture_dependency_job( #[cfg(feature = "php")] { - let reqs = if raw_deps { - if job_raw_code.is_empty() { - return Ok("".to_string()); - } - job_raw_code.to_string() + let composer_content = if let Some(c) = workspace_dependencies.get_php()? { + c } else { match parse_php_imports(job_raw_code)? { Some(reqs) => reqs, - None => { - return Ok("".to_string()); - } + None => return Ok("".to_string()), } }; + composer_install( mem_peak, canceled_by, @@ -2829,20 +2794,14 @@ async fn capture_dependency_job( &Connection::Sql(db.clone()), job_dir, worker_name, - reqs, + composer_content, None, occupancy_metrics, ) - .await + .await? } } ScriptLang::Rust => { - if raw_deps { - return Err(Error::ExecutionErr( - "Raw dependencies not supported for rust".to_string(), - )); - } - #[cfg(not(feature = "rust"))] return Err(Error::internal_err( "Rust requires the rust feature to be enabled".to_string(), @@ -2863,15 +2822,9 @@ async fn capture_dependency_job( .await?; #[cfg(feature = "rust")] - Ok(lockfile) + lockfile } ScriptLang::CSharp => { - if raw_deps { - return Err(Error::ExecutionErr( - "Raw dependencies not supported for C#".to_string(), - )); - } - generate_nuget_lockfile( job_id, job_raw_code, @@ -2883,16 +2836,10 @@ async fn capture_dependency_job( w_id, occupancy_metrics, ) - .await + .await? } #[cfg(feature = "java")] ScriptLang::Java => { - if raw_deps { - return Err(Error::ExecutionErr( - "Raw dependencies not supported for Java".to_string(), - )); - } - java_executor::resolve( job_id, job_raw_code, @@ -2900,16 +2847,10 @@ async fn capture_dependency_job( &Connection::Sql(db.clone()), w_id, ) - .await + .await? } #[cfg(feature = "ruby")] ScriptLang::Ruby => { - if raw_deps { - return Err(Error::ExecutionErr( - "Raw dependencies not supported for Ruby".to_string(), - )); - } - ruby_executor::resolve( job_id, job_raw_code, @@ -2920,9 +2861,32 @@ async fn capture_dependency_job( worker_name, w_id, ) - .await + .await? } // for related places search: ADD_NEW_LANG - _ => Ok("".to_owned()), + _ => "".to_owned(), + }; + { + let mut lines = vec![]; + add_lock_header(&mut lines, workspace_dependencies, *job_language, w_id, db).await?; + Ok(if lines.is_empty() { + lock + } else { + format!("{}\n{lock}", lines.join("\n")) + }) } } + +async fn add_lock_header( + lines: &mut Vec, + wd: WorkspaceDependenciesPrefetched, + _language: ScriptLang, + _workspace_id: &str, + _db: &sqlx::Pool, +) -> error::Result<()> { + if let Some(header) = wd.to_lock_header().await { + lines.push(header); + } + + Ok(()) +} diff --git a/backend/windmill-worker/src/workspace_dependencies.rs b/backend/windmill-worker/src/workspace_dependencies.rs new file mode 100644 index 0000000000..dd060122a7 --- /dev/null +++ b/backend/windmill-worker/src/workspace_dependencies.rs @@ -0,0 +1,268 @@ +use serde::{Deserialize, Serialize}; +use windmill_common::{error, scripts::ScriptLang, workspace_dependencies::WorkspaceDependencies}; + +use crate::{ + scoped_dependency_map::ScopedDependencyMap, trigger_dependents_to_recompute_dependencies, +}; + +#[derive(sqlx::FromRow, Clone, Serialize, Deserialize, Hash, Debug)] +pub struct NewWorkspaceDependencies { + pub workspace_id: String, + pub language: ScriptLang, + pub name: Option, + /// If None, will use description of previous version + /// If there is no older versions, will set to default + pub description: Option, + // TODO: Make Option, or optimize it in any other way. + pub content: String, +} + +impl NewWorkspaceDependencies { + /// Creates a new workspace dependencies entry in the database. + /// + /// Archives any existing dependencies with the same name/language/workspace, + /// then inserts the new dependencies. Triggers recomputation of dependent scripts + /// and rebuilds the dependency map if this is the first unnamed dependency for the workspace. + pub async fn create<'c>( + self, + email: &str, + created_by: &str, + permissioned_as: &str, + db: &sqlx::Pool, + ) -> error::Result { + // Check if all workers support workspace dependencies feature + windmill_common::workspace_dependencies::min_version_supports_v0_workspace_dependencies() + .await?; + + let path = WorkspaceDependencies::to_path(&self.name, self.language)?; + + // If it is unnamed then we want to rebuild dependency map. Otherwise trigger dependents to recompute locks will not work + // NOTE: We rebuild first, even before creating new w deps. We want to make sure that if rebuild failed, then no new default workspace dependencies were created. + if self.name.is_none() { + // Check if we already rebuilt the map for this workspace by checking if the setting exists + let setting_name = format!("workspace_dependencies_map_rebuilt:{}", self.workspace_id); + let already_rebuilt = + windmill_common::global_settings::load_value_from_global_settings( + db, + &setting_name, + ) + .await? + .is_some(); + + if !already_rebuilt { + tracing::info!( + workspace_id = %self.workspace_id, + "Rebuilding workspace dependencies map for first unnamed workspace dependencies" + ); + ScopedDependencyMap::rebuild_map_unchecked(&self.workspace_id, db).await?; + + // Mark as rebuilt by creating the setting + windmill_common::global_settings::set_value_in_global_settings( + db, + &setting_name, + serde_json::json!({}), + ) + .await?; + tracing::info!( + workspace_id = %self.workspace_id, + "Marked workspace dependencies map as rebuilt" + ); + } else { + tracing::info!( + workspace_id = %self.workspace_id, + "Skipping workspace dependencies map rebuild - already rebuilt for this workspace" + ); + } + }; + + let mut tx = db.begin().await?; + let prev_description = sqlx::query_scalar!( + " + UPDATE workspace_dependencies + SET archived = true + WHERE archived = false + AND name IS NOT DISTINCT FROM $1 + AND workspace_id = $2 + AND language = $3 + RETURNING description + ", + self.name, + self.workspace_id, + self.language as ScriptLang + ) + .fetch_optional(&mut *tx) + .await?; + + let new_id = sqlx::query_scalar!( + " + INSERT INTO workspace_dependencies(name, workspace_id, content, language, description) + VALUES ($1, $2, $3, $4, $5) + RETURNING id + ", + self.name.clone(), + self.workspace_id, + self.content, + self.language as ScriptLang, + self.description + .or(prev_description.clone()) + .unwrap_or("Default Workspace Dependencies".to_owned()) + ) + .fetch_one(&mut *tx) + .await?; + tx.commit().await?; + + // Make sure trigger dependents will have latest view. + // NOTE: Uncomment for tests + // #[cfg(test)] + // assert_eq!( + // sqlx::query_scalar!( + // " + // SELECT id FROM workspace_dependencies + // WHERE archived = false + // AND name IS NOT DISTINCT FROM $1 + // AND workspace_id = $2 + // AND language = $3 + // ", + // self.name, + // self.workspace_id, + // self.language as ScriptLang, + // ) + // .fetch_one(db) // Use db + // .await?, + // new_id + // ); + + // It's ok to fail, it will return an error and user will get notified that they should redeploy workspace dependencies + trigger_dependents_to_recompute_dependencies( + &self.workspace_id, + crate::scoped_dependency_map::ScopedDependencyMap::get_dependents( + path.as_str(), + &self.workspace_id, + db, + ) + .await?, + None, + None, + email, + created_by, + permissioned_as, + db, + vec![], + ) + .await?; + + Ok(new_id) + } +} + +// Type aliases for backward compatibility +pub type RawRequirements = WorkspaceDependencies; +pub type NewRawRequirements = NewWorkspaceDependencies; + +#[cfg(test)] +mod workspace_dependencies_tests { + + // // TODO: test all cases when it should reject. + // #[cfg(feature = "python")] + // mod new_workspace_dependencies { + // use windmill_common::scripts::ScriptLang; + + // use crate::workspace_dependencies::NewWorkspaceDependencies; + + // #[sqlx::test( + // fixtures("../../tests/fixtures/base.sql",), + // migrations = "../migrations" + // )] + // async fn test_create(db: sqlx::Pool) -> anyhow::Result<()> { + // assert_eq!( + // NewWorkspaceDependencies { + // workspace_id: "test-workspace".into(), + // language: ScriptLang::Python3, + // name: None, + // description: None, + // content: "global:rev1".to_owned(), + // } + // .create("", "", "", &db) + // .await + // .unwrap(), + // 1 + // ); + + // assert_eq!( + // NewWorkspaceDependencies { + // workspace_id: "test-workspace".into(), + // language: ScriptLang::Python3, + // name: Some("rrs1".to_owned()), + // description: None, + // content: "rrs1:rev1".to_owned(), + // } + // .create("", "", "", &db) + // .await + // .unwrap(), + // 2 + // ); + + // assert!(NewWorkspaceDependencies { + // workspace_id: "test-workspace".into(), + // language: ScriptLang::DuckDb, + // description: None, + // name: None, + // content: "".to_owned(), + // } + // .create("", "", "", &db) + // .await + // .is_err()); + + // // Will act as redeployment + // assert_eq!( + // NewWorkspaceDependencies { + // workspace_id: "test-workspace".into(), + // language: ScriptLang::Python3, + // description: None, + // name: Some("rrs1".to_owned()), + // content: "rrs1:rev2".to_owned(), + // } + // .create("", "", "", &db) + // .await + // .unwrap(), + // // It will just increment id + // 3 + // ); + // Ok(()) + // } + + // #[sqlx::test( + // fixtures("../../tests/fixtures/base.sql",), + // migrations = "../migrations" + // )] + // async fn violate_constraints(db: sqlx::Pool) -> anyhow::Result<()> { + // let db = &db; + // let create = |name| { + // sqlx::query_scalar!( + // " + // INSERT INTO workspace_dependencies(name, workspace_id, content, language) + // VALUES ($1, 'test-workspace', 'test', 'python3') + // RETURNING id + // ", + // name + // ) + // .fetch_one(db) + // }; + + // assert_eq!(create(Some("test".to_owned())).await.unwrap(), 1); + // assert_eq!(create(None).await.unwrap(), 2); + + // assert!(create(Some("test".to_owned())).await.is_err()); + // assert!(create(None).await.is_err()); + // assert_eq!( + // sqlx::query_scalar!("SELECT COUNT(*) FROM workspace_dependencies",) + // .fetch_one(db) + // .await + // .unwrap() + // .unwrap(), + // 2 + // ); + // Ok(()) + // } + // } +} diff --git a/cli/src/commands/dependencies/dependencies.ts b/cli/src/commands/dependencies/dependencies.ts new file mode 100644 index 0000000000..5c1d4aa385 --- /dev/null +++ b/cli/src/commands/dependencies/dependencies.ts @@ -0,0 +1,105 @@ +// deno-lint-ignore-file no-explicit-any +import { requireLogin } from "../../core/auth.ts"; +import { resolveWorkspace } from "../../core/context.ts"; +import { GlobalOptions } from "../../types.ts"; +import { colors, Command, log } from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; +import type { ScriptLang } from "../../../gen/types.gen.ts"; +import fs from "node:fs"; +import { generateHash } from "../../utils/utils.ts"; +import { checkifMetadataUptodate, updateMetadataGlobalLock, workspaceDependenciesPathToLanguageAndFilename } from "../../utils/metadata.ts"; + +async function push( + opts: GlobalOptions, + filePath: string, + language?: ScriptLang, + name?: string +): Promise { + const workspace = await resolveWorkspace(opts); + await requireLogin(opts); + + if (!fs.existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + + const content = fs.readFileSync(filePath, "utf8"); + + // Use the existing pushWorkspaceDependencies function + await pushWorkspaceDependencies(workspace.workspaceId, filePath, null, content); +} + +const command = new Command() + .alias("deps") + .description("workspace dependencies related commands") + .command( + "push", + "Push workspace dependencies from a local file" + ) + .arguments("") + .option( + "--language ", + "Programming language (python3, typescript, go, php). If not specified, will be inferred from file extension." + ) + .option( + "--name ", + "Name for the dependencies. If not specified, creates workspace default dependencies." + ) + .action(push as any); + +export async function pushWorkspaceDependencies( + workspace: string, + path: string, + _befObj: any, + newDependenciesContent: string +): Promise { + try { + + let res = workspaceDependenciesPathToLanguageAndFilename(path); + if (!res) { + throw new Error(`Unknown workspace dependencies file format: ${path}`); + } + + let { + language, + name + } = res; + + // TODO: include workspace? + // Generate hash for workspace dependencies content and metadata + const contentHash = await generateHash(newDependenciesContent + path); + + // Check if dependencies are up-to-date using wmill-lock.yaml tracking + const isUpToDate = await checkifMetadataUptodate(path, contentHash, undefined); + + if (isUpToDate) { + const displayName = name ? `named dependencies "${name}"` : `workspace default dependencies`; + log.info(colors.green(`${displayName} for ${language} are up-to-date, skipping push`)); + return; + } + + log.info(colors.yellow(`Pushing ${name ? 'named' : 'workspace default'} dependencies for ${language}...`)); + + await wmill.createWorkspaceDependencies({ + workspace, + requestBody: { + name, + content: newDependenciesContent, + language, + workspace_id: workspace, + // Description is not supported in cli, it will use old description + description: undefined + } + }); + + // Update wmill-lock.yaml with new hash after successful push + await updateMetadataGlobalLock(path, contentHash); + + const displayName = name ? `named dependencies "${name}"` : `workspace default dependencies`; + log.info(colors.green(`Successfully pushed ${displayName} for ${language}`)); + } catch (error: any) { + log.error(colors.red(`Failed to push workspace dependencies: ${error.message}`)); + throw error; + } +} + +export default command; diff --git a/cli/src/commands/flow/flow.ts b/cli/src/commands/flow/flow.ts index e16f2fbcb3..4be9e53867 100644 --- a/cli/src/commands/flow/flow.ts +++ b/cli/src/commands/flow/flow.ts @@ -200,9 +200,6 @@ async function generateLocks( } & SyncOptions, folder: string | undefined ) { - const useRawReqs = - opts.useRawRequirements || Deno.env.get("USE_RAW_REQUIREMENTS") === "true"; - const workspace = await resolveWorkspace(opts); await requireLogin(opts); opts = await mergeConfigWithConfigFile(opts); @@ -212,10 +209,7 @@ async function generateLocks( folder, false, workspace, - opts, - undefined, - undefined, - useRawReqs + opts ); } else { const ignore = await ignoreF(opts); @@ -241,10 +235,7 @@ async function generateLocks( folder, true, workspace, - opts, - undefined, - undefined, - useRawReqs + opts ); if (candidate) { hasAny = true; @@ -271,10 +262,7 @@ async function generateLocks( folder, false, workspace, - opts, - undefined, - undefined, - useRawReqs + opts ); } } diff --git a/cli/src/commands/script/script.ts b/cli/src/commands/script/script.ts index de1ed3289b..ff513336a9 100644 --- a/cli/src/commands/script/script.ts +++ b/cli/src/commands/script/script.ts @@ -26,13 +26,14 @@ import { import { Workspace } from "../workspace/workspace.ts"; import { generateScriptMetadataInternal, + getRawWorkspaceDependencies, parseMetadataFile, } from "../../utils/metadata.ts"; import { - LanguageWithRawReqsSupport, + WorkspaceDependenciesLanguage, ScriptLanguage, inferContentTypeFromFilePath, - languagesWithRawReqsSupport, + workspaceDependenciesLanguages, } from "../../utils/script_common.ts"; import { elementsToMap, @@ -88,14 +89,13 @@ async function push(opts: PushOptions, filePath: string) { await requireLogin(opts); const codebases = await listSyncCodebases(opts as SyncOptions); - const globalDeps = await findGlobalDeps(); await handleFile( filePath, workspace, [], undefined, opts, - globalDeps, + await getRawWorkspaceDependencies(), codebases ); log.info(colors.bold.underline.green(`Script ${filePath} pushed`)); @@ -156,7 +156,7 @@ export async function handleScriptMetadata( workspace: Workspace, alreadySynced: string[], message: string | undefined, - globalDeps: GlobalDeps, + rawWorkspaceDependencies: Record, codebases: SyncCodebase[], opts: GlobalOptions ): Promise { @@ -172,7 +172,7 @@ export async function handleScriptMetadata( alreadySynced, message, opts, - globalDeps, + rawWorkspaceDependencies, codebases ); } else { @@ -194,7 +194,7 @@ export async function handleFile( alreadySynced: string[], message: string | undefined, opts: (GlobalOptions & { defaultTs?: "bun" | "deno" } & Skips) | undefined, - globalDeps: GlobalDeps, + rawWorkspaceDependencies: Record, codebases: SyncCodebase[] ): Promise { if ( @@ -326,7 +326,7 @@ export async function handleFile( path, workspaceRemote: workspace, schemaOnly: codebase ? true : undefined, - globalDeps, + rawWorkspaceDependencies, codebases, } : undefined @@ -943,39 +943,10 @@ async function bootstrap( } export type GlobalDeps = Map< - LanguageWithRawReqsSupport, + WorkspaceDependenciesLanguage, Record >; -export async function findGlobalDeps(): Promise { - var globalDeps: GlobalDeps = new Map(); - const els = await FSFSElement(Deno.cwd(), [], false); - for await (const entry of readDirRecursiveWithIgnore((p, isDir) => { - p = SEP + p; - return ( - !isDir && - // Skip if the filename is not one of lockfile names - !languagesWithRawReqsSupport.some((lockfile) => - p.endsWith(SEP + lockfile.rrFilename) - ) - ); - }, els)) { - if (entry.isDirectory || entry.ignored) continue; - const content = await entry.getContentText(); - - // Iterate over available languages to find which lockfile - languagesWithRawReqsSupport.map((lock) => { - if (entry.path.endsWith(lock.rrFilename)) { - const current = globalDeps.get(lock) ?? {}; - current[ - entry.path.substring(0, entry.path.length - lock.rrFilename.length) - ] = content; - globalDeps.set(lock, current); - } - }); - } - return globalDeps; -} async function generateMetadata( opts: GlobalOptions & { lockOnly?: boolean; @@ -999,7 +970,7 @@ async function generateMetadata( opts = await mergeConfigWithConfigFile(opts); const codebases = await listSyncCodebases(opts); - const globalDeps = await findGlobalDeps(); + const rawWorkspaceDependencies = await getRawWorkspaceDependencies(); if (scriptPath) { // read script metadata file await generateScriptMetadataInternal( @@ -1008,11 +979,12 @@ async function generateMetadata( opts, false, false, - globalDeps, + rawWorkspaceDependencies, codebases, false ); } else { + // TODO: test this as well. const ignore = await ignoreF(opts); const elems = await elementsToMap( await FSFSElement(Deno.cwd(), codebases, false), @@ -1036,7 +1008,7 @@ async function generateMetadata( opts, true, true, - globalDeps, + rawWorkspaceDependencies, codebases, false ); @@ -1063,6 +1035,7 @@ async function generateMetadata( log.info(colors.green.bold("No metadata to update")); return; } + // TODO: test this for (const e of Object.keys(elems)) { await generateScriptMetadataInternal( e, @@ -1070,7 +1043,7 @@ async function generateMetadata( opts, false, true, - globalDeps, + rawWorkspaceDependencies, codebases, false ); diff --git a/cli/src/commands/sync/pull.ts b/cli/src/commands/sync/pull.ts index 18e0e350fb..27bb04c3ad 100644 --- a/cli/src/commands/sync/pull.ts +++ b/cli/src/commands/sync/pull.ts @@ -17,6 +17,7 @@ export async function downloadZip( includeGroups?: boolean, includeSettings?: boolean, includeKey?: boolean, + skipWorkspaceDependencies?: boolean, defaultTs?: "bun" | "deno" ): Promise { const requestHeaders = new Headers(); @@ -30,8 +31,8 @@ export async function downloadZip( } } - const zipResponse = await fetch( - workspace.remote + + const includeWorkspaceDependenciesValue = !(skipWorkspaceDependencies ?? false); + const url = workspace.remote + "api/w/" + workspace.workspaceId + `/workspaces/tarball?archive_type=zip&plain_secret=${plainSecrets ?? false @@ -39,8 +40,9 @@ export async function downloadZip( }&skip_secrets=${skipSecrets ?? false}&include_schedules=${includeSchedules ?? false }&include_triggers=${includeTriggers ?? false}&include_users=${includeUsers ?? false }&include_groups=${includeGroups ?? false}&include_settings=${includeSettings ?? false - }&include_key=${includeKey ?? false}&default_ts=${defaultTs ?? "bun"}&skip_resource_types=${skipResourceTypes ?? false}`, - { + }&include_key=${includeKey ?? false}&include_workspace_dependencies=${includeWorkspaceDependenciesValue}&default_ts=${defaultTs ?? "bun"}&skip_resource_types=${skipResourceTypes ?? false}`; + + const zipResponse = await fetch(url, { headers: requestHeaders, method: "GET", } diff --git a/cli/src/commands/sync/sync.ts b/cli/src/commands/sync/sync.ts index 3ea6ced18c..4a1befb82f 100644 --- a/cli/src/commands/sync/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -28,14 +28,13 @@ import { downloadZip } from "./pull.ts"; import { exts, findContentFile, - findGlobalDeps, findResourceFile, handleScriptMetadata, removeExtensionToPath, } from "../script/script.ts"; import { handleFile } from "../script/script.ts"; -import { deepEqual, isFileResource } from "../../utils/utils.ts"; +import { deepEqual, isFileResource, isWorkspaceDependencies } from "../../utils/utils.ts"; import { SyncOptions, getEffectiveSettings, @@ -58,7 +57,9 @@ import { SyncCodebase, listSyncCodebases } from "../../utils/codebase.ts"; import { generateFlowLockInternal, generateScriptMetadataInternal, + getRawWorkspaceDependencies, readLockfile, + workspaceDependenciesPathToLanguageAndFilename, } from "../../utils/metadata.ts"; import { OpenFlow } from "../../../gen/types.gen.ts"; import { pushResource } from "../resource/resource.ts"; @@ -311,7 +312,7 @@ function ZipFSElement( p: string, f: JSZip.JSZipObject ): Promise { - const kind: "flow" | "app" | "script" | "resource" | "other" = p.endsWith( + const kind: "flow" | "app" | "script" | "resource" | "dependencies" | "other" = p.endsWith( "flow.json" ) ? "flow" @@ -321,6 +322,8 @@ function ZipFSElement( ? "script" : p.endsWith("resource.json") ? "resource" + : p.startsWith("dependencies/") + ? "dependencies" : "other"; const isJson = p.endsWith(".json"); @@ -330,6 +333,8 @@ function ZipFSElement( return p.replace("flow.json", "flow"); } else if (kind == "app") { return p.replace("app.json", "app"); + } else if (kind == "dependencies") { + return p; } else { return useYaml && isJson ? p.replaceAll(".json", ".yaml") : p; } @@ -489,7 +494,7 @@ function ZipFSElement( : JSON.stringify(parsed, null, 2); } - return useYaml && isJson + return useYaml && isJson && kind != "dependencies" ? (() => { try { return yamlStringify(JSON.parse(content), yamlOptions); @@ -622,7 +627,6 @@ export async function* readDirRecursiveWithIgnore( while (stack.length > 0) { const e = stack.pop()!; - // console.log(e.path); yield e; for await (const e2 of e.c()) { if (e2.isDirectory) { @@ -631,10 +635,9 @@ export async function* readDirRecursiveWithIgnore( continue; } } - // console.log(e2.path); stack.push({ path: e2.path, - ignored: e.ignored || ignore(e2.path, e2.isDirectory), + ignored: e.ignored || e2.isDirectory && e2.path == "dependencies" ? false : ignore(e2.path, e2.isDirectory), isDirectory: e2.isDirectory, // getContentBytes: e2.getContentBytes, getContentText: e2.getContentText, @@ -661,16 +664,20 @@ export async function elementsToMap( ignore: (path: string, isDirectory: boolean) => boolean, json: boolean, skips: Skips, - specificItems?: SpecificItemsConfig + specificItems?: SpecificItemsConfig, ): Promise<{ [key: string]: string }> { const map: { [key: string]: string } = {}; const processedBasePaths = new Set(); - for await (const entry of readDirRecursiveWithIgnore(ignore, els)) { - if (entry.isDirectory || entry.ignored) continue; + if (entry.isDirectory || entry.ignored) { + if (entry.path.includes("dependencies/")) { + log.info(`Ignoring dependencies-related path: ${entry.path} (isDirectory: ${entry.isDirectory}, ignored: ${entry.ignored})`); + } + continue; + } const path = entry.path; - if (json && path.endsWith(".yaml") && !isFileResource(path)) continue; - if (!json && path.endsWith(".json") && !isFileResource(path)) continue; + if (json && path.endsWith(".yaml") && !isFileResource(path) && !isWorkspaceDependencies(path)) continue; + if (!json && path.endsWith(".json") && !isFileResource(path) && !isWorkspaceDependencies(path)) continue; const ext = json ? ".json" : ".yaml"; if (!skips.includeSchedules && path.endsWith(".schedule" + ext)) continue; if ( @@ -694,6 +701,7 @@ export async function elementsToMap( if (skips.skipResourceTypes && path.endsWith(".resource-type" + ext)) continue; + // Use getTypeStrFromPath for consistent type detection try { const fileType = getTypeStrFromPath(path); @@ -702,6 +710,7 @@ export async function elementsToMap( if (skips.skipFlows && fileType === "flow") continue; if (skips.skipApps && fileType === "app") continue; if (skips.skipFolders && fileType === "folder") continue; + if (skips.skipWorkspaceDependencies && fileType === "workspace_dependencies") continue; } catch { // If getTypeStrFromPath can't determine the type, continue processing the file } @@ -728,6 +737,8 @@ export async function elementsToMap( "nu", "java", "rb", + "in", // Python requirements.in files + "mod", // Go go.mod files // for related places search: ADD_NEW_LANG ].includes(path.split(".").pop() ?? "") && !isFileResource(path) @@ -797,7 +808,7 @@ export async function elementsToMap( // No specific items configuration, use regular path map[entry.path] = content; } - } + } return map; } @@ -810,6 +821,7 @@ export interface Skips { skipFlows?: boolean | undefined; skipApps?: boolean | undefined; skipFolders?: boolean | undefined; + skipWorkspaceDependencies?: boolean | undefined; skipScriptsMetadata?: boolean | undefined; includeSchedules?: boolean | undefined; includeTriggers?: boolean | undefined; @@ -878,11 +890,14 @@ async function compareDynFSElement( if (skipMetadata) { continue; } + if (k.startsWith("dependencies/")) { + log.info(`Adding workspace dependencies file: ${k}`); + } changes.push({ name: "added", path: k, content: v }); } else { if (m2[k] == v) { continue; - } else if (k.endsWith(".json")) { + } else if (k.endsWith(".json") && !isWorkspaceDependencies(k)) { let parsedV, parsedM2; try { parsedV = JSON.parse(v); @@ -1093,6 +1108,7 @@ export async function ignoreF(wmillconf: { excludes?: string[]; extraIncludes?: string[]; skipResourceTypes?: boolean; + skipWorkspaceDependencies?: boolean; json?: boolean; includeUsers?: boolean; includeGroups?: boolean; @@ -1151,6 +1167,9 @@ export async function ignoreF(wmillconf: { if (wmillconf.includeKey && fileType === "encryption_key") { return false; // Don't ignore, always include } + if (!wmillconf.skipWorkspaceDependencies && fileType === "workspace_dependencies") { + return false; // Don't ignore workspace dependencies (they are always included unless explicitly skipped) + } } catch { // If getTypeStrFromPath can't determine the type, fall through to normal logic } @@ -1269,8 +1288,7 @@ export async function pull( } catch { // ignore } - const remote = ZipFSElement( - (await downloadZip( + const zipFile = await downloadZip( workspace, opts.plainSecrets, opts.skipVariables, @@ -1283,8 +1301,12 @@ export async function pull( opts.includeGroups, opts.includeSettings, opts.includeKey, + opts.skipWorkspaceDependencies, opts.defaultTs - ))!, + ); + + const remote = ZipFSElement( + zipFile!, !opts.json, opts.defaultTs ?? "bun", resourceTypeToFormatExtension, @@ -1308,6 +1330,19 @@ export async function pull( log.info( `remote (${workspace.name}) -> local: ${changes.length} changes to apply` ); + + + // Debug: show all changes for push operation + if (changes.length > 0) { + log.info("All changes:"); + changes.forEach(change => { + if (change.path.startsWith("dependencies/")) { + log.info(` ${change.name}: ${change.path} [WORKSPACE DEPS]`); + } else { + log.info(` ${change.name}: ${change.path}`); + } + }); + } // Handle JSON output for dry-run if (opts.dryRun && opts.jsonOutput) { @@ -1495,9 +1530,9 @@ export async function pull( } log.info("All local changes pulled, now updating wmill-lock.yaml"); await readLockfile(); // ensure wmill-lock.yaml exists - const globalDeps = await findGlobalDeps(); const tracker: ChangeTracker = await buildTracker(changes); + const rawWorkspaceDependencies: Record = await getRawWorkspaceDependencies(); for (const change of tracker.scripts) { await generateScriptMetadataInternal( @@ -1506,14 +1541,14 @@ export async function pull( opts, false, true, - globalDeps, + rawWorkspaceDependencies, codebases, true ); } for (const change of tracker.flows) { log.info(`Updating lock for flow ${change}`); - await generateFlowLockInternal(change, false, workspace, opts, true); + await generateFlowLockInternal(change, false, workspace, opts, true, false); } if (tracker.apps.length > 0) { log.info( @@ -1733,6 +1768,7 @@ export async function push( opts.includeGroups, opts.includeSettings, opts.includeKey, + opts.skipWorkspaceDependencies, opts.defaultTs ))!, !opts.json, @@ -1754,7 +1790,8 @@ export async function push( specificItems ); - const globalDeps = await findGlobalDeps(); + + const rawWorkspaceDependencies = await getRawWorkspaceDependencies(); const tracker: ChangeTracker = await buildTracker(changes); @@ -1767,7 +1804,7 @@ export async function push( opts, true, true, - globalDeps, + rawWorkspaceDependencies, codebases, false ); @@ -1932,7 +1969,7 @@ export async function push( workspace, alreadySynced, opts.message, - globalDeps, + rawWorkspaceDependencies, codebases, opts ) @@ -1948,7 +1985,7 @@ export async function push( alreadySynced, opts.message, opts, - globalDeps, + rawWorkspaceDependencies, codebases ) ) { @@ -2040,7 +2077,7 @@ export async function push( alreadySynced, opts.message, opts, - globalDeps, + rawWorkspaceDependencies, codebases ) ) { @@ -2225,6 +2262,22 @@ export async function push( ".group.json" ), }); + break; + case "workspace_dependencies": + const relativePath = removePathPrefix(change.path, "dependencies"); + + const res = workspaceDependenciesPathToLanguageAndFilename(change.path); + if (!res) { + throw new Error(`Unknown workspace dependencies file format: ${change.path}`); + } + const { name, language } = res; + + await wmill.deleteWorkspaceDependencies({ + workspace: workspaceId, + language, + name + }); + break; default: break; @@ -2323,6 +2376,7 @@ const command = new Command() .option("--skip-flows", "Skip syncing flows") .option("--skip-apps", "Skip syncing apps") .option("--skip-folders", "Skip syncing folders") + .option("--skip-workspace-dependencies", "Skip syncing workspace dependencies") // .option("--skip-scripts-metadata", "Skip syncing scripts metadata, focus solely on logic") .option("--include-schedules", "Include syncing schedules") .option("--include-triggers", "Include syncing triggers") @@ -2371,6 +2425,7 @@ const command = new Command() .option("--skip-flows", "Skip syncing flows") .option("--skip-apps", "Skip syncing apps") .option("--skip-folders", "Skip syncing folders") + .option("--skip-workspace-dependencies", "Skip syncing workspace dependencies") // .option("--skip-scripts-metadata", "Skip syncing scripts metadata, focus solely on logic") .option("--include-schedules", "Include syncing schedules") .option("--include-triggers", "Include syncing triggers") diff --git a/cli/src/core/conf.ts b/cli/src/core/conf.ts index 0a6dbe8167..aa9a7a83be 100644 --- a/cli/src/core/conf.ts +++ b/cli/src/core/conf.ts @@ -26,6 +26,7 @@ export interface SyncOptions { skipResources?: boolean; skipResourceTypes?: boolean; skipSecrets?: boolean; + skipWorkspaceDependencies?: boolean; skipScripts?: boolean; skipFlows?: boolean; skipApps?: boolean; @@ -317,6 +318,7 @@ export const DEFAULT_SYNC_OPTIONS: Readonly< | "skipSecrets" | "includeSchedules" | "includeTriggers" + | "skipWorkspaceDependencies" | "skipScripts" | "skipFlows" | "skipApps" @@ -346,6 +348,7 @@ export const DEFAULT_SYNC_OPTIONS: Readonly< includeGroups: false, includeSettings: false, includeKey: false, + skipWorkspaceDependencies: false, } as const; export async function mergeConfigWithConfigFile( diff --git a/cli/src/main.ts b/cli/src/main.ts index df61ec8c81..ce1137f369 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -36,6 +36,7 @@ import { pull, push } from "./commands/sync/sync.ts"; import { add as workspaceAdd } from "./commands/workspace/workspace.ts"; import workers from "./commands/workers/workers.ts"; import queues from "./commands/queues/queues.ts"; +import dependencies from "./commands/dependencies/dependencies.ts"; import init from "./commands/init/init.ts"; export { @@ -126,6 +127,7 @@ const command = new Command() .command("worker-groups", workerGroups) .command("workers", workers) .command("queues", queues) + .command("dependencies", dependencies) .command("version --version", "Show version information") .action(async (opts: any) => { console.log("CLI version: " + VERSION); diff --git a/cli/src/types.ts b/cli/src/types.ts index a7b821882a..1e8fd6a9eb 100644 --- a/cli/src/types.ts +++ b/cli/src/types.ts @@ -17,10 +17,11 @@ import { pushResourceType } from "./commands/resource-type/resource-type.ts"; import { pushVariable } from "./commands/variable/variable.ts"; import { yamlOptions } from "./commands/sync/sync.ts"; import { showDiffs } from "./core/conf.ts"; -import { deepEqual, isFileResource } from "./utils/utils.ts"; +import { deepEqual, isFileResource, isWorkspaceDependencies } from "./utils/utils.ts"; import { pushSchedule } from "./commands/schedule/schedule.ts"; import { pushWorkspaceUser } from "./commands/user/user.ts"; import { pushGroup } from "./commands/user/user.ts"; +import { pushWorkspaceDependencies } from "./commands/dependencies/dependencies.ts"; import { pushWorkspaceSettings, pushWorkspaceKey } from "./core/settings.ts"; import { pushTrigger } from "./commands/trigger/trigger.ts"; @@ -187,6 +188,8 @@ export async function pushObj( await pushWorkspaceUser(workspace, p, befObj, newObj); } else if (typeEnding === "group") { await pushGroup(workspace, p, befObj, newObj); + } else if (typeEnding === "workspace_dependencies") { + await pushWorkspaceDependencies(workspace, p, befObj, newObj); } else if (typeEnding === "settings") { await pushWorkspaceSettings(workspace, p, befObj, newObj); } else if (typeEnding === "encryption_key") { @@ -199,7 +202,9 @@ export async function pushObj( } export function parseFromPath(p: string, content: string): any { - return p.endsWith(".yaml") + return isWorkspaceDependencies(p) + ? content + : p.endsWith(".yaml") ? yamlParseContent(p, content) : p.endsWith(".json") ? JSON.parse(content) @@ -237,13 +242,17 @@ export function getTypeStrFromPath( | "user" | "group" | "settings" - | "encryption_key" { + | "encryption_key" + | "workspace_dependencies" { if (p.includes(".flow" + SEP)) { return "flow"; } if (p.includes(".app" + SEP)) { return "app"; } + if (p.startsWith("dependencies" + SEP)) { + return "workspace_dependencies"; + } const parsed = path.parse(p); if ( parsed.ext == ".go" || diff --git a/cli/src/utils/metadata.ts b/cli/src/utils/metadata.ts index 00e2206933..97cc76e504 100644 --- a/cli/src/utils/metadata.ts +++ b/cli/src/utils/metadata.ts @@ -14,12 +14,12 @@ import { } from "../../bootstrap/script_bootstrap.ts"; import { Workspace } from "../commands/workspace/workspace.ts"; import { - languagesWithRawReqsSupport, - LanguageWithRawReqsSupport, + workspaceDependenciesLanguages, + WorkspaceDependenciesLanguage, ScriptLanguage, } from "./script_common.ts"; import { inferContentTypeFromFilePath } from "./script_common.ts"; -import { GlobalDeps, exts, findGlobalDeps } from "../commands/script/script.ts"; +import { exts } from "../commands/script/script.ts"; import { FSFSElement, findCodebase, @@ -48,29 +48,51 @@ export class LockfileGenerationError extends Error { export async function generateAllMetadata() {} -function findClosestRawReqs( - lang: LanguageWithRawReqsSupport | undefined, - remotePath: string, - globalDeps: GlobalDeps -): string | undefined { - let bestCandidate: { k: string; v: string } | undefined = undefined; - if (lang) { - Object.entries(globalDeps.get(lang) ?? {}).forEach(([k, v]) => { - if ( - remotePath.startsWith(k) && - k.length >= (bestCandidate?.k ?? "").length - ) { - bestCandidate = { k, v }; +export async function getRawWorkspaceDependencies(): Promise> { + const rawWorkspaceDeps: Record = {}; + + try { + for await (const entry of Deno.readDir("dependencies")) { + if (entry.isDirectory) continue; + + const filePath = `dependencies/${entry.name}`; + const content = await Deno.readTextFile(filePath); + + // Find matching language + for (const lang of workspaceDependenciesLanguages) { + if (entry.name.endsWith(lang.filename)) { + // Check if out of sync + const contentHash = await generateHash(content + filePath); + const isUpToDate = await checkifMetadataUptodate(filePath, contentHash, undefined); + + if (!isUpToDate) { + rawWorkspaceDeps[filePath] = content; + } + break; + } } - }); + } + } catch { + // dependencies directory doesn't exist } - // @ts-ignore - return bestCandidate?.v; + return rawWorkspaceDeps; +} + +export function workspaceDependenciesPathToLanguageAndFilename(path: string): { name: string | undefined, language: ScriptLanguage } | undefined { + const relativePath = path.replace("dependencies/", ""); + for (const { filename, language } of workspaceDependenciesLanguages) { + if (relativePath.endsWith(filename)) { + return { + name: relativePath === filename ? undefined : relativePath.replace("." + filename, ""), + language + }; + } + } } const TOP_HASH = "__flow_hash"; async function generateFlowHash( - rawReqs: Record | undefined, + rawWorkspaceDependencies: Record, folder: string, defaultTs: "bun" | "deno" | undefined ) { @@ -78,17 +100,9 @@ async function generateFlowHash( const hashes: Record = {}; for await (const f of elems.getChildren()) { if (exts.some((e) => f.path.endsWith(e))) { - let reqs: string | undefined; - if (rawReqs) { - // Get language name from path - const lang = inferContentTypeFromFilePath(f.path, defaultTs); - // Get lock for that language - [, reqs] = - Object.entries(rawReqs).find(([lang2, _]) => lang == lang2) ?? []; - } - // Embed lock into hash + // Embed workspace dependencies into hash hashes[f.path] = await generateHash( - (await f.getContentText()) + (reqs ?? "") + (await f.getContentText()) + JSON.stringify(rawWorkspaceDependencies) ); } } @@ -102,8 +116,7 @@ export async function generateFlowLockInternal( defaultTs?: "bun" | "deno"; }, justUpdateMetadataLock?: boolean, - noStaleMessage?: boolean, - useRawReqs?: boolean + noStaleMessage?: boolean ): Promise { if (folder.endsWith(SEP)) { folder = folder.substring(0, folder.length - 1); @@ -115,24 +128,9 @@ export async function generateFlowLockInternal( log.info(`Generating lock for flow ${folder} at ${remote_path}`); } - let rawReqs: Record | undefined = undefined; - if (useRawReqs) { - // Find all dependency files in the workspace - const globalDeps = await findGlobalDeps(); - - // Find closest dependency files for this flow - rawReqs = {}; - - // TODO: PERF: Only include raw reqs for the languages that are in the flow - languagesWithRawReqsSupport.map((lang) => { - const dep = findClosestRawReqs(lang, folder, globalDeps); - if (dep) { - // @ts-ignore - rawReqs[lang.language] = dep; - } - }); - } - let hashes = await generateFlowHash(rawReqs, folder, opts.defaultTs); + // Always get out-of-sync workspace dependencies + let rawWorkspaceDependencies: Record = await getRawWorkspaceDependencies(); + let hashes = await generateFlowHash(rawWorkspaceDependencies, folder, opts.defaultTs); const conf = await readLockfile(); if (await checkifMetadataUptodate(folder, hashes[TOP_HASH], conf, TOP_HASH)) { @@ -146,15 +144,12 @@ export async function generateFlowLockInternal( return remote_path; } - if (useRawReqs) { - log.warn( - "If using local lockfiles, following redeployments from Web App will inevitably override generated lockfiles by CLI. To maintain your script's lockfiles you will need to redeploy only from CLI. (Behavior is subject to change)" - ); + if (Object.keys(rawWorkspaceDependencies).length > 0) { log.info( (await blueColor())( - `Found raw requirements (${languagesWithRawReqsSupport - .map((l) => l.rrFilename) - .join("/")}) for ${folder}, using it` + `Found workspace dependencies (${workspaceDependenciesLanguages + .map((l) => l.filename) + .join("/")}) for ${folder}, using them` ) ); } @@ -192,7 +187,7 @@ export async function generateFlowLockInternal( workspace, flowValue.value, remote_path, - rawReqs + rawWorkspaceDependencies ); const inlineScripts = extractInlineScriptsForFlows( @@ -212,7 +207,7 @@ export async function generateFlowLockInternal( ); } - hashes = await generateFlowHash(rawReqs, folder, opts.defaultTs); + hashes = await generateFlowHash(rawWorkspaceDependencies, folder, opts.defaultTs); await clearGlobalLock(folder); for (const [path, hash] of Object.entries(hashes)) { await updateMetadataGlobalLock(folder, hash, path); @@ -236,7 +231,7 @@ export async function generateScriptMetadataInternal( }, dryRun: boolean, noStaleMessage: boolean, - globalDeps: GlobalDeps, + rawWorkspaceDependencies: Record, codebases: SyncCodebase[], justUpdateMetadataLock?: boolean ): Promise { @@ -246,6 +241,14 @@ export async function generateScriptMetadataInternal( const language = inferContentTypeFromFilePath(scriptPath, opts.defaultTs); + // Filter workspace dependencies to only include those matching the script's language + const filteredRawWorkspaceDependencies: Record = {}; + for (const [depPath, depContent] of Object.entries(rawWorkspaceDependencies)) { + const depInfo = workspaceDependenciesPathToLanguageAndFilename(depPath); + if (depInfo && depInfo.language === language) { + filteredRawWorkspaceDependencies[depPath] = depContent; + } + } const metadataWithType = await parseMetadataFile( remotePath, @@ -256,14 +259,8 @@ export async function generateScriptMetadataInternal( const scriptContent = await Deno.readTextFile(scriptPath); const metadataContent = await Deno.readTextFile(metadataWithType.path); - const rrLang = languagesWithRawReqsSupport.find( - (l) => language == l.language - ); - - const rawReqs = findClosestRawReqs(rrLang, scriptPath, globalDeps); - - - let hash = await generateScriptHash(rawReqs, scriptContent, metadataContent); + // Note: rawWorkspaceDependencies are now passed in as parameter instead of being searched hierarchically + let hash = await generateScriptHash(filteredRawWorkspaceDependencies, scriptContent, metadataContent); if (await checkifMetadataUptodate(remotePath, hash, undefined)) { if (!noStaleMessage) { @@ -304,7 +301,7 @@ export async function generateScriptMetadataInternal( language, remotePath, metadataParsedContent, - rawReqs + filteredRawWorkspaceDependencies ); } else { metadataParsedContent.lock = ""; @@ -323,7 +320,7 @@ export async function generateScriptMetadataInternal( const metadataContentUsedForHash = newMetadataContent; hash = await generateScriptHash( - rawReqs, + filteredRawWorkspaceDependencies, scriptContent, metadataContentUsedForHash ); @@ -366,11 +363,11 @@ async function updateScriptLock( language: ScriptLanguage, remotePath: string, metadataContent: Record, - rawDeps: string | undefined + rawWorkspaceDependencies: Record ): Promise { if ( !( - languagesWithRawReqsSupport.some((l) => l.language == language) || + workspaceDependenciesLanguages.some((l) => l.language == language) || language == "deno" || language == "rust" || language == "ansible" @@ -379,9 +376,12 @@ async function updateScriptLock( return; } - if (rawDeps) { - log.info(`Generating script lock for ${remotePath} with raw deps`); + + if (Object.keys(rawWorkspaceDependencies).length > 0) { + const dependencyPaths = Object.keys(rawWorkspaceDependencies).join(', '); + log.info(`Generating script lock for ${remotePath} with raw workspace dependencies: ${dependencyPaths}`); } + // generate the script lock running a dependency job in Windmill and update it inplace // TODO: update this once the client is released const extraHeaders = getHeaders(); @@ -402,7 +402,8 @@ async function updateScriptLock( script_path: remotePath, }, ], - raw_deps: rawDeps, + raw_workspace_dependencies: Object.keys(rawWorkspaceDependencies).length > 0 + ? rawWorkspaceDependencies : null, entrypoint: remotePath, }), } @@ -451,12 +452,12 @@ export async function updateFlow( workspace: Workspace, flow_value: FlowValue, remotePath: string, - rawDeps?: Record + rawWorkspaceDependencies: Record ): Promise { let rawResponse; - if (rawDeps != undefined) { - log.info(colors.blue("Using raw requirements for flow dependencies")); + if (Object.keys(rawWorkspaceDependencies).length > 0) { + log.info(colors.blue("Using raw workspace dependencies for flow dependencies")); // generate the script lock running a dependency job in Windmill and update it inplace const extraHeaders = getHeaders(); @@ -473,7 +474,9 @@ export async function updateFlow( flow_value, path: remotePath, use_local_lockfiles: true, - raw_deps: rawDeps, + raw_workspace_dependencies: Object.keys(rawWorkspaceDependencies).length > 0 + ? rawWorkspaceDependencies + : null, }), } ); @@ -767,7 +770,7 @@ export async function parseMetadataFile( path: string; workspaceRemote: Workspace; schemaOnly?: boolean; - globalDeps: GlobalDeps; + rawWorkspaceDependencies: Record; codebases: SyncCodebase[] }) | undefined, @@ -829,7 +832,7 @@ export async function parseMetadataFile( generateMetadataIfMissing, false, false, - generateMetadataIfMissing.globalDeps, + generateMetadataIfMissing.rawWorkspaceDependencies, generateMetadataIfMissing.codebases, false ); @@ -911,12 +914,12 @@ export async function checkifMetadataUptodate( } export async function generateScriptHash( - rawReqs: string | undefined, + rawWorkspaceDependencies: Record, scriptContent: string, newMetadataContent: string ) { return await generateHash( - (rawReqs ?? "") + scriptContent + newMetadataContent + JSON.stringify(rawWorkspaceDependencies) + scriptContent + newMetadataContent ); } diff --git a/cli/src/utils/script_common.ts b/cli/src/utils/script_common.ts index df661d25fe..a5aba792c8 100644 --- a/cli/src/utils/script_common.ts +++ b/cli/src/utils/script_common.ts @@ -26,18 +26,17 @@ export type ScriptLanguage = // To make language support raw requirements: // 1. Add value here // 2. Modify backend to allow raw deps -export type LanguageWithRawReqsSupport = - | { language: "bun", rrFilename /** (raw requirements filename) */: "package.json" } - // TODO: Add `requirements.in` - more intuitive and reflects better what actually happens - | { language: "python3", rrFilename: "requirements.txt" } - | { language: "php", rrFilename: "composer.json" } - | { language: "go", rrFilename: "go.mod" }; +export type WorkspaceDependenciesLanguage = + | { language: "bun", filename /** (raw requirements filename) */: "package.json" } + | { language: "python3", filename: "requirements.in" } + | { language: "php", filename: "composer.json" } + | { language: "go", filename: "go.mod" }; -export const languagesWithRawReqsSupport: LanguageWithRawReqsSupport[] = [ - { language: "bun", rrFilename: "package.json" }, - { language: "python3", rrFilename: "requirements.txt" }, - { language: "php", rrFilename: "composer.json" }, - { language: "go", rrFilename: "go.mod" }, +export const workspaceDependenciesLanguages: WorkspaceDependenciesLanguage[] = [ + { language: "bun", filename: "package.json" }, + { language: "python3", filename: "requirements.in" }, + { language: "php", filename: "composer.json" }, + { language: "go", filename: "go.mod" }, ] as const; export function inferContentTypeFromFilePath( diff --git a/cli/src/utils/utils.ts b/cli/src/utils/utils.ts index d11b751a5b..3d33a53405 100644 --- a/cli/src/utils/utils.ts +++ b/cli/src/utils/utils.ts @@ -145,6 +145,10 @@ export function isFileResource(path: string): boolean { ); } +export function isWorkspaceDependencies(path: string): boolean { + return path.startsWith("dependencies/") +} + export function printSync(input: string | Uint8Array, to = Deno.stdout) { let bytesWritten = 0 const bytes = typeof input === 'string' ? new TextEncoder().encode(input) : input @@ -235,4 +239,4 @@ export function writeIfChanged(path: string, content: string): boolean { // console.log(`Writing content to ${path}`); Deno.writeTextFileSync(path, content); return true; // File was written -} \ No newline at end of file +} diff --git a/flake.nix b/flake.nix index 102c79e8dd..b818e0245a 100644 --- a/flake.nix +++ b/flake.nix @@ -50,7 +50,7 @@ xmlsec.dev libxslt.dev libclang.dev - libffi # For deno_ffi + libffi # For deno_ffi libtool nodejs postgresql @@ -118,20 +118,17 @@ fi wm-cli-deps ''; - buildInputs = buildInputs ++ [ - pkgs.deno - ]; + buildInputs = buildInputs ++ [ pkgs.deno ]; packages = [ (pkgs.writeScriptBin "wm-cli" '' deno run -A --no-check $FLAKE_ROOT/cli/src/main.ts $* '') (pkgs.writeScriptBin "wm-cli-deps" '' pushd $FLAKE_ROOT/cli/ - ${ - if pkgs.stdenv.isDarwin - then "./gen_wm_client_mac.sh && ./windmill-utils-internal/gen_wm_client_mac.sh" - else "./gen_wm_client.sh && ./windmill-utils-internal/gen_wm_client.sh" - } + ${if pkgs.stdenv.isDarwin then + "./gen_wm_client_mac.sh && ./windmill-utils-internal/gen_wm_client_mac.sh" + else + "./gen_wm_client.sh && ./windmill-utils-internal/gen_wm_client.sh"} popd '') ]; @@ -278,6 +275,8 @@ PHP_PATH = "${pkgs.php}/bin/php"; COMPOSER_PATH = "${pkgs.php84Packages.composer}/bin/composer"; BUN_PATH = "${pkgs.bun}/bin/bun"; + NODE_PATH = "${pkgs.nodejs}/bin/node"; + NODE_BIN_PATH = "${pkgs.nodejs}/bin/node"; UV_PATH = "${pkgs.uv}/bin/uv"; NU_PATH = "${pkgs.nushell}/bin/nu"; JAVA_PATH = "${pkgs.jdk21}/bin/java"; @@ -314,9 +313,11 @@ # See https://web.archive.org/web/20220523141208/https://hoverbear.org/blog/rust-bindgen-in-nix/ BINDGEN_EXTRA_CLANG_ARGS = # Prevent clang from using system headers - only use Nix headers - "-nostdinc ${builtins.readFile "${stdenv.cc}/nix-support/libc-crt1-cflags"} ${ - builtins.readFile "${stdenv.cc}/nix-support/libc-cflags" - } ${builtins.readFile "${stdenv.cc}/nix-support/cc-cflags"} ${ + "-nostdinc ${ + builtins.readFile "${stdenv.cc}/nix-support/libc-crt1-cflags" + } ${builtins.readFile "${stdenv.cc}/nix-support/libc-cflags"} ${ + builtins.readFile "${stdenv.cc}/nix-support/cc-cflags" + } ${ builtins.readFile "${stdenv.cc}/nix-support/libcxx-cxxflags" } -idirafter ${pkgs.libiconv}/include ${ lib.optionalString stdenv.cc.isClang diff --git a/frontend/src/lib/components/DependenciesDeploymentWarning.svelte b/frontend/src/lib/components/DependenciesDeploymentWarning.svelte new file mode 100644 index 0000000000..305fc93512 --- /dev/null +++ b/frontend/src/lib/components/DependenciesDeploymentWarning.svelte @@ -0,0 +1,295 @@ + + + +
+ {#if loading} +
+
+ Loading dependencies... +
+ {:else} + +
+
+ + Workspace Dependencies +
+
+ {importedPath} +
+
+ + {#if dependencies.length === 0} + + {#snippet children()} +

No dependent runnables were found for these workspace dependencies, but the action will still proceed.

+ {/snippet} +
+ {:else} + +
+ +
+ + +
+

+ This action will trigger redeployment of {getTotalDependentsCount()} + {getTotalDependentsCount() === 1 ? 'dependent runnable' : 'dependent runnables'}: +

+
+ + +
+ {#each dependencies as dependency} + {@render DependencyNode({ node: dependency, level: 0 })} + {/each} +
+ {/if} + + {/if} +
+ + + + +
+ +{#snippet DependencyNode({ node, level }: { node: DependencyNode, level: number })} + {@const Icon = getIcon(node.kind)} +
+
+ + {#if (node.childrenCount ?? 0) > 0} + + {:else} +
+ {/if} + + +
+ + + {getKindLabel(node.kind)} + +
+ + +
+ + {node.path} + + {#if node.nodeIds && node.nodeIds.length > 0} + + {node.nodeIds.length} node{node.nodeIds.length !== 1 ? 's' : ''} + + {/if} +
+ + + {#if (node.childrenCount ?? 0) > 0} + + {node.childrenCount} dep{node.childrenCount !== 1 ? 's' : ''} + + {/if} +
+ + + {#if node.expanded && node.children} +
+ {#each node.children as child} + {@render DependencyNode({ node: child, level: level + 1 })} + {/each} +
+ {/if} +
+{/snippet} diff --git a/frontend/src/lib/components/HighlightCode.svelte b/frontend/src/lib/components/HighlightCode.svelte index 63e1d8a999..be6e6b6ef0 100644 --- a/frontend/src/lib/components/HighlightCode.svelte +++ b/frontend/src/lib/components/HighlightCode.svelte @@ -19,11 +19,11 @@ import { copyToClipboard } from '$lib/utils' import { ClipboardCopy } from 'lucide-svelte' import HighlightTheme from './HighlightTheme.svelte' - import type { LanguageType } from 'svelte-highlight/languages' + import { json, type LanguageType } from 'svelte-highlight/languages' interface Props { code?: string - language: Script['language'] | 'bunnative' | 'frontend' | undefined + language: Script['language'] | 'bunnative' | 'frontend' | 'json' | undefined highlightLanguage?: LanguageType | undefined lines?: boolean className?: string @@ -43,7 +43,7 @@ applyButtonIcon = undefined }: Props = $props() - function getLang(lang: Script['language'] | 'bunnative' | 'frontend' | undefined) { + function getLang(lang: Script['language'] | 'bunnative' | 'frontend' | 'json' | undefined) { switch (lang) { case 'python3': return python @@ -91,6 +91,8 @@ return java case 'ruby': return ruby + case 'json': + return json // for related places search: ADD_NEW_LANG default: return typescript diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index 4b386aa363..fd2326cb23 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -12,7 +12,7 @@ PostgresTriggerService, CaptureService, type ScriptLang, - WorkerService + WorkerService, } from '$lib/gen' import { inferArgs } from '$lib/infer' import { @@ -189,7 +189,7 @@ : undefined ) const simplifiedPoll = writable(false) - + export function setPrimarySchedule(schedule: ScheduleTrigger | undefined | false) { primaryScheduleStore.set(schedule) loadTriggers() @@ -1759,6 +1759,7 @@ />
{/if} +
{#if $enterpriseLicense && initialPath != ''} diff --git a/frontend/src/lib/components/WorkspaceDependenciesEditor.svelte b/frontend/src/lib/components/WorkspaceDependenciesEditor.svelte new file mode 100644 index 0000000000..86e1bba5d6 --- /dev/null +++ b/frontend/src/lib/components/WorkspaceDependenciesEditor.svelte @@ -0,0 +1,512 @@ + + + + +
+ {#if !can_write} + + You only have read access to this resource and cannot edit it + + {/if} + + {#if showWarning && currentImportedPath} + + {/if} + +
+
+ {#if hasWorkspaceDefault(workspaceDependencies.language) && !edit} + + {#snippet children({ item })} + + {/snippet} + +
+ + + Workspace default already exists for {workspaceDependencies.language} + + +
+ {:else} + + {#snippet children({ item })} + + + {/snippet} + + {/if} + {#if workspaceDependenciesType === 'named'} + + {/if} +
+ + Default Enforced Dependencies are used when no specific Dependencies are referenced from runnables. + Named dependencies can be referenced by scripts using + + annotations + . +
+
+
+ +
+
+ +
+ Provide a brief description to help others understand the purpose of these enforced dependencies. +
+
+ +
+ {#snippet header()} + + ({workspaceDependencies.content.length}/{MAX_WORKSPACE_DEPENDENCIES_LENGTH} characters) + + {/snippet} +
+ {#await import('$lib/components/SimpleEditor.svelte')} + + {:then Module} + handleEditorChange(e.detail)} + fixedOverflowWidgets={false} + disabled={!can_write} + /> + {/await} +
+
+ +
+ + {#snippet actions()} + + {/snippet} +
+
diff --git a/frontend/src/lib/components/WorkspaceDependenciesViewer.svelte b/frontend/src/lib/components/WorkspaceDependenciesViewer.svelte new file mode 100644 index 0000000000..fc3f246fc1 --- /dev/null +++ b/frontend/src/lib/components/WorkspaceDependenciesViewer.svelte @@ -0,0 +1,100 @@ + + + + + + + {#snippet actions()} +
+
+ + {viewLanguage} +
+ {#if canWriteDeps} + + {/if} +
+ {/snippet} + +
+ {#if viewDescription} +
+

{viewDescription}

+
+ {/if} + + {#if viewContent} + + {:else} +
+ +

No workspace dependencies found for this path

+

Create workspace dependencies to define dependencies for scripts in this directory

+
+ {/if} +
+
+
\ No newline at end of file diff --git a/frontend/src/lib/components/workspaceSettings/WorkspaceDependenciesSettings.svelte b/frontend/src/lib/components/workspaceSettings/WorkspaceDependenciesSettings.svelte new file mode 100644 index 0000000000..34cd534ae2 --- /dev/null +++ b/frontend/src/lib/components/workspaceSettings/WorkspaceDependenciesSettings.svelte @@ -0,0 +1,393 @@ + + + + + (x.name || 'Default') + ' ' + (x.language || '') + ' ' + (x.content || '')} +/> + +
+
+
Enforced Dependencies
+ + Enforced Dependencies define dependency specifications for scripts by language. Unnamed dependencies serve as workspace defaults, while named dependencies can be referenced by scripts using #raw_reqs annotations. + +
+ +
+ +
+
+ +
+
+ + +
+
+ +
+ +
+ +
+ {#if !filteredItems} + + {#each new Array(3) as _} + + {/each} + {:else if filteredItems.length == 0} +
+ +
No enforced dependencies found
+
+ Try changing the filters or creating new enforced dependencies +
+ +
+ {:else} + + + + Name + Language + Description + Type + Edited + Actions + + + + {#each filteredItems as deps} + + +
+ +
+ + + {workspaceDependenciesEditor?.getFullFilename(deps.language, deps.name ?? null)} • {deps.language} + +
+
+
+ +
+ + + {deps.language || 'python3'} + +
+
+ + + {deps.description || '-'} + + + + + {deps.name === null ? 'Default' : 'Named'} + + + + + + + + +
+ + + + + + +
+
+
+ {/each} + +
+ {/if} +
+ + + + {#snippet actions()} +
+ + {viewLanguage} +
+ {/snippet} + +
+ {#if viewContent} + + {:else} +
+ +

No content available for this requirement

+
+ {/if} +
+
+
+ +{#if showDependencyWarning && currentImportedPath} + +{/if} diff --git a/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte b/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte index 132b626a21..9294ddbf63 100644 --- a/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte @@ -59,6 +59,7 @@ import UnsavedConfirmationModal from '$lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte' import TextInput from '$lib/components/text_input/TextInput.svelte' import CollapseLink from '$lib/components/CollapseLink.svelte' + import WorkspaceDependenciesSettings from '$lib/components/workspaceSettings/WorkspaceDependenciesSettings.svelte' let slackInitialPath: string = $state('') let slackScriptPath: string = $state('') @@ -134,7 +135,8 @@ | 'windmill_lfs' | 'git_sync' | 'default_app' - | 'encryption') ?? 'users' + | 'encryption' + | 'dependencies') ?? 'users' ) let usingOpenaiClientCredentialsOauth = $state(false) @@ -692,6 +694,13 @@ aiDescription="General workspace settings" label="General" /> +
{#if !loadedSettings} @@ -1124,6 +1133,8 @@
Loading workspace...
{/if} + {:else if tab == 'dependencies'} + {:else if tab == 'default_app'}
From e509449de6291d2a58c21f3df1ff797993cd0f7f Mon Sep 17 00:00:00 2001 From: Pyra <92104930+pyranota@users.noreply.github.com> Date: Fri, 28 Nov 2025 18:40:45 +0100 Subject: [PATCH 10/39] Update ee-repo-ref.txt (#7250) --- backend/ee-repo-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 8dce612c3c..298b2028c0 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -88d5023df7f41bb86e948b35889f962b741c860e +59a8c2dcb362d19ef64881ffe0ef62b4216cd24f From ce48e76a4b17b6d2fb1c5a62291b45cac603e8a6 Mon Sep 17 00:00:00 2001 From: Tsvetomir Bonev Date: Sat, 29 Nov 2025 00:27:08 +0200 Subject: [PATCH 11/39] allow configuring esbuild banner (#7247) --- cli/src/commands/script/script.ts | 1 + cli/src/core/conf.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/cli/src/commands/script/script.ts b/cli/src/commands/script/script.ts index ff513336a9..7b35d859f4 100644 --- a/cli/src/commands/script/script.ts +++ b/cli/src/commands/script/script.ts @@ -246,6 +246,7 @@ export async function handleFile( platform: "node", packages: "bundle", target: format == "cjs" ? "node20.15.1" : "esnext", + ...(codebase.banner != null && { banner: codebase.banner }), }); const endTime = performance.now(); bundleContent = out.outputFiles[0].text; diff --git a/cli/src/core/conf.ts b/cli/src/core/conf.ts index aa9a7a83be..6138e2b264 100644 --- a/cli/src/core/conf.ts +++ b/cli/src/core/conf.ts @@ -102,6 +102,7 @@ export interface Codebase { inject?: string[]; loader?: any, format?: "cjs" | "esm"; + banner?: string | { js?: string }; } function getGitRepoRoot(): string | null { From 6f5489c7dd00adbf0f410e989f6acd5b6590ae53 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 29 Nov 2025 02:23:47 +0100 Subject: [PATCH 12/39] raw apps v2 (#7251) --- backend/windmill-api/src/apps.rs | 16 +- backend/windmill-api/src/workspaces_export.rs | 8 +- .../windmill-worker/src/worker_lockfiles.rs | 1 + cli/deno.lock | 292 +++- cli/deps.ts | 2 +- cli/src/commands/app/app_metadata.ts | 463 ++++++ cli/src/commands/app/apps.ts | 106 +- cli/src/commands/app/bundle.ts | 193 +++ cli/src/commands/app/dev.ts | 764 ++++++++++ cli/src/commands/app/metadata.ts | 21 + cli/src/commands/app/raw_apps.ts | 292 ++++ cli/src/commands/app/wmillTsDev.ts | 87 ++ cli/src/commands/flow/flow.ts | 2 +- cli/src/commands/flow/flow_metadata.ts | 238 +++ cli/src/commands/sync/sync.ts | 359 +++-- cli/src/commands/workspace/workspace.ts | 97 +- cli/src/core/conf.ts | 9 +- cli/src/types.ts | 30 +- cli/src/utils/metadata.ts | 241 +-- cli/src/utils/utils.ts | 57 +- frontend/package-lock.json | 1334 ++--------------- frontend/package.json | 4 +- frontend/package.sharedUtils.json | 12 - frontend/scripts/untar_ui_builder.js | 2 +- frontend/sharedUtils/sharedUtils.d.ts | 40 + .../sharedUtils/vite.sharedUtils.config.js | 95 ++ frontend/src/global.d.ts | 44 +- .../src/lib/components/DeployWorkspace.svelte | 3 +- frontend/src/lib/components/DiffEditor.svelte | 13 +- frontend/src/lib/components/EditorBar.svelte | 19 +- frontend/src/lib/components/IconedPath.svelte | 11 +- frontend/src/lib/components/RunsPage.svelte | 3 +- .../src/lib/components/SaveToWorkspace.svelte | 16 + .../src/lib/components/ScriptEditor.svelte | 9 +- .../display/dbtable/queries/count.ts | 2 +- .../display/dbtable/queries/delete.ts | 2 +- .../display/dbtable/queries/insert.ts | 2 +- .../display/dbtable/queries/select.ts | 2 +- .../display/dbtable/queries/update.ts | 2 +- .../components/helpers/HiddenComponent.svelte | 3 +- .../helpers/RunnableComponent.svelte | 19 +- .../components/helpers/executeRunnable.ts | 10 +- .../lib/components/apps/editor/appPolicy.ts | 10 +- .../apps/editor/component/components.ts | 2 +- .../components/OutputHeader.svelte | 4 +- .../inlineScriptsPanel/AppRunButton.svelte | 8 +- .../EmptyInlineScript.svelte | 2 +- .../InlineScriptEditor.svelte | 4 +- .../InlineScriptEditorDrawer.svelte | 2 +- .../InlineScriptEditorPanel.svelte | 13 +- .../InlineScriptHiddenRunnable.svelte | 13 +- .../InlineScriptRunnableByPath.svelte | 2 +- .../InlineScriptsPanel.svelte | 2 +- .../InlineScriptsPanelList.svelte | 2 +- .../inlineScriptsPanel/RunButton.svelte | 8 +- .../apps/editor/inlineScriptsPanel/utils.ts | 12 +- .../settingsPanel/ComponentPanel.svelte | 2 +- .../settingsPanel/SelectedRunnable.svelte | 11 +- .../settingsPanel/common/PanelSection.svelte | 42 +- .../mainInput/RunnableSelector.svelte | 13 +- .../script/BackgroundScriptSettings.svelte | 5 +- .../script/ComponentScriptSettings.svelte | 10 +- .../shared/BackgroundScriptTriggerBy.svelte | 7 +- .../shared/ComponentScriptTriggerBy.svelte | 7 +- .../script/shared/ScriptTransformer.svelte | 3 +- .../script/shared/ScriptTriggers.svelte | 4 +- .../apps/editor/settingsPanel/script/utils.ts | 4 +- frontend/src/lib/components/apps/inputType.ts | 19 +- .../src/lib/components/apps/sharedTypes.ts | 14 + frontend/src/lib/components/apps/types.ts | 16 +- frontend/src/lib/components/apps/utils.ts | 16 +- .../components/details/createAppFromScript.ts | 4 +- .../flows/content/FlowModuleComponent.svelte | 8 +- .../flows/content/FlowModuleHeader.svelte | 4 +- .../components/raw_apps/FileTreeNode.svelte | 304 ++++ .../raw_apps/RawAppBackgroundRunner.svelte | 29 +- .../components/raw_apps/RawAppEditor.svelte | 123 +- .../raw_apps/RawAppEditorHeader.svelte | 57 +- .../raw_apps/RawAppInlineScriptEditor.svelte | 230 ++- .../RawAppInlineScriptPanelList.svelte | 19 +- .../RawAppInlineScriptRunnable.svelte | 90 +- .../raw_apps/RawAppInlineScriptsPanel.svelte | 81 +- .../components/raw_apps/RawAppModules.svelte | 75 + .../components/raw_apps/RawAppPreview.svelte | 4 +- .../components/raw_apps/RawAppSidebar.svelte | 352 +++++ .../lib/components/raw_apps/fileTreeUtils.ts | 67 + .../lib/components/raw_apps/rawAppPolicy.ts | 61 + frontend/src/lib/components/raw_apps/utils.ts | 101 +- .../src/lib/components/runs/RunRow.svelte | 9 +- .../runs/RunsBatchActionsDropdown.svelte | 5 +- .../src/lib/components/runs/RunsTable.svelte | 2 +- .../src/lib/components/tutorials/utils.ts | 5 +- frontend/src/lib/rawAppWmillTs.ts | 55 + frontend/src/lib/sharedUtils.ts | 9 + frontend/src/lib/toast.ts | 2 +- frontend/src/lib/utils.ts | 7 +- .../(root)/(logged)/apps_raw/add/+page.svelte | 7 +- .../apps_raw/get/[...path]/+page.svelte | 4 +- frontend/use_latest_ui_builder.sh | 5 +- frontend/vite.config.js | 18 +- frontend/vite.sharedUtils.config.js | 59 - 101 files changed, 4643 insertions(+), 2335 deletions(-) create mode 100644 cli/src/commands/app/app_metadata.ts create mode 100644 cli/src/commands/app/bundle.ts create mode 100644 cli/src/commands/app/dev.ts create mode 100644 cli/src/commands/app/metadata.ts create mode 100644 cli/src/commands/app/raw_apps.ts create mode 100644 cli/src/commands/app/wmillTsDev.ts create mode 100644 cli/src/commands/flow/flow_metadata.ts delete mode 100644 frontend/package.sharedUtils.json create mode 100644 frontend/sharedUtils/sharedUtils.d.ts create mode 100644 frontend/sharedUtils/vite.sharedUtils.config.js create mode 100644 frontend/src/lib/components/SaveToWorkspace.svelte create mode 100644 frontend/src/lib/components/apps/sharedTypes.ts create mode 100644 frontend/src/lib/components/raw_apps/FileTreeNode.svelte create mode 100644 frontend/src/lib/components/raw_apps/RawAppModules.svelte create mode 100644 frontend/src/lib/components/raw_apps/RawAppSidebar.svelte create mode 100644 frontend/src/lib/components/raw_apps/fileTreeUtils.ts create mode 100644 frontend/src/lib/components/raw_apps/rawAppPolicy.ts create mode 100644 frontend/src/lib/rawAppWmillTs.ts create mode 100644 frontend/src/lib/sharedUtils.ts delete mode 100644 frontend/vite.sharedUtils.config.js diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 4849824b0a..4c35907d05 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -174,6 +174,7 @@ pub struct AppWithLastVersion { pub extra_perms: Option, #[serde(skip_serializing_if = "Option::is_none")] pub custom_path: Option, + pub raw_app: bool, } #[derive(Serialize, FromRow)] @@ -510,7 +511,7 @@ async fn get_app( sqlx::query_as::<_, AppWithLastVersionAndStarred>( "SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path, app.extra_perms, app_version.value, - app_version.created_at, app_version.created_by, favorite.path IS NOT NULL as starred + app_version.created_at, app_version.created_by, favorite.path IS NOT NULL as starred, app_version.raw_app FROM app JOIN app_version ON app_version.id = app.versions[array_upper(app.versions, 1)] @@ -530,7 +531,7 @@ async fn get_app( sqlx::query_as::<_, AppWithLastVersionAndStarred>( "SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path, app.extra_perms, app_version.value, - app_version.created_at, app_version.created_by, NULL as starred + app_version.created_at, app_version.created_by, NULL as starred, app_version.raw_app FROM app, app_version WHERE app.path = $1 AND app.workspace_id = $2 AND app_version.id = app.versions[array_upper(app.versions, 1)]", ) @@ -557,7 +558,7 @@ async fn get_app_lite( let app_o = sqlx::query_as::<_, AppWithLastVersion>( "SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path, app.extra_perms, coalesce(app_version_lite.value::json, app_version.value) as value, - app_version.created_at, app_version.created_by, NULL as starred + app_version.created_at, app_version.created_by, NULL as starred, app_version.raw_app FROM app, app_version LEFT JOIN app_version_lite ON app_version_lite.id = app_version.id WHERE app.path = $1 AND app.workspace_id = $2 AND app_version.id = app.versions[array_upper(app.versions, 1)]", @@ -596,7 +597,8 @@ async fn get_app_w_draft( app_version.created_at, app_version.created_by, app.draft_only, - draft.value AS "draft" + draft.value AS "draft", + app_version.raw_app FROM app INNER JOIN app_version ON app_version.id = app.versions[array_upper(app.versions, 1)] @@ -739,7 +741,8 @@ async fn get_app_by_id( let app_o = sqlx::query_as::<_, AppWithLastVersion>( "SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path, app.extra_perms, app_version.value, - app_version.created_at, app_version.created_by from app, app_version + app_version.created_at, app_version.created_by, app_version.raw_app + FROM app, app_version WHERE app_version.id = $1 AND app.id = app_version.app_id AND app.workspace_id = $2", ) .bind(&id) @@ -766,7 +769,8 @@ async fn get_public_app_by_secret( let app_o = sqlx::query_as::<_, AppWithLastVersion>( "SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path, null as extra_perms, coalesce(app_version_lite.value::json, app_version.value::json) as value, - app_version.created_at, app_version.created_by from app, app_version + app_version.created_at, app_version.created_by, app_version.raw_app + FROM app, app_version LEFT JOIN app_version_lite ON app_version_lite.id = app_version.id WHERE app.id = $1 AND app.workspace_id = $2 AND app_version.id = app.versions[array_upper(app.versions, 1)]") .bind(&id) diff --git a/backend/windmill-api/src/workspaces_export.rs b/backend/windmill-api/src/workspaces_export.rs index 3af2f5d44a..1c5d2a7300 100644 --- a/backend/windmill-api/src/workspaces_export.rs +++ b/backend/windmill-api/src/workspaces_export.rs @@ -214,6 +214,7 @@ where "error", "last_server_ping", "server_id", + "raw_app", ], ignore_keys.unwrap_or(vec![]), ] @@ -541,8 +542,8 @@ pub(crate) async fn tarball_workspace( let apps = sqlx::query_as::<_, AppWithLastVersion>( "SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path, app.extra_perms, app_version.value, - app_version.created_at, app_version.created_by from app, app_version - WHERE app.workspace_id = $1 AND app_version.id = app.versions[array_upper(app.versions, 1)] AND app_version.raw_app IS false + app_version.created_at, app_version.created_by, app_version.raw_app from app, app_version + WHERE app.workspace_id = $1 AND app_version.id = app.versions[array_upper(app.versions, 1)] AND (app.draft_only IS NULL OR app.draft_only = false)", ) .bind(&w_id) @@ -551,8 +552,9 @@ pub(crate) async fn tarball_workspace( for app in apps { let app_str = &to_string_without_metadata(&app, false, None).unwrap(); + let kind = if app.raw_app { "raw_app" } else { "app" }; archive - .write_to_archive(&app_str, &format!("{}.app.json", app.path)) + .write_to_archive(&app_str, &format!("{}.{}.json", app.path, kind)) .await?; } } diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index b3b6333f05..b00d15f4eb 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -2232,6 +2232,7 @@ pub async fn handle_app_dependency_job( reduce_app(db, &job_path, &mut value_lite, app_id).await?; if let Value::Object(object) = &mut value_lite { object.insert("version".to_string(), json!(id)); + object.remove("files"); } sqlx::query!( "INSERT INTO app_version_lite (id, value) VALUES ($1, $2) diff --git a/cli/deno.lock b/cli/deno.lock index 29195733e5..bf7e990d8b 100644 --- a/cli/deno.lock +++ b/cli/deno.lock @@ -11,6 +11,7 @@ "jsr:@std/assert@1.0.0-rc.2": "1.0.0-rc.2", "jsr:@std/bytes@0.223": "0.223.0", "jsr:@std/bytes@^1.0.2": "1.0.2", + "jsr:@std/bytes@^1.0.5": "1.0.6", "jsr:@std/cli@1.0.0-rc.2": "1.0.0-rc.2", "jsr:@std/encoding@*": "1.0.4", "jsr:@std/encoding@1.0.0-rc.2": "1.0.0-rc.2", @@ -19,30 +20,35 @@ "jsr:@std/fmt@0.223": "0.223.0", "jsr:@std/fmt@1": "1.0.2", "jsr:@std/fmt@^1.0.2": "1.0.2", + "jsr:@std/fmt@^1.0.5": "1.0.8", "jsr:@std/fmt@~0.225.4": "0.225.6", - "jsr:@std/fs@*": "1.0.3", + "jsr:@std/fs@*": "1.0.20", "jsr:@std/fs@0.223": "0.223.0", "jsr:@std/fs@1": "1.0.3", + "jsr:@std/fs@^1.0.11": "1.0.20", "jsr:@std/fs@^1.0.3": "1.0.3", "jsr:@std/fs@~0.229.3": "0.229.3", - "jsr:@std/io@*": "0.224.7", + "jsr:@std/internal@^1.0.12": "1.0.12", + "jsr:@std/io@*": "0.225.2", "jsr:@std/io@0.223": "0.223.0", - "jsr:@std/io@~0.224.2": "0.224.7", + "jsr:@std/io@~0.224.2": "0.224.9", "jsr:@std/io@~0.224.7": "0.224.7", - "jsr:@std/log@*": "0.224.7", + "jsr:@std/io@~0.225.2": "0.225.2", + "jsr:@std/log@*": "0.224.14", "jsr:@std/log@~0.224.7": "0.224.7", "jsr:@std/net@*": "1.0.2", "jsr:@std/net@^1.0.2": "1.0.2", - "jsr:@std/path@*": "1.0.4", + "jsr:@std/path@*": "1.1.3", "jsr:@std/path@0.223": "0.223.0", "jsr:@std/path@1": "1.0.4", "jsr:@std/path@1.0.0-rc.1": "1.0.0-rc.1", "jsr:@std/path@1.0.0-rc.2": "1.0.0-rc.2", "jsr:@std/path@^1.0.4": "1.0.4", + "jsr:@std/path@^1.1.3": "1.1.3", "jsr:@std/path@~0.225.2": "0.225.2", "jsr:@std/streams@^1.0.4": "1.0.4", "jsr:@std/text@1.0.0-rc.1": "1.0.0-rc.1", - "jsr:@std/yaml@*": "1.0.5", + "jsr:@std/yaml@*": "1.0.10", "jsr:@std/yaml@^1.0.5": "1.0.5", "jsr:@ts-morph/bootstrap@0.24": "0.24.0", "jsr:@ts-morph/common@0.24": "0.24.0", @@ -58,6 +64,10 @@ "jsr:@windmill-labs/cliffy-prompt@^1.0.0-rc.5": "1.0.0-rc.6", "jsr:@windmill-labs/cliffy-table@1.0.0-rc.5": "1.0.0-rc.5", "jsr:@windmill-labs/cliffy-table@^1.0.0-rc.5": "1.0.0-rc.5", + "jsr:@windmill-labs/shared-utils@1.0.3": "1.0.3", + "jsr:@windmill-labs/shared-utils@1.0.5": "1.0.5", + "jsr:@windmill-labs/shared-utils@1.0.6": "1.0.6", + "jsr:@windmill-labs/shared-utils@1.0.7": "1.0.7", "npm:@ayonli/jsext@*": "1.8.0", "npm:@types/node@*": "22.12.0", "npm:@windmill-labs/shared-utils@1.0.1": "1.0.1", @@ -66,13 +76,15 @@ "npm:diff@*": "8.0.2", "npm:es-main@*": "1.3.0", "npm:esbuild@*": "0.25.8", + "npm:esbuild@0.24.2": "0.24.2", "npm:express@*": "5.1.0", "npm:get-port@7.1.0": "7.1.0", "npm:jszip@3.7.1": "3.7.1", "npm:jszip@3.8.0": "3.8.0", "npm:minimatch@*": "10.0.3", "npm:open@*": "10.2.0", - "npm:ws@*": "8.18.3" + "npm:ws@*": "8.18.3", + "npm:ws@8.18.0": "8.18.0" }, "jsr": { "@david/code-block-writer@13.0.2": { @@ -117,6 +129,9 @@ "@std/bytes@1.0.2": { "integrity": "fbdee322bbd8c599a6af186a1603b3355e59a5fb1baa139f8f4c3c9a1b3e3d57" }, + "@std/bytes@1.0.6": { + "integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a" + }, "@std/cli@1.0.0-rc.2": { "integrity": "97dfae82b9f0e189768ebfa7a5da53375955b94bad0a1804f8e3b73563b03787" }, @@ -135,6 +150,9 @@ "@std/fmt@1.0.2": { "integrity": "87e9dfcdd3ca7c066e0c3c657c1f987c82888eb8103a3a3baa62684ffeb0f7a7" }, + "@std/fmt@1.0.8": { + "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7" + }, "@std/fs@0.223.0": { "integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c" }, @@ -150,6 +168,16 @@ "jsr:@std/path@^1.0.4" ] }, + "@std/fs@1.0.20": { + "integrity": "e953206aae48d46ee65e8783ded459f23bec7dd1f3879512911c35e5484ea187", + "dependencies": [ + "jsr:@std/internal", + "jsr:@std/path@^1.1.3" + ] + }, + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" + }, "@std/io@0.223.0": { "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1", "dependencies": [ @@ -163,6 +191,15 @@ "jsr:@std/bytes@^1.0.2" ] }, + "@std/io@0.224.9": { + "integrity": "4414664b6926f665102e73c969cfda06d2c4c59bd5d0c603fd4f1b1c840d6ee3" + }, + "@std/io@0.225.2": { + "integrity": "3c740cd4ee4c082e6cfc86458f47e2ab7cb353dc6234d5e9b1f91a2de5f4d6c7", + "dependencies": [ + "jsr:@std/bytes@^1.0.5" + ] + }, "@std/log@0.224.7": { "integrity": "021941e5cd16de60cb11599c9b36f892aea95987fe66c753922808da27909e18", "dependencies": [ @@ -171,6 +208,14 @@ "jsr:@std/io@~0.224.7" ] }, + "@std/log@0.224.14": { + "integrity": "257f7adceee3b53bb2bc86c7242e7d1bc59729e57d4981c4a7e5b876c808f05e", + "dependencies": [ + "jsr:@std/fmt@^1.0.5", + "jsr:@std/fs@^1.0.11", + "jsr:@std/io@~0.225.2" + ] + }, "@std/net@1.0.2": { "integrity": "520c18ddb7f67d3830a1adfef03a155d496fe9683a9cb63bb823b5afb86484dc" }, @@ -195,6 +240,12 @@ "@std/path@1.0.4": { "integrity": "48dd5d8389bcfcd619338a01bdf862cb7799933390146a54ae59356a0acc7105" }, + "@std/path@1.1.3": { + "integrity": "b015962d82a5e6daea980c32b82d2c40142149639968549c649031a230b1afb3", + "dependencies": [ + "jsr:@std/internal" + ] + }, "@std/streams@1.0.4": { "integrity": "a1a5b01c74ca1d2dcaacfe1d4bbb91392e765946d82a3471bd95539adc6da83a", "dependencies": [ @@ -207,6 +258,9 @@ "@std/yaml@1.0.5": { "integrity": "71ba3d334305ee2149391931508b2c293a8490f94a337eef3a09cade1a2a2742" }, + "@std/yaml@1.0.10": { + "integrity": "245706ea3511cc50c8c6d00339c23ea2ffa27bd2c7ea5445338f8feff31fa58e" + }, "@ts-morph/bootstrap@0.24.0": { "integrity": "a826a2ef7fa8a7c3f1042df2c034d20744d94da2ee32bf29275bcd4dffd3c060", "dependencies": [ @@ -283,6 +337,18 @@ "jsr:@std/cli", "jsr:@std/fmt@~0.225.4" ] + }, + "@windmill-labs/shared-utils@1.0.3": { + "integrity": "35bafaf74092ebb63e96c75897337320378c04f93cf9b352fcc2137ffdb3e862" + }, + "@windmill-labs/shared-utils@1.0.5": { + "integrity": "3709140dc40f89443dff5953ec2e7c35d964b71c5e1245fba4072cf513e0db91" + }, + "@windmill-labs/shared-utils@1.0.6": { + "integrity": "34965cbc8e4fda69835fed37435468e8ca1123dabe4ea395d700ecdb2fa49738" + }, + "@windmill-labs/shared-utils@1.0.7": { + "integrity": "528638c7c508910e7f51b1ad9a5f1ff394e3fefb28fd3f96ab958c258a26e978" } }, "npm": { @@ -291,110 +357,215 @@ "dependencies": [ "iconv-lite", "sudo-prompt", - "ws", + "ws@8.18.3", "zod" ] }, + "@esbuild/aix-ppc64@0.24.2": { + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "os": ["aix"], + "cpu": ["ppc64"] + }, "@esbuild/aix-ppc64@0.25.8": { "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", "os": ["aix"], "cpu": ["ppc64"] }, + "@esbuild/android-arm64@0.24.2": { + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "os": ["android"], + "cpu": ["arm64"] + }, "@esbuild/android-arm64@0.25.8": { "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", "os": ["android"], "cpu": ["arm64"] }, + "@esbuild/android-arm@0.24.2": { + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "os": ["android"], + "cpu": ["arm"] + }, "@esbuild/android-arm@0.25.8": { "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", "os": ["android"], "cpu": ["arm"] }, + "@esbuild/android-x64@0.24.2": { + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "os": ["android"], + "cpu": ["x64"] + }, "@esbuild/android-x64@0.25.8": { "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", "os": ["android"], "cpu": ["x64"] }, + "@esbuild/darwin-arm64@0.24.2": { + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "os": ["darwin"], + "cpu": ["arm64"] + }, "@esbuild/darwin-arm64@0.25.8": { "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", "os": ["darwin"], "cpu": ["arm64"] }, + "@esbuild/darwin-x64@0.24.2": { + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "os": ["darwin"], + "cpu": ["x64"] + }, "@esbuild/darwin-x64@0.25.8": { "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", "os": ["darwin"], "cpu": ["x64"] }, + "@esbuild/freebsd-arm64@0.24.2": { + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "os": ["freebsd"], + "cpu": ["arm64"] + }, "@esbuild/freebsd-arm64@0.25.8": { "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", "os": ["freebsd"], "cpu": ["arm64"] }, + "@esbuild/freebsd-x64@0.24.2": { + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "os": ["freebsd"], + "cpu": ["x64"] + }, "@esbuild/freebsd-x64@0.25.8": { "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", "os": ["freebsd"], "cpu": ["x64"] }, + "@esbuild/linux-arm64@0.24.2": { + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "os": ["linux"], + "cpu": ["arm64"] + }, "@esbuild/linux-arm64@0.25.8": { "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", "os": ["linux"], "cpu": ["arm64"] }, + "@esbuild/linux-arm@0.24.2": { + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "os": ["linux"], + "cpu": ["arm"] + }, "@esbuild/linux-arm@0.25.8": { "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", "os": ["linux"], "cpu": ["arm"] }, + "@esbuild/linux-ia32@0.24.2": { + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "os": ["linux"], + "cpu": ["ia32"] + }, "@esbuild/linux-ia32@0.25.8": { "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", "os": ["linux"], "cpu": ["ia32"] }, + "@esbuild/linux-loong64@0.24.2": { + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "os": ["linux"], + "cpu": ["loong64"] + }, "@esbuild/linux-loong64@0.25.8": { "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", "os": ["linux"], "cpu": ["loong64"] }, + "@esbuild/linux-mips64el@0.24.2": { + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "os": ["linux"], + "cpu": ["mips64el"] + }, "@esbuild/linux-mips64el@0.25.8": { "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", "os": ["linux"], "cpu": ["mips64el"] }, + "@esbuild/linux-ppc64@0.24.2": { + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "os": ["linux"], + "cpu": ["ppc64"] + }, "@esbuild/linux-ppc64@0.25.8": { "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", "os": ["linux"], "cpu": ["ppc64"] }, + "@esbuild/linux-riscv64@0.24.2": { + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "os": ["linux"], + "cpu": ["riscv64"] + }, "@esbuild/linux-riscv64@0.25.8": { "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", "os": ["linux"], "cpu": ["riscv64"] }, + "@esbuild/linux-s390x@0.24.2": { + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "os": ["linux"], + "cpu": ["s390x"] + }, "@esbuild/linux-s390x@0.25.8": { "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", "os": ["linux"], "cpu": ["s390x"] }, + "@esbuild/linux-x64@0.24.2": { + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "os": ["linux"], + "cpu": ["x64"] + }, "@esbuild/linux-x64@0.25.8": { "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", "os": ["linux"], "cpu": ["x64"] }, + "@esbuild/netbsd-arm64@0.24.2": { + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "os": ["netbsd"], + "cpu": ["arm64"] + }, "@esbuild/netbsd-arm64@0.25.8": { "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", "os": ["netbsd"], "cpu": ["arm64"] }, + "@esbuild/netbsd-x64@0.24.2": { + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "os": ["netbsd"], + "cpu": ["x64"] + }, "@esbuild/netbsd-x64@0.25.8": { "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", "os": ["netbsd"], "cpu": ["x64"] }, + "@esbuild/openbsd-arm64@0.24.2": { + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "os": ["openbsd"], + "cpu": ["arm64"] + }, "@esbuild/openbsd-arm64@0.25.8": { "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", "os": ["openbsd"], "cpu": ["arm64"] }, + "@esbuild/openbsd-x64@0.24.2": { + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "os": ["openbsd"], + "cpu": ["x64"] + }, "@esbuild/openbsd-x64@0.25.8": { "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", "os": ["openbsd"], @@ -405,21 +576,41 @@ "os": ["openharmony"], "cpu": ["arm64"] }, + "@esbuild/sunos-x64@0.24.2": { + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "os": ["sunos"], + "cpu": ["x64"] + }, "@esbuild/sunos-x64@0.25.8": { "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", "os": ["sunos"], "cpu": ["x64"] }, + "@esbuild/win32-arm64@0.24.2": { + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "os": ["win32"], + "cpu": ["arm64"] + }, "@esbuild/win32-arm64@0.25.8": { "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", "os": ["win32"], "cpu": ["arm64"] }, + "@esbuild/win32-ia32@0.24.2": { + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "os": ["win32"], + "cpu": ["ia32"] + }, "@esbuild/win32-ia32@0.25.8": { "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", "os": ["win32"], "cpu": ["ia32"] }, + "@esbuild/win32-x64@0.24.2": { + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "os": ["win32"], + "cpu": ["x64"] + }, "@esbuild/win32-x64@0.25.8": { "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", "os": ["win32"], @@ -568,35 +759,67 @@ "es-errors" ] }, + "esbuild@0.24.2": { + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "optionalDependencies": [ + "@esbuild/aix-ppc64@0.24.2", + "@esbuild/android-arm@0.24.2", + "@esbuild/android-arm64@0.24.2", + "@esbuild/android-x64@0.24.2", + "@esbuild/darwin-arm64@0.24.2", + "@esbuild/darwin-x64@0.24.2", + "@esbuild/freebsd-arm64@0.24.2", + "@esbuild/freebsd-x64@0.24.2", + "@esbuild/linux-arm@0.24.2", + "@esbuild/linux-arm64@0.24.2", + "@esbuild/linux-ia32@0.24.2", + "@esbuild/linux-loong64@0.24.2", + "@esbuild/linux-mips64el@0.24.2", + "@esbuild/linux-ppc64@0.24.2", + "@esbuild/linux-riscv64@0.24.2", + "@esbuild/linux-s390x@0.24.2", + "@esbuild/linux-x64@0.24.2", + "@esbuild/netbsd-arm64@0.24.2", + "@esbuild/netbsd-x64@0.24.2", + "@esbuild/openbsd-arm64@0.24.2", + "@esbuild/openbsd-x64@0.24.2", + "@esbuild/sunos-x64@0.24.2", + "@esbuild/win32-arm64@0.24.2", + "@esbuild/win32-ia32@0.24.2", + "@esbuild/win32-x64@0.24.2" + ], + "scripts": true, + "bin": true + }, "esbuild@0.25.8": { "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", "optionalDependencies": [ - "@esbuild/aix-ppc64", - "@esbuild/android-arm", - "@esbuild/android-arm64", - "@esbuild/android-x64", - "@esbuild/darwin-arm64", - "@esbuild/darwin-x64", - "@esbuild/freebsd-arm64", - "@esbuild/freebsd-x64", - "@esbuild/linux-arm", - "@esbuild/linux-arm64", - "@esbuild/linux-ia32", - "@esbuild/linux-loong64", - "@esbuild/linux-mips64el", - "@esbuild/linux-ppc64", - "@esbuild/linux-riscv64", - "@esbuild/linux-s390x", - "@esbuild/linux-x64", - "@esbuild/netbsd-arm64", - "@esbuild/netbsd-x64", - "@esbuild/openbsd-arm64", - "@esbuild/openbsd-x64", + "@esbuild/aix-ppc64@0.25.8", + "@esbuild/android-arm@0.25.8", + "@esbuild/android-arm64@0.25.8", + "@esbuild/android-x64@0.25.8", + "@esbuild/darwin-arm64@0.25.8", + "@esbuild/darwin-x64@0.25.8", + "@esbuild/freebsd-arm64@0.25.8", + "@esbuild/freebsd-x64@0.25.8", + "@esbuild/linux-arm@0.25.8", + "@esbuild/linux-arm64@0.25.8", + "@esbuild/linux-ia32@0.25.8", + "@esbuild/linux-loong64@0.25.8", + "@esbuild/linux-mips64el@0.25.8", + "@esbuild/linux-ppc64@0.25.8", + "@esbuild/linux-riscv64@0.25.8", + "@esbuild/linux-s390x@0.25.8", + "@esbuild/linux-x64@0.25.8", + "@esbuild/netbsd-arm64@0.25.8", + "@esbuild/netbsd-x64@0.25.8", + "@esbuild/openbsd-arm64@0.25.8", + "@esbuild/openbsd-x64@0.25.8", "@esbuild/openharmony-arm64", - "@esbuild/sunos-x64", - "@esbuild/win32-arm64", - "@esbuild/win32-ia32", - "@esbuild/win32-x64" + "@esbuild/sunos-x64@0.25.8", + "@esbuild/win32-arm64@0.25.8", + "@esbuild/win32-ia32@0.25.8", + "@esbuild/win32-x64@0.25.8" ], "scripts": true, "bin": true @@ -1005,6 +1228,9 @@ "wrappy@1.0.2": { "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "ws@8.18.0": { + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==" + }, "ws@8.18.3": { "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==" }, diff --git a/cli/deps.ts b/cli/deps.ts index e5fb176c84..d86629b653 100644 --- a/cli/deps.ts +++ b/cli/deps.ts @@ -57,7 +57,7 @@ export { WebSocketServer, WebSocket } from "npm:ws"; export * as getPort from "npm:get-port@7.1.0"; export * as open from "npm:open"; export * as esMain from "npm:es-main"; -export * as windmillUtils from "npm:@windmill-labs/shared-utils@1.0.2"; +export * as windmillUtils from "jsr:@windmill-labs/shared-utils@1.0.9"; import { OpenAPI } from "./gen/index.ts"; diff --git a/cli/src/commands/app/app_metadata.ts b/cli/src/commands/app/app_metadata.ts new file mode 100644 index 0000000000..b4d37c0cba --- /dev/null +++ b/cli/src/commands/app/app_metadata.ts @@ -0,0 +1,463 @@ +// deno-lint-ignore-file no-explicit-any +import path from "node:path"; +import { + SEP, + colors, + log, + yamlParseFile, + yamlStringify, +} from "../../../deps.ts"; +import { GlobalOptions } from "../../types.ts"; +import { + checkifMetadataUptodate, + blueColor, + clearGlobalLock, + updateMetadataGlobalLock, + inferSchema, + getRawWorkspaceDependencies, +} from "../../utils/metadata.ts"; +import { ScriptLanguage, workspaceDependenciesLanguages } from "../../utils/script_common.ts"; +import { + inferContentTypeFromFilePath, +} from "../../utils/script_common.ts"; +import { generateHash, getHeaders, writeIfChanged } from "../../utils/utils.ts"; +import { exts } from "../script/script.ts"; +import { FSFSElement, yamlOptions } from "../sync/sync.ts"; +import { Workspace } from "../workspace/workspace.ts"; +import { AppFile } from "./raw_apps.ts"; +import { replaceInlineScripts } from "./apps.ts"; +import { + newPathAssigner, + SupportedLanguage, +} from "../../../windmill-utils-internal/src/path-utils/path-assigner.ts"; + +const TOP_HASH = "__app_hash"; + +/** + * Generates a hash for all inline scripts in an app directory + */ +async function generateAppHash( + rawReqs: Record | undefined, + folder: string, + defaultTs: "bun" | "deno" | undefined +): Promise> { + const runnablesFolder = path.join(folder, "runnables"); + const hashes: Record = {}; + + try { + const elems = await FSFSElement(runnablesFolder, [], true); + for await (const f of elems.getChildren()) { + if (exts.some((e) => f.path.endsWith(e))) { + let reqs: string | undefined; + if (rawReqs) { + // Get language name from path + const lang = inferContentTypeFromFilePath(f.path, defaultTs); + // Get lock for that language + [, reqs] = + Object.entries(rawReqs).find(([lang2, _]) => lang == lang2) ?? []; + } + // Embed lock into hash + const relativePath = f.path.replace(runnablesFolder + SEP, ""); + hashes[relativePath] = await generateHash( + (await f.getContentText()) + (reqs ?? "") + ); + } + } + } catch (error: any) { + // If runnables folder doesn't exist, that's okay + if (error.name !== "NotFound") { + throw error; + } + } + + return { ...hashes, [TOP_HASH]: await generateHash(JSON.stringify(hashes)) }; +} + +/** + * Updates locks for inline scripts in an app + */ +export async function generateAppLocksInternal( + appFolder: string, + dryRun: boolean, + workspace: Workspace, + opts: GlobalOptions & { + defaultTs?: "bun" | "deno"; + }, + justUpdateMetadataLock?: boolean, + noStaleMessage?: boolean, +): Promise { + if (appFolder.endsWith(SEP)) { + appFolder = appFolder.substring(0, appFolder.length - 1); + } + + const remote_path = appFolder.replaceAll(SEP, "/"); + + if (!justUpdateMetadataLock && !noStaleMessage) { + log.info(`Generating locks for app ${appFolder} at ${remote_path}`); + } + + const rawWorkspaceDependencies: Record = await getRawWorkspaceDependencies(); + + + let hashes = await generateAppHash(rawWorkspaceDependencies, appFolder, opts.defaultTs); + + const conf = await import("../../utils/metadata.ts").then((m) => + m.readLockfile() + ); + if ( + await checkifMetadataUptodate(appFolder, hashes[TOP_HASH], conf, TOP_HASH) + ) { + if (!noStaleMessage) { + log.info( + colors.green(`App ${remote_path} metadata is up-to-date, skipping`) + ); + } + return; + } else if (dryRun) { + return remote_path; + } + + if (Object.keys(rawWorkspaceDependencies).length > 0) { + log.info( + (await blueColor())( + `Found workspace dependencies (${workspaceDependenciesLanguages + .map((l) => l.filename) + .join("/")}) for ${appFolder}, using them` + ) + ); + } + + // Read the app file + const appFilePath = path.join(appFolder, "raw_app.yaml"); + const appFile = (await yamlParseFile(appFilePath)) as AppFile; + + if (!justUpdateMetadataLock) { + const changedScripts = []; + // Find hashes that do not correspond to previous hashes + for (const [scriptPath, hash] of Object.entries(hashes)) { + if (scriptPath == TOP_HASH) { + continue; + } + if (!(await checkifMetadataUptodate(appFolder, hash, conf, scriptPath))) { + changedScripts.push(scriptPath); + } + } + + if (changedScripts.length > 0) { + log.info( + `Recomputing locks of ${changedScripts.join(", ")} in ${appFolder}` + ); + + const runnablesPath = path.join(appFolder, "runnables") + SEP; + + // Replace inline scripts for changed runnables + await replaceInlineScripts(appFile.runnables, runnablesPath); + + // Update the app runnables with new locks + appFile.runnables = await updateAppRunnables( + workspace, + appFile.runnables, + remote_path, + appFolder, + rawWorkspaceDependencies, + opts.defaultTs + ); + + // Write the updated app file + writeIfChanged( + appFilePath, + yamlStringify(appFile as Record, yamlOptions) + ); + } else { + log.info(colors.gray(`No scripts changed in ${appFolder}`)); + } + } + + // Regenerate hashes after updates + hashes = await generateAppHash(rawWorkspaceDependencies, appFolder, opts.defaultTs); + await clearGlobalLock(appFolder); + for (const [scriptPath, hash] of Object.entries(hashes)) { + await updateMetadataGlobalLock(appFolder, hash, scriptPath); + } + log.info(colors.green(`App ${remote_path} lockfiles updated`)); +} + +/** + * Updates locks for all runnables in an app, generating locks inline script by inline script + * Also writes content and locks back to the runnables folder + */ +async function updateAppRunnables( + workspace: Workspace, + runnables: Record, + remotePath: string, + appFolder: string, + rawDeps?: Record, + defaultTs: "bun" | "deno" = "bun" +): Promise> { + const updatedRunnables = { ...runnables }; + const runnablesFolder = path.join(appFolder, "runnables"); + + // Ensure runnables folder exists + try { + await Deno.mkdir(runnablesFolder, { recursive: true }); + } catch { + // Folder may already exist + } + + const pathAssigner = newPathAssigner(defaultTs); + for (const [runnableId, runnable] of Object.entries(runnables)) { + // Only process inline scripts (runnableByName with inlineScript) + if (runnable?.type !== "runnableByName" || !runnable?.inlineScript) { + continue; + } + + const inlineScript = runnable.inlineScript; + const language = inlineScript.language as SupportedLanguage; + const content = inlineScript.content; + + if (!content || !language) { + continue; + } + + // Skip if content is still an !inline reference (should have been replaced by replaceInlineScripts) + if (typeof content === "string" && content.startsWith("!inline ")) { + log.warn( + colors.yellow( + `Runnable ${runnableId} content is still an !inline reference, skipping` + ) + ); + continue; + } + + // Skip frontend scripts - they don't need locks + if (language === "frontend") { + continue; + } + + // Find raw deps for this language if available + const langRawDeps = rawDeps?.[language]; + + log.info( + colors.gray( + `Generating lock for runnable ${runnableId} (${language})${ + langRawDeps ? " with raw deps" : "" + }` + ) + ); + + try { + const lock = await generateInlineScriptLock( + workspace, + content, + language, + `${remotePath}/${runnableId}`, + langRawDeps + ); + + // Determine file extension for this language + const [basePathO, ext] = pathAssigner.assignPath(runnable.name, language); + const basePath = basePathO.replaceAll(SEP, "/"); + const contentPath = path.join(runnablesFolder, `${basePath}${ext}`); + const lockPath = path.join(runnablesFolder, `${basePath}lock`); + + // Write content to file + writeIfChanged(contentPath, content); + + // Write lock to file if it exists + if (lock && lock !== "") { + writeIfChanged(lockPath, lock); + } + + // Update the runnable with !inline references (preserve existing schema) + const inlineContentRef = `!inline ${basePath}${ext}`; + const inlineLockRef = + lock && lock !== "" ? `!inline ${basePath}lock` : ""; + + updatedRunnables[runnableId] = { + ...runnable, + inlineScript: { + ...inlineScript, + content: inlineContentRef, + lock: inlineLockRef, + }, + }; + + log.info( + colors.gray( + ` Written ${basePath}${ext}${lock ? ` and ${basePath}lock` : ""}` + ) + ); + } catch (error: any) { + log.error( + colors.red( + `Failed to generate lock for runnable ${runnableId}: ${error.message}` + ) + ); + // Continue with other runnables even if one fails + } + } + + return updatedRunnables; +} + +/** + * Generates a lock for a single inline script using the dependencies endpoint + */ +async function generateInlineScriptLock( + workspace: Workspace, + content: string, + language: string, + scriptPath: string, + rawDeps?: string +): Promise { + const extraHeaders = getHeaders(); + + const rawResponse = await fetch( + `${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/dependencies`, + { + method: "POST", + headers: { + Cookie: `token=${workspace.token}`, + "Content-Type": "application/json", + ...extraHeaders, + }, + body: JSON.stringify({ + raw_scripts: [ + { + raw_code: content, + language: language, + script_path: scriptPath, + }, + ], + raw_deps: rawDeps, + entrypoint: scriptPath, + }), + } + ); + + if (!rawResponse.ok) { + const text = await rawResponse.text(); + throw new Error( + `Dependency generation failed: ${rawResponse.status} ${rawResponse.statusText}\n${text}` + ); + } + + let responseText = "reading response failed"; + try { + responseText = await rawResponse.text(); + const response = JSON.parse(responseText); + const lock = response.lock; + + if (lock === undefined) { + if (response?.["error"]?.["message"]) { + throw new Error( + `Failed to generate lockfile: ${response?.["error"]?.["message"]}` + ); + } + throw new Error( + `Failed to generate lockfile: ${JSON.stringify(response, null, 2)}` + ); + } + + return lock ?? ""; + } catch (e: any) { + throw new Error( + `Failed to parse dependency response: ${rawResponse.statusText}, ${responseText}, ${e.message}` + ); + } +} + +/** + * Result of schema inference for a runnable + */ +export interface InferredSchemaResult { + runnableId: string; + schema: any; +} + +/** + * Infers schema for a single runnable from its file content. + * Used by dev server to update schema in memory (for wmill.d.ts generation). + * Does NOT write to raw_app.yaml - schema is kept in memory only. + * + * @param appFolder - The folder containing the raw app + * @param runnableFilePath - The path to the changed runnable file (relative to runnables folder) + * @returns The runnable ID and inferred schema, or undefined if inference failed/not applicable + */ +export async function inferRunnableSchemaFromFile( + appFolder: string, + runnableFilePath: string, +): Promise { + // Extract runnable ID from file path (e.g., "myRunnable.inline_script.ts" -> "myRunnable") + const fileName = path.basename(runnableFilePath); + + // Skip lock files + if (fileName.endsWith(".lock")) { + return undefined; + } + + // Match pattern: {runnableId}.inline_script.{ext} + const match = fileName.match(/^(.+)\.inline_script\.[^.]+$/); + if (!match) { + return undefined; + } + + const runnableId = match[1]; + + // Read the app file to get the language + const appFilePath = path.join(appFolder, "raw_app.yaml"); + const appFile = (await yamlParseFile(appFilePath)) as AppFile; + + if (!appFile.runnables?.[runnableId]) { + log.warn(colors.yellow(`Runnable ${runnableId} not found in raw_app.yaml`)); + return undefined; + } + + const runnable = appFile.runnables[runnableId]; + + // Only process inline scripts + if (!runnable?.inlineScript) { + return undefined; + } + + const inlineScript = runnable.inlineScript; + const language = inlineScript.language as SupportedLanguage; + + + + // Read the actual content from the file + const fullFilePath = path.join(appFolder, "runnables", runnableFilePath); + let content: string; + try { + content = await Deno.readTextFile(fullFilePath); + } catch { + log.warn(colors.yellow(`Could not read file: ${fullFilePath}`)); + return undefined; + } + + // Infer schema from script content + const currentSchema = inlineScript.schema; + const remotePath = appFolder.replaceAll(SEP, "/"); + + try { + const schemaResult = await inferSchema( + language as ScriptLanguage, + content, + currentSchema, + `${remotePath}/${runnableId}` + ); + + log.info(colors.green(` Inferred schema for ${runnableId}`)); + return { + runnableId: runnableId, + schema: schemaResult.schema, + }; + } catch (schemaError: any) { + log.warn( + colors.yellow( + `Failed to infer schema for ${runnableId}: ${schemaError.message}` + ) + ); + return undefined; + } +} diff --git a/cli/src/commands/app/apps.ts b/cli/src/commands/app/apps.ts index 9ed1c0d158..08fca6c352 100644 --- a/cli/src/commands/app/apps.ts +++ b/cli/src/commands/app/apps.ts @@ -15,6 +15,7 @@ import { ListableApp, Policy } from "../../../gen/types.gen.ts"; import { GlobalOptions, isSuperset } from "../../types.ts"; import { readInlinePathSync } from "../../utils/utils.ts"; +import devCommand from "./dev.ts"; export interface AppFile { value: any; @@ -25,6 +26,55 @@ export interface AppFile { const alreadySynced: string[] = []; +function respecializeFields(fields: Record) { + Object.entries(fields).forEach(([k, v]) => { + if (typeof v == "object") { + if (v.value !== undefined) { + fields[k] = { value: v.value, type: "static" } + } else if (v.expr !== undefined) { + fields[k] = { expr: v.expr, allowUserResources: v.allowUserResources, type: "javascript" } + } + } + }) +} + +export function repopulateFields(runnables: Record) { + Object.values(runnables).forEach((v) => { + if (typeof v == "object") { + if (v.fields !== undefined) { + respecializeFields(v.fields) + } + } + }) +} +export function replaceInlineScripts(rec: any, localPath: string) { + if (!rec) { + return; + } + if (typeof rec == "object") { + return Object.entries(rec).flatMap(([k, v]) => { + if (k == 'runType') { + rec["type"] = 'path' + + } else if (k == "inlineScript" && typeof v == "object") { + rec["type"] = 'inline' + const o: Record = v as any; + + if (o["content"] && o["content"].startsWith("!inline")) { + const basePath = localPath + o["content"].split(" ")[1]; + o["content"] = readInlinePathSync(basePath); + } + if (o["lock"] && o["lock"].startsWith("!inline")) { + const basePath = localPath + o["lock"].split(" ")[1]; + o["lock"] = readInlinePathSync(basePath); + } + } else { + replaceInlineScripts(v, localPath); + } + }); + } + return []; +} export function isExecutionModeAnonymous(app: any) { return app?.["policy"]?.["execution_mode"] == "anonymous"; } @@ -63,34 +113,8 @@ export async function pushApp( const path = localPath + "app.yaml"; const localApp = (await yamlParseFile(path)) as AppFile; - function replaceInlineScripts(rec: any) { - if (!rec) { - return; - } - if (typeof rec == "object") { - return Object.entries(rec).flatMap(([k, v]) => { - if (k == "inlineScript" && typeof v == "object") { - const o: Record = v as any; - if (o["content"] && o["content"].startsWith("!inline")) { - const basePath = localPath + o["content"].split(" ")[1]; - o["content"] = readInlinePathSync(basePath); - } - if (o["lock"] && o["lock"].startsWith("!inline")) { - const basePath = localPath + o["lock"].split(" ")[1]; - o["lock"] = readInlinePathSync(basePath); - } - } else { - replaceInlineScripts(v); - } - }); - } - return []; - } - - replaceInlineScripts(localApp.value); - // console.log(localApp, localApp?.["policy"]); - await generatingPolicy(localApp, remotePath, localApp?.["public"] ?? (localApp.policy ? isExecutionModeAnonymous(localApp) : false)); - // console.log(localApp, localApp?.["policy"]); + replaceInlineScripts(localApp.value, localPath); + await generatingPolicy(localApp, remotePath, localApp?.["public"] ?? false); if (app) { if (isSuperset(localApp, app)) { log.info(colors.green(`App ${remotePath} is up to date`)); @@ -119,7 +143,11 @@ export async function pushApp( } } -async function generatingPolicy(app: any, path: string, publicApp: boolean) { +export async function generatingPolicy( + app: any, + path: string, + publicApp: boolean +) { log.info(colors.gray(`Generating fresh policy for app ${path}...`)); try { app.policy = await windmillUtils.updatePolicy(app.value, undefined); @@ -167,7 +195,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) { await requireLogin(opts); await pushApp(workspace.workspaceId, remotePath, filePath); - log.info(colors.bold.underline.green("Flow pushed")); + log.info(colors.bold.underline.green("App pushed")); } const command = new Command() @@ -175,6 +203,22 @@ const command = new Command() .action(list as any) .command("push", "push a local app ") .arguments(" ") - .action(push as any); + .action(push as any) + .command("dev", devCommand) + .command( + "generate-locks", + "re-generate the lockfiles for app runnables inline scripts that have changed" + ) + .arguments("[app_folder:string]") + .option("--yes", "Skip confirmation prompt") + .option("--dry-run", "Perform a dry run without making changes") + .option( + "--default-ts ", + "Default TypeScript runtime (bun or deno)" + ) + .action(async (opts: any, appFolder: string | undefined) => { + const { generateLocksCommand } = await import("./raw_apps.ts"); + await generateLocksCommand(opts, appFolder); + }); export default command; diff --git a/cli/src/commands/app/bundle.ts b/cli/src/commands/app/bundle.ts new file mode 100644 index 0000000000..6a64ed9252 --- /dev/null +++ b/cli/src/commands/app/bundle.ts @@ -0,0 +1,193 @@ +// deno-lint-ignore-file no-explicit-any +import * as fs from "node:fs"; +import * as path from "node:path"; +import process from "node:process"; +import { log, colors } from "../../../deps.ts"; +import { windmillUtils } from "../../../deps.ts"; +export interface BundleOptions { + entryPoint?: string; + outDir?: string; + sourcemap?: boolean; + minify?: boolean; + production?: boolean; +} + +export interface BundleResult { + js: string; + css: string; +} + +export const DEFAULT_BUILD_OPTIONS = { + bundle: true, + format: "iife" as const, + platform: "browser" as const, + target: "es2020", + jsx: "automatic" as const, + loader: { + ".css": "css" as const, + }, + logLevel: "info" as const, + write: true, +}; + +/** + * Ensures node_modules exists in the specified directory + * Runs npm install if node_modules is missing + * @param appDir Directory to check for node_modules (defaults to entry point directory) + */ +export async function ensureNodeModules(appDir?: string): Promise { + const targetDir = appDir ?? process.cwd(); + const nodeModulesPath = path.join(targetDir, "node_modules"); + + if (!fs.existsSync(nodeModulesPath)) { + log.info(colors.yellow("📦 node_modules not found, running npm install...")); + const npmInstall = new Deno.Command("npm", { + args: ["install"], + cwd: targetDir, + stdout: "inherit", + stderr: "inherit", + }); + const { code } = await npmInstall.output(); + if (code !== 0) { + throw new Error(`npm install failed with exit code ${code}`); + } + log.info(colors.green("✅ npm install completed")); + } +} + +/** + * Creates an esbuild bundle for the app + * @param options Bundle configuration options + * @returns Bundle result containing JS and CSS blobs + */ +export async function createBundle( + options: BundleOptions = {} +): Promise { + // Dynamically import esbuild + const esbuild = await import("npm:esbuild@0.24.2"); + + const entryPoint = options.entryPoint ?? "index.tsx"; + const outDir = options.outDir ?? "dist"; + const sourcemap = options.sourcemap ?? false; + const minify = options.minify ?? true; + const production = options.production ?? true; + + + // Verify entry point exists + if (!fs.existsSync(entryPoint)) { + throw new Error( + `Entry point "${entryPoint}" not found. Please ensure the file exists.` + ); + } + + // Ensure node_modules exists in the app directory + const appDir = path.dirname(entryPoint); + await ensureNodeModules(appDir); + + // Ensure output directory exists + const distDir = path.join(process.cwd(), outDir); + if (!fs.existsSync(distDir)) { + fs.mkdirSync(distDir, { recursive: true }); + } + + const outfile = path.join(outDir, "bundle.js"); + + // log.info("FOO") + // log.info("wmillTs" + JSON.stringify(wmillTs)); + // Plugin to provide /wmill.ts as a virtual module + const wmillTs = (windmillUtils.wmillTsRaw as any).default ?? windmillUtils.wmillTsRaw; + + const wmillPlugin = { + name: "wmill-virtual", + setup(build: any) { + + + // Intercept imports of /wmill.ts, /wmill, ./wmill.ts, or ./wmill + build.onResolve({ filter: /^(\.\/|\/)?wmill(\.ts)?$/ }, (args: any) => { + log.info(colors.yellow(`[wmill-virtual] Intercepted: ${args.path}`)); + return { + path: args.path, + namespace: "wmill-virtual", + }; + }); + + // Provide the virtual module content + build.onLoad({ filter: /.*/, namespace: "wmill-virtual" }, (args: any) => { + log.info(colors.yellow(`[wmill-virtual] Loading virtual module: ${args.path}`)); + return { + contents: wmillTs, + loader: "ts", + }; + }); + }, + }; + + const buildOptions = { + ...DEFAULT_BUILD_OPTIONS, + entryPoints: [entryPoint], + outfile, + sourcemap, + minify, + define: { + "process.env.NODE_ENV": production ? '"production"' : '"development"', + }, + plugins: [wmillPlugin], + }; + + log.info(colors.blue("📦 Building bundle...")); + + try { + const result = await esbuild.build(buildOptions); + + if (result.errors.length > 0) { + log.error(colors.red("❌ Build failed:")); + result.errors.forEach((error: any) => { + log.error(colors.red(error.text)); + }); + throw new Error("Build failed with errors"); + } + + log.info(colors.green("✅ Bundle created successfully")); + + // Read the generated files + const jsPath = path.join(process.cwd(), outfile); + const cssPath = path.join(process.cwd(), outDir, "bundle.css"); + + if (!fs.existsSync(jsPath)) { + throw new Error(`Expected JS bundle at ${jsPath} but file not found`); + } + + const jsContent = fs.readFileSync(jsPath, "utf-8"); + const cssContent = fs.existsSync(cssPath) + ? fs.readFileSync(cssPath, "utf-8") + : ""; + + try { + fs.rmSync(distDir, { recursive: true }); + } catch { + //ignore + } + return { js: jsContent, css: cssContent }; + + } finally { + // Stop esbuild + await esbuild.stop(); + } +} + +/** + * Gets the esbuild build options for use in watch mode (dev server) + * @param entryPoint Entry point file + * @returns esbuild build options + */ +export function getDevBuildOptions(entryPoint: string = "index.tsx") { + return { + ...DEFAULT_BUILD_OPTIONS, + entryPoints: [entryPoint], + outfile: "dist/bundle.js", + sourcemap: true, + define: { + "process.env.NODE_ENV": '"development"', + }, + }; +} diff --git a/cli/src/commands/app/dev.ts b/cli/src/commands/app/dev.ts new file mode 100644 index 0000000000..d34905935d --- /dev/null +++ b/cli/src/commands/app/dev.ts @@ -0,0 +1,764 @@ +// deno-lint-ignore-file no-explicit-any +import { + Command, + colors, + log, + getPort, + open, + windmillUtils, + yamlParseFile, +} from "../../../deps.ts"; +import { GlobalOptions } from "../../types.ts"; +import * as http from "node:http"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import process from "node:process"; +import { Buffer } from "node:buffer"; +import { writeFileSync } from "node:fs"; +import { WebSocketServer, WebSocket } from "npm:ws@8.18.0"; +import { getDevBuildOptions, ensureNodeModules } from "./bundle.ts"; +import { wmillTsDev as wmillTs } from "./wmillTsDev.ts"; +import * as wmill from "../../../gen/services.gen.ts"; +import { resolveWorkspace } from "../../core/context.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { GLOBAL_CONFIG_OPT } from "../../core/conf.ts"; +import { replaceInlineScripts } from "./apps.ts"; +import { Runnable } from "./metadata.ts"; +import { inferRunnableSchemaFromFile } from "./app_metadata.ts"; + +const DEFAULT_PORT = 4000; +const DEFAULT_HOST = "localhost"; + +// HTML template with live reload +const createHTML = (jsPath: string, cssPath: string) => ` + + + + + + Windmill App Dev Preview + + + + +
+ + + + +`; + +interface DevOptions extends GlobalOptions { + port?: number; + host?: string; + entry?: string; + open?: boolean; +} + +async function dev(opts: DevOptions) { + GLOBAL_CONFIG_OPT.noCdToRoot = true; + // Resolve workspace and authenticate + const workspace = await resolveWorkspace(opts); + await requireLogin(opts); + const workspaceId = workspace.workspaceId; + + // Load app path from raw_app.yaml + const rawAppPath = path.join(process.cwd(), "raw_app.yaml"); + const rawApp = fs.existsSync(rawAppPath) + ? ((await yamlParseFile(rawAppPath)) as any) + : {}; + const appPath = rawApp?.custom_path ?? "u/unknown/newapp"; + + // Dynamically import esbuild only when the dev command is called + const esbuild = await import("npm:esbuild@0.24.2"); + + const port = + opts.port ?? + (await getPort.default({ + port: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((p) => p + DEFAULT_PORT), + })); + const host = opts.host ?? DEFAULT_HOST; + const entryPoint = opts.entry ?? "index.tsx"; + const shouldOpen = opts.open ?? true; + + // Verify entry point exists + if (!fs.existsSync(entryPoint)) { + log.error( + colors.red( + `Entry point "${entryPoint}" not found. Please specify a valid entry point with --entry.` + ) + ); + Deno.exit(1); + } + + // Ensure node_modules exists + const appDir = path.dirname(entryPoint); + await ensureNodeModules(appDir); + + // In-memory cache of inferred schemas (runnableId -> schema) + // Used to generate wmill.d.ts without modifying raw_app.yaml + const inferredSchemas: Record = {}; + + genRunnablesTs(inferredSchemas); + + // Ensure dist directory exists + const distDir = path.join(process.cwd(), "dist"); + if (!fs.existsSync(distDir)) { + fs.mkdirSync(distDir); + } + + // SSE clients for live reload + const clients: http.ServerResponse[] = []; + + function notifyClients() { + clients.forEach((client) => { + client.write(`event: change\ndata: reload\n\n`); + }); + } + + const buildOptions = getDevBuildOptions(entryPoint); + + const wmillPlugin = { + name: "wmill-virtual", + setup(build: any) { + // Intercept imports of /wmill.ts, /wmill, ./wmill.ts, or ./wmill + build.onResolve({ filter: /^(\.\/|\/)?wmill(\.ts)?$/ }, (args: any) => { + log.info(colors.yellow(`[wmill-virtual] Intercepted: ${args.path}`)); + return { + path: args.path, + namespace: "wmill-virtual", + }; + }); + + // Provide the virtual module content + build.onLoad( + { filter: /.*/, namespace: "wmill-virtual" }, + (args: any) => { + log.info( + colors.yellow( + `[wmill-virtual] Loading virtual module: ${args.path}` + ) + ); + return { + contents: wmillTs(port), + loader: "ts", + }; + } + ); + }, + }; + + // Create esbuild context + const ctx = await esbuild.context({ + ...buildOptions, + plugins: [ + { + name: "notify-on-rebuild", + setup(build: any) { + build.onEnd((result: any) => { + if (result.errors.length === 0) { + log.info( + colors.green("✅ Build succeeded, notifying clients...") + ); + notifyClients(); + } else { + log.error(colors.red("❌ Build failed:")); + result.errors.forEach((error: any) => { + log.error(colors.red(error.text)); + }); + } + }); + }, + }, + wmillPlugin, + ], + }); + + // Start watching + await ctx.watch(); + log.info(colors.blue("👀 Watching for file changes...\n")); + + // Initial build + await ctx.rebuild(); + + // Watch runnables folder for changes + const runnablesPath = path.join(process.cwd(), "runnables"); + let runnablesWatcher: Deno.FsWatcher | undefined; + + if (fs.existsSync(runnablesPath)) { + log.info( + colors.blue(`👁️ Watching runnables folder at: ${runnablesPath}\n`) + ); + runnablesWatcher = Deno.watchFs(runnablesPath); + + // Per-file debounce timeouts for schema inference (longer debounce for typing) + const schemaInferenceTimeouts: Record = {}; + const SCHEMA_DEBOUNCE_MS = 500; // Wait 500ms after last change before inferring schema + + // Handle runnables file changes in the background + (async () => { + try { + for await (const event of runnablesWatcher!) { + // Process each changed path with individual debouncing + for (const changedPath of event.paths) { + const relativePath = path.relative(process.cwd(), changedPath); + const relativeToRunnables = path.relative(runnablesPath, changedPath); + + // Skip non-modify events for schema inference + if (event.kind !== "modify" && event.kind !== "create") { + continue; + } + + // Skip lock files + if (changedPath.endsWith(".lock")) { + continue; + } + + // Log the change event + log.info( + colors.cyan(`📝 Runnable changed [${event.kind}]: ${relativePath}`) + ); + + // Debounce schema inference per file (wait for typing to finish) + if (schemaInferenceTimeouts[changedPath]) { + clearTimeout(schemaInferenceTimeouts[changedPath]); + } + + schemaInferenceTimeouts[changedPath] = setTimeout(async () => { + delete schemaInferenceTimeouts[changedPath]; + + try { + log.info(colors.cyan(`📝 Inferring schema for: ${relativeToRunnables}`)); + // Infer schema for this runnable (returns schema in memory, doesn't write to file) + const result = await inferRunnableSchemaFromFile( + process.cwd(), + relativeToRunnables + ); + if (result) { + // log.info(colors.green(` Schema: ${JSON.stringify(result.schema, null, 2)}`)); + // log.info(colors.green(` Runnable ID: ${result.runnableId}`)); + // Store inferred schema in memory + inferredSchemas[result.runnableId] = result.schema; + log.info(colors.green(` Inferred Schemas: ${JSON.stringify(inferredSchemas, null, 2)}`)); + // Regenerate wmill.d.ts with updated schema from memory + await genRunnablesTs(inferredSchemas); + } + } catch (error: any) { + log.error( + colors.red(`Error inferring schema: ${error.message}`) + ); + } + }, SCHEMA_DEBOUNCE_MS); + } + } + } catch (error: any) { + if (error.name !== "Interrupted") { + log.error(colors.red(`Error watching runnables: ${error.message}`)); + } + } + })(); + } else { + log.info( + colors.gray( + "ℹ️ No runnables folder found (will not watch for runnable changes)\n" + ) + ); + } + + // Create HTTP server + const server = http.createServer((req, res) => { + const url = req.url || "/"; + + // SSE endpoint for live reload + if (url === "/__events") { + res.writeHead(200, { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }); + res.write("data: connected\n\n"); + clients.push(res); + + req.on("close", () => { + const index = clients.indexOf(res); + if (index !== -1) clients.splice(index, 1); + }); + return; + } + + // Serve the bundled JS + if (url === "/dist/bundle.js" || url === "/bundle.js") { + const jsPath = path.join(process.cwd(), "dist/bundle.js"); + if (fs.existsSync(jsPath)) { + res.writeHead(200, { "Content-Type": "application/javascript" }); + res.end(fs.readFileSync(jsPath)); + } else { + res.writeHead(404); + res.end("Bundle not found"); + } + return; + } + + // Serve the bundled CSS + if (url === "/dist/bundle.css" || url === "/bundle.css") { + const cssPath = path.join(process.cwd(), "dist/bundle.css"); + if (fs.existsSync(cssPath)) { + res.writeHead(200, { "Content-Type": "text/css" }); + res.end(fs.readFileSync(cssPath)); + } else { + res.writeHead(404); + res.end("CSS not found"); + } + return; + } + + // Serve source maps + if (url === "/dist/bundle.js.map" || url === "/bundle.js.map") { + const mapPath = path.join(process.cwd(), "dist/bundle.js.map"); + if (fs.existsSync(mapPath)) { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(fs.readFileSync(mapPath)); + } else { + res.writeHead(404); + res.end("Source map not found"); + } + return; + } + + if (url === "/dist/bundle.css.map" || url === "/bundle.css.map") { + const mapPath = path.join(process.cwd(), "dist/bundle.css.map"); + if (fs.existsSync(mapPath)) { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(fs.readFileSync(mapPath)); + } else { + res.writeHead(404); + res.end("Source map not found"); + } + return; + } + + // Serve injected HTML for root and any other path + res.writeHead(200, { "Content-Type": "text/html" }); + res.end(createHTML("/dist/bundle.js", "/dist/bundle.css")); + }); + + // Create WebSocket server on the same HTTP server + const wss = new WebSocketServer({ server }); + + wss.on("connection", (ws: WebSocket) => { + log.info(colors.cyan("[WebSocket] Client connected")); + + ws.on("message", async (data: Buffer) => { + try { + const message = JSON.parse(data.toString()); + log.info( + colors.cyan(`[WebSocket] Received: ${JSON.stringify(message)}`) + ); + + const { type, reqId, runnable_id, v, jobId } = message; + + // Helper to send response + const respond = (responseType: string, result: any, error: boolean) => { + ws.send(JSON.stringify({ type: responseType, reqId, result, error })); + }; + + // Helper to execute and wait for result + const runAndWaitForResult = async ( + runnableId: string, + args: any + ): Promise<{ uuid: string; result: any }> => { + const runnables = await loadRunnables(); + const runnable = runnables[runnableId]; + + if (!runnable) { + throw new Error(`Runnable not found: ${runnableId}`); + } + + const uuid = await executeRunnable( + runnable, + workspaceId, + appPath, + runnableId, + args + ); + log.info(colors.gray(`[runBg] Job started: ${uuid}`)); + + const result = await waitForJob(workspaceId, uuid); + return { uuid, result }; + }; + + switch (type) { + case "runBg": { + // Run a runnable synchronously and wait for result + log.info(colors.blue(`[runBg] Running runnable: ${runnable_id}`)); + try { + const { result } = await runAndWaitForResult(runnable_id, v); + respond("runBgRes", result, false); + } catch (error: any) { + log.error(colors.red(`[runBg] Error: ${error.message}`)); + respond( + "runBgRes", + { message: error.message, stack: error.stack }, + true + ); + } + break; + } + + case "runBgAsync": { + // Run a runnable asynchronously and return job ID immediately + log.info( + colors.blue(`[runBgAsync] Running runnable async: ${runnable_id}`) + ); + try { + const runnables = await loadRunnables(); + const runnable = runnables[runnable_id]; + + if (!runnable) { + throw new Error(`Runnable not found: ${runnable_id}`); + } + + const uuid = await executeRunnable( + runnable, + workspaceId, + appPath, + runnable_id, + v + ); + log.info(colors.gray(`[runBgAsync] Job started: ${uuid}`)); + + // Return job ID immediately + respond("runBgAsyncRes", uuid, false); + + // Wait for result in the background and send it when done + waitForJob(workspaceId, uuid) + .then((result) => { + respond("runBgRes", result, false); + }) + .catch((error: any) => { + respond( + "runBgRes", + { message: error.message, stack: error.stack }, + true + ); + }); + } catch (error: any) { + log.error(colors.red(`[runBgAsync] Error: ${error.message}`)); + respond( + "runBgAsyncRes", + { message: error.message, stack: error.stack }, + true + ); + } + break; + } + + case "waitJob": { + // Wait for a job to complete and return its result + log.info(colors.blue(`[waitJob] Waiting for job: ${jobId}`)); + try { + const result = await waitForJob(workspaceId, jobId); + respond("runBgRes", result, false); + } catch (error: any) { + log.error(colors.red(`[waitJob] Error: ${error.message}`)); + respond( + "runBgRes", + { message: error.message, stack: error.stack }, + true + ); + } + break; + } + + case "getJob": { + // Get the current status/result of a job + log.info(colors.blue(`[getJob] Getting job status: ${jobId}`)); + try { + const result = await getJobStatus(workspaceId, jobId); + respond("runBgRes", result, false); + } catch (error: any) { + log.error(colors.red(`[getJob] Error: ${error.message}`)); + respond( + "runBgRes", + { message: error.message, stack: error.stack }, + true + ); + } + break; + } + + default: + log.warn( + colors.yellow(`[WebSocket] Unknown message type: ${type}`) + ); + respond( + "error", + { message: `Unknown message type: ${type}` }, + true + ); + } + } catch (error: any) { + log.error( + colors.red(`[WebSocket] Failed to parse message: ${error.message}`) + ); + } + }); + + ws.on("close", () => { + log.info(colors.cyan("[WebSocket] Client disconnected")); + }); + + ws.on("error", (error: Error) => { + log.error(colors.red(`[WebSocket] Error: ${error.message}`)); + }); + }); + + server.listen(port, host, () => { + const url = `http://${host}:${port}`; + log.info(colors.bold.green(`🚀 Dev server running at ${url}`)); + log.info( + colors.cyan(`🔌 WebSocket server running at ws://${host}:${port}`) + ); + log.info(colors.gray(`📦 Serving files from: ${process.cwd()}`)); + log.info(colors.gray(`🔄 Live reload enabled\n`)); + + // Open browser if requested + if (shouldOpen) { + try { + open + .openApp(open.apps.browser, { arguments: [url] }) + .catch((error: any) => { + log.error( + colors.yellow( + `Failed to open browser automatically: ${error.message}` + ) + ); + }); + log.info(colors.gray("Opened browser for you")); + } catch (error: any) { + log.error(colors.yellow(`Failed to open browser: ${error.message}`)); + } + } + }); + + // Graceful shutdown + process.on("SIGINT", async () => { + log.info(colors.yellow("\n\n🛑 Shutting down...")); + clients.forEach((client) => client.end()); + server.close(); + + // Close runnables watcher if it exists + if (runnablesWatcher) { + runnablesWatcher.close(); + } + + await ctx.dispose(); + process.exit(0); + }); +} + +const command = new Command() + .description( + "Start a development server for building apps with live reload and hot module replacement" + ) + .option( + "--port ", + "Port to run the dev server on (will find next available port if occupied)" + ) + .option("--host ", "Host to bind the dev server to", { + default: DEFAULT_HOST, + }) + .option("--entry ", "Entry point file for the application", { + default: "index.tsx", + }) + .option("--no-open", "Don't automatically open the browser") + .action(dev as any); + +export default command; + +/** + * Generates wmill.d.ts with type definitions for runnables. + * Merges in-memory inferred schemas with runnables from raw_app.yaml. + * + * @param schemaOverrides - In-memory schema overrides (runnableId -> schema) + */ +async function genRunnablesTs(schemaOverrides: Record = {}) { + log.info(colors.blue("🔄 Generating wmill.d.ts...")); + const rawApp = (await yamlParseFile( + path.join(process.cwd(), "raw_app.yaml") + )) as any; + const runnables = rawApp?.["runnables"] as any; + + // Apply schema overrides from in-memory cache + if (runnables && Object.keys(schemaOverrides).length > 0) { + for (const [runnableId, schema] of Object.entries(schemaOverrides)) { + if (runnables[runnableId]?.inlineScript) { + runnables[runnableId].inlineScript.schema = schema; + runnables[runnableId].type = "inline"; + } + } + } + + try { + const newWmillTs = windmillUtils.genWmillTs(runnables); + writeFileSync(path.join(process.cwd(), "wmill.d.ts"), newWmillTs); + } catch (error: any) { + log.error(colors.red(`Failed to generate wmill.d.ts: ${error.message}`)); + } +} + +async function loadRunnables(): Promise> { + try { + const localPath = process.cwd(); + const rawApp = (await yamlParseFile( + path.join(localPath, "raw_app.yaml") + )) as any; + replaceInlineScripts(rawApp.runnables, path.join(localPath, "runnables/")); + + return rawApp?.runnables ?? {}; + } catch (error: any) { + log.error(colors.red(`Failed to load runnables: ${error.message}`)); + return {}; + } +} + +async function executeRunnable( + runnable: Runnable, + workspace: string, + appPath: string, + runnableId: string, + args: any +): Promise { + const requestBody: any = { + component: runnableId, + args, + force_viewer_static_fields: {}, + force_viewer_one_of_fields: {}, + force_viewer_allow_user_resources: [], + }; + + // Handle static fields + if (runnable.fields) { + for (const [key, field] of Object.entries(runnable.fields)) { + if (field?.type === "static") { + requestBody.force_viewer_static_fields[key] = field.value; + } + if (field?.type === "user" && field?.allowUserResources) { + requestBody.force_viewer_allow_user_resources.push(key); + } + } + } + + if ((runnable.type === "inline" || runnable.type === "runnableByName") && runnable.inlineScript) { + const inlineScript = runnable.inlineScript; + if (inlineScript.id !== undefined) { + requestBody.id = inlineScript.id; + } + requestBody.raw_code = { + content: inlineScript.id === undefined ? inlineScript.content : "", + language: inlineScript.language ?? "", + path: `${appPath}/${runnableId}`, + lock: inlineScript.id === undefined ? inlineScript.lock : undefined, + cache_ttl: inlineScript.cache_ttl, + }; + } else if ((runnable.type === "path" || runnable.type === "runnableByPath") && runnable.path) { + const runType = runnable.runType ?? "script"; + requestBody.path = + runType !== "hubscript" + ? `${runType}/${runnable.path}` + : `script/${runnable.path}`; + } + + const uuid = await wmill.executeComponent({ + workspace, + path: appPath, + requestBody, + }); + + return uuid; +} + +const ITERATIONS_BEFORE_SLOW_REFRESH = 10; +const ITERATIONS_BEFORE_SUPER_SLOW_REFRESH = 100; + +async function waitForJob(workspace: string, jobId: string): Promise { + if (!jobId) { + throw new Error("Job ID is required"); + } + + let syncIteration = 0; + + return new Promise((resolve, reject) => { + async function checkJob() { + try { + const maybeJob = await wmill.getCompletedJobResultMaybe({ + workspace, + id: jobId, + getStarted: false, + }); + + if (maybeJob.completed) { + if ( + !maybeJob.success && + typeof maybeJob.result === "object" && + maybeJob.result !== null && + "error" in maybeJob.result + ) { + reject((maybeJob.result as any).error); + } else { + resolve(maybeJob.result); + } + return; + } + } catch (err: any) { + log.error(colors.red(`Error checking job ${jobId}: ${err.message}`)); + } + + syncIteration++; + + let nextIteration = 50; + if (syncIteration > ITERATIONS_BEFORE_SUPER_SLOW_REFRESH) { + nextIteration = 2000; + } else if (syncIteration > ITERATIONS_BEFORE_SLOW_REFRESH) { + nextIteration = 500; + } + + setTimeout(checkJob, nextIteration); + } + + checkJob(); + }); +} + +async function getJobStatus(workspace: string, jobId: string): Promise { + return await wmill.getJob({ + workspace, + id: jobId, + }); +} diff --git a/cli/src/commands/app/metadata.ts b/cli/src/commands/app/metadata.ts new file mode 100644 index 0000000000..a9e9dee812 --- /dev/null +++ b/cli/src/commands/app/metadata.ts @@ -0,0 +1,21 @@ +export type Runnable = + | { + name: string; + type?: "runnableByName" | "inline"; + path?: string; + inlineScript?: { + content: string; + language: string; + lock?: string; + cache_ttl?: number; + id?: number; + }; + fields?: Record; + } + | { + type: "runnableByPath" | "path"; + path: string; + runType?: "script" | "flow" | "hubscript"; + fields?: Record; + schema?: any; + }; diff --git a/cli/src/commands/app/raw_apps.ts b/cli/src/commands/app/raw_apps.ts new file mode 100644 index 0000000000..5bb1cb756c --- /dev/null +++ b/cli/src/commands/app/raw_apps.ts @@ -0,0 +1,292 @@ +// deno-lint-ignore-file no-explicit-any +import { requireLogin } from "../../core/auth.ts"; +import { resolveWorkspace, validatePath } from "../../core/context.ts"; +import { + colors, + log, + SEP, + windmillUtils, + yamlParseFile, +} from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; +import { Policy } from "../../../gen/types.gen.ts"; + +import { GlobalOptions, isSuperset } from "../../types.ts"; + +import { replaceInlineScripts, repopulateFields } from "./apps.ts"; +import { createBundle } from "./bundle.ts"; +import { mergeConfigWithConfigFile, SyncOptions } from "../../core/conf.ts"; + +export interface AppFile { + runnables: any; + custom_path: string; + public?: boolean; + summary: string; + policy: Policy; +} + +const alreadySynced: string[] = []; + +async function collectAppFiles( + localPath: string +): Promise> { + const files: Record = {}; + + async function readDirRecursive(dir: string, basePath: string = "/") { + for await (const entry of Deno.readDir(dir)) { + const fullPath = dir + entry.name; + const relativePath = basePath + entry.name; + + if (entry.isDirectory) { + // Skip the runnables and node_modules subfolders + if (entry.name === "runnables" || entry.name === "node_modules" || entry.name === "dist") { + continue; + } + await readDirRecursive(fullPath + SEP, relativePath + SEP); + } else if (entry.isFile) { + // Skip raw_app.yaml as it's metadata, not an app file + // Skip node_modules and package-lock.json as they are generated + if ( + relativePath === "raw_app.yaml" || + relativePath === "package-lock.json" + ) { + continue; + } + const content = await Deno.readTextFile(fullPath); + files[relativePath] = content; + } + } + } + + await readDirRecursive(localPath); + return files; +} + +export async function pushRawApp( + workspace: string, + remotePath: string, + localPath: string, + message?: string +): Promise { + if (alreadySynced.includes(localPath)) { + return; + } + alreadySynced.push(localPath); + remotePath = remotePath.replaceAll(SEP, "/"); + let app: any = undefined; + // deleting old app if it exists in raw mode + try { + app = await wmill.getAppByPath({ + workspace, + path: remotePath, + }); + } catch { + //ignore + } + if (app?.["policy"]?.["execution_mode"] == "anonymous") { + app.public = true; + } + // console.log(app); + if (app) { + app.policy = undefined; + } + + if (!localPath.endsWith(SEP)) { + localPath += SEP; + } + const path = localPath + "raw_app.yaml"; + const localApp = (await yamlParseFile(path)) as AppFile; + replaceInlineScripts(localApp.runnables, localPath + SEP + "runnables/"); + repopulateFields(localApp.runnables) + await generatingPolicy(localApp, remotePath, localApp?.["public"] ?? false); + const files = await collectAppFiles(localPath); + async function createBundleRaw() { + log.info(colors.yellow.bold(`Creating raw app ${remotePath} bundle...`)); + const entryPoint = localPath + "index.tsx"; + return await createBundle({ + entryPoint: entryPoint, + production: true, + minify: true, + }); + } + if (app) { + if (isSuperset(localApp, app)) { + log.info(colors.green(`App ${remotePath} is up to date`)); + return; + } + const { js, css } = await createBundleRaw(); + log.info(colors.bold.yellow(`Updating app ${remotePath}...`)); + await wmill.updateAppRaw({ + workspace, + path: remotePath, + formData: { + app: { + value: { runnables: localApp.runnables, files }, + path: remotePath, + summary: localApp.summary, + policy: localApp.policy, + deployment_message: message, + custom_path: localApp.custom_path, + }, + js, + css, + }, + }); + } else { + const { js, css } = await createBundleRaw(); + await wmill.createAppRaw({ + workspace, + formData: { + app: { + value: { runnables: localApp.runnables, files }, + path: remotePath, + summary: localApp.summary, + policy: localApp.policy, + deployment_message: message, + custom_path: localApp.custom_path, + }, + js, + css, + }, + }); + // await wmill.createApp({ + // workspace, + // requestBody: { + // path: remotePath, + // deployment_message: message, + // value: { runnables: localApp.runnables, files }, + // summary: localApp.summary, + // policy: localApp.policy, + // }, + // }); + } +} + +export async function generatingPolicy( + app: any, + path: string, + publicApp: boolean +) { + log.info(colors.gray(`Generating fresh policy for app ${path}...`)); + try { + app.policy = await windmillUtils.updateRawAppPolicy( + app.runnables, + app.policy + ); + app.policy.execution_mode = publicApp ? "anonymous" : "publisher"; + } catch (e) { + log.error(colors.red(`Error generating policy for app ${path}: ${e}`)); + throw e; + } +} + +export async function generateLocksCommand( + opts: GlobalOptions & { + yes?: boolean; + dryRun?: boolean; + defaultTs?: "bun" | "deno"; + } & SyncOptions, + appPath: string | undefined +) { + const { generateAppLocksInternal } = await import("./app_metadata.ts"); + const { elementsToMap, FSFSElement } = await import("../sync/sync.ts"); + const { ignoreF } = await import("../sync/sync.ts"); + const { Confirm } = await import("../../../deps.ts"); + + if (appPath == "") { + appPath = undefined; + } + + const workspace = await resolveWorkspace(opts); + await requireLogin(opts); + opts = await mergeConfigWithConfigFile(opts); + + if (appPath) { + // Generate metadata for a specific app + await generateAppLocksInternal( + appPath, + false, + workspace, + opts, + false, + false, + ); + } else { + // Generate metadata for all apps + const ignore = await ignoreF(opts); + const elems = await elementsToMap( + await FSFSElement(Deno.cwd(), [], true), + (p, isD) => { + return ignore(p, isD) || (!isD && !p.endsWith(SEP + "raw_app.yaml")); + }, + false, + {} + ); + + const appFolders = Object.keys(elems) + .filter((p) => p.endsWith(SEP + "raw_app.yaml")) + .map((p) => p.substring(0, p.length - (SEP + "raw_app.yaml").length)); + + let hasAny = false; + log.info("Checking metadata for all apps:"); + for (const appFolder of appFolders) { + const candidate = await generateAppLocksInternal( + appFolder, + true, + workspace, + opts, + false, + true, + ); + if (candidate) { + hasAny = true; + log.info(colors.green(`+ ${candidate}`)); + } + } + + if (hasAny) { + if (opts.dryRun) { + log.info(colors.gray(`Dry run complete.`)); + return; + } + if ( + !opts.yes && + !(await Confirm.prompt({ + message: "Update the metadata of the above apps?", + default: true, + })) + ) { + return; + } + } else { + log.info(colors.green.bold("No metadata to update")); + return; + } + + for (const appFolder of appFolders) { + await generateAppLocksInternal( + appFolder, + false, + workspace, + opts, + false, + true, + ); + } + } +} + +async function pushRawAppCommand( + opts: GlobalOptions, + filePath: string, + remotePath: string +) { + if (!validatePath(remotePath)) { + return; + } + const workspace = await resolveWorkspace(opts); + await requireLogin(opts); + + await pushRawApp(workspace.workspaceId, remotePath, filePath); + log.info(colors.bold.underline.green("Raw app pushed")); +} diff --git a/cli/src/commands/app/wmillTsDev.ts b/cli/src/commands/app/wmillTsDev.ts new file mode 100644 index 0000000000..ca9db42004 --- /dev/null +++ b/cli/src/commands/app/wmillTsDev.ts @@ -0,0 +1,87 @@ +//comment this line and last to dev +export function wmillTsDev(port: number) { return ` +let reqs: Record = {} +let ws: WebSocket | null = null +let wsReady: Promise +let wsReadyResolve: () => void + +function initWebSocket() { + wsReady = new Promise((resolve) => { + wsReadyResolve = resolve + }) + + ws = new WebSocket('ws://localhost:${port}') + + ws.onopen = () => { + console.log('[wmill] WebSocket connected') + wsReadyResolve() + } + + ws.onmessage = (event) => { + const data = JSON.parse(event.data) + if (data.type === 'runBgRes' || data.type === 'runBgAsyncRes') { + console.log('Message from WebSocket runBg', data) + const job = reqs[data.reqId] + if (job) { + const result = data.result + if (data.error) { + job.reject(new Error(result.stack ?? result.message)) + } else { + job.resolve(result) + } + delete reqs[data.reqId] + } else { + console.error('No job found for', data.reqId) + } + } + } + + ws.onerror = (error) => { + console.error('[wmill] WebSocket error:', error) + } + + ws.onclose = () => { + console.log('[wmill] WebSocket closed, reconnecting...') + setTimeout(initWebSocket, 1000) + } +} + +initWebSocket() + +async function doRequest(type: string, o: object) { + await wsReady + return new Promise((resolve, reject) => { + const reqId = Math.random().toString(36) + reqs[reqId] = { resolve, reject } + ws?.send(JSON.stringify({ ...o, type, reqId })) + }) +} + +export const runBg = new Proxy( + {}, + { + get(_, runnable_id: string) { + return (v: any) => { + return doRequest('runBg', { runnable_id, v }) + } + } + }) + +export const runBgAsync = new Proxy( + {}, + { + get(_, runnable_id: string) { + return (v: any) => { + return doRequest('runBgAsync', { runnable_id, v }) + } + } + }) + +export function waitJob(jobId: string) { + return doRequest('waitJob', { jobId }) +} + +export function getJob(jobId: string) { + return doRequest('getJob', { jobId }) +} +`} \ No newline at end of file diff --git a/cli/src/commands/flow/flow.ts b/cli/src/commands/flow/flow.ts index 4be9e53867..d3475f9cd8 100644 --- a/cli/src/commands/flow/flow.ts +++ b/cli/src/commands/flow/flow.ts @@ -8,11 +8,11 @@ import { requireLogin } from "../../core/auth.ts"; import { resolveWorkspace, validatePath } from "../../core/context.ts"; import { resolve, track_job } from "../script/script.ts"; import { defaultFlowDefinition } from "../../../bootstrap/flow_bootstrap.ts"; -import { generateFlowLockInternal } from "../../utils/metadata.ts"; import { SyncOptions, mergeConfigWithConfigFile } from "../../core/conf.ts"; import { FSFSElement, elementsToMap, ignoreF } from "../sync/sync.ts"; import { Flow } from "../../../gen/types.gen.ts"; import { replaceInlineScripts } from "../../../windmill-utils-internal/src/inline-scripts/replacer.ts"; +import { generateFlowLockInternal } from "./flow_metadata.ts"; export interface FlowFile { summary: string; diff --git a/cli/src/commands/flow/flow_metadata.ts b/cli/src/commands/flow/flow_metadata.ts new file mode 100644 index 0000000000..fb5c5539da --- /dev/null +++ b/cli/src/commands/flow/flow_metadata.ts @@ -0,0 +1,238 @@ +import { + SEP, + colors, + log, + path, + yamlParseFile, + yamlStringify, +} from "../../../deps.ts"; +import { GlobalOptions } from "../../types.ts"; +import { + readLockfile, + checkifMetadataUptodate, + blueColor, + clearGlobalLock, + updateMetadataGlobalLock, + LockfileGenerationError, + getRawWorkspaceDependencies, +} from "../../utils/metadata.ts"; +import { extractInlineScripts as extractInlineScriptsForFlows } from "../../../windmill-utils-internal/src/inline-scripts/extractor.ts"; + + +import { generateHash, getHeaders, writeIfChanged } from "../../utils/utils.ts"; +import { exts, } from "../script/script.ts"; +import { FSFSElement } from "../sync/sync.ts"; +import { Workspace } from "../workspace/workspace.ts"; +import { FlowFile } from "./flow.ts"; +import { FlowValue } from "../../../gen/types.gen.ts"; +import { replaceInlineScripts } from "../../../windmill-utils-internal/src/inline-scripts/replacer.ts"; +import { workspaceDependenciesLanguages } from "../../utils/script_common.ts"; + +const TOP_HASH = "__flow_hash"; +async function generateFlowHash( + rawWorkspaceDependencies: Record, + folder: string, + defaultTs: "bun" | "deno" | undefined +) { + const elems = await FSFSElement(path.join(Deno.cwd(), folder), [], true); + const hashes: Record = {}; + for await (const f of elems.getChildren()) { + if (exts.some((e) => f.path.endsWith(e))) { + // Embed workspace dependencies into hash + hashes[f.path] = await generateHash( + (await f.getContentText()) + JSON.stringify(rawWorkspaceDependencies) + ); + } + } + return { ...hashes, [TOP_HASH]: await generateHash(JSON.stringify(hashes)) }; +} +export async function generateFlowLockInternal( + folder: string, + dryRun: boolean, + workspace: Workspace, + opts: GlobalOptions & { + defaultTs?: "bun" | "deno"; + }, + justUpdateMetadataLock?: boolean, + noStaleMessage?: boolean +): Promise { + if (folder.endsWith(SEP)) { + folder = folder.substring(0, folder.length - 1); + } + const remote_path = folder + .replaceAll(SEP, "/") + .substring(0, folder.length - ".flow".length); + if (!justUpdateMetadataLock && !noStaleMessage) { + log.info(`Generating lock for flow ${folder} at ${remote_path}`); + } + + // Always get out-of-sync workspace dependencies + const rawWorkspaceDependencies: Record = await getRawWorkspaceDependencies(); + let hashes = await generateFlowHash(rawWorkspaceDependencies, folder, opts.defaultTs); + + const conf = await readLockfile(); + if (await checkifMetadataUptodate(folder, hashes[TOP_HASH], conf, TOP_HASH)) { + if (!noStaleMessage) { + log.info( + colors.green(`Flow ${remote_path} metadata is up-to-date, skipping`) + ); + } + return; + } else if (dryRun) { + return remote_path; + } + + if (Object.keys(rawWorkspaceDependencies).length > 0) { + log.info( + (await blueColor())( + `Found workspace dependencies (${workspaceDependenciesLanguages + .map((l) => l.filename) + .join("/")}) for ${folder}, using them` + ) + ); + } + + const flowValue = (await yamlParseFile( + folder! + SEP + "flow.yaml" + )) as FlowFile; + + if (!justUpdateMetadataLock) { + const changedScripts = []; + //find hashes that do not correspond to previous hashes + for (const [path, hash] of Object.entries(hashes)) { + if (path == TOP_HASH) { + continue; + } + if (!(await checkifMetadataUptodate(folder, hash, conf, path))) { + changedScripts.push(path); + } + } + + log.info(`Recomputing locks of ${changedScripts.join(", ")} in ${folder}`); + await replaceInlineScripts( + flowValue.value.modules, + async (path: string) => await Deno.readTextFile(folder + SEP + path), + log, + folder + SEP!, + SEP, + changedScripts, + // (path: string, newPath: string) => Deno.renameSync(path, newPath), + // (path: string) => Deno.removeSync(path) + ); + + //removeChangedLocks + flowValue.value = await updateFlow( + workspace, + flowValue.value, + remote_path, + rawWorkspaceDependencies + ); + + const inlineScripts = extractInlineScriptsForFlows( + flowValue.value.modules, + {}, + SEP, + opts.defaultTs + ); + inlineScripts.forEach((s) => { + writeIfChanged(Deno.cwd() + SEP + folder + SEP + s.path, s.content); + }); + + // Overwrite `flow.yaml` with the new lockfile references + writeIfChanged( + Deno.cwd() + SEP + folder + SEP + "flow.yaml", + yamlStringify(flowValue as Record) + ); + } + + hashes = await generateFlowHash(rawWorkspaceDependencies, folder, opts.defaultTs); + await clearGlobalLock(folder); + for (const [path, hash] of Object.entries(hashes)) { + await updateMetadataGlobalLock(folder, hash, path); + } + log.info(colors.green(`Flow ${remote_path} lockfiles updated`)); +} + + + +export async function updateFlow( + workspace: Workspace, + flow_value: FlowValue, + remotePath: string, + rawWorkspaceDependencies: Record +): Promise { + let rawResponse; + + if (Object.keys(rawWorkspaceDependencies).length > 0) { + log.info(colors.blue("Using raw workspace dependencies for flow dependencies")); + + // generate the script lock running a dependency job in Windmill and update it inplace + const extraHeaders = getHeaders(); + rawResponse = await fetch( + `${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/flow_dependencies`, + { + method: "POST", + headers: { + Cookie: `token=${workspace.token}`, + "Content-Type": "application/json", + ...extraHeaders, + }, + body: JSON.stringify({ + flow_value, + path: remotePath, + use_local_lockfiles: true, + raw_workspace_dependencies: Object.keys(rawWorkspaceDependencies).length > 0 + ? rawWorkspaceDependencies + : null, + }), + } + ); + } else { + // Standard dependency resolution on the server + const extraHeaders = getHeaders(); + rawResponse = await fetch( + `${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/flow_dependencies`, + { + method: "POST", + headers: { + Cookie: `token=${workspace.token}`, + "Content-Type": "application/json", + ...extraHeaders, + }, + body: JSON.stringify({ + flow_value, + path: remotePath, + }), + } + ); + } + + let responseText = "reading response failed"; + try { + const res = (await rawResponse.json()) as + | { updated_flow_value: any } + | { error: { message: string } } + | undefined; + if (rawResponse.status != 200) { + const msg = (res as any)?.["error"]?.["message"]; + if (msg) { + throw new LockfileGenerationError( + `Failed to generate lockfile: ${msg}` + ); + } + throw new LockfileGenerationError( + `Failed to generate lockfile: ${rawResponse.statusText}, ${responseText}` + ); + } + return (res as any).updated_flow_value; + } catch (e) { + try { + responseText = await rawResponse.text(); + } catch { + responseText = ""; + } + throw new Error( + `Failed to generate lockfile. Status was: ${rawResponse.statusText}, ${responseText}, ${e}` + ); + } +} diff --git a/cli/src/commands/sync/sync.ts b/cli/src/commands/sync/sync.ts index 4a1befb82f..ff0d6b64c0 100644 --- a/cli/src/commands/sync/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -5,13 +5,13 @@ import { Command, Confirm, ensureDir, - minimatch, JSZip, - path, log, - yamlStringify, - yamlParseContent, + minimatch, + path, SEP, + yamlParseContent, + yamlStringify, } from "../../../deps.ts"; import * as wmill from "../../../gen/services.gen.ts"; @@ -34,28 +34,27 @@ import { } from "../script/script.ts"; import { handleFile } from "../script/script.ts"; -import { deepEqual, isFileResource, isWorkspaceDependencies } from "../../utils/utils.ts"; +import { deepEqual, isFileResource, isRawAppFile, isWorkspaceDependencies } from "../../utils/utils.ts"; import { - SyncOptions, getEffectiveSettings, - validateBranchConfiguration, mergeConfigWithConfigFile, + SyncOptions, + validateBranchConfiguration, } from "../../core/conf.ts"; import { - SpecificItemsConfig, - getSpecificItemsForCurrentBranch, - isSpecificItem, - getBranchSpecificPath, fromBranchSpecificPath, - isCurrentBranchFile, + getBranchSpecificPath, + getSpecificItemsForCurrentBranch, isBranchSpecificFile, + isCurrentBranchFile, + isSpecificItem, + SpecificItemsConfig, } from "../../core/specific_items.ts"; import { getCurrentGitBranch } from "../../utils/git.ts"; import { Workspace } from "../workspace/workspace.ts"; import { removePathPrefix } from "../../types.ts"; -import { SyncCodebase, listSyncCodebases } from "../../utils/codebase.ts"; +import { listSyncCodebases, SyncCodebase } from "../../utils/codebase.ts"; import { - generateFlowLockInternal, generateScriptMetadataInternal, getRawWorkspaceDependencies, readLockfile, @@ -68,6 +67,7 @@ import { PathAssigner, } from "../../../windmill-utils-internal/src/path-utils/path-assigner.ts"; import { extractInlineScripts as extractInlineScriptsForFlows } from "../../../windmill-utils-internal/src/inline-scripts/extractor.ts"; +import { generateFlowLockInternal } from "../flow/flow_metadata.ts"; import { isExecutionModeAnonymous } from "../app/apps.ts"; // Merge CLI options with effective settings, preserving CLI flags as overrides @@ -90,6 +90,7 @@ async function resolveEffectiveSyncOptions( type DynFSElement = { isDirectory: boolean; path: string; + whitelistedExt?: boolean; // getContentBytes(): Promise; getContentText(): Promise; getChildren(): AsyncIterable; @@ -262,19 +263,55 @@ export interface InlineScript { content: string; } +function extractFields(fields: Record) { + Object.entries(fields).forEach(([k, v]) => { + if (typeof v == "object") { + if (v.type == "static") { + fields[k] = { value: v.value } + } else if (v.type == "javascript") { + fields[k] = { expr: v.expr, allowUserResources: v.allowUserResources } + } else if (v.type == "user") { + fields[k] = undefined + } + } + // if (k == 'runType') { + // fields["type"] = undefined + // fields["schema"] = undefined + // } + }) +} + + +export function extractFieldsForRawApps(runnables: Record) { + Object.values(runnables).forEach((v) => { + if (typeof v == "object") { + if (v.fields !== undefined) { + extractFields(v.fields) + } + } + }) +} export function extractInlineScriptsForApps( + key: string | undefined, rec: any, - pathAssigner: PathAssigner + pathAssigner: PathAssigner, + toId: (key: string, val: any) => string ): InlineScript[] { if (!rec) { return []; } if (typeof rec == "object") { return Object.entries(rec).flatMap(([k, v]) => { - if (k == "inlineScript" && typeof v == "object") { + if (k == 'runType') { + rec["type"] = undefined + rec["schema"] = undefined + return [] + } else if (k == "inlineScript" && typeof v == "object") { + rec["type"] = undefined const o: Record = v as any; - const name = rec["name"]; - const [basePath, ext] = pathAssigner.assignPath(name, o["language"]); + const name = toId(key ?? "", rec); + const [basePathO, ext] = pathAssigner.assignPath(name, o["language"]); + const basePath = basePathO.replaceAll(SEP, "/"); const r = []; if (o["content"]) { const content = o["content"]; @@ -292,9 +329,10 @@ export function extractInlineScriptsForApps( content: lock, }); } + o.schema = undefined; return r; } else { - return extractInlineScriptsForApps(v, pathAssigner); + return extractInlineScriptsForApps(k, v, pathAssigner, toId); } }); } @@ -312,19 +350,20 @@ function ZipFSElement( p: string, f: JSZip.JSZipObject ): Promise { - const kind: "flow" | "app" | "script" | "resource" | "dependencies" | "other" = p.endsWith( - "flow.json" - ) - ? "flow" - : p.endsWith("app.json") - ? "app" - : p.endsWith("script.json") - ? "script" - : p.endsWith("resource.json") - ? "resource" - : p.startsWith("dependencies/") + const kind: "flow" | "app" | "script" | "resource" | "other" | "raw_app" | "dependencies" = + p.endsWith(".flow.json") + ? "flow" + : p.endsWith(".app.json") + ? "app" + : p.endsWith(".raw_app.json") + ? "raw_app" + : p.endsWith(".script.json") + ? "script" + : p.endsWith(".resource.json") + ? "resource" + : p.startsWith("dependencies/") ? "dependencies" - : "other"; + : "other"; const isJson = p.endsWith(".json"); @@ -333,6 +372,8 @@ function ZipFSElement( return p.replace("flow.json", "flow"); } else if (kind == "app") { return p.replace("app.json", "app"); + } else if (kind == "raw_app") { + return p.replace("raw_app.json", "raw_app"); } else if (kind == "dependencies") { return p; } else { @@ -341,9 +382,10 @@ function ZipFSElement( } const finalPath = transformPath(); + const r = [ { - isDirectory: kind == "flow" || kind == "app", + isDirectory: kind == "flow" || kind == "app" || kind == "raw_app", path: finalPath, async *getChildren(): AsyncIterable { if (kind == "flow") { @@ -400,8 +442,10 @@ function ZipFSElement( let inlineScripts; try { inlineScripts = extractInlineScriptsForApps( + undefined, app?.["value"], - newPathAssigner(defaultTs) + newPathAssigner(defaultTs), + (_, val) => val["name"] ); } catch (error) { log.error( @@ -409,6 +453,7 @@ function ZipFSElement( ); throw error; } + for (const s of inlineScripts) { yield { isDirectory: false, @@ -434,6 +479,88 @@ function ZipFSElement( return yamlStringify(app, yamlOptions); }, }; + } else if (kind == "raw_app") { + let rawApp; + try { + rawApp = JSON.parse(await f.async("text")); + } catch (error) { + log.error(`Failed to parse app.yaml at path: ${p}`); + throw error; + } + if (rawApp?.["policy"]?.["execution_mode"] == "anonymous") { + rawApp.public = true; + } + // console.log("rawApp", rawApp); + rawApp.policy = undefined; + let inlineScripts; + const value = rawApp?.["value"]; + // console.log("FOOB", value?.["runnables"]) + extractFieldsForRawApps(value?.["runnables"]); + try { + inlineScripts = extractInlineScriptsForApps( + undefined, + value, + newPathAssigner(defaultTs), + (key, val_) => key + ); + } catch (error) { + log.error( + `Failed to extract inline scripts for raw app at path: ${p}` + ); + throw error; + } + + try { + for (const [filePath, content] of Object.entries( + value?.["files"] ?? [] + )) { + yield { + isDirectory: false, + path: path.join(finalPath, filePath.substring(1)), + async *getChildren() {}, + // deno-lint-ignore require-await + async getContentText() { + if (typeof content !== "string") { + throw new Error( + `Content of raw app file ${filePath} is not a string` + ); + } + return content as string; + }, + }; + } + } catch (error) { + log.error(`Failed to extract files for raw app at path: ${p}`); + throw error; + } + + for (const s of inlineScripts) { + yield { + isDirectory: false, + path: path.join(finalPath, "runnables", s.path), + async *getChildren() {}, + // deno-lint-ignore require-await + async getContentText() { + return s.content; + }, + }; + } + + const runnables = value?.["runnables"]; + if (runnables) { + rawApp.runnables = runnables; + delete rawApp?.["value"]; + } + + yield { + isDirectory: false, + path: path.join(finalPath, "raw_app.yaml"), + async *getChildren() {}, + // deno-lint-ignore require-await + async getContentText() { + return yamlStringify(rawApp, yamlOptions); + }, + }; } }, @@ -637,7 +764,7 @@ export async function* readDirRecursiveWithIgnore( } stack.push({ path: e2.path, - ignored: e.ignored || e2.isDirectory && e2.path == "dependencies" ? false : ignore(e2.path, e2.isDirectory), + ignored: e.ignored || ignore(e2.path, e2.isDirectory), isDirectory: e2.isDirectory, // getContentBytes: e2.getContentBytes, getContentText: e2.getContentText, @@ -669,15 +796,51 @@ export async function elementsToMap( const map: { [key: string]: string } = {}; const processedBasePaths = new Set(); for await (const entry of readDirRecursiveWithIgnore(ignore, els)) { + // console.log("FOO", entry.path, entry.ignored, entry.isDirectory) if (entry.isDirectory || entry.ignored) { - if (entry.path.includes("dependencies/")) { - log.info(`Ignoring dependencies-related path: ${entry.path} (isDirectory: ${entry.isDirectory}, ignored: ${entry.ignored})`); - } continue; } const path = entry.path; - if (json && path.endsWith(".yaml") && !isFileResource(path) && !isWorkspaceDependencies(path)) continue; - if (!json && path.endsWith(".json") && !isFileResource(path) && !isWorkspaceDependencies(path)) continue; + if (!isFileResource(path) && !isRawAppFile(path) && !isWorkspaceDependencies(path)) { + if (json && path.endsWith(".yaml")) continue; + if (!json && path.endsWith(".json")) continue; + + if ( + ![ + "json", + "yaml", + "go", + "sh", + "ts", + "py", + "sql", + "gql", + "ps1", + "php", + "js", + "lock", + "rs", + "cs", + "yml", + "nu", + "java", + "rb", + // for related places search: ADD_NEW_LANG + ].includes(path.split(".").pop() ?? "") + ) { + continue; + } + } + + if (isRawAppFile(path)) { + const suffix = path.split(".raw_app" + SEP).pop(); + if (suffix?.startsWith("dist/") || suffix == "wmill.d.ts" || suffix == "package-lock.json") { + continue; + } + } + + if (skips.skipResources && isFileResource(path)) continue; + const ext = json ? ".json" : ".yaml"; if (!skips.includeSchedules && path.endsWith(".schedule" + ext)) continue; if ( @@ -691,15 +854,17 @@ export async function elementsToMap( path.endsWith(".sqs_trigger" + ext) || path.endsWith(".gcp_trigger" + ext) || path.endsWith(".email_trigger" + ext)) - ) + ) { continue; + } if (!skips.includeUsers && path.endsWith(".user" + ext)) continue; if (!skips.includeGroups && path.endsWith(".group" + ext)) continue; if (!skips.includeSettings && path === "settings" + ext) continue; if (!skips.includeKey && path === "encryption_key") continue; if (skips.skipResources && path.endsWith(".resource" + ext)) continue; - if (skips.skipResourceTypes && path.endsWith(".resource-type" + ext)) + if (skips.skipResourceTypes && path.endsWith(".resource-type" + ext)) { continue; + } // Use getTypeStrFromPath for consistent type detection @@ -715,35 +880,6 @@ export async function elementsToMap( // If getTypeStrFromPath can't determine the type, continue processing the file } - if (skips.skipResources && isFileResource(path)) continue; - - if ( - ![ - "json", - "yaml", - "go", - "sh", - "ts", - "py", - "sql", - "gql", - "ps1", - "php", - "js", - "lock", - "rs", - "cs", - "yml", - "nu", - "java", - "rb", - "in", // Python requirements.in files - "mod", // Go go.mod files - // for related places search: ADD_NEW_LANG - ].includes(path.split(".").pop() ?? "") && - !isFileResource(path) - ) - continue; // Handle branch-specific files - skip files for other branches if (specificItems && isBranchSpecificFile(path)) { @@ -953,6 +1089,7 @@ async function compareDynFSElement( const remoteCodebase: Record = {}; for (const [k] of Object.entries(m2)) { + if (m1[k] === undefined) { if ( !ignoreMetadataDeletion || @@ -960,7 +1097,7 @@ async function compareDynFSElement( ) { changes.push({ name: "deleted", path: k }); } else if (k?.endsWith(".script.yaml")) { - let o = parseYaml(k, m2[k]); + const o = parseYaml(k, m2[k]); if (o.codebase != undefined) { remoteCodebase[k] = o.codebase; } @@ -1065,7 +1202,8 @@ const isNotWmillFile = (p: string, isDirectory: boolean) => { !p.startsWith("f" + SEP) && !p.startsWith("g" + SEP) && !p.startsWith("users" + SEP) && - !p.startsWith("groups" + SEP) + !p.startsWith("groups" + SEP) && + !p.startsWith("dependencies" + SEP) ); } @@ -1083,7 +1221,8 @@ const isNotWmillFile = (p: string, isDirectory: boolean) => { !p.startsWith("f" + SEP) && !p.startsWith("g" + SEP) && !p.startsWith("users" + SEP) && - !p.startsWith("groups" + SEP) + !p.startsWith("groups" + SEP) && + !p.startsWith("dependencies" + SEP) ); } } catch { @@ -1099,7 +1238,8 @@ export const isWhitelisted = (p: string) => { p == "f" || p == "g" || p == "users" || - p == "groups" + p == "groups" || + p == "dependencies" ); }; @@ -1187,22 +1327,31 @@ interface ChangeTracker { scripts: string[]; flows: string[]; apps: string[]; + rawApps: string[]; } +const FLOW_EXT = ".flow" + SEP; +const APP_EXT = ".app" + SEP; +const RAW_APP_EXT = ".raw_app" + SEP; // deno-lint-ignore no-inner-declarations async function addToChangedIfNotExists(p: string, tracker: ChangeTracker) { const isScript = exts.some((e) => p.endsWith(e)); if (isScript) { - if (p.includes(".flow" + SEP)) { - const folder = p.substring(0, p.indexOf(".flow" + SEP)) + ".flow" + SEP; + if (p.includes(FLOW_EXT)) { + const folder = p.substring(0, p.indexOf(FLOW_EXT)) + FLOW_EXT; if (!tracker.flows.includes(folder)) { tracker.flows.push(folder); } - } else if (p.includes(".app" + SEP)) { - const folder = p.substring(0, p.indexOf(".app" + SEP)) + ".app" + SEP; + } else if (p.includes(APP_EXT)) { + const folder = p.substring(0, p.indexOf(APP_EXT)) + APP_EXT; if (!tracker.apps.includes(folder)) { tracker.apps.push(folder); } + } else if (p.includes(RAW_APP_EXT)) { + const folder = p.substring(0, p.indexOf(RAW_APP_EXT)) + RAW_APP_EXT; + if (!tracker.rawApps.includes(folder)) { + tracker.rawApps.push(folder); + } } else { if (!tracker.scripts.includes(p)) { tracker.scripts.push(p); @@ -1225,6 +1374,7 @@ async function buildTracker(changes: Change[]) { scripts: [], flows: [], apps: [], + rawApps: [], }; for (const change of changes) { if (change.name == "added" || change.name == "edited") { @@ -1330,19 +1480,7 @@ export async function pull( log.info( `remote (${workspace.name}) -> local: ${changes.length} changes to apply` ); - - - // Debug: show all changes for push operation - if (changes.length > 0) { - log.info("All changes:"); - changes.forEach(change => { - if (change.path.startsWith("dependencies/")) { - log.info(` ${change.name}: ${change.path} [WORKSPACE DEPS]`); - } else { - log.info(` ${change.name}: ${change.path}`); - } - }); - } + // Handle JSON output for dry-run if (opts.dryRun && opts.jsonOutput) { @@ -1552,9 +1690,20 @@ export async function pull( } if (tracker.apps.length > 0) { log.info( - `Apps ${tracker.apps.join( - ", " - )} scripts were changed but ignoring for now` + colors.gray( + `Apps ${tracker.apps.join( + ", " + )} inline scripts were changed but ignoring metadata regeneration for now` + ) + ); + } + if (tracker.rawApps.length > 0) { + log.info( + colors.gray( + `Raw apps ${tracker.rawApps.join( + ", " + )} inline scripts were changed but ignoring metadata regeneration for now` + ) ); } if (opts.jsonOutput) { @@ -1947,10 +2096,19 @@ export async function push( while (queue.length > 0 || pool.size > 0) { // Fill the pool until we reach parallelizationFactor while (pool.size < parallelizationFactor && queue.length > 0) { - const [_basePath, changes] = queue.shift()!; + let [_basePath, changes] = queue.shift()!; const promise = (async () => { const alreadySynced: string[] = []; - + const isRawApp = isRawAppFile(changes[0].path); + if (isRawApp) { + const deleteRawApp = changes.find(change => change.name === "deleted" && change.path.endsWith(".raw_app/raw_app.yaml")) + if (deleteRawApp) { + changes = [deleteRawApp]; + } else { + changes.splice(1, changes.length - 1); + } + } + for await (const change of changes) { let stateTarget = undefined; if (stateful) { @@ -2169,6 +2327,14 @@ export async function push( path: removeSuffix(target, ".app/app.json"), }); break; + case "raw_app": + if (target.endsWith(".raw_app/raw_app.yaml") || target.endsWith(".raw_app/raw_app.json")) { + await wmill.deleteApp({ + workspace: workspaceId, + path: removeSuffix(target, ".raw_app/raw_app.json"), + }); + } + break; case "schedule": await wmill.deleteSchedule({ workspace: workspaceId, @@ -2355,7 +2521,6 @@ const command = new Command() .description( "sync local with a remote workspaces or the opposite (push or pull)" ) - .action(() => log.info("2 actions available, pull and push. Use -h to display help.") ) diff --git a/cli/src/commands/workspace/workspace.ts b/cli/src/commands/workspace/workspace.ts index cf74a1f4da..9c13f6b66c 100644 --- a/cli/src/commands/workspace/workspace.ts +++ b/cli/src/commands/workspace/workspace.ts @@ -1,8 +1,19 @@ // deno-lint-ignore-file no-explicit-any import { GlobalOptions } from "../../types.ts"; -import { getActiveWorkspaceConfigFilePath, getWorkspaceConfigFilePath } from "../../../windmill-utils-internal/src/config/config.ts"; +import { + getActiveWorkspaceConfigFilePath, + getWorkspaceConfigFilePath, +} from "../../../windmill-utils-internal/src/config/config.ts"; import { loginInteractive, tryGetLoginInfo } from "../../core/login.ts"; -import { colors, Command, Confirm, Input, log, setClient, Table } from "../../../deps.ts"; +import { + colors, + Command, + Confirm, + Input, + log, + setClient, + Table, +} from "../../../deps.ts"; import { requireLogin } from "../../core/auth.ts"; import { createWorkspaceFork, deleteWorkspaceFork } from "./fork.ts"; @@ -15,7 +26,9 @@ export interface Workspace { token: string; } -export async function allWorkspaces(configDirOverride?: string): Promise { +export async function allWorkspaces( + configDirOverride?: string +): Promise { try { const file = await getWorkspaceConfigFilePath(configDirOverride); const txt = await Deno.readTextFile(file); @@ -119,15 +132,21 @@ async function switchC(opts: GlobalOptions, workspaceName: string) { } await setActiveWorkspace(workspaceName, opts.configDir); + const workspace = await getWorkspaceByName(workspaceName, opts.configDir); + log.info( + colors.green.bold( + `Switched to workspace ${workspaceName} (${workspace?.workspaceId} on ${workspace?.remote})` + ) + ); return; } -export async function setActiveWorkspace(workspaceName: string, configDirOverride?: string) { +export async function setActiveWorkspace( + workspaceName: string, + configDirOverride?: string +) { const file = await getActiveWorkspaceConfigFilePath(configDirOverride); - await Deno.writeTextFile( - file, - workspaceName - ); + await Deno.writeTextFile(file, workspaceName); } export async function add( @@ -264,26 +283,44 @@ export async function addWorkspace(workspace: Workspace, opts: any) { // Check for conflicts before adding const existingWorkspaces = await allWorkspaces(opts.configDir); - const isInteractive = Deno.stdin.isTerminal() && Deno.stdout.isTerminal() && !opts.force; + const isInteractive = + Deno.stdin.isTerminal() && Deno.stdout.isTerminal() && !opts.force; // Check 1: Workspace name already exists - const nameConflict = existingWorkspaces.find(w => w.name === workspace.name); + const nameConflict = existingWorkspaces.find( + (w) => w.name === workspace.name + ); if (nameConflict) { // If it's the exact same workspace (same remote + workspaceId), just update the token - if (nameConflict.remote === workspace.remote && nameConflict.workspaceId === workspace.workspaceId) { - log.info(colors.yellow(`Updating token for existing workspace "${workspace.name}"`)); + if ( + nameConflict.remote === workspace.remote && + nameConflict.workspaceId === workspace.workspaceId + ) { + log.info( + colors.yellow( + `Updating token for existing workspace "${workspace.name}"` + ) + ); } else { // Different remote or workspaceId - this is a conflict - log.info(colors.red.bold(`❌ Workspace name "${workspace.name}" already exists!`)); - log.info(` Existing: ${nameConflict.workspaceId} on ${nameConflict.remote}`); + log.info( + colors.red.bold(`❌ Workspace name "${workspace.name}" already exists!`) + ); + log.info( + ` Existing: ${nameConflict.workspaceId} on ${nameConflict.remote}` + ); log.info(` New: ${workspace.workspaceId} on ${workspace.remote}`); if (!isInteractive) { // In non-interactive mode (tests, scripts), auto-overwrite with force flag if (opts.force) { - log.info(colors.yellow("Force flag enabled, overwriting existing workspace.")); + log.info( + colors.yellow("Force flag enabled, overwriting existing workspace.") + ); } else { - throw new Error("Workspace name conflict. Use --force to overwrite or choose a different name."); + throw new Error( + "Workspace name conflict. Use --force to overwrite or choose a different name." + ); } } else { const overwrite = await Confirm.prompt({ @@ -364,7 +401,9 @@ async function bind( opts: GlobalOptions & { branch?: string }, bindWorkspace?: boolean ) { - const { isGitRepository, getCurrentGitBranch } = await import("../../utils/git.ts"); + const { isGitRepository, getCurrentGitBranch } = await import( + "../../utils/git.ts" + ); if (!isGitRepository()) { log.error(colors.red("Not in a Git repository")); @@ -382,13 +421,19 @@ async function bind( const activeWorkspace = await getActiveWorkspace(opts); if (!activeWorkspace && bindWorkspace) { - log.error(colors.red("No active workspace. Use 'wmill workspace add' or 'wmill workspace switch' first")); + log.error( + colors.red( + "No active workspace. Use 'wmill workspace add' or 'wmill workspace switch' first" + ) + ); return; } // For unbind, check if branch exists if (!bindWorkspace && (!config.gitBranches || !config.gitBranches[branch])) { - log.error(colors.red(`Branch '${branch}' not found in wmill.yaml gitBranches`)); + log.error( + colors.red(`Branch '${branch}' not found in wmill.yaml gitBranches`) + ); return; } @@ -404,16 +449,20 @@ async function bind( config.gitBranches[branch].baseUrl = activeWorkspace.remote; config.gitBranches[branch].workspaceId = activeWorkspace.workspaceId; - log.info(colors.green( - `✓ Bound branch '${branch}' to workspace '${activeWorkspace.name}'\n` + - ` ${activeWorkspace.workspaceId} on ${activeWorkspace.remote}` - )); + log.info( + colors.green( + `✓ Bound branch '${branch}' to workspace '${activeWorkspace.name}'\n` + + ` ${activeWorkspace.workspaceId} on ${activeWorkspace.remote}` + ) + ); } else { // Unbind delete config.gitBranches[branch].baseUrl; delete config.gitBranches[branch].workspaceId; - log.info(colors.green(`✓ Removed workspace binding from branch '${branch}'`)); + log.info( + colors.green(`✓ Removed workspace binding from branch '${branch}'`) + ); } // Write back the updated config diff --git a/cli/src/core/conf.ts b/cli/src/core/conf.ts index 6138e2b264..d56a97c5b4 100644 --- a/cli/src/core/conf.ts +++ b/cli/src/core/conf.ts @@ -100,7 +100,7 @@ export interface Codebase { external?: string[]; define?: { [key: string]: string }; inject?: string[]; - loader?: any, + loader?: any; format?: "cjs" | "esm"; banner?: string | { js?: string }; } @@ -117,6 +117,7 @@ function getGitRepoRoot(): string | null { } } +export const GLOBAL_CONFIG_OPT = { noCdToRoot: false }; function findWmillYaml(): string | null { const startDir = resolve(Deno.cwd()); const isInGitRepo = isGitRepository(); @@ -155,7 +156,11 @@ function findWmillYaml(): string | null { } // If wmill.yaml was found in a parent directory, warn the user and change working directory - if (foundPath && resolve(dirname(foundPath)) !== resolve(startDir)) { + if ( + !GLOBAL_CONFIG_OPT.noCdToRoot && + foundPath && + resolve(dirname(foundPath)) !== resolve(startDir) + ) { const configDir = dirname(foundPath); const relativePath = relative(startDir, foundPath); log.warn(`⚠️ wmill.yaml found in parent directory: ${relativePath}`); diff --git a/cli/src/types.ts b/cli/src/types.ts index 1e8fd6a9eb..982e1eb320 100644 --- a/cli/src/types.ts +++ b/cli/src/types.ts @@ -1,11 +1,11 @@ // deno-lint-ignore-file no-explicit-any import { - Diff, - SEP, colors, + Diff, log, path, + SEP, yamlParseContent, yamlStringify, } from "../deps.ts"; @@ -24,6 +24,7 @@ import { pushGroup } from "./commands/user/user.ts"; import { pushWorkspaceDependencies } from "./commands/dependencies/dependencies.ts"; import { pushWorkspaceSettings, pushWorkspaceKey } from "./core/settings.ts"; import { pushTrigger } from "./commands/trigger/trigger.ts"; +import { pushRawApp } from "./commands/app/raw_apps.ts"; export interface DifferenceCreate { type: "CREATE"; @@ -47,15 +48,15 @@ export interface DifferenceChange { export type Difference = DifferenceCreate | DifferenceRemove | DifferenceChange; export const TRIGGER_TYPES = [ - 'http', - 'websocket', - 'kafka', - 'nats', - 'postgres', - 'mqtt', - 'sqs', - 'gcp', - 'email', + "http", + "websocket", + "kafka", + "nats", + "postgres", + "mqtt", + "sqs", + "gcp", + "email", ] as const; export type GlobalOptions = { @@ -150,6 +151,9 @@ export async function pushObj( if (typeEnding === "app") { const appName = p.split(".app" + SEP)[0]; await pushApp(workspace, appName, appName + ".app", message); + } else if (typeEnding === "raw_app") { + const rawAppName = p.split(".raw_app" + SEP)[0]; + await pushRawApp(workspace, rawAppName, rawAppName + ".raw_app", message); } else if (typeEnding === "folder") { await pushFolder(workspace, p, befObj, newObj); } else if (typeEnding === "variable") { @@ -229,6 +233,7 @@ export function getTypeStrFromPath( | "resource-type" | "folder" | "app" + | "raw_app" | "schedule" | "http_trigger" | "websocket_trigger" @@ -250,6 +255,9 @@ export function getTypeStrFromPath( if (p.includes(".app" + SEP)) { return "app"; } + if (p.includes(".raw_app" + SEP)) { + return "raw_app"; + } if (p.startsWith("dependencies" + SEP)) { return "workspace_dependencies"; } diff --git a/cli/src/utils/metadata.ts b/cli/src/utils/metadata.ts index 97cc76e504..e52830ba6a 100644 --- a/cli/src/utils/metadata.ts +++ b/cli/src/utils/metadata.ts @@ -1,43 +1,22 @@ // deno-lint-ignore-file no-explicit-any import { GlobalOptions } from "../types.ts"; -import { - SEP, - colors, - log, - path, - yamlParseFile, - yamlStringify, -} from "../../deps.ts"; +import { SEP, colors, log, yamlParseFile, yamlStringify } from "../../deps.ts"; import { ScriptMetadata, defaultScriptMetadata, } from "../../bootstrap/script_bootstrap.ts"; import { Workspace } from "../commands/workspace/workspace.ts"; import { - workspaceDependenciesLanguages, - WorkspaceDependenciesLanguage, ScriptLanguage, + workspaceDependenciesLanguages, } from "./script_common.ts"; import { inferContentTypeFromFilePath } from "./script_common.ts"; -import { exts } from "../commands/script/script.ts"; -import { - FSFSElement, - findCodebase, - yamlOptions, -} from "../commands/sync/sync.ts"; -import { - generateHash, - readInlinePathSync, - getHeaders, - writeIfChanged, -} from "./utils.ts"; +import { findCodebase, yamlOptions } from "../commands/sync/sync.ts"; +import { generateHash, readInlinePathSync, getHeaders } from "./utils.ts"; + import { SyncCodebase } from "./codebase.ts"; -import { FlowFile } from "../commands/flow/flow.ts"; -import { replaceInlineScripts } from "../../windmill-utils-internal/src/inline-scripts/replacer.ts"; -import { extractInlineScripts as extractInlineScriptsForFlows } from "../../windmill-utils-internal/src/inline-scripts/extractor.ts"; import { argSigToJsonSchemaType } from "../../windmill-utils-internal/src/parse/parse-schema.ts"; import { getIsWin } from "./utils.ts"; -import { FlowValue } from "../../gen/types.gen.ts"; export class LockfileGenerationError extends Error { constructor(message: string) { @@ -90,131 +69,6 @@ export function workspaceDependenciesPathToLanguageAndFilename(path: string): { } } -const TOP_HASH = "__flow_hash"; -async function generateFlowHash( - rawWorkspaceDependencies: Record, - folder: string, - defaultTs: "bun" | "deno" | undefined -) { - const elems = await FSFSElement(path.join(Deno.cwd(), folder), [], true); - const hashes: Record = {}; - for await (const f of elems.getChildren()) { - if (exts.some((e) => f.path.endsWith(e))) { - // Embed workspace dependencies into hash - hashes[f.path] = await generateHash( - (await f.getContentText()) + JSON.stringify(rawWorkspaceDependencies) - ); - } - } - return { ...hashes, [TOP_HASH]: await generateHash(JSON.stringify(hashes)) }; -} -export async function generateFlowLockInternal( - folder: string, - dryRun: boolean, - workspace: Workspace, - opts: GlobalOptions & { - defaultTs?: "bun" | "deno"; - }, - justUpdateMetadataLock?: boolean, - noStaleMessage?: boolean -): Promise { - if (folder.endsWith(SEP)) { - folder = folder.substring(0, folder.length - 1); - } - const remote_path = folder - .replaceAll(SEP, "/") - .substring(0, folder.length - ".flow".length); - if (!justUpdateMetadataLock && !noStaleMessage) { - log.info(`Generating lock for flow ${folder} at ${remote_path}`); - } - - // Always get out-of-sync workspace dependencies - let rawWorkspaceDependencies: Record = await getRawWorkspaceDependencies(); - let hashes = await generateFlowHash(rawWorkspaceDependencies, folder, opts.defaultTs); - - const conf = await readLockfile(); - if (await checkifMetadataUptodate(folder, hashes[TOP_HASH], conf, TOP_HASH)) { - if (!noStaleMessage) { - log.info( - colors.green(`Flow ${remote_path} metadata is up-to-date, skipping`) - ); - } - return; - } else if (dryRun) { - return remote_path; - } - - if (Object.keys(rawWorkspaceDependencies).length > 0) { - log.info( - (await blueColor())( - `Found workspace dependencies (${workspaceDependenciesLanguages - .map((l) => l.filename) - .join("/")}) for ${folder}, using them` - ) - ); - } - - const flowValue = (await yamlParseFile( - folder! + SEP + "flow.yaml" - )) as FlowFile; - - if (!justUpdateMetadataLock) { - const changedScripts = []; - //find hashes that do not correspond to previous hashes - for (const [path, hash] of Object.entries(hashes)) { - if (path == TOP_HASH) { - continue; - } - if (!(await checkifMetadataUptodate(folder, hash, conf, path))) { - changedScripts.push(path); - } - } - - log.info(`Recomputing locks of ${changedScripts.join(", ")} in ${folder}`); - await replaceInlineScripts( - flowValue.value.modules, - async (path: string) => await Deno.readTextFile(folder + SEP + path), - log, - folder + SEP!, - SEP, - changedScripts, - // (path: string, newPath: string) => Deno.renameSync(path, newPath), - // (path: string) => Deno.removeSync(path) - ); - - //removeChangedLocks - flowValue.value = await updateFlow( - workspace, - flowValue.value, - remote_path, - rawWorkspaceDependencies - ); - - const inlineScripts = extractInlineScriptsForFlows( - flowValue.value.modules, - {}, - SEP, - opts.defaultTs - ); - inlineScripts.forEach((s) => { - writeIfChanged(Deno.cwd() + SEP + folder + SEP + s.path, s.content); - }); - - // Overwrite `flow.yaml` with the new lockfile references - writeIfChanged( - Deno.cwd() + SEP + folder + SEP + "flow.yaml", - yamlStringify(flowValue as Record) - ); - } - - hashes = await generateFlowHash(rawWorkspaceDependencies, folder, opts.defaultTs); - await clearGlobalLock(folder); - for (const [path, hash] of Object.entries(hashes)) { - await updateMetadataGlobalLock(folder, hash, path); - } - log.info(colors.green(`Flow ${remote_path} lockfiles updated`)); -} - // on windows, when using powershell, blue is not readable export async function blueColor(): Promise<(x: string) => void> { const isWin = await getIsWin(); @@ -448,88 +302,6 @@ async function updateScriptLock( } } -export async function updateFlow( - workspace: Workspace, - flow_value: FlowValue, - remotePath: string, - rawWorkspaceDependencies: Record -): Promise { - let rawResponse; - - if (Object.keys(rawWorkspaceDependencies).length > 0) { - log.info(colors.blue("Using raw workspace dependencies for flow dependencies")); - - // generate the script lock running a dependency job in Windmill and update it inplace - const extraHeaders = getHeaders(); - rawResponse = await fetch( - `${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/flow_dependencies`, - { - method: "POST", - headers: { - Cookie: `token=${workspace.token}`, - "Content-Type": "application/json", - ...extraHeaders, - }, - body: JSON.stringify({ - flow_value, - path: remotePath, - use_local_lockfiles: true, - raw_workspace_dependencies: Object.keys(rawWorkspaceDependencies).length > 0 - ? rawWorkspaceDependencies - : null, - }), - } - ); - } else { - // Standard dependency resolution on the server - const extraHeaders = getHeaders(); - rawResponse = await fetch( - `${workspace.remote}api/w/${workspace.workspaceId}/jobs/run/flow_dependencies`, - { - method: "POST", - headers: { - Cookie: `token=${workspace.token}`, - "Content-Type": "application/json", - ...extraHeaders, - }, - body: JSON.stringify({ - flow_value, - path: remotePath, - }), - } - ); - } - - let responseText = "reading response failed"; - try { - const res = (await rawResponse.json()) as - | { updated_flow_value: any } - | { error: { message: string } } - | undefined; - if (rawResponse.status != 200) { - const msg = (res as any)?.["error"]?.["message"]; - if (msg) { - throw new LockfileGenerationError( - `Failed to generate lockfile: ${msg}` - ); - } - throw new LockfileGenerationError( - `Failed to generate lockfile: ${rawResponse.statusText}, ${responseText}` - ); - } - return (res as any).updated_flow_value; - } catch (e) { - try { - responseText = await rawResponse.text(); - } catch { - responseText = ""; - } - throw new Error( - `Failed to generate lockfile. Status was: ${rawResponse.statusText}, ${responseText}, ${e}` - ); - } -} - //////////////////////////////////////////////////////////////////////////////////////////// // below functions copied from Windmill's FE inferArgs function. TODO: refactor // //////////////////////////////////////////////////////////////////////////////////////////// @@ -773,8 +545,7 @@ export async function parseMetadataFile( rawWorkspaceDependencies: Record; codebases: SyncCodebase[] }) - | undefined, - + | undefined ): Promise<{ isJson: boolean; payload: any; path: string }> { let metadataFilePath = scriptPath + ".script.json"; try { diff --git a/cli/src/utils/utils.ts b/cli/src/utils/utils.ts index 3d33a53405..bda8dea78a 100644 --- a/cli/src/utils/utils.ts +++ b/cli/src/utils/utils.ts @@ -2,7 +2,7 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck This file is copied from a JS project, so it's not type-safe. -import { colors, log, encodeHex, SEP } from "../../deps.ts"; +import { colors, encodeHex, log, SEP } from "../../deps.ts"; import crypto from "node:crypto"; export function deepEqual(a: T, b: T): boolean { @@ -15,7 +15,7 @@ export function deepEqual(a: T, b: T): boolean { if (Array.isArray(a)) { length = a.length; if (length != b.length) return false; - for (i = length; i-- !== 0;) { + for (i = length; i-- !== 0; ) { if (!deepEqual(a[i], b[i])) return false; } return true; @@ -43,7 +43,7 @@ export function deepEqual(a: T, b: T): boolean { if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) { length = a.length; if (length != b.length) return false; - for (i = length; i-- !== 0;) { + for (i = length; i-- !== 0; ) { if (a[i] !== b[i]) return false; } return true; @@ -66,11 +66,11 @@ export function deepEqual(a: T, b: T): boolean { length = keys.length; if (length !== Object.keys(b).length) return false; - for (i = length; i-- !== 0;) { + for (i = length; i-- !== 0; ) { if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; } - for (i = length; i-- !== 0;) { + for (i = length; i-- !== 0; ) { const key = keys[i]; if (!deepEqual(a[key], b[key])) return false; } @@ -127,7 +127,12 @@ export async function generateHashFromBuffer( // } export function readInlinePathSync(path: string): string { - return Deno.readTextFileSync(path.replaceAll("/", SEP)); + try { + return Deno.readTextFileSync(path.replaceAll("/", SEP)); + } catch (error) { + log.warn(`Error reading inline path: ${path}, ${error}`); + return ""; + } } export function sleep(ms: number) { @@ -145,15 +150,20 @@ export function isFileResource(path: string): boolean { ); } +export function isRawAppFile(path: string): boolean { + return path.includes(".raw_app" + SEP) ; +} + export function isWorkspaceDependencies(path: string): boolean { return path.startsWith("dependencies/") } export function printSync(input: string | Uint8Array, to = Deno.stdout) { - let bytesWritten = 0 - const bytes = typeof input === 'string' ? new TextEncoder().encode(input) : input + let bytesWritten = 0; + const bytes = + typeof input === "string" ? new TextEncoder().encode(input) : input; while (bytesWritten < bytes.length) { - bytesWritten += to.writeSync(bytes.subarray(bytesWritten)) + bytesWritten += to.writeSync(bytes.subarray(bytesWritten)); } } @@ -172,7 +182,10 @@ export async function selectRepository( } if (repositories.length === 1) { - const repoPath = repositories[0].git_repo_resource_path.replace(/^\$res:/, ""); + const repoPath = repositories[0].git_repo_resource_path.replace( + /^\$res:/, + "" + ); log.info(colors.cyan(`Auto-selected repository: ${colors.bold(repoPath)}`)); return repositories[0]; } @@ -181,24 +194,34 @@ export async function selectRepository( const isInteractive = Deno.stdin.isTerminal() && Deno.stdout.isTerminal(); if (!isInteractive) { - const repoPaths = repositories.map(r => r.git_repo_resource_path.replace(/^\$res:/, "")); - throw new Error(`Multiple repositories found: ${repoPaths.join(', ')}. Use --repository to specify which one to ${operation || 'use'}.`); + const repoPaths = repositories.map((r) => + r.git_repo_resource_path.replace(/^\$res:/, "") + ); + throw new Error( + `Multiple repositories found: ${repoPaths.join( + ", " + )}. Use --repository to specify which one to ${operation || "use"}.` + ); } // Import Select dynamically to avoid dependency issues const { Select } = await import("../../deps.ts"); - console.log(`\nMultiple repositories found. Please select which repository to ${operation || 'use'}:\n`); + console.log( + `\nMultiple repositories found. Please select which repository to ${ + operation || "use" + }:\n` + ); const selectedRepo = await Select.prompt({ - message: `Select repository for ${operation || 'operation'}:`, + message: `Select repository for ${operation || "operation"}:`, options: repositories.map((repo, index) => { const displayPath = repo.git_repo_resource_path.replace(/^\$res:/, ""); return { name: `${index + 1}. ${displayPath}`, - value: repo.git_repo_resource_path + value: repo.git_repo_resource_path, }; - }) + }), }); return repositories.find((r) => r.git_repo_resource_path === selectedRepo)!; @@ -216,7 +239,7 @@ export async function getIsWin(): Promise { /** * Writes content to a file only if it differs from existing content. * Creates parent directories if they don't exist. - * + * * @param path - The file path to write to * @param content - The content to write * @returns true if file was written, false if skipped (content unchanged) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d5360c5257..a4aee1c84e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -118,6 +118,7 @@ "autoprefixer": "^10.4.13", "cssnano": "^6.0.1", "d3-dag": "^0.11.5", + "dts-bundle-generator": "^9.5.1", "eslint": "^8.47.0", "eslint-config-prettier": "^8.6.0", "eslint-plugin-svelte": "^2.45.1", @@ -143,7 +144,6 @@ "tslib": "^2.6.1", "typescript": "^5.5.0", "vite": "npm:rolldown-vite@7.2.8", - "vite-plugin-dts": "^4.5.4", "vite-plugin-mkcert": "^1.17.5", "yootils": "^0.3.1" }, @@ -267,42 +267,17 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/runtime": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", @@ -312,20 +287,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@codingame/monaco-vscode-05a2a821-e4de-5941-b7f9-bbf01c09f229-common": { "version": "21.6.0", "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-05a2a821-e4de-5941-b7f9-bbf01c09f229-common/-/monaco-vscode-05a2a821-e4de-5941-b7f9-bbf01c09f229-common-21.6.0.tgz", @@ -1903,40 +1864,6 @@ "@csstools/css-tokenizer": "^2.4.1" } }, - "node_modules/@emnapi/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", - "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -2416,154 +2343,6 @@ "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.118" } }, - "node_modules/@microsoft/api-extractor": { - "version": "7.55.0", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.55.0.tgz", - "integrity": "sha512-TYc5OtAK/9E3HGgd2bIfSjQDYIwPc0dysf9rPiwXZGsq916I6W2oww9bhm1OxPOeg6rMfOX3PoroGd7oCryYog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@microsoft/api-extractor-model": "7.32.0", - "@microsoft/tsdoc": "~0.16.0", - "@microsoft/tsdoc-config": "~0.18.0", - "@rushstack/node-core-library": "5.18.0", - "@rushstack/rig-package": "0.6.0", - "@rushstack/terminal": "0.19.3", - "@rushstack/ts-command-line": "5.1.3", - "diff": "~8.0.2", - "lodash": "~4.17.15", - "minimatch": "10.0.3", - "resolve": "~1.22.1", - "semver": "~7.5.4", - "source-map": "~0.6.1", - "typescript": "5.8.2" - }, - "bin": { - "api-extractor": "bin/api-extractor" - } - }, - "node_modules/@microsoft/api-extractor-model": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.32.0.tgz", - "integrity": "sha512-QIVJSreb8fGGJy1Qx0yzGVXxvHJN1WXgkFNHFheVv1iBJNqgvp+xeT3ienJmRwXmPPc5Es/cxBrXtKZJR3i7iw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@microsoft/tsdoc": "~0.16.0", - "@microsoft/tsdoc-config": "~0.18.0", - "@rushstack/node-core-library": "5.18.0" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/diff": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz", - "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/typescript": { - "version": "5.8.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", - "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/@microsoft/tsdoc": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", - "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@microsoft/tsdoc-config": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.18.0.tgz", - "integrity": "sha512-8N/vClYyfOH+l4fLkkr9+myAoR6M7akc8ntBJ4DJdWH2b09uVfr71+LTMpNyG19fNqWDg8KEDZhx5wxuqHyGjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@microsoft/tsdoc": "0.16.0", - "ajv": "~8.12.0", - "jju": "~1.4.0", - "resolve": "~1.22.2" - } - }, - "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", - "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.5.0", - "@emnapi/runtime": "^1.5.0", - "@tybys/wasm-util": "^0.10.1" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2678,91 +2457,6 @@ "integrity": "sha512-dSMyuNPN2k+tFeNZ0+QJ7S1zDJ0UeNL+lpnPFR9K5avj2V4uG4m6FdjrApQ9Zi35AIocaDp/KGfBD9gR5MLUbQ==", "license": "SEE LICENSE IN LICENSE" }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-beta.52", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.52.tgz", - "integrity": "sha512-MBGIgysimZPqTDcLXI+i9VveijkP5C3EAncEogXhqfax6YXj1Tr2LY3DVuEOMIjWfMPMhtQSPup4fSTAmgjqIw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-beta.52", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.52.tgz", - "integrity": "sha512-MmKeoLnKu1d9j6r19K8B+prJnIZ7u+zQ+zGQ3YHXGnr41rzE3eqQLovlkvoZnRoxDGPA4ps0pGiwXy6YE3lJyg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-beta.52", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.52.tgz", - "integrity": "sha512-qpHedvQBmIjT8zdnjN3nWPR2qjQyJttbXniCEKKdHeAbZG9HyNPBUzQF7AZZGwmS9coQKL+hWg9FhWzh2dZ2IA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-beta.52", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.52.tgz", - "integrity": "sha512-dDp7WbPapj/NVW0LSiH/CLwMhmLwwKb3R7mh2kWX+QW85X1DGVnIEyKh9PmNJjB/+suG1dJygdtdNPVXK1hylg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-beta.52", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.52.tgz", - "integrity": "sha512-9e4l6vy5qNSliDPqNfR6CkBOAx6PH7iDV4OJiEJzajajGrVy8gc/IKKJUsoE52G8ud8MX6r3PMl97NfwgOzB7g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-linux-arm64-gnu": { "version": "1.0.0-beta.52", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.52.tgz", @@ -2780,142 +2474,6 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-beta.52", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.52.tgz", - "integrity": "sha512-ENLmSQCWqSA/+YN45V2FqTIemg7QspaiTjlm327eUAMeOLdqmSOVVyrQexJGNTQ5M8sDYCgVAig2Kk01Ggmqaw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-beta.52", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.52.tgz", - "integrity": "sha512-klahlb2EIFltSUubn/VLjuc3qxp1E7th8ukayPfdkcKvvYcQ5rJztgx8JsJSuAKVzKtNTqUGOhy4On71BuyV8g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-beta.52", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.52.tgz", - "integrity": "sha512-UuA+JqQIgqtkgGN2c/AQ5wi8M6mJHrahz/wciENPTeI6zEIbbLGoth5XN+sQe2pJDejEVofN9aOAp0kaazwnVg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-beta.52", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.52.tgz", - "integrity": "sha512-1BNQW8u4ro8bsN1+tgKENJiqmvc+WfuaUhXzMImOVSMw28pkBKdfZtX2qJPADV3terx+vNJtlsgSGeb3+W6Jiw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-beta.52", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.52.tgz", - "integrity": "sha512-K/p7clhCqJOQpXGykrFaBX2Dp9AUVIDHGc+PtFGBwg7V+mvBTv/tsm3LC3aUmH02H2y3gz4y+nUTQ0MLpofEEg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-beta.52", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.52.tgz", - "integrity": "sha512-a4EkXBtnYYsKipjS7QOhEBM4bU5IlR9N1hU+JcVEVeuTiaslIyhWVKsvf7K2YkQHyVAJ+7/A9BtrGqORFcTgng==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-ia32-msvc": { - "version": "1.0.0-beta.52", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.52.tgz", - "integrity": "sha512-5ZXcYyd4GxPA6QfbGrNcQjmjbuLGvfz6728pZMsQvGHI+06LT06M6TPtXvFvLgXtexc+OqvFe1yAIXJU1gob/w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-beta.52", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.52.tgz", - "integrity": "sha512-tzpnRQXJrSzb8Z9sm97UD3cY0toKOImx+xRKsDLX4zHaAlRXWh7jbaKBePJXEN7gNw7Nm03PBNwphdtA8KSUYQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.52", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.52.tgz", @@ -2923,53 +2481,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", - "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", + "version": "4.53.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", "cpu": [ "x64" ], @@ -2979,169 +2494,6 @@ "linux" ] }, - "node_modules/@rushstack/node-core-library": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.18.0.tgz", - "integrity": "sha512-XDebtBdw5S3SuZIt+Ra2NieT8kQ3D2Ow1HxhDQ/2soinswnOu9e7S69VSwTOLlQnx5mpWbONu+5JJjDxMAb6Fw==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "~8.13.0", - "ajv-draft-04": "~1.0.0", - "ajv-formats": "~3.0.1", - "fs-extra": "~11.3.0", - "import-lazy": "~4.0.0", - "jju": "~1.4.0", - "resolve": "~1.22.1", - "semver": "~7.5.4" - }, - "peerDependencies": { - "@types/node": "*" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@rushstack/node-core-library/node_modules/ajv": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@rushstack/node-core-library/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@rushstack/node-core-library/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@rushstack/node-core-library/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "license": "ISC" - }, - "node_modules/@rushstack/problem-matcher": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@rushstack/problem-matcher/-/problem-matcher-0.1.1.tgz", - "integrity": "sha512-Fm5XtS7+G8HLcJHCWpES5VmeMyjAKaWeyZU5qPzZC+22mPlJzAsOxymHiWIfuirtPckX3aptWws+K2d0BzniJA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/node": "*" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@rushstack/rig-package": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.6.0.tgz", - "integrity": "sha512-ZQmfzsLE2+Y91GF15c65L/slMRVhF6Hycq04D4TwtdGaUAbIXXg9c5pKA5KFU7M4QMaihoobp9JJYpYcaY3zOw==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve": "~1.22.1", - "strip-json-comments": "~3.1.1" - } - }, - "node_modules/@rushstack/terminal": { - "version": "0.19.3", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.19.3.tgz", - "integrity": "sha512-0P8G18gK9STyO+CNBvkKPnWGMxESxecTYqOcikHOVIHXa9uAuTK+Fw8TJq2Gng1w7W6wTC9uPX6hGNvrMll2wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rushstack/node-core-library": "5.18.0", - "@rushstack/problem-matcher": "0.1.1", - "supports-color": "~8.1.1" - }, - "peerDependencies": { - "@types/node": "*" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@rushstack/terminal/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/@rushstack/ts-command-line": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.1.3.tgz", - "integrity": "sha512-Kdv0k/BnnxIYFlMVC1IxrIS0oGQd4T4b7vKfx52Y2+wk2WZSDFIvedr7JrhenzSlm3ou5KwtoTGTGd5nbODRug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rushstack/terminal": "0.19.3", - "@types/argparse": "1.0.38", - "argparse": "~1.0.9", - "string-argv": "~0.3.1" - } - }, - "node_modules/@rushstack/ts-command-line/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, "node_modules/@scalar/openapi-parser": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/@scalar/openapi-parser/-/openapi-parser-0.15.0.tgz", @@ -3415,24 +2767,6 @@ "svelte": "^5.0.0" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/argparse": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", - "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -4036,162 +3370,12 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, - "node_modules/@volar/language-core": { - "version": "2.4.23", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.23.tgz", - "integrity": "sha512-hEEd5ET/oSmBC6pi1j6NaNYRWoAiDhINbT8rmwtINugR39loROSlufGdYMF9TaKGfz+ViGs1Idi3mAhnuPcoGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/source-map": "2.4.23" - } - }, - "node_modules/@volar/source-map": { - "version": "2.4.23", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.23.tgz", - "integrity": "sha512-Z1Uc8IB57Lm6k7q6KIDu/p+JWtf3xsXJqAX/5r18hYOTpJyBn0KXUR8oTJ4WFYOcDzWC9n3IflGgHowx6U6z9Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@volar/typescript": { - "version": "2.4.23", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.23.tgz", - "integrity": "sha512-lAB5zJghWxVPqfcStmAP1ZqQacMpe90UrP5RJ3arDyrhy4aCUQqmxPPLB2PWDKugvylmO41ljK7vZ+t6INMTag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.23", - "path-browserify": "^1.0.1", - "vscode-uri": "^3.0.8" - } - }, "node_modules/@vscode/iconv-lite-umd": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz", "integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==", "license": "MIT" }, - "node_modules/@vue/compiler-core": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.24.tgz", - "integrity": "sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.24", - "entities": "^4.5.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-core/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/@vue/compiler-core/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.24.tgz", - "integrity": "sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.24", - "@vue/shared": "3.5.24" - } - }, - "node_modules/@vue/compiler-vue2": { - "version": "2.7.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", - "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", - "dev": true, - "license": "MIT", - "dependencies": { - "de-indent": "^1.0.2", - "he": "^1.2.0" - } - }, - "node_modules/@vue/language-core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz", - "integrity": "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "~2.4.11", - "@vue/compiler-dom": "^3.5.0", - "@vue/compiler-vue2": "^2.7.16", - "@vue/shared": "^3.5.0", - "alien-signals": "^0.4.9", - "minimatch": "^9.0.3", - "muggle-string": "^0.4.1", - "path-browserify": "^1.0.1" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@vue/language-core/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vue/language-core/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@vue/language-core/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.24", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.24.tgz", - "integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==", - "dev": true, - "license": "MIT" - }, "node_modules/@windmill-labs/svelte-dnd-action": { "version": "0.9.48", "resolved": "https://registry.npmjs.org/@windmill-labs/svelte-dnd-action/-/svelte-dnd-action-0.9.48.tgz", @@ -4387,13 +3571,6 @@ } } }, - "node_modules/alien-signals": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.14.tgz", - "integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==", - "dev": true, - "license": "MIT" - }, "node_modules/amator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/amator/-/amator-1.1.0.tgz", @@ -5139,6 +4316,39 @@ "consola": "^3.2.3" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -5223,13 +4433,6 @@ "node": ">=18" } }, - "node_modules/compare-versions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", - "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", - "dev": true, - "license": "MIT" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5665,13 +4868,6 @@ "url": "https://opencollective.com/date-fns" } }, - "node_modules/de-indent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", - "dev": true, - "license": "MIT" - }, "node_modules/debounce-promise": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz", @@ -6093,6 +5289,23 @@ "integrity": "sha512-g2nNuu+tWmPpuoyk3ffpT9vKhjPz4NrJzq6mkRDZIwXCrFhrKdDJ9TX5tJOBpvCTBrBYjgRQ17XlcQB15q4gMg==", "license": "MIT" }, + "node_modules/dts-bundle-generator": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/dts-bundle-generator/-/dts-bundle-generator-9.5.1.tgz", + "integrity": "sha512-DxpJOb2FNnEyOzMkG11sxO2dmxPjthoVWxfKqWYJ/bI/rT1rvTMktF5EKjAYrRZu6Z6t3NhOUZ0sZ5ZXevOfbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "typescript": ">=5.0.2", + "yargs": "^17.6.0" + }, + "bin": { + "dts-bundle-generator": "dist/bin/dts-bundle-generator.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -6705,13 +5918,6 @@ "node": ">=6" } }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "dev": true, - "license": "MIT" - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -6978,21 +6184,6 @@ "license": "MIT", "optional": true }, - "node_modules/fs-extra": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -7084,6 +6275,16 @@ "node": ">=10.19" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -7375,13 +6576,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -7659,16 +6853,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/highlight.js": { "version": "11.11.1", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", @@ -7804,6 +6988,7 @@ "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -8101,13 +7286,6 @@ "jiti": "bin/jiti.js" } }, - "node_modules/jju": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", - "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", - "dev": true, - "license": "MIT" - }, "node_modules/js-base64": { "version": "3.7.8", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", @@ -8178,19 +7356,6 @@ "integrity": "sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ==", "license": "MIT" }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/jsonpointer": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", @@ -8238,13 +7403,6 @@ "dev": true, "license": "MIT" }, - "node_modules/kolorist": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lerc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz", @@ -8478,111 +7636,6 @@ "lightningcss-win32-x64-msvc": "1.30.2" } }, - "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lightningcss-linux-arm64-gnu": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", @@ -8625,90 +7678,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -8729,50 +7698,6 @@ "dev": true, "license": "MIT" }, - "node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/local-pkg/node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/local-pkg/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/local-pkg/node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, "node_modules/locate-character": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", @@ -10086,13 +9011,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "dev": true, - "license": "MIT" - }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -11732,23 +10650,6 @@ "node": ">=8.x" } }, - "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT" - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -12166,6 +11067,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -12628,13 +11539,6 @@ "license": "CC0-1.0", "peer": true }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -12644,16 +11548,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string-argv": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.19" - } - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -13999,16 +12893,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -14194,33 +13078,6 @@ } } }, - "node_modules/vite-plugin-dts": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-4.5.4.tgz", - "integrity": "sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@microsoft/api-extractor": "^7.50.1", - "@rollup/pluginutils": "^5.1.4", - "@volar/typescript": "^2.4.11", - "@vue/language-core": "2.2.0", - "compare-versions": "^6.1.1", - "debug": "^4.4.0", - "kolorist": "^1.8.0", - "local-pkg": "^1.0.0", - "magic-string": "^0.30.17" - }, - "peerDependencies": { - "typescript": "*", - "vite": "*" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, "node_modules/vite-plugin-mkcert": { "version": "1.17.9", "resolved": "https://registry.npmjs.org/vite-plugin-mkcert/-/vite-plugin-mkcert-1.17.9.tgz", @@ -14801,6 +13658,16 @@ "async-limiter": "~1.0.0" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", @@ -14823,6 +13690,25 @@ "node": ">= 14.6" } }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/yargs-parser": { "version": "20.2.9", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", @@ -14834,6 +13720,16 @@ "node": ">=10" } }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/yjs": { "version": "13.6.27", "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.27.tgz", diff --git a/frontend/package.json b/frontend/package.json index e5107cb02c..a666bf7369 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "scripts": { "dev": "vite dev", "build": "vite build", - "build:utils": "vite build --config vite.sharedUtils.config.js", + "build:utils": "vite build --config sharedUtils/vite.sharedUtils.config.js", "preview": "vite preview", "postinstall": "node -e \"if (require('fs').existsSync('./scripts/untar_ui_builder.js')) { require('child_process').execSync('node ./scripts/untar_ui_builder.js', {stdio: 'inherit'}) }\"", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --threshold warning", @@ -41,6 +41,7 @@ "autoprefixer": "^10.4.13", "cssnano": "^6.0.1", "d3-dag": "^0.11.5", + "dts-bundle-generator": "^9.5.1", "eslint": "^8.47.0", "eslint-config-prettier": "^8.6.0", "eslint-plugin-svelte": "^2.45.1", @@ -66,7 +67,6 @@ "tslib": "^2.6.1", "typescript": "^5.5.0", "vite": "npm:rolldown-vite@7.2.8", - "vite-plugin-dts": "^4.5.4", "vite-plugin-mkcert": "^1.17.5", "yootils": "^0.3.1" }, diff --git a/frontend/package.sharedUtils.json b/frontend/package.sharedUtils.json deleted file mode 100644 index e2ebbee6fd..0000000000 --- a/frontend/package.sharedUtils.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "@windmill-labs/shared-utils", - "version": "1.0.2", - "type": "module", - "private": false, - "main": "./lib.es.js", - "module": "./lib.es.js", - "exports": { - ".": "./lib.es.js" - }, - "types": "./lib.d.ts" -} diff --git a/frontend/scripts/untar_ui_builder.js b/frontend/scripts/untar_ui_builder.js index 843ee15580..20c9188215 100644 --- a/frontend/scripts/untar_ui_builder.js +++ b/frontend/scripts/untar_ui_builder.js @@ -20,7 +20,7 @@ console.log('Running postinstall for root project'); import { x } from 'tar' -const tarUrl = 'https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-a3f259c.tar.gz' +const tarUrl = 'https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-00e139d.tar.gz' const outputTarPath = path.join(process.cwd(), 'ui_builder.tar.gz') const extractTo = path.join(process.cwd(), 'static/ui_builder/') diff --git a/frontend/sharedUtils/sharedUtils.d.ts b/frontend/sharedUtils/sharedUtils.d.ts new file mode 100644 index 0000000000..5d203fd4f5 --- /dev/null +++ b/frontend/sharedUtils/sharedUtils.d.ts @@ -0,0 +1,40 @@ +/// + +// Vite environment variables +interface ImportMetaEnv { + readonly VITE_APP_TITLE: string + // Add other env variables as needed + readonly REMOTE?: string + readonly REMOTE_LSP?: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} + +// Global __pkg__ variable from Vite's define +declare const __pkg__: { + version: string +} + +// Svelte component imports +declare module '*.svelte' { + import type { ComponentType } from 'svelte' + const component: ComponentType + export default component + // Add named exports that your code uses + export const Runnable: ComponentType + export const RunsSelectionMode: ComponentType +} + +// JSON raw imports +declare module '*.json?raw' { + const content: string + export default content +} + +// For other raw imports if needed +declare module '*?raw' { + const content: string + export default content +} diff --git a/frontend/sharedUtils/vite.sharedUtils.config.js b/frontend/sharedUtils/vite.sharedUtils.config.js new file mode 100644 index 0000000000..d20cfa893f --- /dev/null +++ b/frontend/sharedUtils/vite.sharedUtils.config.js @@ -0,0 +1,95 @@ +import { defineConfig } from 'vite' +import { resolve } from 'path' +import { writeFileSync } from 'fs' +import { exec } from 'child_process' +import { promisify } from 'util' + +const execAsync = promisify(exec) +const VERSION = '1.0.9' + +export default defineConfig({ + build: { + lib: { + entry: resolve(__dirname, '../src/lib/sharedUtils.ts'), + name: 'sharedUtils', + fileName: (format) => `lib.${format}.js`, + formats: ['es'] + }, + outDir: 'dist/sharedUtils', + rollupOptions: { + external: [], + output: { + globals: {} + } + } + }, + plugins: [ + { + name: 'bundle-types', + async closeBundle() { + try { + console.log('Bundling types...') + + // Create a temporary tsconfig for this specific build + const tempTsConfig = { + extends: './tsconfig.json', + compilerOptions: { + declaration: true, + emitDeclarationOnly: true, + outDir: 'dist/sharedUtils', + skipLibCheck: true, + noEmit: false + }, + include: ['src/lib/sharedUtils.ts', 'sharedUtils/sharedUtils.d.ts'], + exclude: ['node_modules'] + } + + writeFileSync('tsconfig.sharedUtils.json', JSON.stringify(tempTsConfig, null, 2)) + + // Use the temporary tsconfig + await execAsync('npx tsc -p tsconfig.sharedUtils.json') + console.log('Generating types...') + + // Clean up temp tsconfig + await execAsync('rm tsconfig.sharedUtils.json') + + // Rename sharedUtils.d.ts to lib.d.ts + await execAsync('mv dist/sharedUtils/sharedUtils.d.ts dist/sharedUtils/lib.d.ts') + + const pkgJson = { + name: '@windmill-labs/shared-utils', + version: VERSION, + type: 'module', + private: false, + main: './lib.es.js', + module: './lib.es.js', + exports: { + '.': './lib.es.js' + }, + types: './lib.d.ts' + } + + const jsrJson = { + name: '@windmill-labs/shared-utils', + version: VERSION, + license: 'MIT', + exports: './lib.es.js' + } + writeFileSync( + resolve(__dirname, '../dist/sharedUtils/package.json'), + JSON.stringify(pkgJson, null, 2) + ) + writeFileSync( + resolve(__dirname, '../dist/sharedUtils/jsr.json'), + JSON.stringify(jsrJson, null, 2) + ) + + console.log('Types bundled successfully') + } catch (error) { + console.error('Error bundling types:', error) + throw error + } + } + } + ] +}) diff --git a/frontend/src/global.d.ts b/frontend/src/global.d.ts index bd06974068..a216c1a038 100644 --- a/frontend/src/global.d.ts +++ b/frontend/src/global.d.ts @@ -1,10 +1,52 @@ /// declare type Item = import('@windmill-labs/svelte-dnd-action').Item -declare type DndEvent = import('@windmill-labs/svelte-dnd-action').DndEvent +declare type DndEvent = + import('@windmill-labs/svelte-dnd-action').DndEvent declare namespace svelte.JSX { interface HTMLAttributes { onconsider?: (event: CustomEvent> & { target: EventTarget & T }) => void onfinalize?: (event: CustomEvent> & { target: EventTarget & T }) => void } } + +/// + +// Vite environment variables +interface ImportMetaEnv { + readonly VITE_APP_TITLE: string + // Add other env variables as needed + readonly REMOTE?: string + readonly REMOTE_LSP?: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} + +// Global __pkg__ variable from Vite's define +declare const __pkg__: { + version: string +} + +// Svelte component imports +declare module '*.svelte' { + import type { ComponentType } from 'svelte' + const component: ComponentType + export default component + // Add named exports that your code uses + export const Runnable: ComponentType + export const RunsSelectionMode: ComponentType +} + +// JSON raw imports +declare module '*.json?raw' { + const content: string + export default content +} + +// For other raw imports if needed +declare module '*?raw' { + const content: string + export default content +} diff --git a/frontend/src/lib/components/DeployWorkspace.svelte b/frontend/src/lib/components/DeployWorkspace.svelte index 9cccd0cb29..fb1e25d200 100644 --- a/frontend/src/lib/components/DeployWorkspace.svelte +++ b/frontend/src/lib/components/DeployWorkspace.svelte @@ -33,6 +33,7 @@ import type { TriggerKind } from './triggers' import type { App } from './apps/types' import { getAllGridItems } from './apps/editor/appUtils' + import { isRunnableByPath } from './apps/inputType' const dispatch = createEventDispatcher() @@ -145,7 +146,7 @@ let result: { kind: Kind; path: string }[] = [] getAllGridItems(appValue).forEach((gridItem) => { const ci = gridItem.data.componentInput - if (ci?.type == 'runnable' && ci.runnable?.type == 'runnableByPath') { + if (ci?.type == 'runnable' && isRunnableByPath(ci.runnable)) { if (ci.runnable.runType == 'script') { result.push({ kind: 'script', path: ci.runnable.path }) } else if (ci.runnable.runType == 'flow') { diff --git a/frontend/src/lib/components/DiffEditor.svelte b/frontend/src/lib/components/DiffEditor.svelte index 186712f214..67ac62d11a 100644 --- a/frontend/src/lib/components/DiffEditor.svelte +++ b/frontend/src/lib/components/DiffEditor.svelte @@ -32,7 +32,7 @@ defaultModified?: string readOnly?: boolean buttons?: ButtonProp[] - modifiedModel?: meditor.ITextModel + modifiedModel?: meditor.ITextModel | meditor.IEditorModel } let { @@ -75,6 +75,7 @@ scrollbar: { alwaysConsumeMouseWheel: false } }) + console.log('defaultModified', defaultModified) if (defaultLang !== undefined) { setupModel(defaultLang, defaultOriginal, defaultModified, defaultModifiedLang) } @@ -90,7 +91,7 @@ const m = modifiedModel ?? meditor.createModel(modified ?? '', modifiedLang ?? lang) diffEditor?.setModel({ original: o, - modified: m + modified: m as meditor.ITextModel }) } @@ -117,6 +118,14 @@ }) } + export function showWithModelAndOriginal( + original: string, + model: meditor.ITextModel | meditor.IEditorModel + ) { + setOriginal(original) + setModifiedModel(model as meditor.ITextModel) + show() + } export function getModified(): string { return diffEditor?.getModel()?.modified.getValue() ?? '' } diff --git a/frontend/src/lib/components/EditorBar.svelte b/frontend/src/lib/components/EditorBar.svelte index 9085d48bfa..1b3d6ee8a1 100644 --- a/frontend/src/lib/components/EditorBar.svelte +++ b/frontend/src/lib/components/EditorBar.svelte @@ -3,8 +3,6 @@ -
+
{#if path.startsWith('hub/')}
diff --git a/frontend/src/lib/components/RunsPage.svelte b/frontend/src/lib/components/RunsPage.svelte index 86bcac405d..f637e21737 100644 --- a/frontend/src/lib/components/RunsPage.svelte +++ b/frontend/src/lib/components/RunsPage.svelte @@ -38,8 +38,7 @@ import ConcurrentJobsChart from '$lib/components/ConcurrentJobsChart.svelte' import { goto } from '$app/navigation' import { base } from '$app/paths' - import type { RunsSelectionMode } from '$lib/components/runs/RunsBatchActionsDropdown.svelte' - import { isJobSelectable } from '$lib/utils' + import { isJobSelectable, type RunsSelectionMode } from '$lib/utils' import BatchReRunOptionsPane, { type BatchReRunOptions } from '$lib/components/runs/BatchReRunOptionsPane.svelte' diff --git a/frontend/src/lib/components/SaveToWorkspace.svelte b/frontend/src/lib/components/SaveToWorkspace.svelte new file mode 100644 index 0000000000..b1edfd583b --- /dev/null +++ b/frontend/src/lib/components/SaveToWorkspace.svelte @@ -0,0 +1,16 @@ + + + diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index cd9356e34b..1e1c50c232 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -489,10 +489,10 @@ } function showDiffMode() { + const model = editor?.getModel() + if (model == undefined) return diffMode = true - diffEditor?.setOriginal(lastDeployedCode ?? '') - diffEditor?.setModifiedModel(editor?.getModel() as meditor.ITextModel) - diffEditor?.show() + diffEditor?.showWithModelAndOriginal(lastDeployedCode ?? '', model) editor?.hide() } @@ -572,7 +572,7 @@ }} on:showDiffMode={showDiffMode} on:hideDiffMode={hideDiffMode} - customUi={{ ...customUi?.editorBar, aiGen: false }} + customUi={customUi?.editorBar} collabLive={wsProvider?.shouldConnect} {collabMode} {validCode} @@ -585,7 +585,6 @@ collabUsers={peers} kind={asKind(kind)} {template} - {diffEditor} {args} {noHistory} {saveToWorkspace} diff --git a/frontend/src/lib/components/apps/components/display/dbtable/queries/count.ts b/frontend/src/lib/components/apps/components/display/dbtable/queries/count.ts index ada70e3519..b418c1a702 100644 --- a/frontend/src/lib/components/apps/components/display/dbtable/queries/count.ts +++ b/frontend/src/lib/components/apps/components/display/dbtable/queries/count.ts @@ -162,7 +162,7 @@ export function getCountInput( const updateRunnable: RunnableByName = { name: 'AppDbExplorer', - type: 'runnableByName', + type: 'inline', inlineScript: { content: query, language: getLanguageByResourceType(dbType), diff --git a/frontend/src/lib/components/apps/components/display/dbtable/queries/delete.ts b/frontend/src/lib/components/apps/components/display/dbtable/queries/delete.ts index 3450203fbb..3edd847db4 100644 --- a/frontend/src/lib/components/apps/components/display/dbtable/queries/delete.ts +++ b/frontend/src/lib/components/apps/components/display/dbtable/queries/delete.ts @@ -83,7 +83,7 @@ export function getDeleteInput( if (dbInput.type === 'ducklake') query = wrapDucklakeQuery(query, dbInput.ducklake) const deleteRunnable: RunnableByName = { name: 'AppDbExplorer', - type: 'runnableByName', + type: 'inline', inlineScript: { content: query, language: getLanguageByResourceType(dbType), diff --git a/frontend/src/lib/components/apps/components/display/dbtable/queries/insert.ts b/frontend/src/lib/components/apps/components/display/dbtable/queries/insert.ts index a028523aaa..3ac7ff76c8 100644 --- a/frontend/src/lib/components/apps/components/display/dbtable/queries/insert.ts +++ b/frontend/src/lib/components/apps/components/display/dbtable/queries/insert.ts @@ -111,7 +111,7 @@ export function getInsertInput(dbInput: DbInput, table: string, columns: ColumnD return { runnable: { name: 'AppDbExplorer', - type: 'runnableByName', + type: 'inline', inlineScript: { content: query, language: getLanguageByResourceType(dbType), diff --git a/frontend/src/lib/components/apps/components/display/dbtable/queries/select.ts b/frontend/src/lib/components/apps/components/display/dbtable/queries/select.ts index a1d31058ed..b64ce4493c 100644 --- a/frontend/src/lib/components/apps/components/display/dbtable/queries/select.ts +++ b/frontend/src/lib/components/apps/components/display/dbtable/queries/select.ts @@ -280,7 +280,7 @@ export function getSelectInput( if (dbInput.type === 'ducklake') content = wrapDucklakeQuery(content, dbInput.ducklake) const getRunnable: RunnableByName = { name: 'AppDbExplorer', - type: 'runnableByName', + type: 'inline', inlineScript: { content, language: getLanguageByResourceType(dbType) } } diff --git a/frontend/src/lib/components/apps/components/display/dbtable/queries/update.ts b/frontend/src/lib/components/apps/components/display/dbtable/queries/update.ts index 5156bed216..a6007dd4c9 100644 --- a/frontend/src/lib/components/apps/components/display/dbtable/queries/update.ts +++ b/frontend/src/lib/components/apps/components/display/dbtable/queries/update.ts @@ -95,7 +95,7 @@ export function getUpdateInput( const updateRunnable: RunnableByName = { name: 'AppDbExplorer', - type: 'runnableByName', + type: 'inline', inlineScript: { content: query, language: getLanguageByResourceType(dbType), diff --git a/frontend/src/lib/components/apps/components/helpers/HiddenComponent.svelte b/frontend/src/lib/components/apps/components/helpers/HiddenComponent.svelte index 4785897bcf..ee32c17726 100644 --- a/frontend/src/lib/components/apps/components/helpers/HiddenComponent.svelte +++ b/frontend/src/lib/components/apps/components/helpers/HiddenComponent.svelte @@ -5,6 +5,7 @@ import type { AppViewerContext, HiddenRunnable } from '../../types' import RunnableComponent from './RunnableComponent.svelte' import InitializeComponent from './InitializeComponent.svelte' + import { isRunnableByName, isRunnableByPath } from '../../inputType' interface Props { id: string @@ -37,7 +38,7 @@ }) -{#if runnable && (runnable.type == 'runnableByPath' || (runnable.type == 'runnableByName' && runnable.inlineScript != undefined))} +{#if runnable && (isRunnableByPath(runnable) || (isRunnableByName(runnable) && runnable.inlineScript != undefined))} { ;(autoRefresh || forceSchemaDisplay) && @@ -846,7 +849,7 @@ {/if} {/each} -{#if runnable?.type == 'runnableByName' && runnable.inlineScript?.language == 'frontend'} +{#if isRunnableByName(runnable) && runnable.inlineScript?.language == 'frontend'} {#each runnable.inlineScript.refreshOn ?? [] as { id: tid, key } (`${tid}-${key}`)} {@const fkey = `${tid}-${key}${extraKey}`} + queryParams?: Record, ) { let appPath = defaultIfEmptyString(path, `u/${username ?? 'unknown'}/newapp`) - if (runnable?.type === 'runnableByName') { + if (isRunnableByName(runnable)) { const { inlineScript } = inlineScriptOverride ? { inlineScript: inlineScriptOverride } : runnable @@ -32,7 +32,7 @@ export async function executeRunnable( cache_ttl: inlineScript.cache_ttl } } - } else if (runnable?.type === 'runnableByPath') { + } else if (isRunnableByPath(runnable)) { const { path, runType } = runnable requestBody['path'] = runType !== 'hubscript' ? `${runType}/${path}` : `script/${path}` } diff --git a/frontend/src/lib/components/apps/editor/appPolicy.ts b/frontend/src/lib/components/apps/editor/appPolicy.ts index b3e804abf7..d5a33f0928 100644 --- a/frontend/src/lib/components/apps/editor/appPolicy.ts +++ b/frontend/src/lib/components/apps/editor/appPolicy.ts @@ -5,7 +5,7 @@ import { getInsertInput } from '../components/display/dbtable/queries/insert' import { getSelectInput } from '../components/display/dbtable/queries/select' import { getUpdateInput } from '../components/display/dbtable/queries/update' import { getPrimaryKeys, type ColumnDef } from '../components/display/dbtable/utils' -import type { AppInput, Runnable } from '../inputType' +import { isRunnableByName, isRunnableByPath, type AppInput, type Runnable } from '../inputType' import type { App } from '../types' import { computeS3FileInputPolicy, @@ -145,7 +145,7 @@ export async function updatePolicy(app: App, currentPolicy: Policy | undefined): const props = c.type === 'schemaformcomponent' ? (c.componentInput as any)?.value?.properties - : (c.componentInput as any)?.runnable?.type === 'runnableByName' + : isRunnableByName((c.componentInput as any)?.runnable) ? (c.componentInput as any)?.runnable?.inlineScript?.schema?.properties : (c.componentInput as any)?.runnable?.schema?.properties return ( @@ -181,7 +181,7 @@ export async function updatePolicy(app: App, currentPolicy: Policy | undefined): } } -async function processRunnable( +export async function processRunnable( id: string, runnable: Runnable, fields: Record, @@ -195,7 +195,7 @@ async function processRunnable( }) .filter(Boolean) as string[] - if (runnable?.type == 'runnableByName') { + if (isRunnableByName(runnable)) { let hex = await hash(runnable.inlineScript?.content) console.debug('hex', hex, id) return [ @@ -206,7 +206,7 @@ async function processRunnable( allow_user_resources: allowUserResources } ] - } else if (runnable?.type == 'runnableByPath') { + } else if (isRunnableByPath(runnable)) { let prefix = runnable.runType !== 'hubscript' ? runnable.runType : 'script' return [ `${id}:${prefix}/${runnable.path}`, diff --git a/frontend/src/lib/components/apps/editor/component/components.ts b/frontend/src/lib/components/apps/editor/component/components.ts index 328743c3c7..bc8a24b0e6 100644 --- a/frontend/src/lib/components/apps/editor/component/components.ts +++ b/frontend/src/lib/components/apps/editor/component/components.ts @@ -83,7 +83,7 @@ export type BaseComponent = { } export type RecomputeOthersSource = { - recomputeIds: string[] | undefined + recomputeIds?: string[] | undefined } export type CustomComponentConfig = { diff --git a/frontend/src/lib/components/apps/editor/contextPanel/components/OutputHeader.svelte b/frontend/src/lib/components/apps/editor/contextPanel/components/OutputHeader.svelte index 994261e947..14166b1517 100644 --- a/frontend/src/lib/components/apps/editor/contextPanel/components/OutputHeader.svelte +++ b/frontend/src/lib/components/apps/editor/contextPanel/components/OutputHeader.svelte @@ -9,7 +9,7 @@ import { allsubIds } from '../../appUtils' import IdEditor from './IdEditor.svelte' import type { AppComponent } from '../../component' - import type { Runnable } from '$lib/components/apps/inputType' + import { isRunnableByName, type Runnable } from '$lib/components/apps/inputType' import DocLink from '../../settingsPanel/DocLink.svelte' import { findGridItem, allItems } from '../../appUtilsCore' @@ -159,7 +159,7 @@ function processRunnable(from: string, to: string, runnable: Runnable) { if ( - runnable?.type === 'runnableByName' && + isRunnableByName(runnable) && runnable?.inlineScript?.refreshOn?.find((x) => x.id === from) ) { runnable.inlineScript.refreshOn = runnable.inlineScript.refreshOn.map((x) => { diff --git a/frontend/src/lib/components/apps/editor/inlineScriptsPanel/AppRunButton.svelte b/frontend/src/lib/components/apps/editor/inlineScriptsPanel/AppRunButton.svelte index 65737951aa..be5ac12c51 100644 --- a/frontend/src/lib/components/apps/editor/inlineScriptsPanel/AppRunButton.svelte +++ b/frontend/src/lib/components/apps/editor/inlineScriptsPanel/AppRunButton.svelte @@ -1,13 +1,9 @@
-
+
{title} @@ -49,7 +74,18 @@ {/if}
+ {#if collapsible} + + {/if} {@render action?.()}
- {@render children?.()} + {#if !collapsed} + {@render children?.()} + {/if}
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/mainInput/RunnableSelector.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/mainInput/RunnableSelector.svelte index d55ed3ce8e..af6f20e510 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/mainInput/RunnableSelector.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/mainInput/RunnableSelector.svelte @@ -3,7 +3,7 @@ import PickHubScript from '$lib/components/flows/pickers/PickHubScript.svelte' import { Building, Globe2, MousePointer, Plus } from 'lucide-svelte' import InlineScriptList from './InlineScriptList.svelte' - import type { Runnable, StaticAppInput } from '$lib/components/apps/inputType' + import type { InlineScript, Runnable, StaticAppInput } from '$lib/components/apps/inputType' import WorkspaceScriptList from './WorkspaceScriptList.svelte' import WorkspaceFlowList from './WorkspaceFlowList.svelte' import { createEventDispatcher } from 'svelte' @@ -12,7 +12,6 @@ import { defaultIfEmptyString, emptySchema } from '$lib/utils' import { loadSchema } from '$lib/infer' import { workspaceStore } from '$lib/stores' - import type { InlineScript } from '$lib/components/apps/types' type TabType = 'hubscripts' | 'workspacescripts' | 'workspaceflows' | 'inlinescripts' @@ -66,7 +65,7 @@ const schema = await loadSchemaFromTriggerable(path, 'script') const fields = schemaToInputsSpec(schema.schema, defaultUserInput) const runnable = { - type: 'runnableByPath', + type: 'path', path, runType: 'script', schema: schema.schema, @@ -83,7 +82,7 @@ const schema = await loadSchemaFromTriggerable(path, 'flow') const fields = schemaToInputsSpec(schema.schema, defaultUserInput) const runnable = { - type: 'runnableByPath', + type: 'path', path, runType: 'flow', schema, @@ -99,7 +98,7 @@ const schema = await loadSchemaFromTriggerable(path, 'hubscript') const fields = schemaToInputsSpec(schema.schema, defaultUserInput) const runnable = { - type: 'runnableByPath', + type: 'path', path, runType: 'hubscript', schema: schema.schema, @@ -116,7 +115,7 @@ const unusedInlineScript = unusedInlineScripts?.[unusedInlineScriptIndex] dispatch('pick', { runnable: { - type: 'runnableByName', + type: 'inline', name, inlineScript: unusedInlineScript.inlineScript }, @@ -132,7 +131,7 @@ dispatch('pick', { runnable: { - type: 'runnableByName', + type: 'inline', name: newScriptName, inlineScript: undefined }, diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/script/BackgroundScriptSettings.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/script/BackgroundScriptSettings.svelte index 41c183f7b8..ace586bb74 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/script/BackgroundScriptSettings.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/script/BackgroundScriptSettings.svelte @@ -6,6 +6,7 @@ import { getContext } from 'svelte' import ScriptSettingsSection from './shared/ScriptSettingsSection.svelte' import ScriptTransformer from './shared/ScriptTransformer.svelte' + import { isRunnableByPath } from '$lib/components/apps/inputType' interface Props { runnable: HiddenRunnable @@ -31,11 +32,11 @@
- {#if runnable.type == 'runnableByPath' || runnable.inlineScript} + {#if isRunnableByPath(runnable) || runnable.inlineScript} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/script/ComponentScriptSettings.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/script/ComponentScriptSettings.svelte index 2c431f9a21..991242a688 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/script/ComponentScriptSettings.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/script/ComponentScriptSettings.svelte @@ -8,7 +8,11 @@ -{#if script.type == 'runnableByName' && script.inlineScript} +{#if isRunnableByName(script) && script.inlineScript} -{:else if script.type === 'runnableByName'} +{:else if isRunnableByName(script)} - import type { ResultAppInput } from '$lib/components/apps/inputType' + import { isRunnableByName, type ResultAppInput } from '$lib/components/apps/inputType' import type { AppComponent } from '../../../component' import { getAllTriggerEvents, isTriggerable, getDependencies } from '../utils' @@ -14,15 +14,14 @@ let triggerEvents = $derived(getAllTriggerEvents(appComponent, appInput.autoRefresh)) let isFrontend = $derived( - appInput.runnable?.type == 'runnableByName' && - appInput.runnable?.inlineScript?.language === 'frontend' + isRunnableByName(appInput.runnable) && appInput.runnable?.inlineScript?.language === 'frontend' ) let shoudlDisplayChangeEvents = $derived( appInput.recomputeOnInputChanged && !isTriggerable(appComponent.type) ) -{#if appInput?.runnable?.type === 'runnableByName'} +{#if isRunnableByName(appInput.runnable)} - import type { AppEditorContext, InlineScript } from '$lib/components/apps/types' + import type { AppEditorContext } from '$lib/components/apps/types' import { getContext } from 'svelte' import { Button } from '$lib/components/common' import { Plus, X } from 'lucide-svelte' import Section from '$lib/components/Section.svelte' + import type { InlineScript } from '$lib/components/apps/sharedTypes' const { selectedComponentInEditor } = getContext('AppEditorContext') diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptTriggers.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptTriggers.svelte index 532191ac91..4ad537dd23 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptTriggers.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/script/shared/ScriptTriggers.svelte @@ -1,11 +1,11 @@ + +
+
(isHovered = true)} + onmouseleave={() => (isHovered = false)} + > + {#if isEditing} +
+ {#if node.isFolder} + {@const IconComponent = fileIcon.icon} + + {#if expanded} + + {:else} + + {/if} + + + {:else} + {@const IconComponent = fileIcon.icon} + + + {/if} + +
+ {:else} + + + {#if isHovered && !isEditing} +
+ {#if !noEdit} + + + {:else} + + {/if} +
+ {/if} + {/if} +
+ + {#if node.isFolder && expanded && sortedChildren} + {#each sortedChildren as child (child.path)} + + {/each} + {/if} +
diff --git a/frontend/src/lib/components/raw_apps/RawAppBackgroundRunner.svelte b/frontend/src/lib/components/raw_apps/RawAppBackgroundRunner.svelte index 1650eb225a..bd67e55a71 100644 --- a/frontend/src/lib/components/raw_apps/RawAppBackgroundRunner.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppBackgroundRunner.svelte @@ -2,13 +2,14 @@ import { executeRunnable } from '../apps/components/helpers/executeRunnable' import { userStore } from '$lib/stores' import { waitJob } from '../waitJob' - import type { HiddenRunnable, JobById } from '../apps/types' + import type { JobById } from '../apps/types' import { JobService } from '$lib/gen' + import type { Runnable } from './rawAppPolicy' interface Props { iframe: HTMLIFrameElement | undefined path: string - runnables: Record + runnables: Record jobs?: string[] jobsById?: Record editor: boolean @@ -60,15 +61,21 @@ { component: runnable_id, args: data.v, - force_viewer_allow_user_resources: Object.keys(runnable.fields).filter( - (k) => runnable.fields[k]?.type == 'user' && runnable.fields[k]?.allowUserResources - ), - force_viewer_one_of_fields: {}, - force_viewer_static_fields: Object.fromEntries( - Object.entries(runnable.fields) - .filter(([k, v]) => v.type == 'static') - .map(([k, v]) => [k, v?.['value']]) - ) + force_viewer_allow_user_resources: editor + ? Object.keys(runnable?.fields ?? {}).filter( + (k) => + runnable?.fields?.[k]?.type == 'user' && + runnable?.fields?.[k]?.allowUserResources + ) + : undefined, + force_viewer_one_of_fields: editor ? {} : undefined, + force_viewer_static_fields: editor + ? Object.fromEntries( + Object.entries(runnable?.fields ?? {}) + .filter(([k, v]) => v.type == 'static') + .map(([k, v]) => [k, v?.['value']]) + ) + : undefined }, undefined ) diff --git a/frontend/src/lib/components/raw_apps/RawAppEditor.svelte b/frontend/src/lib/components/raw_apps/RawAppEditor.svelte index dd2ec62cd1..dd4fe7cf41 100644 --- a/frontend/src/lib/components/raw_apps/RawAppEditor.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppEditor.svelte @@ -1,9 +1,7 @@ @@ -172,47 +205,45 @@ {runnables} {getBundle} /> - - - + + + + files, + (newFiles) => { + files = newFiles + setFilesInIframe(newFiles ?? {}) + } + } + onSelectFile={handleSelectFile} + bind:selectedRunnable + bind:selectedDocument + {runnables} + {modules} + > + + - - - -
- { - appPanelSize = 100 - }} - appPath={path} - bind:selectedRunnable - {runnables} - /> -
+ {#if selectedRunnable !== undefined} + +
+ +
+ {/if} +
- {#if appPanelSize == 100} -
- { - appPanelSize = 70 - }} - direction="bottom" - hidden - btnClasses="border bg-surface" - /> -
- {/if}
diff --git a/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte b/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte index c29f9e8a3b..d9a0426158 100644 --- a/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte @@ -26,15 +26,14 @@ import Summary from '$lib/components/Summary.svelte' import DeployOverrideConfirmationModal from '$lib/components/common/confirmationModal/DeployOverrideConfirmationModal.svelte' - import type { HiddenRunnable } from '../apps/types' import AppJobsDrawer from '../apps/editor/AppJobsDrawer.svelte' - import type { Runnable } from '../apps/inputType' - import { collectStaticFields, hash, type TriggerableV2 } from '../apps/editor/commonAppUtils' import type { SavedAndModifiedValue } from '../common/confirmationModal/unsavedTypes' import DropdownV2 from '../DropdownV2.svelte' import { stateSnapshot } from '$lib/svelte5Utils.svelte' import AppEditorHeaderDeployInitialDraft from '../apps/editor/AppEditorHeaderDeployInitialDraft.svelte' import AppEditorHeaderDeploy from '../apps/editor/AppEditorHeaderDeploy.svelte' + import type { Runnable } from './RawAppInlineScriptRunnable.svelte' + import { updateRawAppPolicy } from './rawAppPolicy' // async function hash(message) { // try { @@ -72,7 +71,7 @@ newApp: boolean newPath?: string appPath: string - runnables: Record + runnables: Record files: Record | undefined jobs: string[] jobsById: Record @@ -130,55 +129,7 @@ } async function computeTriggerables() { - policy.execution_mode = 'publisher' - policy.on_behalf_of_email = $userStore?.email - policy.on_behalf_of = $userStore?.username.includes('@') - ? $userStore?.username - : `u/${$userStore?.username}` - policy.triggerables_v2 = Object.fromEntries( - (await Promise.all( - Object.values(runnables).map(async (runnable) => { - return await processRunnable(runnable.name, runnable, runnable.fields) - }) - )) as [string, TriggerableV2][] - ) - return policy - } - - async function processRunnable( - id: string, - runnable: Runnable, - fields: Record - ): Promise<[string, TriggerableV2] | undefined> { - const staticInputs = collectStaticFields(fields) - const allowUserResources: string[] = Object.entries(fields) - .map(([k, v]) => { - return v['allowUserResources'] ? k : undefined - }) - .filter(Boolean) as string[] - - if (runnable?.type == 'runnableByName') { - let hex = await hash(runnable.inlineScript?.content) - console.log('hex', hex, id) - return [ - `${id}:rawscript/${hex}`, - { - static_inputs: staticInputs, - one_of_inputs: {}, - allow_user_resources: allowUserResources - } - ] - } else if (runnable?.type == 'runnableByPath') { - let prefix = runnable.runType !== 'hubscript' ? runnable.runType : 'script' - return [ - `${id}:${prefix}/${runnable.path}`, - { - static_inputs: staticInputs, - one_of_inputs: {}, - allow_user_resources: allowUserResources - } - ] - } + policy = await updateRawAppPolicy(runnables, policy) } async function createApp(path: string) { diff --git a/frontend/src/lib/components/raw_apps/RawAppInlineScriptEditor.svelte b/frontend/src/lib/components/raw_apps/RawAppInlineScriptEditor.svelte index ff2556db23..3f1b77e63b 100644 --- a/frontend/src/lib/components/raw_apps/RawAppInlineScriptEditor.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppInlineScriptEditor.svelte @@ -3,29 +3,25 @@ const bubble = createBubbler() import Button from '$lib/components/common/button/Button.svelte' - import type { Preview } from '$lib/gen' + import type { Preview, ScriptLang } from '$lib/gen' import { createEventDispatcher, onMount } from 'svelte' - import { Maximize2, Trash2 } from 'lucide-svelte' + import { Trash2 } from 'lucide-svelte' import { inferArgs } from '$lib/infer' import type { Schema } from '$lib/common' import Editor from '$lib/components/Editor.svelte' import { emptySchema } from '$lib/utils' import { scriptLangToEditorLang } from '$lib/scripts' - import ScriptGen from '$lib/components/copilot/ScriptGen.svelte' import DiffEditor from '$lib/components/DiffEditor.svelte' - import EditorSettings from '$lib/components/EditorSettings.svelte' - import InlineScriptEditorDrawer from '../apps/editor/inlineScriptsPanel/InlineScriptEditorDrawer.svelte' - import type { InlineScript } from '../apps/types' - import type { AppInput } from '../apps/inputType' + import type { AppInput, InlineScript } from '../apps/inputType' import CacheTtlPopup from '../apps/editor/inlineScriptsPanel/CacheTtlPopup.svelte' import RunButton from '$lib/components/RunButton.svelte' import { computeFields } from '../apps/editor/inlineScriptsPanel/utils' - - let inlineScriptEditorDrawer = $state() as InlineScriptEditorDrawer | undefined + import EditorBar from '../EditorBar.svelte' + import { LanguageIcon } from '../common/languageIcons' interface Props { - inlineScript: InlineScript | undefined + inlineScript: (InlineScript & { language: ScriptLang }) | undefined name?: string | undefined id: string fields?: Record @@ -34,18 +30,20 @@ onRun: () => Promise onCancel: () => Promise editor?: Editor | undefined + lastDeployedCode?: string | undefined } let { inlineScript = $bindable(), name = $bindable(undefined), id, - fields = $bindable({}), + fields = $bindable(undefined), path, isLoading = false, onRun, onCancel, - editor = $bindable(undefined) + editor = $bindable(undefined), + lastDeployedCode }: Props = $props() let diffEditor = $state() as DiffEditor | undefined let validCode = $state(true) @@ -66,15 +64,36 @@ return schema } + let websocketAlive = $state({ + pyright: false, + deno: false, + go: false, + ruff: false, + shellcheck: false + }) + + let diffMode = $state(false) + + function showDiffMode() { + const model = editor?.getModel() + if (model == undefined) return + diffMode = true + diffEditor?.showWithModelAndOriginal(lastDeployedCode ?? '', model) + editor?.hide() + } + function hideDiffMode() { + diffMode = false + diffEditor?.hide() + editor?.show() + } + onMount(async () => { if (inlineScript && !inlineScript.schema) { - if (inlineScript.language != 'frontend') { - inlineScript.schema = await inferInlineScriptSchema( - inlineScript?.language, - inlineScript?.content, - emptySchema() - ) - } + inlineScript.schema = await inferInlineScriptSchema( + inlineScript?.language, + inlineScript?.content, + emptySchema() + ) } syncFields() }) @@ -82,32 +101,20 @@ async function syncFields() { if (inlineScript) { const newSchema = inlineScript.schema ?? emptySchema() - fields = computeFields(newSchema, true, fields) + fields = computeFields(newSchema, true, fields ?? {}) } } const dispatch = createEventDispatcher() - - let drawerIsOpen: boolean | undefined = $state(undefined) + let width = $state(0) {#if inlineScript} - {#if inlineScript.language != 'frontend'} - { - dispatch('createScriptFromInlineScript') - drawerIsOpen = false - }} - /> - {/if} -
+
+
+ +
{#if name !== undefined}
{ - // $app = $app - // if (stateId) { - // $stateId++ - // } - }} /> -
- {/if} -
- {#if inlineScript} - - {/if} - { - acc[key] = obj.type === 'static' ? obj.value : undefined - return acc - }, {})} - /> - - @@ -184,51 +153,66 @@
- +
+ +
-
- {#if !drawerIsOpen && inlineScript.language != 'frontend'} - onRun()} - on:change={async (e) => { - if (inlineScript && inlineScript.language != 'frontend') { - if (inlineScript.lock != undefined) { - inlineScript.lock = undefined - } - const oldSchema = JSON.stringify(inlineScript.schema) - if (inlineScript.schema == undefined) { - inlineScript.schema = emptySchema() - } - await inferInlineScriptSchema(inlineScript?.language, e.detail, inlineScript.schema) - if (JSON.stringify(inlineScript.schema) != oldSchema) { - inlineScript = inlineScript - syncFields() - } +
+ onRun()} + bind:websocketAlive + on:change={async (e) => { + if (inlineScript) { + if (inlineScript.lock != undefined) { + inlineScript.lock = undefined } - // $app = $app - }} - args={Object.entries(fields ?? {}).reduce((acc, [key, obj]) => { - acc[key] = obj.type === 'static' ? obj.value : undefined - return acc - }, {})} - /> + const oldSchema = JSON.stringify(inlineScript.schema) + if (inlineScript.schema == undefined) { + inlineScript.schema = emptySchema() + } + await inferInlineScriptSchema(inlineScript?.language, e.detail, inlineScript.schema) + if (JSON.stringify(inlineScript.schema) != oldSchema) { + inlineScript = inlineScript + syncFields() + } + } + // $app = $app + }} + args={Object.entries(fields ?? {}).reduce((acc, [key, obj]) => { + acc[key] = obj.type === 'static' ? obj.value : undefined + return acc + }, {})} + /> - - {/if} +
{/if} diff --git a/frontend/src/lib/components/raw_apps/RawAppInlineScriptPanelList.svelte b/frontend/src/lib/components/raw_apps/RawAppInlineScriptPanelList.svelte index 56f12a90c0..04f6148bb1 100644 --- a/frontend/src/lib/components/raw_apps/RawAppInlineScriptPanelList.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppInlineScriptPanelList.svelte @@ -1,10 +1,8 @@ - + {#snippet action()}
- { - dispatch('hidePanel') - }} - /> - + +
+
+ + +
+
+ {/snippet} +
+ {#each fileTree as node (node.path)} + + {/each} + +
+ + + + diff --git a/frontend/src/lib/components/raw_apps/fileTreeUtils.ts b/frontend/src/lib/components/raw_apps/fileTreeUtils.ts new file mode 100644 index 0000000000..b7c277af6d --- /dev/null +++ b/frontend/src/lib/components/raw_apps/fileTreeUtils.ts @@ -0,0 +1,67 @@ +export interface TreeNode { + name: string + path: string + isFolder: boolean + children?: TreeNode[] +} + +export function buildFileTree(filePaths: string[]): TreeNode[] { + const root: TreeNode[] = [] + const nodeMap = new Map() + + // Sort paths to ensure parent folders are processed before children + const sortedPaths = filePaths.slice().sort() + for (const filePath of sortedPaths) { + // Check if this path represents a folder (ends with /) + const pathEndsWithSlash = filePath.endsWith('/') + const parts = filePath.split('/').filter(Boolean) + let currentPath = '' + let parentChildren = root + + for (let i = 0; i < parts.length; i++) { + const part = parts[i] + currentPath = currentPath ? `${currentPath}/${part}` : part + // It's a folder if it's not the last part, or if the original path ended with / + const isFolder = i < parts.length - 1 || (i === parts.length - 1 && pathEndsWithSlash) + const isLastPart = i === parts.length - 1 + + // Check if this node already exists + if (!nodeMap.has(currentPath)) { + // Build the node path with trailing / for folders + let nodePath = '/' + currentPath + if (isFolder && isLastPart && pathEndsWithSlash) { + nodePath = nodePath + '/' + } + + const node: TreeNode = { + name: part, + path: nodePath, + isFolder, + children: isFolder ? [] : undefined + } + + nodeMap.set(currentPath, node) + parentChildren.push(node) + } else if (isFolder) { + // If the node exists but wasn't marked as a folder, update it + const existingNode = nodeMap.get(currentPath)! + if (!existingNode.isFolder) { + existingNode.isFolder = true + existingNode.children = [] + // Update path to include trailing / + if (isLastPart && pathEndsWithSlash && !existingNode.path.endsWith('/')) { + existingNode.path = existingNode.path + '/' + } + } + } + + // Move to the next level for folders + if (isFolder) { + const node = nodeMap.get(currentPath)! + parentChildren = node.children! + } + } + } + + return root +} diff --git a/frontend/src/lib/components/raw_apps/rawAppPolicy.ts b/frontend/src/lib/components/raw_apps/rawAppPolicy.ts new file mode 100644 index 0000000000..b90f1d3985 --- /dev/null +++ b/frontend/src/lib/components/raw_apps/rawAppPolicy.ts @@ -0,0 +1,61 @@ +import type { Policy, ScriptLang } from '$lib/gen' +import { collectStaticFields, hash, type TriggerableV2 } from '../apps/editor/commonAppUtils' +import { isRunnableByName, isRunnableByPath, type InlineScript, type RunnableWithFields } from '../apps/inputType' + +export async function updateRawAppPolicy( + runnables: Record, + currentPolicy: Policy | undefined +): Promise { + const triggerables_v2 = Object.fromEntries( + (await Promise.all( + Object.entries(runnables).map(async ([id, runnable]) => { + return await processRunnable(id, runnable, runnable?.fields ?? {}) + }) + )) as [string, TriggerableV2][] + ) + return { + ...currentPolicy, + triggerables_v2 + } +} + +type RunnableWithInlineScript = RunnableWithFields & { + inlineScript?: InlineScript & { language: ScriptLang } +} +export type Runnable = RunnableWithInlineScript | undefined + +async function processRunnable( + id: string, + runnable: Runnable, + fields: Record +): Promise<[string, TriggerableV2] | undefined> { + const staticInputs = collectStaticFields(fields) + const allowUserResources: string[] = Object.entries(fields) + .map(([k, v]) => { + return v['allowUserResources'] ? k : undefined + }) + .filter(Boolean) as string[] + + if (isRunnableByName(runnable)) { + let hex = await hash(runnable.inlineScript?.content) + console.log('hex', hex, id) + return [ + `${id}:rawscript/${hex}`, + { + static_inputs: staticInputs, + one_of_inputs: {}, + allow_user_resources: allowUserResources + } + ] + } else if (isRunnableByPath(runnable)) { + let prefix = runnable.runType !== 'hubscript' ? runnable.runType : 'script' + return [ + `${id}:${prefix}/${runnable.path}`, + { + static_inputs: staticInputs, + one_of_inputs: {}, + allow_user_resources: allowUserResources + } + ] + } +} diff --git a/frontend/src/lib/components/raw_apps/utils.ts b/frontend/src/lib/components/raw_apps/utils.ts index 8a2b218d77..4f6c78ac72 100644 --- a/frontend/src/lib/components/raw_apps/utils.ts +++ b/frontend/src/lib/components/raw_apps/utils.ts @@ -1,7 +1,16 @@ -import type { Schema } from '$lib/common' -import { schemaToTsType } from '$lib/schema' -import { capitalize } from '$lib/utils' -import type { HiddenRunnable } from '../apps/types' +import type { ScriptLang } from '../../gen/types.gen' +import type { Schema } from '../../common' +import { schemaToTsType } from '../../schema' +import { capitalize } from '../../sharedUtils' +import { isRunnableByName, isRunnableByPath, type RunnableWithFields } from '../apps/inputType' +import type { InlineScript } from '../apps/sharedTypes' + +// export type RunnableWithFields = any + +type RunnableWithInlineScript = RunnableWithFields & { + inlineScript?: InlineScript & { language: ScriptLang } +} +export type Runnable = RunnableWithInlineScript | undefined export type RawApp = { files: string[] @@ -39,71 +48,63 @@ function removeStaticFields(schema: Schema, fields: Record) { +export function genWmillTs(runnables: Record) { return `// THIS FILE IS READ-ONLY // AND GENERATED AUTOMATICALLY FROM YOUR RUNNABLES - + ${Object.entries(runnables) - .map(([k, v]) => `export type RunBg${capitalize(k)} = ${hiddenRunnableToTsType(v)}\n`) + .map(([k, v]) => `export type RunBg${capitalize(k)} = ${hiddenRunnableToTsType(v)};`) + .join('\n\n')} - .join('\n')} +export declare const runBg: { +${Object.keys(runnables) + .map((k) => ` ${k}: (data: RunBg${capitalize(k)}) => Promise;`) + .join('\n')} +}; -export const runBg = { +export declare const runBgAsync: { ${Object.keys(runnables) - .map((k) => ` ${k}: null as unknown as (data: RunBg${capitalize(k)}) => Promise`) - .join(',\n')} -} - -export const runBgAsync = { -${Object.keys(runnables) - .map((k) => ` ${k}: null as unknown as (data: RunBg${capitalize(k)}) => Promise`) - .join(',\n')} -} - + .map((k) => ` ${k}: (data: RunBg${capitalize(k)}) => Promise;`) + .join('\n')} +}; export type Job = { - type: 'QueuedJob' | 'CompletedJob' - id: string - created_at: number - started_at: number | undefined - duration_ms: number - success: boolean - args: any - result: any -} + type: "QueuedJob" | "CompletedJob"; + id: string; + created_at: number; + started_at: number | undefined; + duration_ms: number; + success: boolean; + args: any; + result: any; +}; /** -* Execute a job and wait for it to complete and return the completed job -* @param id -*/ -// @ts-ignore -export function waitJob(id: string): Promise { - // implementation passed when bundling/deploying - return null as unknown as Promise -} + * Execute a job and wait for it to complete and return the completed job + * @param id + */ +export declare function waitJob(id: string): Promise; /** -* Get a job by id and return immediately with the current state of the job -* @param id -*/ -// @ts-ignore -export function getJob(id: string): Promise { - // implementation passed when bundling/deploying - return null as unknown as Promise -} + * Get a job by id and return immediately with the current state of the job + * @param id + */ +export declare function getJob(id: string): Promise; ` } diff --git a/frontend/src/lib/components/runs/RunRow.svelte b/frontend/src/lib/components/runs/RunRow.svelte index 99dc5f6dec..d32392038c 100644 --- a/frontend/src/lib/components/runs/RunRow.svelte +++ b/frontend/src/lib/components/runs/RunRow.svelte @@ -9,7 +9,8 @@ isScriptPreview, isJobSelectable, msToReadableTime, - isFlowPreview + isFlowPreview, + type RunsSelectionMode } from '$lib/utils' import { Badge, Button } from '../common' import ScheduleEditor from '$lib/components/triggers/schedules/ScheduleEditor.svelte' @@ -37,7 +38,6 @@ import Portal from '$lib/components/Portal.svelte' import WaitTimeWarning from '../common/waitTimeWarning/WaitTimeWarning.svelte' - import type { RunsSelectionMode } from './RunsBatchActionsDropdown.svelte' import DropdownV2 from '../DropdownV2.svelte' import { Tooltip } from '../meltComponents' import { GitIcon } from '../icons' @@ -205,10 +205,7 @@ Cancelling job... (created ) {/if} {:else if `scheduled_for` in job && job.scheduled_for && forLater(job.scheduled_for)} - Waiting executor () + Waiting executor () {:else} Waiting executor () {/if} diff --git a/frontend/src/lib/components/runs/RunsBatchActionsDropdown.svelte b/frontend/src/lib/components/runs/RunsBatchActionsDropdown.svelte index 5c59aebdfc..81892e605a 100644 --- a/frontend/src/lib/components/runs/RunsBatchActionsDropdown.svelte +++ b/frontend/src/lib/components/runs/RunsBatchActionsDropdown.svelte @@ -1,12 +1,9 @@ - - diff --git a/frontend/use_latest_ui_builder.sh b/frontend/use_latest_ui_builder.sh index 306a5d6124..c8e959ae52 100755 --- a/frontend/use_latest_ui_builder.sh +++ b/frontend/use_latest_ui_builder.sh @@ -1,5 +1,6 @@ -cd ../../../windmill-code-ui-builder +cd ../../windmill-code-ui-builder HASH=$(git rev-parse --short HEAD) HASH=${HASH::-1} -sed -i "s/ui_builder-[^.]*\.tar\.gz/ui_builder-${HASH}.tar.gz/" ../git/windmill/frontend/scripts/untar_ui_builder.js +echo "Using UI Builder hash: ${HASH}" +sed -i "s/ui_builder-[^.]*\.tar\.gz/ui_builder-${HASH}.tar.gz/" ../windmill/frontend/scripts/untar_ui_builder.js diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 522a6be139..0c746db886 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -7,6 +7,17 @@ const file = fileURLToPath(new URL('package.json', import.meta.url)) const json = readFileSync(file, 'utf8') const version = JSON.parse(json) +let plugin = { + name: 'configure-response-headers', + configureServer: (server) => { + server.middlewares.use((_req, res, next) => { + res.setHeader('Cross-Origin-Opener-Policy', 'same-origin') + res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp') + next() + }) + } +} + /** @type {import('vite').UserConfig} */ const config = { server: { @@ -21,6 +32,9 @@ const config = { 'public.windmill.xyz' ], port: 3000, + cors: { + origin: '*' + }, proxy: { '^/api/w/[^/]+/s3_proxy/.*': { target: process.env.REMOTE ?? 'https://app.windmill.dev/', @@ -61,9 +75,9 @@ const config = { } }, preview: { - port: 3000 + port: 3001 }, - plugins: [sveltekit(), ...(process.env.HTTPS === 'true' ? [mkcert()] : [])], + plugins: [sveltekit(), ...(process.env.HTTPS === 'true' ? [mkcert()] : []), plugin], define: { __pkg__: version }, diff --git a/frontend/vite.sharedUtils.config.js b/frontend/vite.sharedUtils.config.js deleted file mode 100644 index 24428edc10..0000000000 --- a/frontend/vite.sharedUtils.config.js +++ /dev/null @@ -1,59 +0,0 @@ -import { defineConfig } from 'vite' -import { resolve } from 'path' -import { copyFileSync, readFileSync, writeFileSync } from 'fs' -import dts from 'vite-plugin-dts' - -export default defineConfig({ - build: { - lib: { - entry: resolve(__dirname, 'src/lib/components/apps/editor/appPolicy.ts'), - name: 'sharedUtils', - fileName: (format) => `lib.${format}.js`, - formats: ['es'] - }, - outDir: 'dist/sharedUtils', - rollupOptions: { - // Externalize dependencies you don't want bundled - external: [], - output: { - globals: {} - } - } - }, - plugins: [ - dts({ - include: ['src/lib/components/apps/editor/appPolicy.ts'], - outDir: 'dist/sharedUtils', - rollupTypes: false, - // Generate individual .d.ts files - insertTypesEntry: false, - // Skip diagnostics to avoid TypeScript errors from other files - skipDiagnostics: true, - tsconfigPath: './tsconfig.json', - entryRoot: 'src/lib/components/apps/editor' - }), - { - name: 'rename-and-fix-types', - closeBundle() { - const dtsPath = resolve(__dirname, 'dist/sharedUtils/appPolicy.d.ts') - const targetPath = resolve(__dirname, 'dist/sharedUtils/lib.d.ts') - - // Read the generated .d.ts file - const content = readFileSync(dtsPath, 'utf-8') - - // Write to lib.d.ts (main entry point for types) - writeFileSync(targetPath, content) - } - }, - { - name: 'copy-package-json', - closeBundle() { - // Copy package.json to dist - copyFileSync( - resolve(__dirname, 'package.sharedUtils.json'), - resolve(__dirname, 'dist/sharedUtils/package.json') - ) - } - } - ] -}) From b3a2e91caf5ed980a5ef154a635412d9450c7b5f Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 29 Nov 2025 01:28:58 +0000 Subject: [PATCH 13/39] update pkg lock --- frontend/package-lock.json | 1449 +++++++++++++++++++++--------------- 1 file changed, 849 insertions(+), 600 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a4aee1c84e..6e1120c6d1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -230,12 +230,12 @@ "license": "0BSD" }, "node_modules/@aws-sdk/types": { - "version": "3.901.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", - "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.6.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -1864,6 +1864,40 @@ "@csstools/css-tokenizer": "^2.4.1" } }, + "node_modules/@emnapi/core": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", @@ -1884,9 +1918,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { @@ -2103,78 +2137,6 @@ "node": "20 || >=22" } }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -2343,6 +2305,19 @@ "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.118" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz", + "integrity": "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2407,25 +2382,14 @@ "integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==", "license": "MIT" }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/@playwright/test": { - "version": "1.56.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.0.tgz", - "integrity": "sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", + "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.56.0" + "playwright": "1.57.0" }, "bin": { "playwright": "cli.js" @@ -2457,6 +2421,91 @@ "integrity": "sha512-dSMyuNPN2k+tFeNZ0+QJ7S1zDJ0UeNL+lpnPFR9K5avj2V4uG4m6FdjrApQ9Zi35AIocaDp/KGfBD9gR5MLUbQ==", "license": "SEE LICENSE IN LICENSE" }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.52.tgz", + "integrity": "sha512-MBGIgysimZPqTDcLXI+i9VveijkP5C3EAncEogXhqfax6YXj1Tr2LY3DVuEOMIjWfMPMhtQSPup4fSTAmgjqIw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.52.tgz", + "integrity": "sha512-MmKeoLnKu1d9j6r19K8B+prJnIZ7u+zQ+zGQ3YHXGnr41rzE3eqQLovlkvoZnRoxDGPA4ps0pGiwXy6YE3lJyg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.52.tgz", + "integrity": "sha512-qpHedvQBmIjT8zdnjN3nWPR2qjQyJttbXniCEKKdHeAbZG9HyNPBUzQF7AZZGwmS9coQKL+hWg9FhWzh2dZ2IA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.52.tgz", + "integrity": "sha512-dDp7WbPapj/NVW0LSiH/CLwMhmLwwKb3R7mh2kWX+QW85X1DGVnIEyKh9PmNJjB/+suG1dJygdtdNPVXK1hylg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.52.tgz", + "integrity": "sha512-9e4l6vy5qNSliDPqNfR6CkBOAx6PH7iDV4OJiEJzajajGrVy8gc/IKKJUsoE52G8ud8MX6r3PMl97NfwgOzB7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/binding-linux-arm64-gnu": { "version": "1.0.0-beta.52", "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.52.tgz", @@ -2474,6 +2523,142 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.52.tgz", + "integrity": "sha512-ENLmSQCWqSA/+YN45V2FqTIemg7QspaiTjlm327eUAMeOLdqmSOVVyrQexJGNTQ5M8sDYCgVAig2Kk01Ggmqaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.52.tgz", + "integrity": "sha512-klahlb2EIFltSUubn/VLjuc3qxp1E7th8ukayPfdkcKvvYcQ5rJztgx8JsJSuAKVzKtNTqUGOhy4On71BuyV8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.52.tgz", + "integrity": "sha512-UuA+JqQIgqtkgGN2c/AQ5wi8M6mJHrahz/wciENPTeI6zEIbbLGoth5XN+sQe2pJDejEVofN9aOAp0kaazwnVg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.52.tgz", + "integrity": "sha512-1BNQW8u4ro8bsN1+tgKENJiqmvc+WfuaUhXzMImOVSMw28pkBKdfZtX2qJPADV3terx+vNJtlsgSGeb3+W6Jiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.52.tgz", + "integrity": "sha512-K/p7clhCqJOQpXGykrFaBX2Dp9AUVIDHGc+PtFGBwg7V+mvBTv/tsm3LC3aUmH02H2y3gz4y+nUTQ0MLpofEEg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.52.tgz", + "integrity": "sha512-a4EkXBtnYYsKipjS7QOhEBM4bU5IlR9N1hU+JcVEVeuTiaslIyhWVKsvf7K2YkQHyVAJ+7/A9BtrGqORFcTgng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-ia32-msvc": { + "version": "1.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.52.tgz", + "integrity": "sha512-5ZXcYyd4GxPA6QfbGrNcQjmjbuLGvfz6728pZMsQvGHI+06LT06M6TPtXvFvLgXtexc+OqvFe1yAIXJU1gob/w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.52", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.52.tgz", + "integrity": "sha512-tzpnRQXJrSzb8Z9sm97UD3cY0toKOImx+xRKsDLX4zHaAlRXWh7jbaKBePJXEN7gNw7Nm03PBNwphdtA8KSUYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.52", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.52.tgz", @@ -2512,9 +2697,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.7.0.tgz", - "integrity": "sha512-KM8Or+jCDCrUI3wYYhj7ehrC7aATB1NdJ1aFEE/YLKNLVH257k9RNeOqKdg0JOxjyEpVD7KKsmmob9mRy1Ho2g==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", + "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -2546,9 +2731,9 @@ } }, "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz", - "integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.7.tgz", + "integrity": "sha512-znp1A/Y1Jj4l/Zy7PX5DZKBE0ZNY+5QBngiE21NJkfSTyzzC5iKNWOtwFXKtIrn7MXEFBck4jD95iBNkGjK92Q==", "license": "MIT", "peerDependencies": { "acorn": "^8.9.0" @@ -2565,9 +2750,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.46.5", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.46.5.tgz", - "integrity": "sha512-7TSvMrCdmig5TMyYDW876C5FljhA0wlGixtvASCiqUqtLfmyEEpaysXjC7GhR5mWcGRrCGF+L2Bl1eEaW1wTCA==", + "version": "2.49.0", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.49.0.tgz", + "integrity": "sha512-oH8tXw7EZnie8FdOWYrF7Yn4IKrqTFHhXvl8YxXxbKwTMcD/5NNCryUSEXRk2ZR4ojnub0P8rNrsVGHXWqIDtA==", "dev": true, "license": "MIT", "dependencies": { @@ -2604,13 +2789,13 @@ } }, "node_modules/@sveltejs/package": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-2.5.4.tgz", - "integrity": "sha512-8+1hccAt0M3PPkHVPKH54Wc+cc1PNxRqCrICZiv/hEEto8KwbQVRghxNgTB4htIPyle+4CIB8RayTQH5zRQh9A==", + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-2.5.7.tgz", + "integrity": "sha512-qqD9xa9H7TDiGFrF6rz7AirOR8k15qDK/9i4MIE8te4vWsv5GEogPks61rrZcLy+yWph+aI6pIj2MdoK3YI8AQ==", "dev": true, "license": "MIT", "dependencies": { - "chokidar": "^4.0.3", + "chokidar": "^5.0.0", "kleur": "^4.1.5", "sade": "^1.8.1", "semver": "^7.5.4", @@ -2767,6 +2952,17 @@ "svelte": "^5.0.0" } }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -3104,9 +3300,9 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", "dev": true, "license": "MIT" }, @@ -3162,9 +3358,9 @@ "license": "MIT" }, "node_modules/@types/vscode": { - "version": "1.105.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.105.0.tgz", - "integrity": "sha512-Lotk3CTFlGZN8ray4VxJE7axIyLZZETQJVWi/lYoUVQuqfRxlQhVOfoejsD2V3dVXPSbS15ov5ZyowMAzgUqcw==", + "version": "1.106.1", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.106.1.tgz", + "integrity": "sha512-R/HV8u2h8CAddSbX8cjpdd7B8/GnE4UjgjpuGuHcbp1xV6yh4OeqU4L1pKjlwujCrSFS0MOpwJAIs/NexMB1fQ==", "dev": true, "license": "MIT" }, @@ -3402,22 +3598,22 @@ "peer": true }, "node_modules/@xyflow/svelte": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@xyflow/svelte/-/svelte-1.3.1.tgz", - "integrity": "sha512-aLr2v0/nr+zER5+dCzEmR5qCu9l7FCKZwYiRvCX15U2FVIdO2M522pyPEr7Siwq6EEx0QjECACeN+rLZCDSzeA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@xyflow/svelte/-/svelte-1.4.2.tgz", + "integrity": "sha512-E6mw8wt3NXS5imGJLWKrdOEOGHAkbFhsVwzb9MPG8ohkPjQ8lMeDM9o3fBSoDNp7xr16s7Q3AOVS2JzVyoY2og==", "license": "MIT", "dependencies": { "@svelte-put/shortcut": "^4.1.0", - "@xyflow/system": "0.0.70" + "@xyflow/system": "0.0.73" }, "peerDependencies": { "svelte": "^5.25.0" } }, "node_modules/@xyflow/system": { - "version": "0.0.70", - "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.70.tgz", - "integrity": "sha512-PpC//u9zxdjj0tfTSmZrg3+sRbTz6kop/Amky44U2Dl51sxzDTIUfXMwETOYpmr2dqICWXBIJwXL2a9QWtX2XA==", + "version": "0.0.73", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.73.tgz", + "integrity": "sha512-C2ymH2V4mYDkdVSiRx0D7R0s3dvfXiupVBcko6tXP5K4tVdSBMo22/e3V9yRNdn+2HQFv44RFKzwOyCcUUDAVQ==", "license": "MIT", "dependencies": { "@types/d3-drag": "^3.0.7", @@ -3471,6 +3667,31 @@ "node": ">=6" } }, + "node_modules/abstract-leveldown/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -3705,9 +3926,9 @@ "license": "MIT" }, "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "version": "10.4.22", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", + "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", "dev": true, "funding": [ { @@ -3725,9 +3946,9 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", + "browserslist": "^4.27.0", + "caniuse-lite": "^1.0.30001754", + "fraction.js": "^5.3.4", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -3743,9 +3964,9 @@ } }, "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "dev": true, "license": "MIT", "dependencies": { @@ -3802,9 +4023,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.16", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz", - "integrity": "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==", + "version": "2.8.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz", + "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3842,6 +4063,46 @@ "readable-stream": "^3.4.0" } }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -3887,9 +4148,9 @@ "license": "MIT" }, "node_modules/browserslist": { - "version": "4.26.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", + "version": "4.28.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", + "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", "dev": true, "funding": [ { @@ -3907,11 +4168,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" @@ -3921,9 +4182,9 @@ } }, "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "funding": [ { "type": "github", @@ -3939,10 +4200,9 @@ } ], "license": "MIT", - "optional": true, "dependencies": { "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "ieee754": "^1.2.1" } }, "node_modules/c12": { @@ -4173,9 +4433,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001750", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz", - "integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==", + "version": "1.0.30001757", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", + "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", "dev": true, "funding": [ { @@ -4281,16 +4541,16 @@ } }, "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^4.0.1" + "readdirp": "^5.0.0" }, "engines": { - "node": ">= 14.16.0" + "node": ">= 20.19.0" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -4331,24 +4591,6 @@ "node": ">=12" } }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -5121,10 +5363,9 @@ } }, "node_modules/devalue": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.3.2.tgz", - "integrity": "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==", - "dev": true, + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.5.0.tgz", + "integrity": "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==", "license": "MIT" }, "node_modules/devlop": { @@ -5284,9 +5525,9 @@ } }, "node_modules/driver.js": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/driver.js/-/driver.js-1.3.6.tgz", - "integrity": "sha512-g2nNuu+tWmPpuoyk3ffpT9vKhjPz4NrJzq6mkRDZIwXCrFhrKdDJ9TX5tJOBpvCTBrBYjgRQ17XlcQB15q4gMg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/driver.js/-/driver.js-1.4.0.tgz", + "integrity": "sha512-Gm64jm6PmcU+si21sQhBrTAM1JvUrR0QhNmjkprNLxohOBzul9+pNHXgQaT9lW84gwg9GMLB3NZGuGolsz5uew==", "license": "MIT" }, "node_modules/dts-bundle-generator": { @@ -5326,13 +5567,6 @@ "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", "license": "ISC" }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, "node_modules/easy-reactive": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/easy-reactive/-/easy-reactive-1.0.4.tgz", @@ -5345,9 +5579,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.235", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.235.tgz", - "integrity": "sha512-i/7ntLFwOdoHY7sgjlTIDo4Sl8EdoTjWIaKinYOVfC6bOp71bmwenyZthWHcasxgHDNWbWxvG9M3Ia116zIaYQ==", + "version": "1.5.262", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", + "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", "dev": true, "license": "ISC" }, @@ -5771,16 +6005,6 @@ "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", "license": "MIT" }, - "node_modules/esm-env-robust": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/esm-env-robust/-/esm-env-robust-0.0.3.tgz", - "integrity": "sha512-90Gnuw2DALOqlL1581VxP3GHPUNHX9U+fQ+8FNcTTFClhY5gEggAAnJ3q1b2Oq23knRsjv8YpNeMRPaMLUymOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esm-env": "^1.0.0" - } - }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -5823,9 +6047,9 @@ } }, "node_modules/esrap": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", - "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.0.tgz", + "integrity": "sha512-WBmtxe7R9C5mvL4n2le8nMUe4mD5V9oiK2vJpQ9I3y20ENPUomPcphBXE8D1x/Bm84oN1V+lOfgXxtqmxTp3Xg==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -5937,9 +6161,9 @@ "license": "Apache-2.0" }, "node_modules/fast-equals": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.2.tgz", - "integrity": "sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.3.tgz", + "integrity": "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -6099,13 +6323,13 @@ "license": "ISC" }, "node_modules/focus-trap": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.5.tgz", - "integrity": "sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==", + "version": "7.6.6", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.6.tgz", + "integrity": "sha512-v/Z8bvMCajtx4mEXmOo7QEsIzlIOqRXTIwgUfsFOF9gEsespdbD0AkPIka1bSXZ8Y8oZ+2IVDQZePkTfEHZl7Q==", "dev": true, "license": "MIT", "dependencies": { - "tabbable": "^6.2.0" + "tabbable": "^6.3.0" } }, "node_modules/follow-redirects": { @@ -6129,27 +6353,10 @@ } } }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "license": "MIT", "dependencies": { @@ -6164,16 +6371,16 @@ } }, "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "dev": true, "license": "MIT", "engines": { "node": "*" }, "funding": { - "type": "patreon", + "type": "github", "url": "https://github.com/sponsors/rawify" } }, @@ -6584,9 +6791,9 @@ "license": "MIT" }, "node_modules/graphql": { - "version": "16.11.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", - "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", + "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" @@ -7253,22 +7460,6 @@ "url": "https://github.com/sponsors/dmonad" } }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/javascript-lp-solver": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/javascript-lp-solver/-/javascript-lp-solver-0.4.24.tgz", @@ -7301,9 +7492,9 @@ "peer": true }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -7442,6 +7633,31 @@ "node": ">=6" } }, + "node_modules/level-codec/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/level-concat-iterator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", @@ -7482,6 +7698,21 @@ "node": ">=6" } }, + "node_modules/level-iterator-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/level-js": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz", @@ -7496,6 +7727,31 @@ "ltgt": "^2.1.2" } }, + "node_modules/level-js/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/level-packager": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", @@ -7636,6 +7892,111 @@ "lightningcss-win32-x64-msvc": "1.30.2" } }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lightningcss-linux-arm64-gnu": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", @@ -7678,6 +8039,90 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", @@ -7827,9 +8272,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.19", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", - "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -8066,9 +8511,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -8803,10 +9248,10 @@ } }, "node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "license": "ISC", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/brace-expansion": "^5.0.0" }, @@ -8977,9 +9422,9 @@ } }, "node_modules/monaco-vim": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/monaco-vim/-/monaco-vim-0.4.2.tgz", - "integrity": "sha512-rdbQC3O2rmpwX2Orzig/6gZjZfH7q7TIeB+uEl49sa+QyNm3jCKJOw5mwxBdFzTqbrPD+URfg6A2lEkuL5kymw==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/monaco-vim/-/monaco-vim-0.4.4.tgz", + "integrity": "sha512-LNChAb//WEm/W+eyeHG/0+pdVEHotk2hLTN+M3sQZx5E8cAlSWSgqcxpcRuQnxDybSln7pfHF9i63HmbIQvrWw==", "license": "MIT", "peerDependencies": { "monaco-editor": "*" @@ -9095,9 +9540,9 @@ } }, "node_modules/node-abi": { - "version": "3.78.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.78.0.tgz", - "integrity": "sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ==", + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", "license": "MIT", "optional": true, "dependencies": { @@ -9154,9 +9599,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.23", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", - "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -9335,9 +9780,9 @@ } }, "node_modules/openai": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.9.0.tgz", - "integrity": "sha512-n2sJRYmM+xfJ0l3OfH8eNnIyv3nQY7L08gZQu3dw6wSdfPtKAk92L83M2NIP5SS8Cl/bsBBG3yKzEOjkx0O+7A==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.9.1.tgz", + "integrity": "sha512-vQ5Rlt0ZgB3/BNmTa7bIijYFhz3YBceAA3Z4JuoMSBftBF9YqFHIEhZakSs+O/Ad7EaoEimZvHxD5ylRjN11Lg==", "license": "Apache-2.0", "bin": { "openai": "bin/cli" @@ -9439,13 +9884,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, "node_modules/pako": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", @@ -9575,30 +10013,6 @@ "dev": true, "license": "MIT" }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -9731,13 +10145,13 @@ "license": "MIT" }, "node_modules/playwright": { - "version": "1.56.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.0.tgz", - "integrity": "sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.56.0" + "playwright-core": "1.57.0" }, "bin": { "playwright": "cli.js" @@ -9750,9 +10164,9 @@ } }, "node_modules/playwright-core": { - "version": "1.56.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.0.tgz", - "integrity": "sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==", + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -10555,9 +10969,9 @@ } }, "node_modules/prettier": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", - "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.2.tgz", + "integrity": "sha512-n3HV2J6QhItCXndGa3oMWvWFAgN1ibnS7R9mt6iokScBOC0Ul9/iZORmU2IWUMcyAQaMPjTlY3uT34TqocUxMA==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -10711,52 +11125,12 @@ "yaml": "^2.4.1" } }, - "node_modules/quicktype-core/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/quicktype-core/node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "license": "(MIT AND Zlib)" }, - "node_modules/quicktype-core/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/quill": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz", @@ -10909,28 +11283,29 @@ } }, "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "license": "MIT", - "optional": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 14.18.0" + "node": ">= 20.19.0" }, "funding": { "type": "individual", @@ -11087,13 +11462,13 @@ } }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -11276,9 +11651,9 @@ } }, "node_modules/set-cookie-parser": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", "dev": true, "license": "MIT" }, @@ -11343,6 +11718,7 @@ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">=14" }, @@ -11562,22 +11938,6 @@ "node": ">=8" } }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -11590,20 +11950,6 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-indent": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", @@ -11826,18 +12172,18 @@ } }, "node_modules/sucrase": { - "version": "3.35.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", - "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", - "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { @@ -11848,23 +12194,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/sucrase/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/sucrase/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/sucrase/node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -11875,43 +12204,6 @@ "node": ">= 6" } }, - "node_modules/sucrase/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sucrase/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -11957,9 +12249,9 @@ } }, "node_modules/svelte": { - "version": "5.39.12", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.39.12.tgz", - "integrity": "sha512-CEzwxFuEycokU8K8CE/OuwVbmei+ivu2HvBGYIdASfMa1hCRSNr4RRkzNSvbAvu6h+BOig2CsZTAEY+WKvwZpA==", + "version": "5.45.2", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.45.2.tgz", + "integrity": "sha512-yyXdW2u3H0H/zxxWoGwJoQlRgaSJLp+Vhktv12iRw2WRDlKqUPT54Fi0K/PkXqrdkcQ98aBazpy0AH4BCBVfoA==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.4", @@ -11970,8 +12262,9 @@ "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", + "devalue": "^5.5.0", "esm-env": "^1.2.1", - "esrap": "^2.1.0", + "esrap": "^2.2.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", @@ -12015,9 +12308,9 @@ } }, "node_modules/svelte-check": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.3.tgz", - "integrity": "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.4.tgz", + "integrity": "sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw==", "dev": true, "license": "MIT", "dependencies": { @@ -12038,6 +12331,22 @@ "typescript": ">=5.0.0" } }, + "node_modules/svelte-check/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/svelte-check/node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -12071,6 +12380,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/svelte-check/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/svelte-eslint-parser": { "version": "0.43.0", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz", @@ -12152,9 +12475,9 @@ } }, "node_modules/svelte-highlight": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/svelte-highlight/-/svelte-highlight-7.8.4.tgz", - "integrity": "sha512-aVp+Q0hH9kI7PlSDrklmFTF4Uj7wYj7UGuqkREnkXlqpEffxr2g6esZcMMaTgECdg5rb2mJqM88+nwS10ecoTg==", + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/svelte-highlight/-/svelte-highlight-7.9.0.tgz", + "integrity": "sha512-226LBTtvTnM2L2JkQq8mZeKEeMfPLYyta7VxZatFT4UPX5zdHEerKeMTvrfbxm7MVTWc7TPThsNoVdhWC177KQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12242,16 +12565,13 @@ "license": "MPL-2.0" }, "node_modules/svelte-splitpanes": { - "version": "8.0.9", - "resolved": "https://registry.npmjs.org/svelte-splitpanes/-/svelte-splitpanes-8.0.9.tgz", - "integrity": "sha512-L3oLXTC99M191FInTXJ/f/2i0welRql1QuVbPaU8iy6nvCR6X9VyjHCsCpLqKGWHwqkWo/AM9CQ1c0nzlb+MkA==", + "version": "8.0.12", + "resolved": "https://registry.npmjs.org/svelte-splitpanes/-/svelte-splitpanes-8.0.12.tgz", + "integrity": "sha512-HJ07HgbtY0Q/35TEuJquGy47dtgCVavV7ay9r1FhWRx3boyUs3RpeiHlwMKliznCKy2ZotNeT+8GG+mIoNjgRA==", "dev": true, "license": "MIT", - "dependencies": { - "esm-env-robust": "0.0.3" - }, "peerDependencies": { - "svelte": "^4.2.19 || ^5.1.0" + "svelte": "^5.43.0" } }, "node_modules/svelte2tsx": { @@ -12313,9 +12633,9 @@ } }, "node_modules/tabbable": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", - "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.3.0.tgz", + "integrity": "sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==", "dev": true, "license": "MIT" }, @@ -12451,11 +12771,11 @@ } }, "node_modules/tar": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", - "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", + "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", @@ -12504,6 +12824,21 @@ "node": ">=6" } }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -12826,9 +13161,9 @@ } }, "node_modules/unist-util-is": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -12880,9 +13215,9 @@ } }, "node_modules/unist-util-visit-parents": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -12894,9 +13229,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "dev": true, "funding": [ { @@ -13403,25 +13738,6 @@ "license": "MIT" }, "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", @@ -13439,73 +13755,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -13528,6 +13777,16 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "license": "MIT", + "optional": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, "node_modules/xml-utils": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.10.2.tgz", @@ -13648,16 +13907,6 @@ "yjs": "^13.5.6" } }, - "node_modules/y-websocket/node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", - "license": "MIT", - "optional": true, - "dependencies": { - "async-limiter": "~1.0.0" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -13748,9 +13997,9 @@ } }, "node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", "license": "MIT", "engines": { "node": ">=12.20" @@ -13782,12 +14031,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", - "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", + "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", "license": "ISC", "peerDependencies": { - "zod": "^3.24.1" + "zod": "^3.25 || ^4" } }, "node_modules/zstddec": { From b2d5eac11a7e35e9d8cf4dcc41faaf62b6817516 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 29 Nov 2025 01:32:05 +0000 Subject: [PATCH 14/39] update pkg lock --- frontend/package-lock.json | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6e1120c6d1..1f4bd9ab00 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13778,13 +13778,26 @@ } }, "node_modules/ws": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", - "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", "optional": true, - "dependencies": { - "async-limiter": "~1.0.0" + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/xml-utils": { @@ -13907,6 +13920,16 @@ "yjs": "^13.5.6" } }, + "node_modules/y-websocket/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "license": "MIT", + "optional": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", From b40ae56de5dab3ff49afb6603bbf9fda6569196a Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 29 Nov 2025 09:48:18 +0000 Subject: [PATCH 15/39] fix lock --- frontend/package-lock.json | 990 ++++++++++++++++++++++--------------- 1 file changed, 593 insertions(+), 397 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1f4bd9ab00..4701b21c29 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -230,12 +230,12 @@ "license": "0BSD" }, "node_modules/@aws-sdk/types": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", - "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "version": "3.901.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.901.0.tgz", + "integrity": "sha512-FfEM25hLEs4LoXsLXQ/q6X6L4JmKkKkbVFpKD4mwfVHtRVQG6QxJiCPcrkcPISquiy6esbwK2eh64TWbiD60cg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.6.0", "tslib": "^2.6.2" }, "engines": { @@ -1918,9 +1918,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, "license": "MIT", "engines": { @@ -2137,6 +2137,78 @@ "node": "20 || >=22" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -2382,14 +2454,25 @@ "integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==", "license": "MIT" }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@playwright/test": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", - "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.56.0.tgz", + "integrity": "sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.57.0" + "playwright": "1.56.0" }, "bin": { "playwright": "cli.js" @@ -2667,9 +2750,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", - "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "version": "4.52.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", + "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", "cpu": [ "x64" ], @@ -2697,9 +2780,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", - "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.7.0.tgz", + "integrity": "sha512-KM8Or+jCDCrUI3wYYhj7ehrC7aATB1NdJ1aFEE/YLKNLVH257k9RNeOqKdg0JOxjyEpVD7KKsmmob9mRy1Ho2g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -2731,9 +2814,9 @@ } }, "node_modules/@sveltejs/acorn-typescript": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.7.tgz", - "integrity": "sha512-znp1A/Y1Jj4l/Zy7PX5DZKBE0ZNY+5QBngiE21NJkfSTyzzC5iKNWOtwFXKtIrn7MXEFBck4jD95iBNkGjK92Q==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz", + "integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==", "license": "MIT", "peerDependencies": { "acorn": "^8.9.0" @@ -2750,9 +2833,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.49.0", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.49.0.tgz", - "integrity": "sha512-oH8tXw7EZnie8FdOWYrF7Yn4IKrqTFHhXvl8YxXxbKwTMcD/5NNCryUSEXRk2ZR4ojnub0P8rNrsVGHXWqIDtA==", + "version": "2.46.5", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.46.5.tgz", + "integrity": "sha512-7TSvMrCdmig5TMyYDW876C5FljhA0wlGixtvASCiqUqtLfmyEEpaysXjC7GhR5mWcGRrCGF+L2Bl1eEaW1wTCA==", "dev": true, "license": "MIT", "dependencies": { @@ -2789,13 +2872,13 @@ } }, "node_modules/@sveltejs/package": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-2.5.7.tgz", - "integrity": "sha512-qqD9xa9H7TDiGFrF6rz7AirOR8k15qDK/9i4MIE8te4vWsv5GEogPks61rrZcLy+yWph+aI6pIj2MdoK3YI8AQ==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@sveltejs/package/-/package-2.5.4.tgz", + "integrity": "sha512-8+1hccAt0M3PPkHVPKH54Wc+cc1PNxRqCrICZiv/hEEto8KwbQVRghxNgTB4htIPyle+4CIB8RayTQH5zRQh9A==", "dev": true, "license": "MIT", "dependencies": { - "chokidar": "^5.0.0", + "chokidar": "^4.0.3", "kleur": "^4.1.5", "sade": "^1.8.1", "semver": "^7.5.4", @@ -3300,9 +3383,9 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", "dev": true, "license": "MIT" }, @@ -3358,9 +3441,9 @@ "license": "MIT" }, "node_modules/@types/vscode": { - "version": "1.106.1", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.106.1.tgz", - "integrity": "sha512-R/HV8u2h8CAddSbX8cjpdd7B8/GnE4UjgjpuGuHcbp1xV6yh4OeqU4L1pKjlwujCrSFS0MOpwJAIs/NexMB1fQ==", + "version": "1.105.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.105.0.tgz", + "integrity": "sha512-Lotk3CTFlGZN8ray4VxJE7axIyLZZETQJVWi/lYoUVQuqfRxlQhVOfoejsD2V3dVXPSbS15ov5ZyowMAzgUqcw==", "dev": true, "license": "MIT" }, @@ -3598,22 +3681,22 @@ "peer": true }, "node_modules/@xyflow/svelte": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@xyflow/svelte/-/svelte-1.4.2.tgz", - "integrity": "sha512-E6mw8wt3NXS5imGJLWKrdOEOGHAkbFhsVwzb9MPG8ohkPjQ8lMeDM9o3fBSoDNp7xr16s7Q3AOVS2JzVyoY2og==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@xyflow/svelte/-/svelte-1.3.1.tgz", + "integrity": "sha512-aLr2v0/nr+zER5+dCzEmR5qCu9l7FCKZwYiRvCX15U2FVIdO2M522pyPEr7Siwq6EEx0QjECACeN+rLZCDSzeA==", "license": "MIT", "dependencies": { "@svelte-put/shortcut": "^4.1.0", - "@xyflow/system": "0.0.73" + "@xyflow/system": "0.0.70" }, "peerDependencies": { "svelte": "^5.25.0" } }, "node_modules/@xyflow/system": { - "version": "0.0.73", - "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.73.tgz", - "integrity": "sha512-C2ymH2V4mYDkdVSiRx0D7R0s3dvfXiupVBcko6tXP5K4tVdSBMo22/e3V9yRNdn+2HQFv44RFKzwOyCcUUDAVQ==", + "version": "0.0.70", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.70.tgz", + "integrity": "sha512-PpC//u9zxdjj0tfTSmZrg3+sRbTz6kop/Amky44U2Dl51sxzDTIUfXMwETOYpmr2dqICWXBIJwXL2a9QWtX2XA==", "license": "MIT", "dependencies": { "@types/d3-drag": "^3.0.7", @@ -3667,31 +3750,6 @@ "node": ">=6" } }, - "node_modules/abstract-leveldown/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -3926,9 +3984,9 @@ "license": "MIT" }, "node_modules/autoprefixer": { - "version": "10.4.22", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", - "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", "dev": true, "funding": [ { @@ -3946,9 +4004,9 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.27.0", - "caniuse-lite": "^1.0.30001754", - "fraction.js": "^5.3.4", + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -3964,9 +4022,9 @@ } }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", "dev": true, "license": "MIT", "dependencies": { @@ -4023,9 +4081,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.32", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz", - "integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==", + "version": "2.8.16", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz", + "integrity": "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4063,46 +4121,6 @@ "readable-stream": "^3.4.0" } }, - "node_modules/bl/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "optional": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -4148,9 +4166,9 @@ "license": "MIT" }, "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "version": "4.26.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", + "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", "dev": true, "funding": [ { @@ -4168,11 +4186,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" + "baseline-browser-mapping": "^2.8.9", + "caniuse-lite": "^1.0.30001746", + "electron-to-chromium": "^1.5.227", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -4182,9 +4200,9 @@ } }, "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "funding": [ { "type": "github", @@ -4200,9 +4218,10 @@ } ], "license": "MIT", + "optional": true, "dependencies": { "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "ieee754": "^1.1.13" } }, "node_modules/c12": { @@ -4433,9 +4452,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001757", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", - "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", + "version": "1.0.30001750", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz", + "integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==", "dev": true, "funding": [ { @@ -4541,16 +4560,16 @@ } }, "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "license": "MIT", "dependencies": { - "readdirp": "^5.0.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 20.19.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -4591,6 +4610,24 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -5363,9 +5400,10 @@ } }, "node_modules/devalue": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.5.0.tgz", - "integrity": "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.3.2.tgz", + "integrity": "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==", + "dev": true, "license": "MIT" }, "node_modules/devlop": { @@ -5525,9 +5563,9 @@ } }, "node_modules/driver.js": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/driver.js/-/driver.js-1.4.0.tgz", - "integrity": "sha512-Gm64jm6PmcU+si21sQhBrTAM1JvUrR0QhNmjkprNLxohOBzul9+pNHXgQaT9lW84gwg9GMLB3NZGuGolsz5uew==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/driver.js/-/driver.js-1.3.6.tgz", + "integrity": "sha512-g2nNuu+tWmPpuoyk3ffpT9vKhjPz4NrJzq6mkRDZIwXCrFhrKdDJ9TX5tJOBpvCTBrBYjgRQ17XlcQB15q4gMg==", "license": "MIT" }, "node_modules/dts-bundle-generator": { @@ -5567,6 +5605,13 @@ "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==", "license": "ISC" }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/easy-reactive": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/easy-reactive/-/easy-reactive-1.0.4.tgz", @@ -5579,9 +5624,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.262", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", - "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", + "version": "1.5.235", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.235.tgz", + "integrity": "sha512-i/7ntLFwOdoHY7sgjlTIDo4Sl8EdoTjWIaKinYOVfC6bOp71bmwenyZthWHcasxgHDNWbWxvG9M3Ia116zIaYQ==", "dev": true, "license": "ISC" }, @@ -6005,6 +6050,16 @@ "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", "license": "MIT" }, + "node_modules/esm-env-robust": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/esm-env-robust/-/esm-env-robust-0.0.3.tgz", + "integrity": "sha512-90Gnuw2DALOqlL1581VxP3GHPUNHX9U+fQ+8FNcTTFClhY5gEggAAnJ3q1b2Oq23knRsjv8YpNeMRPaMLUymOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esm-env": "^1.0.0" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -6047,9 +6102,9 @@ } }, "node_modules/esrap": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.0.tgz", - "integrity": "sha512-WBmtxe7R9C5mvL4n2le8nMUe4mD5V9oiK2vJpQ9I3y20ENPUomPcphBXE8D1x/Bm84oN1V+lOfgXxtqmxTp3Xg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", + "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -6161,9 +6216,9 @@ "license": "Apache-2.0" }, "node_modules/fast-equals": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.3.tgz", - "integrity": "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.2.tgz", + "integrity": "sha512-6rxyATwPCkaFIL3JLqw8qXqMpIZ942pTX/tbQFkRsDGblS8tNGtlUauA/+mt6RUfqn/4MoEr+WDkYoIQbibWuQ==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -6323,13 +6378,13 @@ "license": "ISC" }, "node_modules/focus-trap": { - "version": "7.6.6", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.6.tgz", - "integrity": "sha512-v/Z8bvMCajtx4mEXmOo7QEsIzlIOqRXTIwgUfsFOF9gEsespdbD0AkPIka1bSXZ8Y8oZ+2IVDQZePkTfEHZl7Q==", + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.6.5.tgz", + "integrity": "sha512-7Ke1jyybbbPZyZXFxEftUtxFGLMpE2n6A+z//m4CRDlj0hW+o3iYSmh8nFlYMurOiJVDmJRilUQtJr08KfIxlg==", "dev": true, "license": "MIT", "dependencies": { - "tabbable": "^6.3.0" + "tabbable": "^6.2.0" } }, "node_modules/follow-redirects": { @@ -6353,10 +6408,27 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, "license": "MIT", "dependencies": { @@ -6371,16 +6443,16 @@ } }, "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true, "license": "MIT", "engines": { "node": "*" }, "funding": { - "type": "github", + "type": "patreon", "url": "https://github.com/sponsors/rawify" } }, @@ -6791,9 +6863,9 @@ "license": "MIT" }, "node_modules/graphql": { - "version": "16.12.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz", - "integrity": "sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==", + "version": "16.11.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz", + "integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==", "license": "MIT", "engines": { "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" @@ -7460,6 +7532,22 @@ "url": "https://github.com/sponsors/dmonad" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/javascript-lp-solver": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/javascript-lp-solver/-/javascript-lp-solver-0.4.24.tgz", @@ -7492,9 +7580,9 @@ "peer": true }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "license": "MIT", "dependencies": { @@ -7633,31 +7721,6 @@ "node": ">=6" } }, - "node_modules/level-codec/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/level-concat-iterator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", @@ -7698,21 +7761,6 @@ "node": ">=6" } }, - "node_modules/level-iterator-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "optional": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/level-js": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz", @@ -7727,31 +7775,6 @@ "ltgt": "^2.1.2" } }, - "node_modules/level-js/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/level-packager": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", @@ -8272,9 +8295,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "version": "0.30.19", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", + "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -8511,9 +8534,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -9248,10 +9271,10 @@ } }, "node_modules/minimatch": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", - "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", - "license": "BlueOak-1.0.0", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", + "license": "ISC", "dependencies": { "@isaacs/brace-expansion": "^5.0.0" }, @@ -9422,9 +9445,9 @@ } }, "node_modules/monaco-vim": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/monaco-vim/-/monaco-vim-0.4.4.tgz", - "integrity": "sha512-LNChAb//WEm/W+eyeHG/0+pdVEHotk2hLTN+M3sQZx5E8cAlSWSgqcxpcRuQnxDybSln7pfHF9i63HmbIQvrWw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/monaco-vim/-/monaco-vim-0.4.2.tgz", + "integrity": "sha512-rdbQC3O2rmpwX2Orzig/6gZjZfH7q7TIeB+uEl49sa+QyNm3jCKJOw5mwxBdFzTqbrPD+URfg6A2lEkuL5kymw==", "license": "MIT", "peerDependencies": { "monaco-editor": "*" @@ -9540,9 +9563,9 @@ } }, "node_modules/node-abi": { - "version": "3.85.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", - "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "version": "3.78.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.78.0.tgz", + "integrity": "sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ==", "license": "MIT", "optional": true, "dependencies": { @@ -9599,9 +9622,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", + "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", "dev": true, "license": "MIT" }, @@ -9780,9 +9803,9 @@ } }, "node_modules/openai": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.9.1.tgz", - "integrity": "sha512-vQ5Rlt0ZgB3/BNmTa7bIijYFhz3YBceAA3Z4JuoMSBftBF9YqFHIEhZakSs+O/Ad7EaoEimZvHxD5ylRjN11Lg==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.9.0.tgz", + "integrity": "sha512-n2sJRYmM+xfJ0l3OfH8eNnIyv3nQY7L08gZQu3dw6wSdfPtKAk92L83M2NIP5SS8Cl/bsBBG3yKzEOjkx0O+7A==", "license": "Apache-2.0", "bin": { "openai": "bin/cli" @@ -9884,6 +9907,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/pako": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", @@ -10013,6 +10043,30 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -10145,13 +10199,13 @@ "license": "MIT" }, "node_modules/playwright": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", - "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.0.tgz", + "integrity": "sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.57.0" + "playwright-core": "1.56.0" }, "bin": { "playwright": "cli.js" @@ -10164,9 +10218,9 @@ } }, "node_modules/playwright-core": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", - "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "version": "1.56.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.0.tgz", + "integrity": "sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -10969,9 +11023,9 @@ } }, "node_modules/prettier": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.2.tgz", - "integrity": "sha512-n3HV2J6QhItCXndGa3oMWvWFAgN1ibnS7R9mt6iokScBOC0Ul9/iZORmU2IWUMcyAQaMPjTlY3uT34TqocUxMA==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -11125,12 +11179,52 @@ "yaml": "^2.4.1" } }, + "node_modules/quicktype-core/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/quicktype-core/node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "license": "(MIT AND Zlib)" }, + "node_modules/quicktype-core/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/quill": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.7.tgz", @@ -11283,29 +11377,28 @@ } }, "node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", + "optional": true, "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 6" } }, "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "license": "MIT", "engines": { - "node": ">= 20.19.0" + "node": ">= 14.18.0" }, "funding": { "type": "individual", @@ -11462,13 +11555,13 @@ } }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.1", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -11651,9 +11744,9 @@ } }, "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", "dev": true, "license": "MIT" }, @@ -11718,7 +11811,6 @@ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": ">=14" }, @@ -11938,6 +12030,22 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -11950,6 +12058,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-indent": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", @@ -12172,18 +12294,18 @@ } }, "node_modules/sucrase": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", - "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", + "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { @@ -12194,6 +12316,23 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/sucrase/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/sucrase/node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -12204,6 +12343,43 @@ "node": ">= 6" } }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -12249,9 +12425,9 @@ } }, "node_modules/svelte": { - "version": "5.45.2", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.45.2.tgz", - "integrity": "sha512-yyXdW2u3H0H/zxxWoGwJoQlRgaSJLp+Vhktv12iRw2WRDlKqUPT54Fi0K/PkXqrdkcQ98aBazpy0AH4BCBVfoA==", + "version": "5.39.12", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.39.12.tgz", + "integrity": "sha512-CEzwxFuEycokU8K8CE/OuwVbmei+ivu2HvBGYIdASfMa1hCRSNr4RRkzNSvbAvu6h+BOig2CsZTAEY+WKvwZpA==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.4", @@ -12262,9 +12438,8 @@ "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", - "devalue": "^5.5.0", "esm-env": "^1.2.1", - "esrap": "^2.2.0", + "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", @@ -12308,9 +12483,9 @@ } }, "node_modules/svelte-check": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.4.tgz", - "integrity": "sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.3.tgz", + "integrity": "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg==", "dev": true, "license": "MIT", "dependencies": { @@ -12331,22 +12506,6 @@ "typescript": ">=5.0.0" } }, - "node_modules/svelte-check/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/svelte-check/node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -12380,20 +12539,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/svelte-check/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/svelte-eslint-parser": { "version": "0.43.0", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz", @@ -12475,9 +12620,9 @@ } }, "node_modules/svelte-highlight": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/svelte-highlight/-/svelte-highlight-7.9.0.tgz", - "integrity": "sha512-226LBTtvTnM2L2JkQq8mZeKEeMfPLYyta7VxZatFT4UPX5zdHEerKeMTvrfbxm7MVTWc7TPThsNoVdhWC177KQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/svelte-highlight/-/svelte-highlight-7.8.4.tgz", + "integrity": "sha512-aVp+Q0hH9kI7PlSDrklmFTF4Uj7wYj7UGuqkREnkXlqpEffxr2g6esZcMMaTgECdg5rb2mJqM88+nwS10ecoTg==", "dev": true, "license": "MIT", "dependencies": { @@ -12565,13 +12710,16 @@ "license": "MPL-2.0" }, "node_modules/svelte-splitpanes": { - "version": "8.0.12", - "resolved": "https://registry.npmjs.org/svelte-splitpanes/-/svelte-splitpanes-8.0.12.tgz", - "integrity": "sha512-HJ07HgbtY0Q/35TEuJquGy47dtgCVavV7ay9r1FhWRx3boyUs3RpeiHlwMKliznCKy2ZotNeT+8GG+mIoNjgRA==", + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/svelte-splitpanes/-/svelte-splitpanes-8.0.9.tgz", + "integrity": "sha512-L3oLXTC99M191FInTXJ/f/2i0welRql1QuVbPaU8iy6nvCR6X9VyjHCsCpLqKGWHwqkWo/AM9CQ1c0nzlb+MkA==", "dev": true, "license": "MIT", + "dependencies": { + "esm-env-robust": "0.0.3" + }, "peerDependencies": { - "svelte": "^5.43.0" + "svelte": "^4.2.19 || ^5.1.0" } }, "node_modules/svelte2tsx": { @@ -12633,9 +12781,9 @@ } }, "node_modules/tabbable": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.3.0.tgz", - "integrity": "sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", "dev": true, "license": "MIT" }, @@ -12771,11 +12919,11 @@ } }, "node_modules/tar": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", - "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz", + "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", @@ -12824,21 +12972,6 @@ "node": ">=6" } }, - "node_modules/tar-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "optional": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -13161,9 +13294,9 @@ } }, "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -13215,9 +13348,9 @@ } }, "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -13229,9 +13362,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -13738,6 +13871,25 @@ "license": "MIT" }, "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", @@ -13755,6 +13907,73 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -13777,29 +13996,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xml-utils": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.10.2.tgz", @@ -14020,9 +14216,9 @@ } }, "node_modules/yocto-queue": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", - "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", "license": "MIT", "engines": { "node": ">=12.20" @@ -14054,12 +14250,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.25.0", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.0.tgz", - "integrity": "sha512-HvWtU2UG41LALjajJrML6uQejQhNJx+JBO9IflpSja4R03iNWfKXrj6W2h7ljuLyc1nKS+9yDyL/9tD1U/yBnQ==", + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz", + "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==", "license": "ISC", "peerDependencies": { - "zod": "^3.25 || ^4" + "zod": "^3.24.1" } }, "node_modules/zstddec": { From 2628caf8adc95e15166c0df962acf5acdef81fef Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 29 Nov 2025 11:28:51 +0000 Subject: [PATCH 16/39] extend oauth refresh account size --- .../20251129112655_extend_oauth_refresh_token.down.sql | 1 + .../migrations/20251129112655_extend_oauth_refresh_token.up.sql | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 backend/migrations/20251129112655_extend_oauth_refresh_token.down.sql create mode 100644 backend/migrations/20251129112655_extend_oauth_refresh_token.up.sql diff --git a/backend/migrations/20251129112655_extend_oauth_refresh_token.down.sql b/backend/migrations/20251129112655_extend_oauth_refresh_token.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20251129112655_extend_oauth_refresh_token.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20251129112655_extend_oauth_refresh_token.up.sql b/backend/migrations/20251129112655_extend_oauth_refresh_token.up.sql new file mode 100644 index 0000000000..227892fa3c --- /dev/null +++ b/backend/migrations/20251129112655_extend_oauth_refresh_token.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER TABLE account ALTER COLUMN refresh_token TYPE VARCHAR(10000); From 43499073d939c8f8214ab464a33f55ffde2d240b Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 29 Nov 2025 11:55:18 +0000 Subject: [PATCH 17/39] fix overusage of workspace dependencies + overzealous check --- .../src/workspace_dependencies.rs | 9 +- backend/windmill-worker/src/worker.rs | 104 ++++++++++++------ 2 files changed, 80 insertions(+), 33 deletions(-) diff --git a/backend/windmill-common/src/workspace_dependencies.rs b/backend/windmill-common/src/workspace_dependencies.rs index e93f6686f7..c264f249f7 100644 --- a/backend/windmill-common/src/workspace_dependencies.rs +++ b/backend/windmill-common/src/workspace_dependencies.rs @@ -542,7 +542,14 @@ impl WorkspaceDependenciesPrefetched { (Python3, Explicit(wdar)) => wdar.assert_no_external()?, (Python3, wdp) => wdp.assert_no_implicit()?, - _ => return Err(format!("language is unsupported")), + (lang @ _, _) => { + tracing::warn!( + self.runnable_path, + "skipping workspace dependencies for unsupported language {}", + lang.as_str() + ); + return Ok(()); + } } Ok(()) } diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 9dd8e89219..0b40386e3d 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -3635,22 +3635,33 @@ mount {{ ))?; }; - let maybe_lock = if let Some(lock) = lock.clone() { - MaybeLock::Resolved { lock } - } else { - MaybeLock::Unresolved { - workspace_dependencies: WorkspaceDependenciesPrefetched::extract( - code, - language, - &job.workspace_id, - // TODO: implement - &None, - job.runnable_path(), - conn.clone(), - ) - .await?, + /// Resolves MaybeLock for languages that need workspace dependencies prefetching. + /// Only call this for Bun, Bunnative, Go, and Php. + async fn resolve_maybe_lock( + lock: &Option, + code: &str, + language: ScriptLang, + workspace_id: &str, + runnable_path: &str, + conn: Connection, + ) -> error::Result { + if let Some(lock) = lock.clone() { + Ok(MaybeLock::Resolved { lock }) + } else { + Ok(MaybeLock::Unresolved { + workspace_dependencies: WorkspaceDependenciesPrefetched::extract( + code, + language, + workspace_id, + // TODO: implement + &None, + runnable_path, + conn, + ) + .await?, + }) } - }; + } // Box::pin all language handlers to prevent large match enum on stack let result: error::Result> = match language { @@ -3704,6 +3715,15 @@ mount {{ .await } ScriptLang::Bun | ScriptLang::Bunnative => { + let maybe_lock = resolve_maybe_lock( + &lock, + &code, + language, + &job.workspace_id, + job.runnable_path(), + conn.clone(), + ) + .await?; Box::pin(handle_bun_job( maybe_lock, codebase.as_ref(), @@ -3727,6 +3747,15 @@ mount {{ .await } ScriptLang::Go => { + let maybe_lock = resolve_maybe_lock( + &lock, + &code, + language, + &job.workspace_id, + job.runnable_path(), + conn.clone(), + ) + .await?; Box::pin(handle_go_job( mem_peak, canceled_by, @@ -3789,23 +3818,34 @@ mount {{ )); #[cfg(feature = "php")] - Box::pin(handle_php_job( - maybe_lock, - mem_peak, - canceled_by, - job, - conn, - client, - parent_runnable_path, - job_dir, - &code, - base_internal_url, - worker_name, - envs, - &shared_mount, - occupancy_metrics, - )) - .await + { + let maybe_lock = resolve_maybe_lock( + &lock, + &code, + language, + &job.workspace_id, + job.runnable_path(), + conn.clone(), + ) + .await?; + Box::pin(handle_php_job( + maybe_lock, + mem_peak, + canceled_by, + job, + conn, + client, + parent_runnable_path, + job_dir, + &code, + base_internal_url, + worker_name, + envs, + &shared_mount, + occupancy_metrics, + )) + .await + } } ScriptLang::Rust => { #[cfg(not(feature = "rust"))] From 3c1dff97c264d1a8d51cc34971e64f29e094c19f Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 29 Nov 2025 15:19:03 +0000 Subject: [PATCH 18/39] wmill.d.ts nits --- frontend/src/lib/components/raw_apps/RawAppSidebar.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/components/raw_apps/RawAppSidebar.svelte b/frontend/src/lib/components/raw_apps/RawAppSidebar.svelte index 7c98aed2ef..66b12525aa 100644 --- a/frontend/src/lib/components/raw_apps/RawAppSidebar.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppSidebar.svelte @@ -335,8 +335,8 @@ {/each} Date: Sat, 29 Nov 2025 16:27:02 +0100 Subject: [PATCH 19/39] feat(aichat): stream tool arguments (#7244) --- .../copilot/chat/ToolContentDisplay.svelte | 54 ++++++++++--- .../copilot/chat/ToolExecutionDisplay.svelte | 36 ++++++--- .../lib/components/copilot/chat/anthropic.ts | 76 ++++++++++++++----- .../copilot/chat/openai-responses.ts | 49 +++++++++++- .../components/copilot/chat/script/core.ts | 16 +++- .../src/lib/components/copilot/chat/shared.ts | 41 ++++++++++ frontend/src/lib/components/copilot/lib.ts | 34 ++++++++- 7 files changed, 258 insertions(+), 48 deletions(-) diff --git a/frontend/src/lib/components/copilot/chat/ToolContentDisplay.svelte b/frontend/src/lib/components/copilot/chat/ToolContentDisplay.svelte index 99e1251a26..c4214640d4 100644 --- a/frontend/src/lib/components/copilot/chat/ToolContentDisplay.svelte +++ b/frontend/src/lib/components/copilot/chat/ToolContentDisplay.svelte @@ -1,5 +1,6 @@ -{#if showWhileLoading || (!loading && hasContent)} +{#if showWhileLoading || (!loading && hasContent) || streaming}
- + {title}: - {#if showCopy && hasContent} + {#if showCopy && hasContent && !streaming}
- - {:else} + + {:else if !message.isStreamingArguments} { let toolCallsToProcess: ChatCompletionMessageFunctionToolCall[] = [] let error = null - let tempToolId: string | undefined = undefined - // When we receive a JSON input, we need to show a temporary tool call in loading state - completion.on('inputJson', (_: string) => { - if (!tempToolId) { - callbacks.onMessageEnd() - tempToolId = `temp-${generateRandomString(12)}` - callbacks.setToolStatus(tempToolId, { isLoading: true, content: 'Calling tool...' }) + let currentStreamingTool: + | { tempId: string; shouldStream: boolean; toolName: string } + | undefined = undefined + let accumulatedJson = '' + + completion.on('streamEvent', (event: RawMessageStreamEvent) => { + if (event.type === 'content_block_start') { + const block = event.content_block + if (block.type === 'tool_use') { + const toolName = block.name + const toolId = block.id as string + + const tool = tools.find((t) => t.def.function.name === toolName) + const shouldStream = tool?.streamArguments ?? false + + callbacks.onMessageEnd() + + // Reset accumulated JSON for new tool + accumulatedJson = '' + currentStreamingTool = { tempId: toolId, shouldStream, toolName } + + callbacks.setToolStatus(toolId, { + isLoading: true, + content: `Calling ${toolName}...`, + toolName, + isStreamingArguments: shouldStream, + showFade: tool?.showFade, + showDetails: tool?.showDetails + }) + } + } + }) + + completion.on('inputJson', (partialJson: string) => { + if (currentStreamingTool?.shouldStream && currentStreamingTool.tempId) { + // Accumulate the partial JSON + accumulatedJson += partialJson + + // Try to parse and display + try { + const parsed = JSON.parse(accumulatedJson) + callbacks.setToolStatus(currentStreamingTool.tempId, { + parameters: parsed, + isStreamingArguments: true, + isLoading: true + }) + } catch { + // JSON incomplete, display as raw string + callbacks.setToolStatus(currentStreamingTool.tempId, { + parameters: accumulatedJson, + isStreamingArguments: true, + isLoading: true + }) + } } }) @@ -86,11 +133,6 @@ export async function parseAnthropicCompletion( addedMessages.push(assistantMessage) callbacks.onMessageEnd() } else if (block.type === 'tool_use') { - // Remove temp display if it exists - if (tempToolId) { - callbacks.removeToolStatus(tempToolId) - } - // Convert Anthropic tool calls to OpenAI format for compatibility toolCallsToProcess.push({ id: block.id, @@ -109,15 +151,11 @@ export async function parseAnthropicCompletion( } // Clear temp tracking after processing - tempToolId = undefined + currentStreamingTool = undefined }) // Handle errors completion.on('error', (e: any) => { - if (tempToolId) { - callbacks.removeToolStatus(tempToolId) - tempToolId = undefined - } console.error('Anthropic stream error:', e) error = e }) diff --git a/frontend/src/lib/components/copilot/chat/openai-responses.ts b/frontend/src/lib/components/copilot/chat/openai-responses.ts index 4229af0d16..d7e5ad5c96 100644 --- a/frontend/src/lib/components/copilot/chat/openai-responses.ts +++ b/frontend/src/lib/components/copilot/chat/openai-responses.ts @@ -211,6 +211,12 @@ export async function parseOpenAIResponsesCompletion( let textContent = '' let toolCallsMap: Record = {} + // Streaming state tracking + let currentStreamingTool: + | { itemId: string; shouldStream: boolean; toolName: string } + | undefined = undefined + let accumulatedJson = '' + // Handle text streaming runner.on('response.output_text.delta', (event) => { callbacks.onNewToken(event.delta) @@ -221,22 +227,62 @@ export async function parseOpenAIResponsesCompletion( runner.on('response.output_item.added', (event) => { const item = event.item if (item.type === 'function_call' && item.id) { + const tool = tools.find((t) => t.def.function.name === item.name) + const shouldStream = tool?.streamArguments ?? false + toolCallsMap[item.id] = { name: item.name, call_id: item.call_id } + // Reset streaming state for new tool + accumulatedJson = '' + currentStreamingTool = { itemId: item.id, shouldStream, toolName: item.name } + // Show temporary loading state for the tool call callbacks.onMessageEnd() callbacks.setToolStatus(`${item.id}`, { isLoading: true, - content: `Calling ${item.name} tool...` + content: `Calling ${item.name}...`, + toolName: item.name, + isStreamingArguments: shouldStream, + showFade: tool?.showFade, + showDetails: tool?.showDetails }) } }) + // Stream function call arguments incrementally + runner.on('response.function_call_arguments.delta', (event) => { + if (currentStreamingTool?.shouldStream && currentStreamingTool.itemId === event.item_id) { + accumulatedJson += event.delta + + try { + const parsed = JSON.parse(accumulatedJson) + callbacks.setToolStatus(`${event.item_id}`, { + parameters: parsed, + isStreamingArguments: true, + isLoading: true + }) + } catch { + // JSON incomplete, display as raw string + callbacks.setToolStatus(`${event.item_id}`, { + parameters: accumulatedJson, + isStreamingArguments: true, + isLoading: true + }) + } + } + }) + // Handle function call arguments done runner.on('response.function_call_arguments.done', (event) => { + // Clear streaming state + currentStreamingTool = undefined + callbacks.setToolStatus(`${event.item_id}`, { + isStreamingArguments: false + }) + // Retrieve tool call metadata from map const metadata = toolCallsMap[event.item_id] if (!metadata) { @@ -257,6 +303,7 @@ export async function parseOpenAIResponsesCompletion( // Handle errors runner.on('error', (err: OpenAIError | ResponseErrorEvent) => { + currentStreamingTool = undefined console.error('OpenAI Responses stream error:', err) error = err }) diff --git a/frontend/src/lib/components/copilot/chat/script/core.ts b/frontend/src/lib/components/copilot/chat/script/core.ts index 123198c347..d74c85eb72 100644 --- a/frontend/src/lib/components/copilot/chat/script/core.ts +++ b/frontend/src/lib/components/copilot/chat/script/core.ts @@ -897,6 +897,9 @@ const TEST_RUN_SCRIPT_TOOL: ChatCompletionFunctionTool = { export const editCodeToolWithDiff: Tool = { def: EDIT_CODE_TOOL_WITH_DIFF, + streamArguments: true, + showDetails: true, + showFade: true, fn: async function ({ args, helpers, toolCallbacks, toolId }) { const scriptOptions = helpers.getScriptOptions() @@ -947,7 +950,8 @@ export const editCodeToolWithDiff: Tool = { await helpers.applyCode(oldCode, { mode: 'revert' }) toolCallbacks.setToolStatus(toolId, { - content: `Code changes applied` + content: `Code changes applied`, + result: 'Success' }) return `Applied changes to the script editor.` } catch (error) { @@ -963,6 +967,9 @@ export const editCodeToolWithDiff: Tool = { export const editCodeTool: Tool = { def: EDIT_CODE_TOOL, + streamArguments: true, + showDetails: true, + showFade: true, fn: async function ({ args, helpers, toolCallbacks, toolId }) { const scriptOptions = helpers.getScriptOptions() @@ -984,8 +991,6 @@ export const editCodeTool: Tool = { throw new Error('Code parameter is required and must be a string') } - toolCallbacks.setToolStatus(toolId, { content: 'Applying code changes...' }) - try { // Save old code const oldCode = scriptOptions.code @@ -996,7 +1001,10 @@ export const editCodeTool: Tool = { // Show revert mode await helpers.applyCode(oldCode, { mode: 'revert' }) - toolCallbacks.setToolStatus(toolId, { content: 'Code changes applied' }) + toolCallbacks.setToolStatus(toolId, { + content: 'Code changes applied', + result: 'Success' + }) return 'Code has been applied to the script editor.' } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' diff --git a/frontend/src/lib/components/copilot/chat/shared.ts b/frontend/src/lib/components/copilot/chat/shared.ts index 655f44ddc0..0277794632 100644 --- a/frontend/src/lib/components/copilot/chat/shared.ts +++ b/frontend/src/lib/components/copilot/chat/shared.ts @@ -15,6 +15,42 @@ import { scriptLangToEditorLang } from '$lib/scripts' import YAML from 'yaml' import { getCurrentModel } from '$lib/aiStore' +// Prettify function for code arguments - extracts and formats code from JSON +function prettifyCodeArguments(content: string): string { + let codeContent = content + + // If it's a JSON string, try to extract the code property + if (typeof content === 'string' && content.trim().startsWith('{')) { + try { + const parsed = JSON.parse(content) + if (parsed.code) { + codeContent = parsed.code + } + } catch { + // If JSON is incomplete during streaming, try to extract manually + // Remove leading { "code": " or {"code":" + codeContent = content.replace(/^\{\s*"code"\s*:\s*"/, '') + // Remove trailing } if it exists + codeContent = codeContent.replace(/"\s*}\s*$/, '') + } + } + + // Convert escaped newlines to actual newlines + codeContent = codeContent.replace(/\\n/g, '\n') + + // Convert other common escape sequences + codeContent = codeContent.replace(/\\t/g, '\t') + codeContent = codeContent.replace(/\\"/g, '"') + codeContent = codeContent.replace(/\\\\/g, '\\') + + return codeContent +} + +// Map of tool names to their prettify functions +export const TOOL_PRETTIFY_MAP: Record string> = { + edit_code: prettifyCodeArguments +} + export interface ContextStringResult { dbContext: string diffContext: string @@ -217,6 +253,9 @@ export type ToolDisplayMessage = { error?: string needsConfirmation?: boolean showDetails?: boolean + isStreamingArguments?: boolean + toolName?: string + showFade?: boolean } export type AssistantDisplayMessage = BaseDisplayMessage & { @@ -358,6 +397,8 @@ export interface Tool { requiresConfirmation?: boolean confirmationMessage?: string showDetails?: boolean + streamArguments?: boolean + showFade?: boolean } export interface ToolCallbacks { diff --git a/frontend/src/lib/components/copilot/lib.ts b/frontend/src/lib/components/copilot/lib.ts index 1fa2a0d07c..5345517ecf 100644 --- a/frontend/src/lib/components/copilot/lib.ts +++ b/frontend/src/lib/components/copilot/lib.ts @@ -852,6 +852,7 @@ export async function parseOpenAICompletion( helpers: any ): Promise { const finalToolCalls: Record = {} + const streamingTools: Record = {} // Track which tools should stream let answer = '' for await (const chunk of completion) { @@ -909,14 +910,36 @@ export async function parseOpenAICompletion( } = finalToolCall if (funcName && toolCallId) { const tool = tools.find((t) => t.def.function.name === funcName) + + // Track if this tool should stream (only set once per tool) + if (streamingTools[index] === undefined) { + streamingTools[index] = tool?.streamArguments ?? false + } + if (tool && tool.preAction) { tool.preAction({ toolCallbacks: callbacks, toolId: toolCallId }) } - // Display tool call immediately in loading state + const shouldStream = streamingTools[index] + const accumulatedArgs = finalToolCall.function.arguments + let parameters: any = undefined + if (accumulatedArgs) { + try { + parameters = JSON.parse(accumulatedArgs) + } catch { + parameters = accumulatedArgs + } + } + + // Display tool call with streaming parameters if enabled callbacks.setToolStatus(toolCallId, { isLoading: true, - content: `Calling ${funcName} tool...` + content: `Calling ${funcName}...`, + toolName: funcName, + isStreamingArguments: shouldStream, + showFade: tool?.showFade, + showDetails: tool?.showDetails, + parameters: parameters }) } } @@ -931,6 +954,13 @@ export async function parseOpenAICompletion( callbacks.onMessageEnd() + // Clear streaming state for all tool calls + for (const toolCall of Object.values(finalToolCalls)) { + if (toolCall.id) { + callbacks.setToolStatus(toolCall.id, { isStreamingArguments: false }) + } + } + const toolCalls = Object.values(finalToolCalls).filter( (toolCall) => toolCall.id !== undefined && toolCall.function?.arguments !== undefined ) as ChatCompletionMessageFunctionToolCall[] From 3573e890151390d7807eb09448ecc04296ad6eb5 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 29 Nov 2025 15:40:56 +0000 Subject: [PATCH 20/39] nit raw apps --- frontend/src/lib/components/raw_apps/utils.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/frontend/src/lib/components/raw_apps/utils.ts b/frontend/src/lib/components/raw_apps/utils.ts index 4f6c78ac72..9e8d8c8024 100644 --- a/frontend/src/lib/components/raw_apps/utils.ts +++ b/frontend/src/lib/components/raw_apps/utils.ts @@ -68,19 +68,15 @@ export function genWmillTs(runnables: Record) { return `// THIS FILE IS READ-ONLY // AND GENERATED AUTOMATICALLY FROM YOUR RUNNABLES -${Object.entries(runnables) - .map(([k, v]) => `export type RunBg${capitalize(k)} = ${hiddenRunnableToTsType(v)};`) - .join('\n\n')} - export declare const runBg: { -${Object.keys(runnables) - .map((k) => ` ${k}: (data: RunBg${capitalize(k)}) => Promise;`) +${Object.entries(runnables) + .map(([k, v]) => ` ${k}: (args: ${hiddenRunnableToTsType(v)}) => Promise;`) .join('\n')} }; export declare const runBgAsync: { -${Object.keys(runnables) - .map((k) => ` ${k}: (data: RunBg${capitalize(k)}) => Promise;`) +${Object.entries(runnables) + .map(([k, v]) => ` ${k}: (args: ${hiddenRunnableToTsType(v)}) => Promise;`) .join('\n')} }; From 5f06162e29ad63492fc7329ce0b60011b65ca227 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 29 Nov 2025 15:43:13 +0000 Subject: [PATCH 21/39] nit raw apps --- frontend/src/lib/components/raw_apps/RawAppModules.svelte | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/lib/components/raw_apps/RawAppModules.svelte b/frontend/src/lib/components/raw_apps/RawAppModules.svelte index 3b46dde877..dc271d3e51 100644 --- a/frontend/src/lib/components/raw_apps/RawAppModules.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppModules.svelte @@ -37,14 +37,14 @@ - +
{#each ['direct', 'indirect', 'dev'] as type} {@const typeModules = filteredModules[type]} From 5576df0f03590fc96520b5f74e02a2aa651bf6a1 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 29 Nov 2025 15:55:41 +0000 Subject: [PATCH 22/39] runBg -> backend --- frontend/scripts/untar_ui_builder.js | 2 +- .../components/raw_apps/RawAppBackgroundRunner.svelte | 6 +++--- frontend/src/lib/components/raw_apps/utils.ts | 4 ++-- frontend/src/lib/rawAppWmillTs.ts | 10 +++++----- .../routes/(root)/(logged)/apps_raw/add/templates.ts | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/frontend/scripts/untar_ui_builder.js b/frontend/scripts/untar_ui_builder.js index 20c9188215..476e8130bc 100644 --- a/frontend/scripts/untar_ui_builder.js +++ b/frontend/scripts/untar_ui_builder.js @@ -20,7 +20,7 @@ console.log('Running postinstall for root project'); import { x } from 'tar' -const tarUrl = 'https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-00e139d.tar.gz' +const tarUrl = 'https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-ad9e747.tar.gz' const outputTarPath = path.join(process.cwd(), 'ui_builder.tar.gz') const extractTo = path.join(process.cwd(), 'static/ui_builder/') diff --git a/frontend/src/lib/components/raw_apps/RawAppBackgroundRunner.svelte b/frontend/src/lib/components/raw_apps/RawAppBackgroundRunner.svelte index bd67e55a71..79cc50a4ee 100644 --- a/frontend/src/lib/components/raw_apps/RawAppBackgroundRunner.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppBackgroundRunner.svelte @@ -42,12 +42,12 @@ result = e } - if (event.data.type == 'runBg') { + if (event.data.type == 'backend') { respond({ result, error }) } return result } - if (event.data.type == 'runBg' || event.data.type == 'runBgAsync') { + if (event.data.type == 'backend' || event.data.type == 'backendAsync') { const runnable_id = data.runnable_id let runnable = runnables[runnable_id] if (runnable) { @@ -80,7 +80,7 @@ undefined ) let job: JobById = { component: runnable_id, created_at: Date.now(), job: uuid } - if (event.data.type == 'runBgAsync') { + if (event.data.type == 'backendAsync') { let result = uuid respond({ result }) } diff --git a/frontend/src/lib/components/raw_apps/utils.ts b/frontend/src/lib/components/raw_apps/utils.ts index 9e8d8c8024..46db7c8ba3 100644 --- a/frontend/src/lib/components/raw_apps/utils.ts +++ b/frontend/src/lib/components/raw_apps/utils.ts @@ -68,13 +68,13 @@ export function genWmillTs(runnables: Record) { return `// THIS FILE IS READ-ONLY // AND GENERATED AUTOMATICALLY FROM YOUR RUNNABLES -export declare const runBg: { +export declare const backend: { ${Object.entries(runnables) .map(([k, v]) => ` ${k}: (args: ${hiddenRunnableToTsType(v)}) => Promise;`) .join('\n')} }; -export declare const runBgAsync: { +export declare const backendAsync: { ${Object.entries(runnables) .map(([k, v]) => ` ${k}: (args: ${hiddenRunnableToTsType(v)}) => Promise;`) .join('\n')} diff --git a/frontend/src/lib/rawAppWmillTs.ts b/frontend/src/lib/rawAppWmillTs.ts index 1fa4b68efe..d9fd85d58b 100644 --- a/frontend/src/lib/rawAppWmillTs.ts +++ b/frontend/src/lib/rawAppWmillTs.ts @@ -8,22 +8,22 @@ function doRequest(type: string, o: object) { }) } -export const runBg = new Proxy( +export const backend = new Proxy( {}, { get(_, runnable_id: string) { return (v: any) => { - return doRequest('runBg', { runnable_id, v }) + return doRequest('backend', { runnable_id, v }) } } }) -export const runBgAsync = new Proxy( +export const backendAsync = new Proxy( {}, { get(_, runnable_id: string) { return (v: any) => { - return doRequest('runBgAsync', { runnable_id, v }) + return doRequest('backendAsync', { runnable_id, v }) } } }) @@ -39,7 +39,7 @@ export function getJob(jobId: string) { window.addEventListener('message', (e) => { if (e.data.type == 'runBgRes' || e.data.type == 'runBgAsyncRes') { - console.log('Message from parent runBg', e.data) + console.log('Message from parent backend', e.data) let job = reqs[e.data.reqId] if (job) { const result = e.data.result diff --git a/frontend/src/routes/(root)/(logged)/apps_raw/add/templates.ts b/frontend/src/routes/(root)/(logged)/apps_raw/add/templates.ts index afa1057dab..9a719d4bf4 100644 --- a/frontend/src/routes/(root)/(logged)/apps_raw/add/templates.ts +++ b/frontend/src/routes/(root)/(logged)/apps_raw/add/templates.ts @@ -8,7 +8,7 @@ const root = createRoot(document.getElementById('root')!); root.render(); ` const appTsx = `import React, { useState } from 'react' -import { runBg } from './wmill' +import { backend } from './wmill' import './index.css' const App = () => { @@ -18,7 +18,7 @@ const App = () => { async function runA() { setLoading(true) try { - setValue(await runBg.a({ x: 42 })) + setValue(await backend.a({ x: 42 })) } catch (e) { console.error() } From 776dcb22a30b8ae45c40c6b826c51998c8350061 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 29 Nov 2025 16:03:58 +0000 Subject: [PATCH 23/39] runBg -> backend II --- cli/deps.ts | 2 +- cli/src/commands/app/dev.ts | 36 +++++++++---------- cli/src/commands/app/wmillTsDev.ts | 12 +++---- .../sharedUtils/vite.sharedUtils.config.js | 2 +- frontend/src/lib/components/raw_apps/utils.ts | 1 - 5 files changed, 26 insertions(+), 27 deletions(-) diff --git a/cli/deps.ts b/cli/deps.ts index d86629b653..adc49d62c6 100644 --- a/cli/deps.ts +++ b/cli/deps.ts @@ -57,7 +57,7 @@ export { WebSocketServer, WebSocket } from "npm:ws"; export * as getPort from "npm:get-port@7.1.0"; export * as open from "npm:open"; export * as esMain from "npm:es-main"; -export * as windmillUtils from "jsr:@windmill-labs/shared-utils@1.0.9"; +export * as windmillUtils from "jsr:@windmill-labs/shared-utils@1.0.10"; import { OpenAPI } from "./gen/index.ts"; diff --git a/cli/src/commands/app/dev.ts b/cli/src/commands/app/dev.ts index d34905935d..b4c24bffc9 100644 --- a/cli/src/commands/app/dev.ts +++ b/cli/src/commands/app/dev.ts @@ -407,23 +407,23 @@ async function dev(opts: DevOptions) { runnableId, args ); - log.info(colors.gray(`[runBg] Job started: ${uuid}`)); + log.info(colors.gray(`[backend] Job started: ${uuid}`)); const result = await waitForJob(workspaceId, uuid); return { uuid, result }; }; switch (type) { - case "runBg": { + case "backend": { // Run a runnable synchronously and wait for result - log.info(colors.blue(`[runBg] Running runnable: ${runnable_id}`)); + log.info(colors.blue(`[backend] Running runnable: ${runnable_id}`)); try { const { result } = await runAndWaitForResult(runnable_id, v); - respond("runBgRes", result, false); + respond("backendRes", result, false); } catch (error: any) { - log.error(colors.red(`[runBg] Error: ${error.message}`)); + log.error(colors.red(`[backend] Error: ${error.message}`)); respond( - "runBgRes", + "backendRes", { message: error.message, stack: error.stack }, true ); @@ -431,10 +431,10 @@ async function dev(opts: DevOptions) { break; } - case "runBgAsync": { + case "backendAsync": { // Run a runnable asynchronously and return job ID immediately log.info( - colors.blue(`[runBgAsync] Running runnable async: ${runnable_id}`) + colors.blue(`[backendAsync] Running runnable async: ${runnable_id}`) ); try { const runnables = await loadRunnables(); @@ -451,27 +451,27 @@ async function dev(opts: DevOptions) { runnable_id, v ); - log.info(colors.gray(`[runBgAsync] Job started: ${uuid}`)); + log.info(colors.gray(`[backendAsync] Job started: ${uuid}`)); // Return job ID immediately - respond("runBgAsyncRes", uuid, false); + respond("backendAsyncRes", uuid, false); // Wait for result in the background and send it when done waitForJob(workspaceId, uuid) .then((result) => { - respond("runBgRes", result, false); + respond("backendRes", result, false); }) .catch((error: any) => { respond( - "runBgRes", + "backendRes", { message: error.message, stack: error.stack }, true ); }); } catch (error: any) { - log.error(colors.red(`[runBgAsync] Error: ${error.message}`)); + log.error(colors.red(`[backendAsync] Error: ${error.message}`)); respond( - "runBgAsyncRes", + "backendAsyncRes", { message: error.message, stack: error.stack }, true ); @@ -484,11 +484,11 @@ async function dev(opts: DevOptions) { log.info(colors.blue(`[waitJob] Waiting for job: ${jobId}`)); try { const result = await waitForJob(workspaceId, jobId); - respond("runBgRes", result, false); + respond("backendRes", result, false); } catch (error: any) { log.error(colors.red(`[waitJob] Error: ${error.message}`)); respond( - "runBgRes", + "backendRes", { message: error.message, stack: error.stack }, true ); @@ -501,11 +501,11 @@ async function dev(opts: DevOptions) { log.info(colors.blue(`[getJob] Getting job status: ${jobId}`)); try { const result = await getJobStatus(workspaceId, jobId); - respond("runBgRes", result, false); + respond("backendRes", result, false); } catch (error: any) { log.error(colors.red(`[getJob] Error: ${error.message}`)); respond( - "runBgRes", + "backendRes", { message: error.message, stack: error.stack }, true ); diff --git a/cli/src/commands/app/wmillTsDev.ts b/cli/src/commands/app/wmillTsDev.ts index ca9db42004..256dd89fe6 100644 --- a/cli/src/commands/app/wmillTsDev.ts +++ b/cli/src/commands/app/wmillTsDev.ts @@ -19,8 +19,8 @@ function initWebSocket() { ws.onmessage = (event) => { const data = JSON.parse(event.data) - if (data.type === 'runBgRes' || data.type === 'runBgAsyncRes') { - console.log('Message from WebSocket runBg', data) + if (data.type === 'backendRes' || data.type === 'backendAsyncRes') { + console.log('Message from WebSocket backend', data) const job = reqs[data.reqId] if (job) { const result = data.result @@ -57,22 +57,22 @@ async function doRequest(type: string, o: object) { }) } -export const runBg = new Proxy( +export const backend = new Proxy( {}, { get(_, runnable_id: string) { return (v: any) => { - return doRequest('runBg', { runnable_id, v }) + return doRequest('backend', { runnable_id, v }) } } }) -export const runBgAsync = new Proxy( +export const backendAsync = new Proxy( {}, { get(_, runnable_id: string) { return (v: any) => { - return doRequest('runBgAsync', { runnable_id, v }) + return doRequest('backendAsync', { runnable_id, v }) } } }) diff --git a/frontend/sharedUtils/vite.sharedUtils.config.js b/frontend/sharedUtils/vite.sharedUtils.config.js index d20cfa893f..5bf4bcd688 100644 --- a/frontend/sharedUtils/vite.sharedUtils.config.js +++ b/frontend/sharedUtils/vite.sharedUtils.config.js @@ -5,7 +5,7 @@ import { exec } from 'child_process' import { promisify } from 'util' const execAsync = promisify(exec) -const VERSION = '1.0.9' +const VERSION = '1.0.10' export default defineConfig({ build: { diff --git a/frontend/src/lib/components/raw_apps/utils.ts b/frontend/src/lib/components/raw_apps/utils.ts index 46db7c8ba3..a5bede8f2b 100644 --- a/frontend/src/lib/components/raw_apps/utils.ts +++ b/frontend/src/lib/components/raw_apps/utils.ts @@ -1,7 +1,6 @@ import type { ScriptLang } from '../../gen/types.gen' import type { Schema } from '../../common' import { schemaToTsType } from '../../schema' -import { capitalize } from '../../sharedUtils' import { isRunnableByName, isRunnableByPath, type RunnableWithFields } from '../apps/inputType' import type { InlineScript } from '../apps/sharedTypes' From d1c33c195f80b64ab5a7b20cbcaf5d1665ec6a6f Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 29 Nov 2025 16:11:15 +0000 Subject: [PATCH 24/39] runBg -> backend II --- frontend/scripts/untar_ui_builder.js | 2 +- frontend/src/lib/rawAppWmillTs.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/scripts/untar_ui_builder.js b/frontend/scripts/untar_ui_builder.js index 476e8130bc..4a2edc71de 100644 --- a/frontend/scripts/untar_ui_builder.js +++ b/frontend/scripts/untar_ui_builder.js @@ -20,7 +20,7 @@ console.log('Running postinstall for root project'); import { x } from 'tar' -const tarUrl = 'https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-ad9e747.tar.gz' +const tarUrl = 'https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-fa4de3c.tar.gz' const outputTarPath = path.join(process.cwd(), 'ui_builder.tar.gz') const extractTo = path.join(process.cwd(), 'static/ui_builder/') diff --git a/frontend/src/lib/rawAppWmillTs.ts b/frontend/src/lib/rawAppWmillTs.ts index d9fd85d58b..d09e9fae4d 100644 --- a/frontend/src/lib/rawAppWmillTs.ts +++ b/frontend/src/lib/rawAppWmillTs.ts @@ -38,7 +38,7 @@ export function getJob(jobId: string) { window.addEventListener('message', (e) => { - if (e.data.type == 'runBgRes' || e.data.type == 'runBgAsyncRes') { + if (e.data.type == 'backendRes' || e.data.type == 'backendAsyncRes') { console.log('Message from parent backend', e.data) let job = reqs[e.data.reqId] if (job) { From 2d563c6c17f2f20c31b6430e6e60e9ae1bda163f Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sat, 29 Nov 2025 18:12:14 +0000 Subject: [PATCH 25/39] vite + svelte support on local dev --- cli/deno.json | 6 +- cli/deno.lock | 523 +++++++++++------- cli/deps.ts | 3 + cli/src/commands/app/bundle.ts | 89 ++- cli/src/commands/app/dev.ts | 23 +- cli/src/commands/app/raw_apps.ts | 7 +- cli/src/commands/dev/dev.ts | 2 +- cli/src/commands/script/script.ts | 10 +- cli/src/core/conf.ts | 4 +- frontend/scripts/untar_ui_builder.js | 2 +- .../(root)/(logged)/apps_raw/add/+page.svelte | 32 +- .../(root)/(logged)/apps_raw/add/templates.ts | 2 +- 12 files changed, 451 insertions(+), 252 deletions(-) diff --git a/cli/deno.json b/cli/deno.json index fae7e8f7dd..772ab0c3c6 100644 --- a/cli/deno.json +++ b/cli/deno.json @@ -13,6 +13,8 @@ "@std/path": "jsr:@std/path@^1.0.4", "@std/streams": "jsr:@std/streams@^1.0.4", "@std/yaml": "jsr:@std/yaml@^1.0.5", - "@types/diff": "npm:@types/diff@^5.2.2" - } + "@types/diff": "npm:@types/diff@^5.2.2", + "ws": "npm:ws@8.18.0" + }, + "nodeModulesDir": "auto" } \ No newline at end of file diff --git a/cli/deno.lock b/cli/deno.lock index bf7e990d8b..9b0009da9f 100644 --- a/cli/deno.lock +++ b/cli/deno.lock @@ -18,13 +18,13 @@ "jsr:@std/encoding@1.0.4": "1.0.4", "jsr:@std/encoding@^1.0.4": "1.0.4", "jsr:@std/fmt@0.223": "0.223.0", - "jsr:@std/fmt@1": "1.0.2", + "jsr:@std/fmt@1": "1.0.8", "jsr:@std/fmt@^1.0.2": "1.0.2", "jsr:@std/fmt@^1.0.5": "1.0.8", "jsr:@std/fmt@~0.225.4": "0.225.6", "jsr:@std/fs@*": "1.0.20", "jsr:@std/fs@0.223": "0.223.0", - "jsr:@std/fs@1": "1.0.3", + "jsr:@std/fs@1": "1.0.20", "jsr:@std/fs@^1.0.11": "1.0.20", "jsr:@std/fs@^1.0.3": "1.0.3", "jsr:@std/fs@~0.229.3": "0.229.3", @@ -40,7 +40,7 @@ "jsr:@std/net@^1.0.2": "1.0.2", "jsr:@std/path@*": "1.1.3", "jsr:@std/path@0.223": "0.223.0", - "jsr:@std/path@1": "1.0.4", + "jsr:@std/path@1": "1.1.3", "jsr:@std/path@1.0.0-rc.1": "1.0.0-rc.1", "jsr:@std/path@1.0.0-rc.2": "1.0.0-rc.2", "jsr:@std/path@^1.0.4": "1.0.4", @@ -64,18 +64,23 @@ "jsr:@windmill-labs/cliffy-prompt@^1.0.0-rc.5": "1.0.0-rc.6", "jsr:@windmill-labs/cliffy-table@1.0.0-rc.5": "1.0.0-rc.5", "jsr:@windmill-labs/cliffy-table@^1.0.0-rc.5": "1.0.0-rc.5", + "jsr:@windmill-labs/shared-utils@1.0.10": "1.0.10", "jsr:@windmill-labs/shared-utils@1.0.3": "1.0.3", "jsr:@windmill-labs/shared-utils@1.0.5": "1.0.5", "jsr:@windmill-labs/shared-utils@1.0.6": "1.0.6", "jsr:@windmill-labs/shared-utils@1.0.7": "1.0.7", "npm:@ayonli/jsext@*": "1.8.0", - "npm:@types/node@*": "22.12.0", + "npm:@types/diff@^5.2.2": "5.2.3", + "npm:@types/node@*": "24.2.0", + "npm:@types/ws@*": "8.18.1", "npm:@windmill-labs/shared-utils@1.0.1": "1.0.1", "npm:@windmill-labs/shared-utils@1.0.2": "1.0.2", "npm:centdix-utils@*": "1.0.15", "npm:diff@*": "8.0.2", "npm:es-main@*": "1.3.0", - "npm:esbuild@*": "0.25.8", + "npm:esbuild-plugin-vue3@0.5.1": "0.5.1_vue@3.5.25__typescript@4.9.5_typescript@4.9.5", + "npm:esbuild-svelte@0.9.3": "0.9.3_esbuild@0.24.2_svelte@5.45.2__acorn@8.14.1", + "npm:esbuild@*": "0.24.2", "npm:esbuild@0.24.2": "0.24.2", "npm:express@*": "5.1.0", "npm:get-port@7.1.0": "7.1.0", @@ -83,8 +88,10 @@ "npm:jszip@3.8.0": "3.8.0", "npm:minimatch@*": "10.0.3", "npm:open@*": "10.2.0", + "npm:svelte-preprocess@6.0.3": "6.0.3_svelte@5.45.2__acorn@8.14.1", "npm:ws@*": "8.18.3", - "npm:ws@8.18.0": "8.18.0" + "npm:ws@8.18.0": "8.18.0", + "npm:ws@8.18.3": "8.18.3" }, "jsr": { "@david/code-block-writer@13.0.2": { @@ -349,6 +356,9 @@ }, "@windmill-labs/shared-utils@1.0.7": { "integrity": "528638c7c508910e7f51b1ad9a5f1ff394e3fefb28fd3f96ab958c258a26e978" + }, + "@windmill-labs/shared-utils@1.0.10": { + "integrity": "bd1993eb8d693c8ba49da1618f82ff4601eeb59011b2cac13e664291f7a299d8" } }, "npm": { @@ -361,261 +371,151 @@ "zod" ] }, + "@babel/helper-string-parser@7.27.1": { + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" + }, + "@babel/helper-validator-identifier@7.28.5": { + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" + }, + "@babel/parser@7.28.5": { + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dependencies": [ + "@babel/types" + ], + "bin": true + }, + "@babel/types@7.28.5": { + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dependencies": [ + "@babel/helper-string-parser", + "@babel/helper-validator-identifier" + ] + }, "@esbuild/aix-ppc64@0.24.2": { "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", "os": ["aix"], "cpu": ["ppc64"] }, - "@esbuild/aix-ppc64@0.25.8": { - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", - "os": ["aix"], - "cpu": ["ppc64"] - }, "@esbuild/android-arm64@0.24.2": { "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", "os": ["android"], "cpu": ["arm64"] }, - "@esbuild/android-arm64@0.25.8": { - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", - "os": ["android"], - "cpu": ["arm64"] - }, "@esbuild/android-arm@0.24.2": { "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", "os": ["android"], "cpu": ["arm"] }, - "@esbuild/android-arm@0.25.8": { - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", - "os": ["android"], - "cpu": ["arm"] - }, "@esbuild/android-x64@0.24.2": { "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", "os": ["android"], "cpu": ["x64"] }, - "@esbuild/android-x64@0.25.8": { - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", - "os": ["android"], - "cpu": ["x64"] - }, "@esbuild/darwin-arm64@0.24.2": { "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", "os": ["darwin"], "cpu": ["arm64"] }, - "@esbuild/darwin-arm64@0.25.8": { - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", - "os": ["darwin"], - "cpu": ["arm64"] - }, "@esbuild/darwin-x64@0.24.2": { "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", "os": ["darwin"], "cpu": ["x64"] }, - "@esbuild/darwin-x64@0.25.8": { - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", - "os": ["darwin"], - "cpu": ["x64"] - }, "@esbuild/freebsd-arm64@0.24.2": { "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", "os": ["freebsd"], "cpu": ["arm64"] }, - "@esbuild/freebsd-arm64@0.25.8": { - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", - "os": ["freebsd"], - "cpu": ["arm64"] - }, "@esbuild/freebsd-x64@0.24.2": { "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", "os": ["freebsd"], "cpu": ["x64"] }, - "@esbuild/freebsd-x64@0.25.8": { - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", - "os": ["freebsd"], - "cpu": ["x64"] - }, "@esbuild/linux-arm64@0.24.2": { "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", "os": ["linux"], "cpu": ["arm64"] }, - "@esbuild/linux-arm64@0.25.8": { - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", - "os": ["linux"], - "cpu": ["arm64"] - }, "@esbuild/linux-arm@0.24.2": { "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", "os": ["linux"], "cpu": ["arm"] }, - "@esbuild/linux-arm@0.25.8": { - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", - "os": ["linux"], - "cpu": ["arm"] - }, "@esbuild/linux-ia32@0.24.2": { "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", "os": ["linux"], "cpu": ["ia32"] }, - "@esbuild/linux-ia32@0.25.8": { - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", - "os": ["linux"], - "cpu": ["ia32"] - }, "@esbuild/linux-loong64@0.24.2": { "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", "os": ["linux"], "cpu": ["loong64"] }, - "@esbuild/linux-loong64@0.25.8": { - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", - "os": ["linux"], - "cpu": ["loong64"] - }, "@esbuild/linux-mips64el@0.24.2": { "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", "os": ["linux"], "cpu": ["mips64el"] }, - "@esbuild/linux-mips64el@0.25.8": { - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", - "os": ["linux"], - "cpu": ["mips64el"] - }, "@esbuild/linux-ppc64@0.24.2": { "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", "os": ["linux"], "cpu": ["ppc64"] }, - "@esbuild/linux-ppc64@0.25.8": { - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", - "os": ["linux"], - "cpu": ["ppc64"] - }, "@esbuild/linux-riscv64@0.24.2": { "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", "os": ["linux"], "cpu": ["riscv64"] }, - "@esbuild/linux-riscv64@0.25.8": { - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", - "os": ["linux"], - "cpu": ["riscv64"] - }, "@esbuild/linux-s390x@0.24.2": { "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", "os": ["linux"], "cpu": ["s390x"] }, - "@esbuild/linux-s390x@0.25.8": { - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", - "os": ["linux"], - "cpu": ["s390x"] - }, "@esbuild/linux-x64@0.24.2": { "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", "os": ["linux"], "cpu": ["x64"] }, - "@esbuild/linux-x64@0.25.8": { - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", - "os": ["linux"], - "cpu": ["x64"] - }, "@esbuild/netbsd-arm64@0.24.2": { "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", "os": ["netbsd"], "cpu": ["arm64"] }, - "@esbuild/netbsd-arm64@0.25.8": { - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", - "os": ["netbsd"], - "cpu": ["arm64"] - }, "@esbuild/netbsd-x64@0.24.2": { "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", "os": ["netbsd"], "cpu": ["x64"] }, - "@esbuild/netbsd-x64@0.25.8": { - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", - "os": ["netbsd"], - "cpu": ["x64"] - }, "@esbuild/openbsd-arm64@0.24.2": { "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", "os": ["openbsd"], "cpu": ["arm64"] }, - "@esbuild/openbsd-arm64@0.25.8": { - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", - "os": ["openbsd"], - "cpu": ["arm64"] - }, "@esbuild/openbsd-x64@0.24.2": { "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", "os": ["openbsd"], "cpu": ["x64"] }, - "@esbuild/openbsd-x64@0.25.8": { - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", - "os": ["openbsd"], - "cpu": ["x64"] - }, - "@esbuild/openharmony-arm64@0.25.8": { - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", - "os": ["openharmony"], - "cpu": ["arm64"] - }, "@esbuild/sunos-x64@0.24.2": { "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", "os": ["sunos"], "cpu": ["x64"] }, - "@esbuild/sunos-x64@0.25.8": { - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", - "os": ["sunos"], - "cpu": ["x64"] - }, "@esbuild/win32-arm64@0.24.2": { "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", "os": ["win32"], "cpu": ["arm64"] }, - "@esbuild/win32-arm64@0.25.8": { - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", - "os": ["win32"], - "cpu": ["arm64"] - }, "@esbuild/win32-ia32@0.24.2": { "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", "os": ["win32"], "cpu": ["ia32"] }, - "@esbuild/win32-ia32@0.25.8": { - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", - "os": ["win32"], - "cpu": ["ia32"] - }, "@esbuild/win32-x64@0.24.2": { "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", "os": ["win32"], "cpu": ["x64"] }, - "@esbuild/win32-x64@0.25.8": { - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", - "os": ["win32"], - "cpu": ["x64"] - }, "@isaacs/balanced-match@4.0.1": { "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==" }, @@ -625,12 +525,128 @@ "@isaacs/balanced-match" ] }, - "@types/node@22.12.0": { - "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", + "@jridgewell/gen-mapping@0.3.13": { + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dependencies": [ + "@jridgewell/sourcemap-codec", + "@jridgewell/trace-mapping" + ] + }, + "@jridgewell/remapping@2.3.5": { + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dependencies": [ + "@jridgewell/gen-mapping", + "@jridgewell/trace-mapping" + ] + }, + "@jridgewell/resolve-uri@3.1.2": { + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" + }, + "@jridgewell/sourcemap-codec@1.5.5": { + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "@jridgewell/trace-mapping@0.3.31": { + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dependencies": [ + "@jridgewell/resolve-uri", + "@jridgewell/sourcemap-codec" + ] + }, + "@sveltejs/acorn-typescript@1.0.7_acorn@8.14.1": { + "integrity": "sha512-znp1A/Y1Jj4l/Zy7PX5DZKBE0ZNY+5QBngiE21NJkfSTyzzC5iKNWOtwFXKtIrn7MXEFBck4jD95iBNkGjK92Q==", + "dependencies": [ + "acorn" + ] + }, + "@types/diff@5.2.3": { + "integrity": "sha512-K0Oqlrq3kQMaO2RhfrNQX5trmt+XLyom88zS0u84nnIcLvFnRUMRRHmrGny5GSM+kNO9IZLARsdQHDzkhAgmrQ==" + }, + "@types/estree@1.0.8": { + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + }, + "@types/node@24.2.0": { + "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", "dependencies": [ "undici-types" ] }, + "@types/ws@8.18.1": { + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dependencies": [ + "@types/node" + ] + }, + "@vue/compiler-core@3.5.25": { + "integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==", + "dependencies": [ + "@babel/parser", + "@vue/shared", + "entities", + "estree-walker", + "source-map-js" + ] + }, + "@vue/compiler-dom@3.5.25": { + "integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==", + "dependencies": [ + "@vue/compiler-core", + "@vue/shared" + ] + }, + "@vue/compiler-sfc@3.5.25": { + "integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==", + "dependencies": [ + "@babel/parser", + "@vue/compiler-core", + "@vue/compiler-dom", + "@vue/compiler-ssr", + "@vue/shared", + "estree-walker", + "magic-string", + "postcss", + "source-map-js" + ] + }, + "@vue/compiler-ssr@3.5.25": { + "integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==", + "dependencies": [ + "@vue/compiler-dom", + "@vue/shared" + ] + }, + "@vue/reactivity@3.5.25": { + "integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==", + "dependencies": [ + "@vue/shared" + ] + }, + "@vue/runtime-core@3.5.25": { + "integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==", + "dependencies": [ + "@vue/reactivity", + "@vue/shared" + ] + }, + "@vue/runtime-dom@3.5.25": { + "integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==", + "dependencies": [ + "@vue/reactivity", + "@vue/runtime-core", + "@vue/shared", + "csstype" + ] + }, + "@vue/server-renderer@3.5.25_vue@3.5.25__typescript@4.9.5_typescript@4.9.5": { + "integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==", + "dependencies": [ + "@vue/compiler-ssr", + "@vue/shared", + "vue" + ] + }, + "@vue/shared@3.5.25": { + "integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==" + }, "@windmill-labs/shared-utils@1.0.1": { "integrity": "sha512-DUMzPIFCKImuGpbuHXXmGGUT3VXYlgrv/jIIEOW+Iig+9tZvYqOUxfgn32lDhm73k82xBg8MdAf+0qABzfqFeQ==" }, @@ -644,6 +660,16 @@ "negotiator" ] }, + "acorn@8.14.1": { + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "bin": true + }, + "aria-query@5.3.2": { + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==" + }, + "axobject-query@4.1.0": { + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==" + }, "body-parser@2.2.0": { "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", "dependencies": [ @@ -687,6 +713,9 @@ "windmill-client" ] }, + "clsx@2.1.1": { + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" + }, "content-disposition@1.0.0": { "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", "dependencies": [ @@ -705,6 +734,9 @@ "core-util-is@1.0.3": { "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "csstype@3.2.3": { + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + }, "debug@4.4.1": { "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dependencies": [ @@ -727,6 +759,9 @@ "depd@2.0.0": { "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, + "devalue@5.5.0": { + "integrity": "sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==" + }, "diff@8.0.2": { "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==" }, @@ -744,6 +779,9 @@ "encodeurl@2.0.0": { "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" }, + "entities@4.5.0": { + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" + }, "es-define-property@1.0.1": { "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" }, @@ -759,67 +797,49 @@ "es-errors" ] }, + "esbuild-plugin-vue3@0.5.1_vue@3.5.25__typescript@4.9.5_typescript@4.9.5": { + "integrity": "sha512-rhTPImJ1Zi7FbVa4xWlu9dJdt+mqWxc9Z+AQd+ArbHHwtyQRe8FvER8gaTw0O6bNsBjAtU5rq0rpZEkP3QaThg==", + "dependencies": [ + "typescript", + "vue" + ] + }, + "esbuild-svelte@0.9.3_esbuild@0.24.2_svelte@5.45.2__acorn@8.14.1": { + "integrity": "sha512-CgEcGY1r/d16+aggec3czoFBEBaYIrFOnMxpsO6fWNaNEqHregPN5DLAPZDqrL7rXDNplW+WMu8s3GMq9FqgJA==", + "dependencies": [ + "@jridgewell/trace-mapping", + "esbuild", + "svelte" + ] + }, "esbuild@0.24.2": { "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", "optionalDependencies": [ - "@esbuild/aix-ppc64@0.24.2", - "@esbuild/android-arm@0.24.2", - "@esbuild/android-arm64@0.24.2", - "@esbuild/android-x64@0.24.2", - "@esbuild/darwin-arm64@0.24.2", - "@esbuild/darwin-x64@0.24.2", - "@esbuild/freebsd-arm64@0.24.2", - "@esbuild/freebsd-x64@0.24.2", - "@esbuild/linux-arm@0.24.2", - "@esbuild/linux-arm64@0.24.2", - "@esbuild/linux-ia32@0.24.2", - "@esbuild/linux-loong64@0.24.2", - "@esbuild/linux-mips64el@0.24.2", - "@esbuild/linux-ppc64@0.24.2", - "@esbuild/linux-riscv64@0.24.2", - "@esbuild/linux-s390x@0.24.2", - "@esbuild/linux-x64@0.24.2", - "@esbuild/netbsd-arm64@0.24.2", - "@esbuild/netbsd-x64@0.24.2", - "@esbuild/openbsd-arm64@0.24.2", - "@esbuild/openbsd-x64@0.24.2", - "@esbuild/sunos-x64@0.24.2", - "@esbuild/win32-arm64@0.24.2", - "@esbuild/win32-ia32@0.24.2", - "@esbuild/win32-x64@0.24.2" - ], - "scripts": true, - "bin": true - }, - "esbuild@0.25.8": { - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", - "optionalDependencies": [ - "@esbuild/aix-ppc64@0.25.8", - "@esbuild/android-arm@0.25.8", - "@esbuild/android-arm64@0.25.8", - "@esbuild/android-x64@0.25.8", - "@esbuild/darwin-arm64@0.25.8", - "@esbuild/darwin-x64@0.25.8", - "@esbuild/freebsd-arm64@0.25.8", - "@esbuild/freebsd-x64@0.25.8", - "@esbuild/linux-arm@0.25.8", - "@esbuild/linux-arm64@0.25.8", - "@esbuild/linux-ia32@0.25.8", - "@esbuild/linux-loong64@0.25.8", - "@esbuild/linux-mips64el@0.25.8", - "@esbuild/linux-ppc64@0.25.8", - "@esbuild/linux-riscv64@0.25.8", - "@esbuild/linux-s390x@0.25.8", - "@esbuild/linux-x64@0.25.8", - "@esbuild/netbsd-arm64@0.25.8", - "@esbuild/netbsd-x64@0.25.8", - "@esbuild/openbsd-arm64@0.25.8", - "@esbuild/openbsd-x64@0.25.8", - "@esbuild/openharmony-arm64", - "@esbuild/sunos-x64@0.25.8", - "@esbuild/win32-arm64@0.25.8", - "@esbuild/win32-ia32@0.25.8", - "@esbuild/win32-x64@0.25.8" + "@esbuild/aix-ppc64", + "@esbuild/android-arm", + "@esbuild/android-arm64", + "@esbuild/android-x64", + "@esbuild/darwin-arm64", + "@esbuild/darwin-x64", + "@esbuild/freebsd-arm64", + "@esbuild/freebsd-x64", + "@esbuild/linux-arm", + "@esbuild/linux-arm64", + "@esbuild/linux-ia32", + "@esbuild/linux-loong64", + "@esbuild/linux-mips64el", + "@esbuild/linux-ppc64", + "@esbuild/linux-riscv64", + "@esbuild/linux-s390x", + "@esbuild/linux-x64", + "@esbuild/netbsd-arm64", + "@esbuild/netbsd-x64", + "@esbuild/openbsd-arm64", + "@esbuild/openbsd-x64", + "@esbuild/sunos-x64", + "@esbuild/win32-arm64", + "@esbuild/win32-ia32", + "@esbuild/win32-x64" ], "scripts": true, "bin": true @@ -827,6 +847,18 @@ "escape-html@1.0.3": { "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "esm-env@1.2.2": { + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==" + }, + "esrap@2.2.0": { + "integrity": "sha512-WBmtxe7R9C5mvL4n2le8nMUe4mD5V9oiK2vJpQ9I3y20ENPUomPcphBXE8D1x/Bm84oN1V+lOfgXxtqmxTp3Xg==", + "dependencies": [ + "@jridgewell/sourcemap-codec" + ] + }, + "estree-walker@2.0.2": { + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, "etag@1.8.1": { "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, @@ -857,7 +889,7 @@ "router", "send", "serve-static", - "statuses@2.0.2", + "statuses", "type-is", "vary" ] @@ -870,7 +902,7 @@ "escape-html", "on-finished", "parseurl", - "statuses@2.0.2" + "statuses" ] }, "forwarded@0.2.0": { @@ -925,7 +957,7 @@ "depd", "inherits", "setprototypeof", - "statuses@2.0.1", + "statuses", "toidentifier" ] }, @@ -958,6 +990,12 @@ "is-promise@4.0.0": { "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, + "is-reference@3.0.3": { + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dependencies": [ + "@types/estree" + ] + }, "is-wsl@3.1.0": { "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", "dependencies": [ @@ -991,6 +1029,15 @@ "immediate" ] }, + "locate-character@3.0.0": { + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" + }, + "magic-string@0.30.21": { + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dependencies": [ + "@jridgewell/sourcemap-codec" + ] + }, "math-intrinsics@1.1.0": { "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" }, @@ -1018,6 +1065,10 @@ "ms@2.1.3": { "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "nanoid@3.3.11": { + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "bin": true + }, "negotiator@1.0.0": { "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==" }, @@ -1054,6 +1105,17 @@ "path-to-regexp@8.2.0": { "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==" }, + "picocolors@1.1.1": { + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "postcss@8.5.6": { + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dependencies": [ + "nanoid", + "picocolors", + "source-map-js" + ] + }, "process-nextick-args@2.0.1": { "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, @@ -1129,7 +1191,7 @@ "ms", "on-finished", "range-parser", - "statuses@2.0.2" + "statuses" ] }, "serve-static@2.2.0": { @@ -1183,12 +1245,12 @@ "side-channel-weakmap" ] }, + "source-map-js@1.2.1": { + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" + }, "statuses@2.0.1": { "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, - "statuses@2.0.2": { - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==" - }, "string_decoder@1.1.1": { "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dependencies": [ @@ -1199,6 +1261,33 @@ "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", "deprecated": true }, + "svelte-preprocess@6.0.3_svelte@5.45.2__acorn@8.14.1": { + "integrity": "sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==", + "dependencies": [ + "svelte" + ], + "scripts": true + }, + "svelte@5.45.2_acorn@8.14.1": { + "integrity": "sha512-yyXdW2u3H0H/zxxWoGwJoQlRgaSJLp+Vhktv12iRw2WRDlKqUPT54Fi0K/PkXqrdkcQ98aBazpy0AH4BCBVfoA==", + "dependencies": [ + "@jridgewell/remapping", + "@jridgewell/sourcemap-codec", + "@sveltejs/acorn-typescript", + "@types/estree", + "acorn", + "aria-query", + "axobject-query", + "clsx", + "devalue", + "esm-env", + "esrap", + "is-reference", + "locate-character", + "magic-string", + "zimmerframe" + ] + }, "toidentifier@1.0.1": { "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, @@ -1210,8 +1299,12 @@ "mime-types" ] }, - "undici-types@6.20.0": { - "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" + "typescript@4.9.5": { + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "bin": true + }, + "undici-types@7.10.0": { + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==" }, "unpipe@1.0.0": { "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" @@ -1222,6 +1315,20 @@ "vary@1.1.2": { "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, + "vue@3.5.25_typescript@4.9.5": { + "integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==", + "dependencies": [ + "@vue/compiler-dom", + "@vue/compiler-sfc", + "@vue/runtime-dom", + "@vue/server-renderer", + "@vue/shared", + "typescript" + ], + "optionalPeers": [ + "typescript" + ] + }, "windmill-client@1.515.1": { "integrity": "sha512-o6qynOEbPubZTZUOLLs2Z9f+uBZQJUCw/+YWgvI6p8nu5BJ6J3N/wEfbY1X5TTnJNuqahQ0UgimYzhurT5XQFw==" }, @@ -1240,6 +1347,9 @@ "is-wsl" ] }, + "zimmerframe@1.1.4": { + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==" + }, "zod@3.25.76": { "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==" } @@ -1511,7 +1621,8 @@ "jsr:@windmill-labs/cliffy-command@^1.0.0-rc.5", "jsr:@windmill-labs/cliffy-prompt@^1.0.0-rc.5", "jsr:@windmill-labs/cliffy-table@^1.0.0-rc.5", - "npm:@types/diff@^5.2.2" + "npm:@types/diff@^5.2.2", + "npm:ws@8.18.0" ] } } diff --git a/cli/deps.ts b/cli/deps.ts index adc49d62c6..28d8909d3c 100644 --- a/cli/deps.ts +++ b/cli/deps.ts @@ -59,6 +59,9 @@ export * as open from "npm:open"; export * as esMain from "npm:es-main"; export * as windmillUtils from "jsr:@windmill-labs/shared-utils@1.0.10"; +// needed for dnt transform +import * as wsTypes from "npm:@types/ws"; + import { OpenAPI } from "./gen/index.ts"; export function setClient(token?: string, baseUrl?: string) { diff --git a/cli/src/commands/app/bundle.ts b/cli/src/commands/app/bundle.ts index 6a64ed9252..daa1dd24d0 100644 --- a/cli/src/commands/app/bundle.ts +++ b/cli/src/commands/app/bundle.ts @@ -2,6 +2,7 @@ import * as fs from "node:fs"; import * as path from "node:path"; import process from "node:process"; +import { spawn } from "node:child_process"; import { log, colors } from "../../../deps.ts"; import { windmillUtils } from "../../../deps.ts"; export interface BundleOptions { @@ -30,6 +31,66 @@ export const DEFAULT_BUILD_OPTIONS = { write: true, }; +/** + * Detects which frontend frameworks are present in package.json + */ +export function detectFrameworks(appDir: string): { svelte: boolean; vue: boolean } { + const packageJsonPath = path.join(appDir, "package.json"); + if (!fs.existsSync(packageJsonPath)) { + return { svelte: false, vue: false }; + } + + try { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")); + const allDeps = { + ...packageJson.dependencies, + ...packageJson.devDependencies, + }; + + return { + svelte: "svelte" in allDeps, + vue: "vue" in allDeps, + }; + } catch { + return { svelte: false, vue: false }; + } +} + +/** + * Creates framework-specific esbuild plugins based on detected dependencies + */ +export async function createFrameworkPlugins(appDir: string): Promise { + const frameworks = detectFrameworks(appDir); + const plugins: any[] = []; + + if (frameworks.svelte) { + log.info(colors.blue("🔧 Svelte detected, adding svelte plugin...")); + try { + const esbuildSvelte = await import("npm:esbuild-svelte@0.9.3"); + const sveltePreprocess = await import("npm:svelte-preprocess@6.0.3"); + plugins.push( + esbuildSvelte.default({ + preprocess: sveltePreprocess.default(), + }) + ); + } catch (error: any) { + log.warn(colors.yellow(`Failed to load svelte plugin: ${error.message}`)); + } + } + + if (frameworks.vue) { + log.info(colors.blue("🔧 Vue detected, adding vue plugin...")); + try { + const esbuildPluginVue = await import("npm:esbuild-plugin-vue3@0.5.1"); + plugins.push(esbuildPluginVue.default()); + } catch (error: any) { + log.warn(colors.yellow(`Failed to load vue plugin: ${error.message}`)); + } + } + + return plugins; +} + /** * Ensures node_modules exists in the specified directory * Runs npm install if node_modules is missing @@ -41,13 +102,15 @@ export async function ensureNodeModules(appDir?: string): Promise { if (!fs.existsSync(nodeModulesPath)) { log.info(colors.yellow("📦 node_modules not found, running npm install...")); - const npmInstall = new Deno.Command("npm", { - args: ["install"], - cwd: targetDir, - stdout: "inherit", - stderr: "inherit", + const code = await new Promise((resolve, reject) => { + const npmInstall = spawn("npm", ["install"], { + cwd: targetDir, + stdio: "inherit", + shell: true, + }); + npmInstall.on("close", (code) => resolve(code ?? 0)); + npmInstall.on("error", reject); }); - const { code } = await npmInstall.output(); if (code !== 0) { throw new Error(`npm install failed with exit code ${code}`); } @@ -66,13 +129,16 @@ export async function createBundle( // Dynamically import esbuild const esbuild = await import("npm:esbuild@0.24.2"); - const entryPoint = options.entryPoint ?? "index.tsx"; + // Detect frameworks to determine default entry point + const frameworks = detectFrameworks(process.cwd()); + const defaultEntry = (frameworks.svelte || frameworks.vue) ? "index.ts" : "index.tsx"; + + const entryPoint = options.entryPoint ?? defaultEntry; const outDir = options.outDir ?? "dist"; const sourcemap = options.sourcemap ?? false; const minify = options.minify ?? true; const production = options.production ?? true; - // Verify entry point exists if (!fs.existsSync(entryPoint)) { throw new Error( @@ -81,9 +147,12 @@ export async function createBundle( } // Ensure node_modules exists in the app directory - const appDir = path.dirname(entryPoint); + const appDir = path.dirname(entryPoint) || process.cwd(); await ensureNodeModules(appDir); + // Load framework-specific plugins (svelte, vue) based on package.json + const frameworkPlugins = await createFrameworkPlugins(appDir); + // Ensure output directory exists const distDir = path.join(process.cwd(), outDir); if (!fs.existsSync(distDir)) { @@ -131,7 +200,7 @@ export async function createBundle( define: { "process.env.NODE_ENV": production ? '"production"' : '"development"', }, - plugins: [wmillPlugin], + plugins: [...frameworkPlugins, wmillPlugin], }; log.info(colors.blue("📦 Building bundle...")); diff --git a/cli/src/commands/app/dev.ts b/cli/src/commands/app/dev.ts index b4c24bffc9..bf49fab121 100644 --- a/cli/src/commands/app/dev.ts +++ b/cli/src/commands/app/dev.ts @@ -15,8 +15,8 @@ import * as path from "node:path"; import process from "node:process"; import { Buffer } from "node:buffer"; import { writeFileSync } from "node:fs"; -import { WebSocketServer, WebSocket } from "npm:ws@8.18.0"; -import { getDevBuildOptions, ensureNodeModules } from "./bundle.ts"; +import { WebSocketServer, WebSocket } from "npm:ws"; +import { getDevBuildOptions, ensureNodeModules, createFrameworkPlugins, detectFrameworks } from "./bundle.ts"; import { wmillTsDev as wmillTs } from "./wmillTsDev.ts"; import * as wmill from "../../../gen/services.gen.ts"; import { resolveWorkspace } from "../../core/context.ts"; @@ -104,9 +104,13 @@ async function dev(opts: DevOptions) { port: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((p) => p + DEFAULT_PORT), })); const host = opts.host ?? DEFAULT_HOST; - const entryPoint = opts.entry ?? "index.tsx"; const shouldOpen = opts.open ?? true; + // Detect frameworks to determine default entry point + const frameworks = detectFrameworks(process.cwd()); + const defaultEntry = (frameworks.svelte || frameworks.vue) ? "index.ts" : "index.tsx"; + const entryPoint = opts.entry ?? defaultEntry; + // Verify entry point exists if (!fs.existsSync(entryPoint)) { log.error( @@ -118,7 +122,7 @@ async function dev(opts: DevOptions) { } // Ensure node_modules exists - const appDir = path.dirname(entryPoint); + const appDir = path.dirname(entryPoint) || process.cwd(); await ensureNodeModules(appDir); // In-memory cache of inferred schemas (runnableId -> schema) @@ -144,6 +148,9 @@ async function dev(opts: DevOptions) { const buildOptions = getDevBuildOptions(entryPoint); + // Load framework-specific plugins (svelte, vue) based on package.json + const frameworkPlugins = await createFrameworkPlugins(appDir); + const wmillPlugin = { name: "wmill-virtual", setup(build: any) { @@ -174,10 +181,12 @@ async function dev(opts: DevOptions) { }, }; + // Create esbuild context const ctx = await esbuild.context({ ...buildOptions, plugins: [ + ...frameworkPlugins, { name: "notify-on-rebuild", setup(build: any) { @@ -218,7 +227,7 @@ async function dev(opts: DevOptions) { runnablesWatcher = Deno.watchFs(runnablesPath); // Per-file debounce timeouts for schema inference (longer debounce for typing) - const schemaInferenceTimeouts: Record = {}; + const schemaInferenceTimeouts: Record = {}; const SCHEMA_DEBOUNCE_MS = 500; // Wait 500ms after last change before inferring schema // Handle runnables file changes in the background @@ -594,9 +603,7 @@ const command = new Command() .option("--host ", "Host to bind the dev server to", { default: DEFAULT_HOST, }) - .option("--entry ", "Entry point file for the application", { - default: "index.tsx", - }) + .option("--entry ", "Entry point file (default: index.ts for Svelte/Vue, index.tsx otherwise)") .option("--no-open", "Don't automatically open the browser") .action(dev as any); diff --git a/cli/src/commands/app/raw_apps.ts b/cli/src/commands/app/raw_apps.ts index 5bb1cb756c..18361819e5 100644 --- a/cli/src/commands/app/raw_apps.ts +++ b/cli/src/commands/app/raw_apps.ts @@ -14,7 +14,7 @@ import { Policy } from "../../../gen/types.gen.ts"; import { GlobalOptions, isSuperset } from "../../types.ts"; import { replaceInlineScripts, repopulateFields } from "./apps.ts"; -import { createBundle } from "./bundle.ts"; +import { createBundle, detectFrameworks } from "./bundle.ts"; import { mergeConfigWithConfigFile, SyncOptions } from "../../core/conf.ts"; export interface AppFile { @@ -102,7 +102,10 @@ export async function pushRawApp( const files = await collectAppFiles(localPath); async function createBundleRaw() { log.info(colors.yellow.bold(`Creating raw app ${remotePath} bundle...`)); - const entryPoint = localPath + "index.tsx"; + // Detect frameworks to determine entry point + const frameworks = detectFrameworks(localPath); + const entryFile = (frameworks.svelte || frameworks.vue) ? "index.ts" : "index.tsx"; + const entryPoint = localPath + entryFile; return await createBundle({ entryPoint: entryPoint, production: true, diff --git a/cli/src/commands/dev/dev.ts b/cli/src/commands/dev/dev.ts index 87ef3458d1..63c96bb8e8 100644 --- a/cli/src/commands/dev/dev.ts +++ b/cli/src/commands/dev/dev.ts @@ -167,7 +167,7 @@ async function dev(opts: GlobalOptions & SyncOptions) { ws.on("message", (message: WebSocket.RawData) => { let data; try { - data = JSON.parse(message); + data = JSON.parse(message.toString()); } catch (e) { console.log("Received invalid JSON: " + message + " " + e); return; diff --git a/cli/src/commands/script/script.ts b/cli/src/commands/script/script.ts index 7b35d859f4..97a4f085cc 100644 --- a/cli/src/commands/script/script.ts +++ b/cli/src/commands/script/script.ts @@ -228,7 +228,7 @@ export async function handleFile( }).toString(); log.info("Custom bundler executed for " + path); } else { - const esbuild = await import("npm:esbuild"); + const esbuild = await import("npm:esbuild@0.24.2"); log.info(`Started bundling ${path} ...`); const startTime = performance.now(); @@ -246,11 +246,15 @@ export async function handleFile( platform: "node", packages: "bundle", target: format == "cjs" ? "node20.15.1" : "esnext", - ...(codebase.banner != null && { banner: codebase.banner }), + banner: codebase.banner, + // ...(codebase.banner != null && { banner: codebase.banner }), }); const endTime = performance.now(); bundleContent = out.outputFiles[0].text; - outputFiles = out.outputFiles; + outputFiles = out.outputFiles ?? []; + if (outputFiles.length == 0) { + throw new Error(`No output files found for ${path}`); + } log.info( `Finished bundling ${path}: ${(bundleContent.length / 1024).toFixed( 0 diff --git a/cli/src/core/conf.ts b/cli/src/core/conf.ts index d56a97c5b4..b2d04c5f6b 100644 --- a/cli/src/core/conf.ts +++ b/cli/src/core/conf.ts @@ -102,7 +102,9 @@ export interface Codebase { inject?: string[]; loader?: any; format?: "cjs" | "esm"; - banner?: string | { js?: string }; + banner?: { + [type: string]: string; +}; } function getGitRepoRoot(): string | null { diff --git a/frontend/scripts/untar_ui_builder.js b/frontend/scripts/untar_ui_builder.js index 4a2edc71de..06704bc4f4 100644 --- a/frontend/scripts/untar_ui_builder.js +++ b/frontend/scripts/untar_ui_builder.js @@ -20,7 +20,7 @@ console.log('Running postinstall for root project'); import { x } from 'tar' -const tarUrl = 'https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-fa4de3c.tar.gz' +const tarUrl = 'https://pub-06154ed168a24e73a86ab84db6bf15d8.r2.dev/ui_builder-b4fcf00.tar.gz' const outputTarPath = path.join(process.cwd(), 'ui_builder.tar.gz') const extractTo = path.join(process.cwd(), 'static/ui_builder/') diff --git a/frontend/src/routes/(root)/(logged)/apps_raw/add/+page.svelte b/frontend/src/routes/(root)/(logged)/apps_raw/add/+page.svelte index c884dbc2e4..c4d2861404 100644 --- a/frontend/src/routes/(root)/(logged)/apps_raw/add/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/apps_raw/add/+page.svelte @@ -24,10 +24,10 @@ $importStore = undefined } - const state = nodraft ? undefined : localStorage.getItem('rawapp') + const appState = nodraft ? undefined : localStorage.getItem('rawapp') - let summary = '' - let files: Record = react19Template + let summary = $state('') + let files: Record = $state(react19Template) afterNavigate(() => { if (nodraft) { let url = new URL($page.url.href) @@ -35,15 +35,15 @@ replaceState(url.toString(), $page.state) } }) - let policy: Policy = { + let policy: Policy = $state({ on_behalf_of: $userStore?.username.includes('@') ? $userStore?.username : `u/${$userStore?.username}`, on_behalf_of_email: $userStore?.email, execution_mode: 'publisher' - } + }) - let runnables: Record = { + let runnables: Record = $state({ a: { name: 'a', fields: {}, @@ -67,7 +67,7 @@ } } } - } + }) loadApp() function extractValue(value: any) { @@ -104,7 +104,7 @@ console.log('App loaded from template id') sendUserToast('App loaded from template') goto('?', { replaceState: true }) - } else if (!templatePath && state) { + } else if (!templatePath && appState) { console.log('App loaded from browser stored autosave') sendUserToast('App restored from browser stored autosave', false, [ { @@ -115,7 +115,7 @@ } } ]) - let decoded = decodeState(state) + let decoded = decodeState(appState) extractValue(decoded) } } @@ -143,8 +143,8 @@ files: vueTemplate } ] - let templatePicker = nodraft != null - let hide = false + let templatePicker = $state(nodraft != null) + let reloadCounter = $state(0) {#if templatePicker} @@ -152,12 +152,10 @@
{#each templates as t}
{/if} -{#if !hide} +{#key reloadCounter} { goto(`/apps_raw/edit/${event.detail}`) @@ -186,4 +184,4 @@ {summary} newApp /> -{/if} +{/key} diff --git a/frontend/src/routes/(root)/(logged)/apps_raw/add/templates.ts b/frontend/src/routes/(root)/(logged)/apps_raw/add/templates.ts index 9a719d4bf4..a6d149ee4a 100644 --- a/frontend/src/routes/(root)/(logged)/apps_raw/add/templates.ts +++ b/frontend/src/routes/(root)/(logged)/apps_raw/add/templates.ts @@ -122,7 +122,7 @@ export const svelte5Template = { '/index.css': indexCss, '/package.json': `{ "dependencies": { - "svelte": "5.16.1", + "svelte": "5.45.2", "windmill-client": "^1" } }`, From 214d757a9afac02caa16ec81350e5e2e4434c0d3 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 30 Nov 2025 11:31:27 +0000 Subject: [PATCH 26/39] improve svelte support on cli --- cli/deno.lock | 6 +++- cli/src/commands/app/bundle.ts | 58 +++++++++++++++++++++++++++------- 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/cli/deno.lock b/cli/deno.lock index 9b0009da9f..c7007123ba 100644 --- a/cli/deno.lock +++ b/cli/deno.lock @@ -89,6 +89,7 @@ "npm:minimatch@*": "10.0.3", "npm:open@*": "10.2.0", "npm:svelte-preprocess@6.0.3": "6.0.3_svelte@5.45.2__acorn@8.14.1", + "npm:svelte@5.45.2": "5.45.2_acorn@8.14.1", "npm:ws@*": "8.18.3", "npm:ws@8.18.0": "8.18.0", "npm:ws@8.18.3": "8.18.3" @@ -199,7 +200,10 @@ ] }, "@std/io@0.224.9": { - "integrity": "4414664b6926f665102e73c969cfda06d2c4c59bd5d0c603fd4f1b1c840d6ee3" + "integrity": "4414664b6926f665102e73c969cfda06d2c4c59bd5d0c603fd4f1b1c840d6ee3", + "dependencies": [ + "jsr:@std/bytes@^1.0.2" + ] }, "@std/io@0.225.2": { "integrity": "3c740cd4ee4c082e6cfc86458f47e2ab7cb353dc6234d5e9b1f91a2de5f4d6c7", diff --git a/cli/src/commands/app/bundle.ts b/cli/src/commands/app/bundle.ts index daa1dd24d0..23e67872cf 100644 --- a/cli/src/commands/app/bundle.ts +++ b/cli/src/commands/app/bundle.ts @@ -56,6 +56,52 @@ export function detectFrameworks(appDir: string): { svelte: boolean; vue: boolea } } +/** + * Creates a Svelte esbuild plugin + * Uses the svelte compiler from the project's node_modules + */ +function createSveltePlugin(appDir: string): any { + return { + name: "svelte", + setup(build: any) { + build.onLoad({ filter: /\.svelte$/ }, async (args: any) => { + // Import svelte compiler from the project's node_modules + const svelte = await import("npm:svelte@5.45.2/compiler"); + + // Load the file from the file system + const source = await fs.promises.readFile(args.path, "utf8"); + const filename = path.relative(process.cwd(), args.path); + + // This converts a message in Svelte's format to esbuild's format + const convertMessage = ({ message, start, end }: any) => { + let location; + if (start && end) { + const lineText = source.split(/\r\n|\r|\n/g)[start.line - 1]; + const lineEnd = start.line === end.line ? end.column : lineText.length; + location = { + file: filename, + line: start.line, + column: start.column, + length: lineEnd - start.column, + lineText, + }; + } + return { text: message, location }; + }; + + // Convert Svelte syntax to JavaScript + try { + const { js, warnings } = svelte.compile(source, { filename }); + const contents = js.code + `//# sourceMappingURL=` + js.map.toUrl(); + return { contents, warnings: warnings.map(convertMessage) }; + } catch (e: any) { + return { errors: [convertMessage(e)] }; + } + }); + }, + }; +} + /** * Creates framework-specific esbuild plugins based on detected dependencies */ @@ -65,17 +111,7 @@ export async function createFrameworkPlugins(appDir: string): Promise { if (frameworks.svelte) { log.info(colors.blue("🔧 Svelte detected, adding svelte plugin...")); - try { - const esbuildSvelte = await import("npm:esbuild-svelte@0.9.3"); - const sveltePreprocess = await import("npm:svelte-preprocess@6.0.3"); - plugins.push( - esbuildSvelte.default({ - preprocess: sveltePreprocess.default(), - }) - ); - } catch (error: any) { - log.warn(colors.yellow(`Failed to load svelte plugin: ${error.message}`)); - } + plugins.push(createSveltePlugin(appDir)); } if (frameworks.vue) { From bd3717fe3f225cc43845a8280639f5898e0130fe Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 30 Nov 2025 11:37:05 +0000 Subject: [PATCH 27/39] nit edit button --- .../src/routes/(root)/(logged)/apps/get/[...path]/+page.svelte | 2 +- .../routes/(root)/(logged)/apps_raw/get/[...path]/+page.svelte | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/routes/(root)/(logged)/apps/get/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/apps/get/[...path]/+page.svelte index cd38ee4a39..bd900b38f0 100644 --- a/frontend/src/routes/(root)/(logged)/apps/get/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/apps/get/[...path]/+page.svelte @@ -80,7 +80,7 @@
diff --git a/frontend/src/routes/(root)/(logged)/apps_raw/get/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/apps_raw/get/[...path]/+page.svelte index 6e5badb500..d7318752ab 100644 --- a/frontend/src/routes/(root)/(logged)/apps_raw/get/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/apps_raw/get/[...path]/+page.svelte @@ -57,8 +57,7 @@
From e9e306ceb251a2563580e19d152e4cf6192b5f79 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 30 Nov 2025 11:46:55 +0000 Subject: [PATCH 28/39] nit mobile top bar button --- frontend/src/lib/components/copilot/chat/AiChatLayout.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/lib/components/copilot/chat/AiChatLayout.svelte b/frontend/src/lib/components/copilot/chat/AiChatLayout.svelte index f45c928169..27e1c6cd98 100644 --- a/frontend/src/lib/components/copilot/chat/AiChatLayout.svelte +++ b/frontend/src/lib/components/copilot/chat/AiChatLayout.svelte @@ -60,7 +60,7 @@
From 5fe54b2d6e6f1619276212c0ac2a769302141c46 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 30 Nov 2025 13:15:33 +0000 Subject: [PATCH 29/39] update app locks when pulling them --- cli/src/commands/app/raw_apps.ts | 2 +- cli/src/commands/sync/sync.ts | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/cli/src/commands/app/raw_apps.ts b/cli/src/commands/app/raw_apps.ts index 18361819e5..cb2d42f05b 100644 --- a/cli/src/commands/app/raw_apps.ts +++ b/cli/src/commands/app/raw_apps.ts @@ -39,7 +39,7 @@ async function collectAppFiles( if (entry.isDirectory) { // Skip the runnables and node_modules subfolders - if (entry.name === "runnables" || entry.name === "node_modules" || entry.name === "dist") { + if (entry.name === "runnables" || entry.name === "node_modules" || entry.name === "dist" || entry.name === ".claude") { continue; } await readDirRecursive(fullPath + SEP, relativePath + SEP); diff --git a/cli/src/commands/sync/sync.ts b/cli/src/commands/sync/sync.ts index ff0d6b64c0..4282b42298 100644 --- a/cli/src/commands/sync/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -69,6 +69,7 @@ import { import { extractInlineScripts as extractInlineScriptsForFlows } from "../../../windmill-utils-internal/src/inline-scripts/extractor.ts"; import { generateFlowLockInternal } from "../flow/flow_metadata.ts"; import { isExecutionModeAnonymous } from "../app/apps.ts"; +import { generateAppLocksInternal } from "../app/app_metadata.ts"; // Merge CLI options with effective settings, preserving CLI flags as overrides function mergeCliWithEffectiveOptions< @@ -758,7 +759,7 @@ export async function* readDirRecursiveWithIgnore( for await (const e2 of e.c()) { if (e2.isDirectory) { const dirName = e2.path.split(SEP).pop(); - if (dirName == "node_modules" || dirName?.startsWith(".")) { + if (dirName == "node_modules" || dirName == ".claude" || dirName?.startsWith(".")) { continue; } } @@ -1697,14 +1698,9 @@ export async function pull( ) ); } - if (tracker.rawApps.length > 0) { - log.info( - colors.gray( - `Raw apps ${tracker.rawApps.join( - ", " - )} inline scripts were changed but ignoring metadata regeneration for now` - ) - ); + for (const change of tracker.rawApps) { + log.info(`Updating lock metadata for raw app ${change}`); + await generateAppLocksInternal(change, false, workspace, opts, true, true); } if (opts.jsonOutput) { const result = { From 58ef965e20ec25456da23b27fe16b8abab1889f6 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 30 Nov 2025 15:15:27 +0000 Subject: [PATCH 30/39] fix(cli): cli behave as expected in forked workspaces --- cli/src/commands/workspace/fork.ts | 17 +-- cli/src/core/context.ts | 141 ++++++++++-------- .../src/lib/components/ScriptEditor.svelte | 65 +++++--- 3 files changed, 134 insertions(+), 89 deletions(-) diff --git a/cli/src/commands/workspace/fork.ts b/cli/src/commands/workspace/fork.ts index 1a0d3a45b7..05292e58e0 100644 --- a/cli/src/commands/workspace/fork.ts +++ b/cli/src/commands/workspace/fork.ts @@ -1,7 +1,7 @@ // deno-lint-ignore-file no-explicit-any import { GlobalOptions } from "../../types.ts"; import { colors, Input, log, setClient } from "../../../deps.ts"; -import { addWorkspace, allWorkspaces, list, removeWorkspace } from "./workspace.ts"; +import { allWorkspaces, list, removeWorkspace } from "./workspace.ts"; import * as wmill from "../../../gen/services.gen.ts"; import { getCurrentGitBranch, getOriginalBranchForWorkspaceForks, isGitRepository } from "../../utils/git.ts"; import { WM_FORK_PREFIX } from "../../main.ts"; @@ -123,19 +123,14 @@ async function createWorkspaceFork( throw error; } - await addWorkspace( - { - name: workspaceName, - remote: remote, - workspaceId: trueWorkspaceId, - token: token, - }, - opts - ); const newBranchName = `${WM_FORK_PREFIX}/${clonedBranchName}/${workspaceId}` - log.info(`Created forked workspace ${trueWorkspaceId}. To start contributing to your fork, create and push edits to the branch \`${newBranchName}\` by using the command:\n\n\t`+colors.white(`git checkout -b ${newBranchName}`) + `\n\nThe changes will then be reflected in your fork if you've setup the git sync workflows correctly.`); + log.info(`Created forked workspace ${trueWorkspaceId}. To start contributing to your fork, create and push edits to the branch \`${newBranchName}\` by using the command: + +\t`+colors.white(`git checkout -b ${newBranchName}`) + ` + +When doing operations on the forked workspace, it will use the remote setup in gitBranches for the branch it was forked from.`); } async function deleteWorkspaceFork( diff --git a/cli/src/core/context.ts b/cli/src/core/context.ts index 902c9b7435..a2b31b1125 100644 --- a/cli/src/core/context.ts +++ b/cli/src/core/context.ts @@ -83,6 +83,74 @@ async function selectFromMultipleProfiles( return selectedProfile; } +/** + * Prompts the user to create a new workspace profile interactively + */ +async function createWorkspaceProfileInteractively( + normalizedBaseUrl: string, + workspaceId: string, + currentBranch: string, + opts: GlobalOptions, + context: { rawBranch: string; isForked: boolean } +): Promise { + // Log appropriate message based on context + if (!context.isForked) { + log.info(colors.yellow( + `\nNo workspace profile found for branch '${context.rawBranch}'\n` + + `(${normalizedBaseUrl}, ${workspaceId})` + )); + } else { + log.info(colors.yellow( + `\nNo workspace profile was found for this forked workspace\n` + + `(${normalizedBaseUrl}, ${workspaceId})` + )); + } + + if (!Deno.stdin.isTerminal() || !Deno.stdout.isTerminal()) { + log.info("Not a TTY, cannot create profile interactively. Use 'wmill workspace add' first."); + return undefined; + } + + const shouldCreate = await Confirm.prompt({ + message: "Would you like to create a new workspace profile?", + default: true, + }); + + if (!shouldCreate) { + return undefined; + } + + // Prompt for profile details + const profileName = await Input.prompt({ + message: "Profile name", + default: workspaceId, + }); + + const token = await loginInteractive(normalizedBaseUrl); + if (!token) { + log.error("Failed to obtain token"); + return undefined; + } + + // Create the new profile + const newWorkspace: Workspace = { + name: profileName, + remote: normalizedBaseUrl, + workspaceId: workspaceId, + token: token, + }; + + await addWorkspace(newWorkspace, opts); + + // Set as last used for this branch + await setLastUsedProfile(currentBranch, normalizedBaseUrl, workspaceId, profileName, opts.configDir); + + log.info(colors.green(`✓ Created profile '${profileName}' for ${workspaceId} on ${normalizedBaseUrl}`)); + log.info(colors.green(`✓ Profile '${profileName}' is now active`)); + + return newWorkspace; +} + export type Context = { workspace: string; baseUrl: string; @@ -134,6 +202,7 @@ export async function tryResolveBranchWorkspace( const originalBranchIfForked = getOriginalBranchForWorkspaceForks(rawBranch); const workspaceIdIfForked = getWorkspaceIdForWorkspaceForkFromBranchName(rawBranch); if (originalBranchIfForked) { + log.info(`Using original branch \`${originalBranchIfForked}\` for finding workspace profile from gitBranches section in wmill.yaml`); currentBranch = originalBranchIfForked; } else { currentBranch = rawBranch; @@ -149,10 +218,7 @@ export async function tryResolveBranchWorkspace( } let { baseUrl, workspaceId } = branchConfig; - if (workspaceIdIfForked) { - workspaceId = workspaceIdIfForked; - log.info(`Inferred workspace id \`${workspaceId}\` from branch name because this is a workspace fork branch (\`${rawBranch}\`). `); - } + let normalizedBaseUrl: string; try { normalizedBaseUrl = new URL(baseUrl).toString(); @@ -169,61 +235,18 @@ export async function tryResolveBranchWorkspace( if (matchingProfiles.length === 0) { // No matching profile exists - prompt to create one - if (!originalBranchIfForked) { - log.info(colors.yellow( - `\nNo workspace profile found for branch '${rawBranch}'\n` + - `(${normalizedBaseUrl}, ${workspaceId})` - )); - } else { - log.info(colors.yellow( - `\nNo workspace profile was found for this forked workspace\n` + - `(${normalizedBaseUrl}, ${workspaceId})` - )); - } + return await createWorkspaceProfileInteractively( + normalizedBaseUrl, + workspaceId, + currentBranch, + opts, + { rawBranch, isForked: !!originalBranchIfForked } + ); + } - if (!Deno.stdin.isTerminal() || !Deno.stdout.isTerminal()) { - log.info("Not a TTY, cannot create profile interactively. Use 'wmill workspace add' first."); - return undefined; - } - - const shouldCreate = await Confirm.prompt({ - message: "Would you like to create a new workspace profile?", - default: true, - }); - - if (!shouldCreate) { - return undefined; - } - - // Prompt for profile details - const profileName = await Input.prompt({ - message: "Profile name", - default: workspaceId, - }); - - const token = await loginInteractive(normalizedBaseUrl); - if (!token) { - log.error("Failed to obtain token"); - return undefined; - } - - // Create the new profile - const newWorkspace: Workspace = { - name: profileName, - remote: normalizedBaseUrl, - workspaceId: workspaceId, - token: token, - }; - - await addWorkspace(newWorkspace, opts); - - // Set as last used for this branch - await setLastUsedProfile(currentBranch, normalizedBaseUrl, workspaceId, profileName, opts.configDir); - - log.info(colors.green(`✓ Created profile '${profileName}' for ${workspaceId} on ${normalizedBaseUrl}`)); - log.info(colors.green(`✓ Profile '${profileName}' is now active`)); - - return newWorkspace; + if (workspaceIdIfForked) { + workspaceId = workspaceIdIfForked; + log.info(`Inferred workspace id \`${workspaceId}\` from branch name because this is a workspace fork branch (\`${rawBranch}\`). `); } // Handle multiple profiles - use special branch-aware logic @@ -231,7 +254,7 @@ export async function tryResolveBranchWorkspace( if (matchingProfiles.length === 1) { selectedProfile = matchingProfiles[0]; - log.info(colors.green(`Using workspace profile '${selectedProfile.name}' for branch '${currentBranch}'`)); + log.info(colors.green(`Using workspace profile '${selectedProfile.name}' for branch '${currentBranch} with workspace id \`${workspaceId}\``)); } else { // For multiple profiles, check branch-specific last used first const lastUsedName = await getLastUsedProfile( diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index 1e1c50c232..78cf810eb1 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -56,6 +56,8 @@ import GitRepoResourcePicker from './GitRepoResourcePicker.svelte' import { updateDelegateToGitRepoConfig, insertAdditionalInventories } from '$lib/ansibleUtils' import { copilotInfo } from '$lib/aiStore' + import JsonInputs from '$lib/components/JsonInputs.svelte' + import Toggle from './Toggle.svelte' interface Props { // Exported @@ -124,6 +126,9 @@ }: Props = $props() let initialArgs = structuredClone($state.snapshot(args)) + let jsonEditor: JsonInputs | undefined = $state(undefined) + let jsonView = $state(false) + let schemaHeight = $state(0) $effect.pre(() => { if (schema == undefined) { @@ -707,29 +712,51 @@ {/if}
{/if} +
-
-
- {#key argsRender} - - {/key} + {#if jsonView} +
+ { + if (e.detail) { + args = e.detail + } + }} + updateOnBlur={false} + placeholder={`Write args as JSON.

Example:

{
  "foo": "12"
}`} + />
-
+ {:else} +
+
+ {#key argsRender} + + {/key} +
+
+ {/if} Date: Sun, 30 Nov 2025 15:24:24 +0000 Subject: [PATCH 31/39] further cli improvements --- cli/src/core/context.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/cli/src/core/context.ts b/cli/src/core/context.ts index a2b31b1125..60612991b5 100644 --- a/cli/src/core/context.ts +++ b/cli/src/core/context.ts @@ -244,10 +244,7 @@ export async function tryResolveBranchWorkspace( ); } - if (workspaceIdIfForked) { - workspaceId = workspaceIdIfForked; - log.info(`Inferred workspace id \`${workspaceId}\` from branch name because this is a workspace fork branch (\`${rawBranch}\`). `); - } + // Handle multiple profiles - use special branch-aware logic let selectedProfile: Workspace; @@ -291,6 +288,14 @@ export async function tryResolveBranchWorkspace( ); log.info(colors.green(`Using workspace profile '${selectedProfile.name}' for branch '${currentBranch}'`)); + + + } + + if (workspaceIdIfForked) { + selectedProfile.name = `${selectedProfile.name}/${workspaceIdIfForked}`; + selectedProfile.workspaceId = workspaceIdIfForked; + log.info(`Inferred workspace id \`${workspaceId}\` from branch name because this is a workspace fork branch (\`${rawBranch}\`). `); } return selectedProfile; From 49b2ea65316be3cf0b95af4b2e69c10c01f35d1a Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 30 Nov 2025 17:42:16 +0000 Subject: [PATCH 32/39] fix(git-sync): initialize repo with gitBranches set --- cli/src/commands/app/bundle.ts | 13 +- cli/src/commands/init/init.ts | 50 ++---- cli/src/commands/workspace/workspace.ts | 14 +- cli/src/core/context.ts | 159 +++++++++++++----- .../git_sync/GitSyncRepositoryCard.svelte | 15 +- frontend/src/lib/hubPaths.json | 13 +- 6 files changed, 158 insertions(+), 106 deletions(-) diff --git a/cli/src/commands/app/bundle.ts b/cli/src/commands/app/bundle.ts index 23e67872cf..8f45cb5819 100644 --- a/cli/src/commands/app/bundle.ts +++ b/cli/src/commands/app/bundle.ts @@ -116,12 +116,13 @@ export async function createFrameworkPlugins(appDir: string): Promise { if (frameworks.vue) { log.info(colors.blue("🔧 Vue detected, adding vue plugin...")); - try { - const esbuildPluginVue = await import("npm:esbuild-plugin-vue3@0.5.1"); - plugins.push(esbuildPluginVue.default()); - } catch (error: any) { - log.warn(colors.yellow(`Failed to load vue plugin: ${error.message}`)); - } + throw new Error("Vue plugin not supported yet"); + // try { + // const esbuildPluginVue = await import("npm:esbuild-plugin-vue3@0.5.1"); + // plugins.push(esbuildPluginVue.default()); + // } catch (error: any) { + // log.warn(colors.yellow(`Failed to load vue plugin: ${error.message}`)); + // } } return plugins; diff --git a/cli/src/commands/init/init.ts b/cli/src/commands/init/init.ts index 53fe2835c2..24b2fbfeb6 100644 --- a/cli/src/commands/init/init.ts +++ b/cli/src/commands/init/init.ts @@ -1,14 +1,9 @@ -import { - colors, - Command, - log, - yamlStringify, - Confirm, -} from "../../../deps.ts"; +import { colors, Command, log, yamlStringify, Confirm } from "../../../deps.ts"; import { GlobalOptions } from "../../types.ts"; import { readLockfile } from "../../utils/metadata.ts"; import { SCRIPT_GUIDANCE } from "../../guidance/script_guidance.ts"; import { FLOW_GUIDANCE } from "../../guidance/flow_guidance.ts"; +import { getActiveWorkspaceOrFallback } from "../workspace/workspace.ts"; export interface InitOptions { useDefault?: boolean; @@ -61,12 +56,10 @@ async function initAction(opts: InitOptions) { // Offer to bind workspace profile to current branch if (isGitRepository()) { - const { getActiveWorkspace } = await import("../workspace/workspace.ts"); - const activeWorkspace = await getActiveWorkspace( + const activeWorkspace = await getActiveWorkspaceOrFallback( opts as GlobalOptions ); const currentBranch = getCurrentGitBranch(); - if (activeWorkspace && currentBranch) { // Determine binding behavior based on flags const shouldBind = opts.bindProfile === true; @@ -74,10 +67,10 @@ async function initAction(opts: InitOptions) { opts.bindProfile === undefined && Deno.stdin.isTerminal() && !opts.useDefault; + const shouldSkip = - opts.bindProfile === false || - opts.useDefault || - (!Deno.stdin.isTerminal() && opts.bindProfile === undefined); + opts.bindProfile != true && + (opts.useDefault || !Deno.stdin.isTerminal()); if (shouldSkip) { return; @@ -86,15 +79,11 @@ async function initAction(opts: InitOptions) { // Show workspace info if we're binding or prompting if (shouldBind || shouldPrompt) { log.info( - colors.yellow( - `\nCurrent Git branch: ${colors.bold(currentBranch)}` - ) + colors.yellow(`\nCurrent Git branch: ${colors.bold(currentBranch)}`) ); log.info( colors.yellow( - `Active workspace profile: ${colors.bold( - activeWorkspace.name - )}` + `Active workspace profile: ${colors.bold(activeWorkspace.name)}` ) ); log.info( @@ -123,15 +112,15 @@ async function initAction(opts: InitOptions) { currentConfig.gitBranches[currentBranch] = { overrides: {} }; } + log.info( + `binding branch ${currentBranch} to workspace ${activeWorkspace.name} on ${activeWorkspace.remote}` + ); currentConfig.gitBranches[currentBranch].baseUrl = activeWorkspace.remote; currentConfig.gitBranches[currentBranch].workspaceId = activeWorkspace.workspaceId; - await Deno.writeTextFile( - "wmill.yaml", - yamlStringify(currentConfig) - ); + await Deno.writeTextFile("wmill.yaml", yamlStringify(currentConfig)); log.info( colors.green( @@ -149,10 +138,10 @@ async function initAction(opts: InitOptions) { const { resolveWorkspace } = await import("../../core/context.ts"); // Check if user has workspace configured - const { getActiveWorkspace } = await import("../workspace/workspace.ts"); - const activeWorkspace = await getActiveWorkspace( - opts as GlobalOptions + const { getActiveWorkspace } = await import( + "../workspace/workspace.ts" ); + const activeWorkspace = await getActiveWorkspace(opts as GlobalOptions); if (!activeWorkspace) { log.info("No workspace configured. Using default settings."); @@ -233,9 +222,7 @@ async function initAction(opts: InitOptions) { replace: true, // Auto-replace when using backend settings during init }); - log.info( - colors.green("Git-sync settings applied from backend") - ); + log.info(colors.green("Git-sync settings applied from backend")); } } } catch (error) { @@ -266,10 +253,7 @@ async function initAction(opts: InitOptions) { } if (!(await Deno.stat(".cursor/rules/flow.mdc").catch(() => null))) { - await Deno.writeTextFile( - ".cursor/rules/flow.mdc", - flowGuidanceContent - ); + await Deno.writeTextFile(".cursor/rules/flow.mdc", flowGuidanceContent); log.info(colors.green("Created .cursor/rules/flow.mdc")); } diff --git a/cli/src/commands/workspace/workspace.ts b/cli/src/commands/workspace/workspace.ts index 9c13f6b66c..f5878f1251 100644 --- a/cli/src/commands/workspace/workspace.ts +++ b/cli/src/commands/workspace/workspace.ts @@ -397,6 +397,18 @@ async function whoami(_opts: GlobalOptions) { log.info("Active: " + colors.green.bold(activeName || "none")); } +export async function getActiveWorkspaceOrFallback(opts: GlobalOptions) { + let activeWorkspace = await getActiveWorkspace(opts); + if (!activeWorkspace && opts.baseUrl && opts.workspace) { + activeWorkspace = { + name: opts.workspace, + remote: opts.baseUrl, + workspaceId: opts.workspace, + token: "", + }; + } + return activeWorkspace; +} async function bind( opts: GlobalOptions & { branch?: string }, bindWorkspace?: boolean @@ -419,7 +431,7 @@ async function bind( const { readConfigFile } = await import("../../core/conf.ts"); const config = await readConfigFile(); - const activeWorkspace = await getActiveWorkspace(opts); + const activeWorkspace = await getActiveWorkspaceOrFallback(opts); if (!activeWorkspace && bindWorkspace) { log.error( colors.red( diff --git a/cli/src/core/context.ts b/cli/src/core/context.ts index 60612991b5..7422ff3a96 100644 --- a/cli/src/core/context.ts +++ b/cli/src/core/context.ts @@ -12,12 +12,14 @@ import { allWorkspaces, addWorkspace, } from "../commands/workspace/workspace.ts"; -import { - getLastUsedProfile, - setLastUsedProfile -} from "./branch-profiles.ts"; +import { getLastUsedProfile, setLastUsedProfile } from "./branch-profiles.ts"; import { readConfigFile } from "./conf.ts"; -import { getCurrentGitBranch, getOriginalBranchForWorkspaceForks, getWorkspaceIdForWorkspaceForkFromBranchName, isGitRepository } from "../utils/git.ts"; +import { + getCurrentGitBranch, + getOriginalBranchForWorkspaceForks, + getWorkspaceIdForWorkspaceForkFromBranchName, + isGitRepository, +} from "../utils/git.ts"; import { WM_FORK_PREFIX } from "../main.ts"; // Helper function to select from multiple matching profiles @@ -33,11 +35,22 @@ async function selectFromMultipleProfiles( } // Check for last used profile - const lastUsedProfileName = await getLastUsedProfile("", baseUrl, workspaceId, configDir); + const lastUsedProfileName = await getLastUsedProfile( + "", + baseUrl, + workspaceId, + configDir + ); if (lastUsedProfileName) { - const lastUsedProfile = profiles.find(p => p.name === lastUsedProfileName); + const lastUsedProfile = profiles.find( + (p) => p.name === lastUsedProfileName + ); if (lastUsedProfile) { - log.info(colors.green(`Using last used profile '${lastUsedProfile.name}' for ${context}`)); + log.info( + colors.green( + `Using last used profile '${lastUsedProfile.name}' for ${context}` + ) + ); return lastUsedProfile; } } @@ -45,7 +58,11 @@ async function selectFromMultipleProfiles( // No last used or it no longer exists - prompt for selection if (!Deno.stdin.isTerminal() || !Deno.stdout.isTerminal()) { const selectedProfile = profiles[0]; - log.info(colors.yellow(`Multiple profiles found for ${context}. Using first available profile: '${selectedProfile.name}'`)); + log.info( + colors.yellow( + `Multiple profiles found for ${context}. Using first available profile: '${selectedProfile.name}'` + ) + ); // Save selection for next time await setLastUsedProfile( @@ -59,17 +76,19 @@ async function selectFromMultipleProfiles( return selectedProfile; } - log.info(colors.yellow(`\nMultiple workspace profiles found for ${context}:`)); + log.info( + colors.yellow(`\nMultiple workspace profiles found for ${context}:`) + ); const selectedName = await Select.prompt({ message: "Select profile", - options: profiles.map(p => ({ + options: profiles.map((p) => ({ name: `${p.name} (${p.workspaceId} on ${p.remote})`, value: p.name, })), }); - const selectedProfile = profiles.find(p => p.name === selectedName)!; + const selectedProfile = profiles.find((p) => p.name === selectedName)!; // Save selection for next time await setLastUsedProfile( @@ -95,19 +114,25 @@ async function createWorkspaceProfileInteractively( ): Promise { // Log appropriate message based on context if (!context.isForked) { - log.info(colors.yellow( - `\nNo workspace profile found for branch '${context.rawBranch}'\n` + - `(${normalizedBaseUrl}, ${workspaceId})` - )); + log.info( + colors.yellow( + `\nNo workspace profile found for branch '${context.rawBranch}'\n` + + `(${normalizedBaseUrl}, ${workspaceId})` + ) + ); } else { - log.info(colors.yellow( - `\nNo workspace profile was found for this forked workspace\n` + - `(${normalizedBaseUrl}, ${workspaceId})` - )); + log.info( + colors.yellow( + `\nNo workspace profile was found for this forked workspace\n` + + `(${normalizedBaseUrl}, ${workspaceId})` + ) + ); } if (!Deno.stdin.isTerminal() || !Deno.stdout.isTerminal()) { - log.info("Not a TTY, cannot create profile interactively. Use 'wmill workspace add' first."); + log.info( + "Not a TTY, cannot create profile interactively. Use 'wmill workspace add' first." + ); return undefined; } @@ -143,9 +168,19 @@ async function createWorkspaceProfileInteractively( await addWorkspace(newWorkspace, opts); // Set as last used for this branch - await setLastUsedProfile(currentBranch, normalizedBaseUrl, workspaceId, profileName, opts.configDir); + await setLastUsedProfile( + currentBranch, + normalizedBaseUrl, + workspaceId, + profileName, + opts.configDir + ); - log.info(colors.green(`✓ Created profile '${profileName}' for ${workspaceId} on ${normalizedBaseUrl}`)); + log.info( + colors.green( + `✓ Created profile '${profileName}' for ${workspaceId} on ${normalizedBaseUrl}` + ) + ); log.info(colors.green(`✓ Profile '${profileName}' is now active`)); return newWorkspace; @@ -200,9 +235,12 @@ export async function tryResolveBranchWorkspace( let currentBranch: string; const originalBranchIfForked = getOriginalBranchForWorkspaceForks(rawBranch); - const workspaceIdIfForked = getWorkspaceIdForWorkspaceForkFromBranchName(rawBranch); + const workspaceIdIfForked = + getWorkspaceIdForWorkspaceForkFromBranchName(rawBranch); if (originalBranchIfForked) { - log.info(`Using original branch \`${originalBranchIfForked}\` for finding workspace profile from gitBranches section in wmill.yaml`); + log.info( + `Using original branch \`${originalBranchIfForked}\` for finding workspace profile from gitBranches section in wmill.yaml` + ); currentBranch = originalBranchIfForked; } else { currentBranch = rawBranch; @@ -217,20 +255,26 @@ export async function tryResolveBranchWorkspace( return undefined; } - let { baseUrl, workspaceId } = branchConfig; + log.info( + `Using branch configuration for branch \`${currentBranch}\` set in gitBranches` + ); + + const { baseUrl, workspaceId } = branchConfig; let normalizedBaseUrl: string; try { normalizedBaseUrl = new URL(baseUrl).toString(); } catch (error) { - log.error(colors.red(`Invalid baseUrl in branch configuration: ${baseUrl}`)); + log.error( + colors.red(`Invalid baseUrl in branch configuration: ${baseUrl}`) + ); return undefined; } // Find all profiles matching this baseUrl and workspaceId const allProfiles = await allWorkspaces(opts.configDir); const matchingProfiles = allProfiles.filter( - w => w.remote === normalizedBaseUrl && w.workspaceId === workspaceId + (w) => w.remote === normalizedBaseUrl && w.workspaceId === workspaceId ); if (matchingProfiles.length === 0) { @@ -244,14 +288,16 @@ export async function tryResolveBranchWorkspace( ); } - - // Handle multiple profiles - use special branch-aware logic let selectedProfile: Workspace; if (matchingProfiles.length === 1) { selectedProfile = matchingProfiles[0]; - log.info(colors.green(`Using workspace profile '${selectedProfile.name}' for branch '${currentBranch} with workspace id \`${workspaceId}\``)); + log.info( + colors.green( + `Using workspace profile '${selectedProfile.name}' for branch '${currentBranch}' with workspace id \`${workspaceId}\`` + ) + ); } else { // For multiple profiles, check branch-specific last used first const lastUsedName = await getLastUsedProfile( @@ -262,9 +308,15 @@ export async function tryResolveBranchWorkspace( ); if (lastUsedName) { - const lastUsedProfile = matchingProfiles.find(p => p.name === lastUsedName); + const lastUsedProfile = matchingProfiles.find( + (p) => p.name === lastUsedName + ); if (lastUsedProfile) { - log.info(colors.green(`Using workspace profile '${lastUsedProfile.name}' for branch '${currentBranch}' (last used)`)); + log.info( + colors.green( + `Using workspace profile '${lastUsedProfile.name}' for branch '${currentBranch}' (last used)` + ) + ); return lastUsedProfile; } } @@ -287,15 +339,19 @@ export async function tryResolveBranchWorkspace( opts.configDir ); - log.info(colors.green(`Using workspace profile '${selectedProfile.name}' for branch '${currentBranch}'`)); - - + log.info( + colors.green( + `Using workspace profile '${selectedProfile.name}' for branch '${currentBranch}'` + ) + ); } if (workspaceIdIfForked) { selectedProfile.name = `${selectedProfile.name}/${workspaceIdIfForked}`; selectedProfile.workspaceId = workspaceIdIfForked; - log.info(`Inferred workspace id \`${workspaceId}\` from branch name because this is a workspace fork branch (\`${rawBranch}\`). `); + log.info( + `Inferred workspace id \`${workspaceId}\` from branch name because this is a workspace fork branch (\`${rawBranch}\`). ` + ); } return selectedProfile; @@ -320,14 +376,20 @@ export async function resolveWorkspace( // Try to find existing workspace profile by name, then by workspaceId + remote if (opts.workspace) { // Try by workspace name first - let existingWorkspace = await getWorkspaceByName(opts.workspace, opts.configDir); + let existingWorkspace = await getWorkspaceByName( + opts.workspace, + opts.configDir + ); // If not found by name, try to find by workspaceId + remote match if (!existingWorkspace) { - const { allWorkspaces } = await import("../commands/workspace/workspace.ts"); + const { allWorkspaces } = await import( + "../commands/workspace/workspace.ts" + ); const workspaces = await allWorkspaces(opts.configDir); const matchingWorkspaces = workspaces.filter( - w => w.workspaceId === opts.workspace && w.remote === normalizedBaseUrl + (w) => + w.workspaceId === opts.workspace && w.remote === normalizedBaseUrl ); if (matchingWorkspaces.length >= 1) { @@ -385,7 +447,9 @@ export async function resolveWorkspace( if (!branch || !branch.startsWith(WM_FORK_PREFIX)) { return res.value; } else { - log.info(`Found an active workspace \`${res.value.name}\` but the branch name indicates this is a forked workspace. Ignoring active workspace and trying to resolve the correct workspace from the branch name \`${branch}\``); + log.info( + `Found an active workspace \`${res.value.name}\` but the branch name indicates this is a forked workspace. Ignoring active workspace and trying to resolve the correct workspace from the branch name \`${branch}\`` + ); } } @@ -395,9 +459,13 @@ export async function resolveWorkspace( (opts as any).__secret_workspace = branchWorkspace; return branchWorkspace; } else { - const originalBranch = getOriginalBranchForWorkspaceForks(branch) + const originalBranch = getOriginalBranchForWorkspaceForks(branch); if (originalBranch) { - log.error(colors.red.bold(`Failed to resolve workspace profile for workspace fork. This most likely means that the original branch \`${originalBranch}\` where \`${branch}\` is originally forked from, is not setup in the wmill.yaml. You need to update the \`gitBranches\` section for \`${originalBranch}\` to include workspaceId and baseUrl.`)) + log.error( + colors.red.bold( + `Failed to resolve workspace profile for workspace fork. This most likely means that the original branch \`${originalBranch}\` where \`${branch}\` is originally forked from, is not setup in the wmill.yaml. You need to update the \`gitBranches\` section for \`${originalBranch}\` to include workspaceId and baseUrl.` + ) + ); return Deno.exit(-1); } } @@ -414,7 +482,6 @@ export async function resolveWorkspace( return Deno.exit(-1); } - export async function fetchVersion(baseUrl: string): Promise { const requestHeaders = new Headers(); @@ -433,7 +500,9 @@ export async function fetchVersion(baseUrl: string): Promise { if (!response.ok) { // Consume response body even on error to avoid resource leak await response.text(); - throw new Error(`Failed to fetch version: ${response.status} ${response.statusText}`); + throw new Error( + `Failed to fetch version: ${response.status} ${response.statusText}` + ); } return await response.text(); diff --git a/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte b/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte index 6d8bd34919..7c08e6ae69 100644 --- a/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte +++ b/frontend/src/lib/components/git_sync/GitSyncRepositoryCard.svelte @@ -108,7 +108,7 @@ ) // Determine display description based on variant and mode - const targetOrDefaultBranch = $derived(targetBranch ? `'${targetBranch}'` : 'repo\'s default' ) + const targetOrDefaultBranch = $derived(targetBranch ? `'${targetBranch}'` : "repo's default") const displayDescription = $derived( variant === 'primary-sync' || variant === 'primary-promotion' ? mode === 'sync' @@ -189,7 +189,7 @@ {#snippet headerActions()} {#if !isLegacy} {#if validation?.hasChanges && validation?.isValid && !repo.isUnsavedConnection} - {#if idx !== null && gitSyncContext.initialRepositories[idx] && !repo.legacyImported} @@ -361,10 +361,7 @@ {#if repo.isUnsavedConnection && !emptyString(repo.git_repo_resource_path) && idx !== null} - + {:else}
- +
diff --git a/frontend/src/lib/hubPaths.json b/frontend/src/lib/hubPaths.json index 80ec7eff19..ba77c05503 100644 --- a/frontend/src/lib/hubPaths.json +++ b/frontend/src/lib/hubPaths.json @@ -17,17 +17,10 @@ "gitSync_15": "hub/19816/sync-script-to-git-repo-windmill", "gitSync_16": "hub/19818/sync-script-to-git-repo-windmill", "gitSync_17": "hub/28073/sync-script-to-git-repo-windmill", - "gitSync": "hub/28078/sync-script-to-git-repo-windmill", - "gitSyncTest_0": "hub/9073/git-repo-test-read-write-windmill", - "gitSyncTest_1": "hub/11499/git-repo-test-read-write-windmill", - "gitSyncTest_2": "hub/11667/git-repo-test-read-write-windmill", - "gitSyncTest_3": "hub/11669/git-repo-test-read-write-windmill", + "gitSync_18": "hub/28078/sync-script-to-git-repo-windmill", + "gitSync": "hub/28081/sync-script-to-git-repo-windmill", "gitSyncTest": "hub/19799/git-repo-test-read-write-windmill", - "gitInitRepo_0": "hub/19787/git-sync%3A-init-repository-windmill", - "gitInitRepo_1": "hub/19797/git-sync%3A-init-repository-windmill", - "gitInitRepo_2": "hub/19817/git-sync%3A-init-repository-windmill", - "gitInitRepo_3": "hub/28072/git-sync%3A-init-repository-windmill", - "gitInitRepo": "hub/28077/git-sync%3A-init-repository-windmill", + "gitInitRepo": "hub/28090/git-sync%3A-init-repository-windmill", "slackErrorHandler": "hub/19741/workspace-or-schedule-error-handler-slack", "slackErrorHandler_0": "hub/9079/workspace-or-schedule-error-handler-slack", "slackErrorHandler_1": "hub/9206/workspace-or-schedule-error-handler-slack", From 24829fca5dbc67a4fb12da44e5e235c9d8ed633a Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 30 Nov 2025 17:46:32 +0000 Subject: [PATCH 33/39] fix nit npm run check --- frontend/src/lib/components/ScriptEditor.svelte | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index 78cf810eb1..e77d91ca63 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -126,7 +126,6 @@ }: Props = $props() let initialArgs = structuredClone($state.snapshot(args)) - let jsonEditor: JsonInputs | undefined = $state(undefined) let jsonView = $state(false) let schemaHeight = $state(0) @@ -725,7 +724,6 @@ data-schema-picker > { if (e.detail) { args = e.detail From 0200d2d56855525c6e0ea1463a81a17e85dc0179 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Sun, 30 Nov 2025 18:51:50 +0100 Subject: [PATCH 34/39] chore(main): release 1.587.0 (#7249) * chore(main): release 1.587.0 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 14 +++ backend/Cargo.lock | 88 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 75 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef445042c5..8631149801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## [1.587.0](https://github.com/windmill-labs/windmill/compare/v1.586.0...v1.587.0) (2025-11-30) + + +### Features + +* **aichat:** stream tool arguments ([#7244](https://github.com/windmill-labs/windmill/issues/7244)) ([8d6936a](https://github.com/windmill-labs/windmill/commit/8d6936ae4a8577983405d95ab75f99822f15da3d)) +* workspace dependencies ([#7124](https://github.com/windmill-labs/windmill/issues/7124)) ([d38c96d](https://github.com/windmill-labs/windmill/commit/d38c96db369bf0a9a0640e11d7fee16605a6775a)) + + +### Bug Fixes + +* **cli:** cli behave as expected in forked workspaces ([58ef965](https://github.com/windmill-labs/windmill/commit/58ef965e20ec25456da23b27fe16b8abab1889f6)) +* **git-sync:** initialize repo with gitBranches set ([49b2ea6](https://github.com/windmill-labs/windmill/commit/49b2ea65316be3cf0b95af4b2e69c10c01f35d1a)) + ## [1.586.0](https://github.com/windmill-labs/windmill/compare/v1.585.1...v1.586.0) (2025-11-27) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 06f4a1023e..e892626b78 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2008,9 +2008,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.47" +version = "1.2.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" +checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" dependencies = [ "find-msvc-tools", "jobserver", @@ -4797,9 +4797,9 @@ dependencies = [ [[package]] name = "dlopen2_derive" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" +checksum = "95f4a04e1bfbfa4835a6073177aafb95ead4de0722dbb339195fdc7e0a09599b" dependencies = [ "proc-macro2", "quote", @@ -6538,13 +6538,13 @@ dependencies = [ [[package]] name = "hostname" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" dependencies = [ "cfg-if", "libc", - "windows-link 0.1.3", + "windows-link 0.2.1", ] [[package]] @@ -11232,9 +11232,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" dependencies = [ "web-time", "zeroize", @@ -14073,9 +14073,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" dependencies = [ "log", "pin-project-lite", @@ -14188,9 +14188,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", @@ -15162,7 +15162,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "aws-sdk-config", @@ -15225,7 +15225,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "argon2", @@ -15346,7 +15346,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.586.0" +version = "1.587.0" dependencies = [ "base64 0.22.1", "chrono", @@ -15361,7 +15361,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.586.0" +version = "1.587.0" dependencies = [ "chrono", "lazy_static", @@ -15375,7 +15375,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "axum", @@ -15394,7 +15394,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "async-recursion", @@ -15486,7 +15486,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.586.0" +version = "1.587.0" dependencies = [ "regex", "serde", @@ -15501,7 +15501,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "bytes", @@ -15525,7 +15525,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.586.0" +version = "1.587.0" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15541,7 +15541,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.586.0" +version = "1.587.0" dependencies = [ "convert_case 0.6.0", "serde", @@ -15550,7 +15550,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "lazy_static", @@ -15562,7 +15562,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "serde_json", @@ -15574,7 +15574,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "gosyn", @@ -15586,7 +15586,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "lazy_static", @@ -15598,7 +15598,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "serde_json", @@ -15610,7 +15610,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "nu-parser", @@ -15621,7 +15621,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15632,7 +15632,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15644,7 +15644,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "async-recursion", @@ -15668,7 +15668,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "lazy_static", @@ -15682,7 +15682,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15699,7 +15699,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "lazy_static", @@ -15713,7 +15713,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "lazy_static", @@ -15731,7 +15731,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "serde", @@ -15742,7 +15742,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "async-recursion", @@ -15779,7 +15779,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.586.0" +version = "1.587.0" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15789,7 +15789,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.586.0" +version = "1.587.0" dependencies = [ "anyhow", "async-once-cell", @@ -16586,18 +16586,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea879c944afe8a2b25fef16bb4ba234f47c694565e97383b36f3a878219065c" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.30" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf955aa904d6040f70dc8e9384444cb1030aed272ba3cb09bbc4ab9e7c1f34f5" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index fddebdfaad..568fe103e3 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.586.0" +version = "1.587.0" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.586.0" +version = "1.587.0" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 43c9a40053..91410e32c3 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.586.0 + version: 1.587.0 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 4ad2b6d1fc..aa63ed0b65 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.586.0"; +export const VERSION = "v1.587.0"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index ce1137f369..28f85a0d91 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -69,7 +69,7 @@ export { // } // }); -export const VERSION = "1.586.0"; +export const VERSION = "1.587.0"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4701b21c29..1b951d5f9e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.586.0", + "version": "1.587.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.586.0", + "version": "1.587.0", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index a666bf7369..4778cca630 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.586.0", + "version": "1.587.0", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index be6c80604e..562d50e30c 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.586.0" -wmill_pg = ">=1.586.0" +wmill = ">=1.587.0" +wmill_pg = ">=1.587.0" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 152f360497..31486d4d0b 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.586.0 + version: 1.587.0 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index c732c39dd3..1c80c3d54f 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.586.0' + ModuleVersion = '1.587.0' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 1a902c9adf..4c68224036 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.586.0" +version = "1.587.0" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index 6d7eab219f..c5f50ba44a 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.586.0" +version = "1.587.0" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 240c6617f5..17227682d7 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.586.0", + "version": "1.587.0", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 64895e1b3c..71c0389b21 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.586.0", + "version": "1.587.0", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index 3a14e5c9f3..f3d6fe62fe 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.586.0 +1.587.0 From 31dc6aee53636a3f9b6a52191860656848906ff3 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 1 Dec 2025 06:28:02 +0000 Subject: [PATCH 35/39] nit(cli): app dev will error if not in right folder --- cli/src/commands/app/dev.ts | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/cli/src/commands/app/dev.ts b/cli/src/commands/app/dev.ts index bf49fab121..ed8b565e10 100644 --- a/cli/src/commands/app/dev.ts +++ b/cli/src/commands/app/dev.ts @@ -83,16 +83,41 @@ interface DevOptions extends GlobalOptions { async function dev(opts: DevOptions) { GLOBAL_CONFIG_OPT.noCdToRoot = true; + + // Validate that we're in a .raw_app folder + const cwd = process.cwd(); + const currentDirName = path.basename(cwd); + + if (!currentDirName.endsWith(".raw_app")) { + log.error( + colors.red( + `Error: The dev command must be run inside a .raw_app folder.\n` + + `Current directory: ${currentDirName}\n` + + `Please navigate to a folder ending with '.raw_app' before running this command.` + ) + ); + Deno.exit(1); + } + + // Check for raw_app.yaml + const rawAppPath = path.join(cwd, "raw_app.yaml"); + if (!fs.existsSync(rawAppPath)) { + log.error( + colors.red( + `Error: raw_app.yaml not found in current directory.\n` + + `The dev command must be run in a .raw_app folder containing a raw_app.yaml file.` + ) + ); + Deno.exit(1); + } + // Resolve workspace and authenticate const workspace = await resolveWorkspace(opts); await requireLogin(opts); const workspaceId = workspace.workspaceId; // Load app path from raw_app.yaml - const rawAppPath = path.join(process.cwd(), "raw_app.yaml"); - const rawApp = fs.existsSync(rawAppPath) - ? ((await yamlParseFile(rawAppPath)) as any) - : {}; + const rawApp = (await yamlParseFile(rawAppPath)) as any; const appPath = rawApp?.custom_path ?? "u/unknown/newapp"; // Dynamically import esbuild only when the dev command is called From 9e7be4b55efdc912c39994091a0db0d70ed7e83c Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 1 Dec 2025 07:45:15 +0000 Subject: [PATCH 36/39] fix: fix public apps by custom url --- backend/ee-repo-ref.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 298b2028c0..0993363464 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -59a8c2dcb362d19ef64881ffe0ef62b4216cd24f +2e6cce1bcb9deb750276ffbde4c7ca1f22e70c62 \ No newline at end of file From 1f60cb20c7a1e603ebd87e6154a79a389e0a167f Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Mon, 1 Dec 2025 08:49:22 +0100 Subject: [PATCH 37/39] chore(main): release 1.587.1 (#7263) * chore(main): release 1.587.1 * Apply automatic changes --------- Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com> --- CHANGELOG.md | 7 +++ backend/Cargo.lock | 54 +++++++++---------- backend/Cargo.toml | 4 +- backend/windmill-api/openapi.yaml | 2 +- benchmarks/lib.ts | 2 +- cli/src/main.ts | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- lsp/Pipfile | 4 +- openflow.openapi.yaml | 2 +- .../WindmillClient/WindmillClient.psd1 | 2 +- python-client/wmill/pyproject.toml | 2 +- python-client/wmill_pg/pyproject.toml | 2 +- typescript-client/jsr.json | 2 +- typescript-client/package.json | 2 +- version.txt | 2 +- 16 files changed, 51 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8631149801..8acc2abb5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [1.587.1](https://github.com/windmill-labs/windmill/compare/v1.587.0...v1.587.1) (2025-12-01) + + +### Bug Fixes + +* fix public apps by custom url ([9e7be4b](https://github.com/windmill-labs/windmill/commit/9e7be4b55efdc912c39994091a0db0d70ed7e83c)) + ## [1.587.0](https://github.com/windmill-labs/windmill/compare/v1.586.0...v1.587.0) (2025-11-30) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index e892626b78..23383863dd 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15162,7 +15162,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "aws-sdk-config", @@ -15225,7 +15225,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "argon2", @@ -15346,7 +15346,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.587.0" +version = "1.587.1" dependencies = [ "base64 0.22.1", "chrono", @@ -15361,7 +15361,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.587.0" +version = "1.587.1" dependencies = [ "chrono", "lazy_static", @@ -15375,7 +15375,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "axum", @@ -15394,7 +15394,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "async-recursion", @@ -15486,7 +15486,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.587.0" +version = "1.587.1" dependencies = [ "regex", "serde", @@ -15501,7 +15501,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "bytes", @@ -15525,7 +15525,7 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.587.0" +version = "1.587.1" dependencies = [ "itertools 0.14.0", "lazy_static", @@ -15541,7 +15541,7 @@ dependencies = [ [[package]] name = "windmill-parser" -version = "1.587.0" +version = "1.587.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -15550,7 +15550,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "lazy_static", @@ -15562,7 +15562,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "serde_json", @@ -15574,7 +15574,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "gosyn", @@ -15586,7 +15586,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "lazy_static", @@ -15598,7 +15598,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "serde_json", @@ -15610,7 +15610,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "nu-parser", @@ -15621,7 +15621,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15632,7 +15632,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15644,7 +15644,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "async-recursion", @@ -15668,7 +15668,7 @@ dependencies = [ [[package]] name = "windmill-parser-ruby" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "lazy_static", @@ -15682,7 +15682,7 @@ dependencies = [ [[package]] name = "windmill-parser-rust" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15699,7 +15699,7 @@ dependencies = [ [[package]] name = "windmill-parser-sql" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "lazy_static", @@ -15713,7 +15713,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "lazy_static", @@ -15731,7 +15731,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "serde", @@ -15742,7 +15742,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "async-recursion", @@ -15779,7 +15779,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.587.0" +version = "1.587.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15789,7 +15789,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.587.0" +version = "1.587.1" dependencies = [ "anyhow", "async-once-cell", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 568fe103e3..8b10b6bf84 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.587.0" +version = "1.587.1" authors.workspace = true edition.workspace = true @@ -33,7 +33,7 @@ members = [ exclude = ["./windmill-duckdb-ffi-internal"] [workspace.package] -version = "1.587.0" +version = "1.587.1" authors = ["Ruben Fiszel "] edition = "2021" diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 91410e32c3..63c20a054b 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.587.0 + version: 1.587.1 title: Windmill API contact: diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index aa63ed0b65..678a7773f2 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.587.0"; +export const VERSION = "v1.587.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/src/main.ts b/cli/src/main.ts index 28f85a0d91..2530ff12f0 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -69,7 +69,7 @@ export { // } // }); -export const VERSION = "1.587.0"; +export const VERSION = "1.587.1"; export const WM_FORK_PREFIX = "wm-fork"; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1b951d5f9e..60fda22c7e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "windmill-components", - "version": "1.587.0", + "version": "1.587.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.587.0", + "version": "1.587.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { diff --git a/frontend/package.json b/frontend/package.json index 4778cca630..49cb1c82e8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "windmill-components", - "version": "1.587.0", + "version": "1.587.1", "scripts": { "dev": "vite dev", "build": "vite build", diff --git a/lsp/Pipfile b/lsp/Pipfile index 562d50e30c..2791e2a688 100644 --- a/lsp/Pipfile +++ b/lsp/Pipfile @@ -4,8 +4,8 @@ verify_ssl = true name = "pypi" [packages] -wmill = ">=1.587.0" -wmill_pg = ">=1.587.0" +wmill = ">=1.587.1" +wmill_pg = ">=1.587.1" sendgrid = "*" mysql-connector-python = "*" pymongo = "*" diff --git a/openflow.openapi.yaml b/openflow.openapi.yaml index 31486d4d0b..91174334bc 100644 --- a/openflow.openapi.yaml +++ b/openflow.openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.587.0 + version: 1.587.1 title: OpenFlow Spec contact: name: Ruben Fiszel diff --git a/powershell-client/WindmillClient/WindmillClient.psd1 b/powershell-client/WindmillClient/WindmillClient.psd1 index 1c80c3d54f..7a5eaafacf 100644 --- a/powershell-client/WindmillClient/WindmillClient.psd1 +++ b/powershell-client/WindmillClient/WindmillClient.psd1 @@ -12,7 +12,7 @@ RootModule = 'WindmillClient.psm1' # Version number of this module. - ModuleVersion = '1.587.0' + ModuleVersion = '1.587.1' # Supported PSEditions # CompatiblePSEditions = @() diff --git a/python-client/wmill/pyproject.toml b/python-client/wmill/pyproject.toml index 4c68224036..9d0996c29a 100644 --- a/python-client/wmill/pyproject.toml +++ b/python-client/wmill/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill" -version = "1.587.0" +version = "1.587.1" description = "A client library for accessing Windmill server wrapping the Windmill client API" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/python-client/wmill_pg/pyproject.toml b/python-client/wmill_pg/pyproject.toml index c5f50ba44a..d2a723b880 100644 --- a/python-client/wmill_pg/pyproject.toml +++ b/python-client/wmill_pg/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "wmill-pg" -version = "1.587.0" +version = "1.587.1" description = "An extension client for the wmill client library focused on pg" license = "Apache-2.0" homepage = "https://windmill.dev" diff --git a/typescript-client/jsr.json b/typescript-client/jsr.json index 17227682d7..b278ebd520 100644 --- a/typescript-client/jsr.json +++ b/typescript-client/jsr.json @@ -1,6 +1,6 @@ { "name": "@windmill/windmill", - "version": "1.587.0", + "version": "1.587.1", "exports": "./src/index.ts", "publish": { "exclude": ["!src", "./s3Types.ts", "./client.ts"] diff --git a/typescript-client/package.json b/typescript-client/package.json index 71c0389b21..7347351845 100644 --- a/typescript-client/package.json +++ b/typescript-client/package.json @@ -1,7 +1,7 @@ { "name": "windmill-client", "description": "Windmill SDK client for browsers and Node.js", - "version": "1.587.0", + "version": "1.587.1", "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { diff --git a/version.txt b/version.txt index f3d6fe62fe..27b0e5e498 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.587.0 +1.587.1 From ef031cd3c02540460487ca40ff1c84b6cfc0b9da Mon Sep 17 00:00:00 2001 From: Pyra <92104930+pyranota@users.noreply.github.com> Date: Mon, 1 Dec 2025 12:18:37 +0100 Subject: [PATCH 38/39] nit(workspace-dependencies): better deployment warning (#7265) * nit(workspace-dependencies): better deployment warning Signed-off-by: pyranota * ci Signed-off-by: pyranota * fix npm check Signed-off-by: pyranota --------- Signed-off-by: pyranota --- .../DependenciesDeploymentWarning.svelte | 134 ++++++++++-------- .../WorkspaceDependenciesEditor.svelte | 52 +++---- .../WorkspaceDependenciesSettings.svelte | 1 - 3 files changed, 95 insertions(+), 92 deletions(-) diff --git a/frontend/src/lib/components/DependenciesDeploymentWarning.svelte b/frontend/src/lib/components/DependenciesDeploymentWarning.svelte index 305fc93512..a10592482d 100644 --- a/frontend/src/lib/components/DependenciesDeploymentWarning.svelte +++ b/frontend/src/lib/components/DependenciesDeploymentWarning.svelte @@ -1,9 +1,8 @@ - + + {#if loading} +
+
+ Loading dependencies... +
+ {:else if isUnnamedDefault} + +
+

This will redeploy ALL existing {language} scripts and flows/apps that have {language} steps without explicit named dependencies!

+

Default (unnamed) dependencies are automatically used by any {language} runnable that doesn't specify a named dependency. Changing this affects your entire workspace.

+
+
+ + + Default Workspace Dependencies + +
+
+ {importedPath} +
+
+
+ {:else} +
- {#if loading} -
-
- Loading dependencies... + +
+
+ + Workspace Dependencies
+
+ {importedPath} +
+
+ + {#if dependencies.length === 0} + + {#snippet children()} +

No dependent runnables were found for these workspace dependencies, but the action will still proceed.

+ {/snippet} +
{:else} - -
-
- - Workspace Dependencies -
-
- {importedPath} -
+ +
+
- {#if dependencies.length === 0} - - {#snippet children()} -

No dependent runnables were found for these workspace dependencies, but the action will still proceed.

- {/snippet} -
- {:else} - -
- -
- - -
-

- This action will trigger redeployment of {getTotalDependentsCount()} - {getTotalDependentsCount() === 1 ? 'dependent runnable' : 'dependent runnables'}: -

-
- - -
- {#each dependencies as dependency} - {@render DependencyNode({ node: dependency, level: 0 })} - {/each} -
- {/if} + +
+

+ This action will trigger redeployment of {getTotalDependentsCount()} + {getTotalDependentsCount() === 1 ? 'dependent runnable' : 'dependent runnables'}: +

+
+ +
+ {#each dependencies as dependency} + {@render DependencyNode({ node: dependency, level: 0 })} + {/each} +
{/if}
- - - - - + {/if} + {#snippet DependencyNode({ node, level }: { node: DependencyNode, level: number })} {@const Icon = getIcon(node.kind)} diff --git a/frontend/src/lib/components/WorkspaceDependenciesEditor.svelte b/frontend/src/lib/components/WorkspaceDependenciesEditor.svelte index 86e1bba5d6..07af92fb1d 100644 --- a/frontend/src/lib/components/WorkspaceDependenciesEditor.svelte +++ b/frontend/src/lib/components/WorkspaceDependenciesEditor.svelte @@ -1,6 +1,6 @@