Compare commits

..

155 Commits

Author SHA1 Message Date
centdix
71e08c239b chore: add deliberate type error to fail frontend CI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 20:27:21 +01:00
centdix
ee206b8594 chore: add test.txt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 20:24:31 +01:00
centdix
88696ec29e internal: wmdev config (#8096)
* use gemini

* linked repos
2026-02-25 18:47:59 +00:00
hugocasa
c7c828b56e feat: add resume and cancel button text options to Slack approval API + formatted args + typo (#8095) 2026-02-25 17:28:06 +00:00
wendrul
935b0058e2 feat: show triggers in fork deploy to parent UI. (#8094)
* Add rudimentary trigger display in WorkspaceCOmparison

* Trigger display

* Add email specification

* Add link to trigger page

* fix typo

* fix frontend check
2026-02-25 17:23:38 +00:00
Guilhem
1c9ac97f87 fix: correct asset node x offset inside loops and branches (#8093)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 15:48:09 +00:00
Diego Imbert
8e7ba9b33d feat: Data table as pg resource / trigger (#8088)
* Enable running pg scripts with datatable database input

* Postgres triggers for data tables

* REPLICATION attribute on custom_instance_user

* disable edit for datatables

* Update backend/windmill-trigger-postgres/src/replication_message.rs

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-02-25 15:06:12 +00:00
HugoCasa
f4e9603f3e workmux better ee cleanup + cursor wrapper autocompletion and open-ee 2026-02-25 15:44:42 +01:00
wendrul
7ac93f6ee3 feat: option to preserve on_behalf_of and edited_by for admins and users in the new wm_deployers group (#8079) 2026-02-25 12:05:22 +00:00
Diego Imbert
6943bb6a7f Register sub components in aggrid modal (#8087) 2026-02-25 11:04:38 +00:00
Ruben Fiszel
bc672555a7 fix: delete non-session tokens on workspace archive and reject token creation for archived workspaces (#8082)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 09:04:05 +00:00
hugocasa
5730009404 fix(backend): pass parent_path for trigger renames in git sync (#8059)
* fix(backend): pass parent_path for trigger renames in git sync

When renaming/moving a trigger path, the old path was not included in
the deployment metadata, so git sync never deleted the old file. This
adds parent_path to all 9 trigger DeployedObject variants and computes
it in update_trigger when the path changes.

Fixes #8014

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix path change with common prefix issue

* update ref

* chore: update ee-repo-ref to cb25312072c15c0e9cc375ebc824d41995a52898

This commit updates the EE repository reference after PR #428 was merged in windmill-ee-private.

Previous ee-repo-ref: 7225f7423311f58015a2fab61248c9d89888aef6

New ee-repo-ref: cb25312072c15c0e9cc375ebc824d41995a52898

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-02-25 09:01:59 +00:00
HugoCasa
328a52bca4 expose flow recording replay types 2026-02-25 09:41:34 +01:00
centdix
a482a3fac1 internal: nit workmux sandbox 2026-02-24 19:57:07 +00:00
Roderik-WU
ecf099436b Clarrify documentation in the tooltip for flow for/while loop error handling when "Skip failures" enabled (#8077)
* Update FlowLoop.svelte

Made it clear that only flow level error handlers are triggerd for a failure inside a flow loop with "skip failures" enabled.

* Update FlowWhileLoop.svelte
2026-02-24 19:56:22 +00:00
hugocasa
ff583bfb44 add flow recording and offline replay (#8080)
Add the ability to record a flow test execution and replay it offline
without any API calls. This is useful for debugging, sharing, and
reviewing flow executions outside of a running Windmill instance.

Recording:
- "Test flow & record" option in the flow editor three-dots menu
  opens the test drawer in recording mode
- While in recording mode, running a test captures all job events
  (SSE streams, sub-job completions, flow status transitions) along
  with the flow definition into a downloadable JSON file
- Recording state module (flowRecording.svelte.ts) manages active
  recording/replay instances at the module level

Replay:
- Standalone /replay page where users upload a recording JSON file
  and watch the flow execute with real-time status transitions
- FlowRecordingReplay component handles timestamp rebasing, event
  ordering fixes, and drives FlowStatusViewer with recorded data
- JobLoader intercepts replay mode to feed recorded events via
  timed callbacks instead of real SSE/polling
- FlowStatusViewerInner and FlowLogViewer guard all API call sites
  to prevent network requests during replay
- Job links, log downloads, and resource lookups are suppressed
  in replay mode

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 19:55:20 +00:00
hugocasa
c0d136658f Improve workmux dev workflow scripts and docs (#8078)
- Add CARGO_FEATURES passthrough: backend pane reads from .env.local,
  wm-cursor supports --features flag on add/open commands
- Fix node_modules copy in worktrees: use cp -a to preserve .bin/
  symlinks that cp -r would dereference (fixes openapi-ts errors)
- Fix EE repo discovery from worktrees: resolve main repo root via
  git-common-dir, search multiple candidate paths
- Add cursor session cleanup to worktree-cleanup (pre_remove hook)
- Use workmux -b flag in wmc add, remove npm install from frontend pane
- Change openBrowserOnce for Cursor port forwarding
- Document cargo features usage and fix stale files.symlink reference
  in README

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 16:34:00 +00:00
centdix
71acd88f2a internal: workmux (#8072)
* config

* nit

* add wmdev config

* remove playwright mcp

* add asciicinema

* custom image

* mistake
2026-02-24 15:33:37 +00:00
Ruben Fiszel
0a06485f51 chore(main): release 1.644.0 (#8068)
* chore(main): release 1.644.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-24 09:35:33 +00:00
Ruben Fiszel
27571457a1 sqlx 2026-02-24 09:32:30 +00:00
Ruben Fiszel
d4e711e337 add x-go-name to resolve duplicate JobTriggerKind typename in Go codegen (#8071)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-24 09:26:44 +00:00
Ruben Fiszel
55c172cc59 add schedule and future jobs icon toggles to runs page (#8070) 2026-02-24 09:24:02 +00:00
Ruben Fiszel
d883f647ed nit workmux 2026-02-24 09:10:16 +00:00
Ruben Fiszel
6a7811bdd0 nit workmux 2026-02-24 09:07:22 +00:00
Ruben Fiszel
8ff2340c0c fix: prevent concurrent index migrations from re-running on every startup (#8069)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 09:00:48 +00:00
centdix
835db5d290 feat(cli): detect missing folders on sync push and add 'wmill folder add-missing' (#8011)
* fix: auto-create missing folders during sync push for non-admin users

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: show missing folders in sync push summary before confirmation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: improve sync push folder auto-creation error handling and json output

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: only treat 404 as missing folder in getFolder check

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: remove obsolete Deno compatibility layer from yaml-validator

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore(cli): add @types/bun dev dependency

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(cli): replace auto-create folders with `wmill folder add-missing` command

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(cli): improve folder commands with summary field and simpler push API

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(cli): add confirmation prompt to folder add-missing command

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(cli): simplify missing folder check to use local stat instead of remote API

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* update skills

* feat(cli): warn admins but block non-admins on missing folder.meta.yaml

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* cleaning

* cleaning

* test(cli): add tests for missing folder detection and folder commands

- Add tests for `folder new`, `folder push`, `folder add-missing` commands
- Add tests for sync push missing folder.meta.yaml detection (admin warning, non-admin block)
- Fix getBasePostgresUrl to strip query params (e.g. ?sslmode=disable) from DATABASE_URL
- Add createNonAdminUser and runCLIWithToken test utilities to test_backend.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(cli): unify runCLICommand with optional token parameter

Replace separate runCLIWithToken utility with an optional { workspace?, token? }
options object on the existing runCLICommand across all backends.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* own workspace

* test(cli): isolate folder_missing_meta tests with per-test workspace

* test(cli): shorten isolated workspace id/name for workspace limits

* test(cli): archive temp isolated workspaces after each folder test

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-24 08:38:17 +00:00
Ruben Fiszel
b59d60378c chore(main): release 1.643.0 (#8053)
* chore(main): release 1.643.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-24 08:33:56 +00:00
Ruben Fiszel
8869fde737 ci improvement 2026-02-24 08:33:42 +00:00
Ruben Fiszel
90a6db72a2 disable Reset Code and Apply Changes buttons when no YAML changes (#8067)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-24 08:12:19 +00:00
Ruben Fiszel
3aba0ed250 fix: use correct column name completed_at instead of ended_at in count_completed_jobs_detail (#8066)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-24 08:03:52 +00:00
centdix
207dcdb4f7 internal: workmux config (#8065)
* config

* nit

* add wmdev config

* remove playwright mcp

* add asciicinema
2026-02-24 07:09:49 +00:00
Ruben Fiszel
b97216cf37 adapt hub pull to is_fileset from hub 2026-02-24 06:58:20 +00:00
Ruben Fiszel
b3ac0249de Merge main into fileset-resource-type 2026-02-24 06:43:32 +00:00
Ruben Fiszel
9ac07897cf fix: fileset editor takes full height with matching header
Set fileset editor container to h-[60vh] so both navbar and editor
fill available space. Match editor filename bar height to navbar header.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 06:42:07 +00:00
Ruben Fiszel
c15b9abe5e feat: add fileset resource type support (#8063)
* feat: add fileset resource type support

Add a new "fileset" resource type that represents a collection of files
stored as a relpath→content map. This enables resource types to manage
multiple files (e.g., config directories, template sets) instead of just
a single file.

Backend:
- Add is_fileset column to resource_type table
- Update CRUD operations and workspace duplication to handle is_fileset
- Add integration tests for fileset resource types

Frontend:
- Add FilesetEditor component with file explorer + Monaco editor
- Extract shared FileExplorer component from RawAppSidebar (dedup)
- Add fileset toggle to EditableSchemaWrapper
- Show fileset editor in ResourceEditor and ApiConnectForm
- Show folder icon for fileset resource types in IconedResourceType

CLI:
- Support fileset resources in sync pull (expand to .fileset/ directory)
- Support fileset resources in sync push (reconstruct from directory)
- Handle !inline_fileset YAML tag in resource resolution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* sqlx

* fix: resolve svelte warnings and type error in fileset components

- Fix state_referenced_locally warnings in FilesetEditor by computing
  initial values before creating $state
- Fix Promise<boolean> type error in +page.svelte by making
  resourceNameIsFileset/resourceNameToFileExt synchronous lookups
  with eager map loading

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address code review findings for fileset feature

- Use sqlb.set() instead of set_str() for boolean is_fileset field
  to avoid quoting (SET is_fileset = TRUE not 'TRUE')
- Add JSDoc comment to isFilesetResource explaining it matches
  children inside .fileset/ directories, not the directory itself
- Update OpenAPI spec for file_resource_type_to_file_ext_map endpoint
  to document the new response schema with format_extension and
  is_fileset fields

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address second round of review findings

- Remove bidirectional $effect sync in RawAppSidebar; bind FileExplorer
  directly to files prop with {} default
- Avoid creating new files object on every keystroke in FilesetEditor;
  merge editContent → args in a single effect without intermediate spread
- Simplify no-op `?? undefined` in addResourceType
- Add backend validation: reject create_resource_type when both
  is_fileset and format_extension are set
- Fix fileset alert title showing undefined format extension

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: exclude app_theme resources from workspace tab

Theme resources (app_theme) were showing on the workspace tab alongside
regular resources. Now they are excluded from the workspace tab
(like cache and state) and the theme tab loads only app_theme resources.

Also includes review fixes:
- Remove bidirectional $effect sync in RawAppSidebar
- Avoid spreading new files object on every keystroke in FilesetEditor
- Simplify ?? undefined no-op
- Add backend validation for is_fileset + format_extension conflict
- Fix fileset alert title

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restore full-width file tree items in raw app sidebar

FileExplorer's tree container was missing w-full, causing items to not
stretch inside PanelSection's items-start flex container.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent iframe from overriding file selection after file creation

When files change in the sidebar, setFilesInIframe sends the new files
to the iframe which responds with setActiveDocument defaulting to
App.tsx, overriding the user's selection. Now we ignore setActiveDocument
messages for 500ms after sending setFiles to the iframe.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Revert "fix: prevent iframe from overriding file selection after file creation"

This reverts commit 7f3ddd7edd.

* fix: suppress iframe setActiveDocument during file population

Use setFilesAndSelectInIframe in populateFiles to keep the current
document selected when re-sending files. Suppress setActiveDocument
for 500ms after population to prevent the iframe from defaulting
back to App.tsx on focus changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 06:37:55 +00:00
Ruben Fiszel
1abfeea81a fix: suppress iframe setActiveDocument during file population
Use setFilesAndSelectInIframe in populateFiles to keep the current
document selected when re-sending files. Suppress setActiveDocument
for 500ms after population to prevent the iframe from defaulting
back to App.tsx on focus changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 06:26:28 +00:00
Ruben Fiszel
97c163bb33 Revert "fix: prevent iframe from overriding file selection after file creation"
This reverts commit 7f3ddd7edd.
2026-02-23 23:07:51 +00:00
Ruben Fiszel
7f3ddd7edd fix: prevent iframe from overriding file selection after file creation
When files change in the sidebar, setFilesInIframe sends the new files
to the iframe which responds with setActiveDocument defaulting to
App.tsx, overriding the user's selection. Now we ignore setActiveDocument
messages for 500ms after sending setFiles to the iframe.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 22:54:03 +00:00
Ruben Fiszel
5bac8b093d fix: restore full-width file tree items in raw app sidebar
FileExplorer's tree container was missing w-full, causing items to not
stretch inside PanelSection's items-start flex container.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 22:50:35 +00:00
Ruben Fiszel
9c513b2c62 fix: exclude app_theme resources from workspace tab
Theme resources (app_theme) were showing on the workspace tab alongside
regular resources. Now they are excluded from the workspace tab
(like cache and state) and the theme tab loads only app_theme resources.

Also includes review fixes:
- Remove bidirectional $effect sync in RawAppSidebar
- Avoid spreading new files object on every keystroke in FilesetEditor
- Simplify ?? undefined no-op
- Add backend validation for is_fileset + format_extension conflict
- Fix fileset alert title

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 22:49:24 +00:00
Ruben Fiszel
753c05a030 fix: address second round of review findings
- Remove bidirectional $effect sync in RawAppSidebar; bind FileExplorer
  directly to files prop with {} default
- Avoid creating new files object on every keystroke in FilesetEditor;
  merge editContent → args in a single effect without intermediate spread
- Simplify no-op `?? undefined` in addResourceType
- Add backend validation: reject create_resource_type when both
  is_fileset and format_extension are set
- Fix fileset alert title showing undefined format extension

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 22:32:25 +00:00
Ruben Fiszel
1b4489acac fix: address code review findings for fileset feature
- Use sqlb.set() instead of set_str() for boolean is_fileset field
  to avoid quoting (SET is_fileset = TRUE not 'TRUE')
- Add JSDoc comment to isFilesetResource explaining it matches
  children inside .fileset/ directories, not the directory itself
- Update OpenAPI spec for file_resource_type_to_file_ext_map endpoint
  to document the new response schema with format_extension and
  is_fileset fields

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 21:52:19 +00:00
Diego Imbert
302fea683c Load more button (#8064) 2026-02-23 21:50:05 +00:00
Ruben Fiszel
4c06d74bd0 fix: resolve svelte warnings and type error in fileset components
- Fix state_referenced_locally warnings in FilesetEditor by computing
  initial values before creating $state
- Fix Promise<boolean> type error in +page.svelte by making
  resourceNameIsFileset/resourceNameToFileExt synchronous lookups
  with eager map loading

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 19:25:06 +00:00
Ruben Fiszel
680cac7084 Merge branch 'main' into fileset-resource-type 2026-02-23 19:18:32 +00:00
Ruben Fiszel
cee3198c9b sqlx 2026-02-23 19:16:36 +00:00
Diego Imbert
9b28c85469 feat: Unified filters and new runs page (#8027)
* RunsPage redesign v0

* nit

* Remove manualdatepicker

* remove shadow

* ui nits

* nit scrollbar bg

* prettier cards

* nit

* Remove code

* command/meta multi select

* Shift select

* RightClickPopover

* nit

* Ctrl A

* nit card

* DropdownMenu

* nit

* count hint

* fix stuck keys

* opacity UX

* error toasts pickhubscript

* Improve UX

* fix undefined error

* keyboard nav

* nit batch rerun fixes

* nit fix scroll / height

* Batch reruns actions + nits

* nit

* Cancel selected jobs

* Cancel / re-run all filtered jobs

* Go to job / flow / script action

* nit

* add batch actions back

* nit

* nit

* bar on splitpane hover

* nit

* New Timeframe system

* reset btn

* nit fixes

* dead code

* nits

* typecheck

* naming clarity

* Update frontend/src/lib/components/RightClickPopover.svelte

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* unnecessary json stringify

* dedup 'the'

* Code deletion to prepare for changes

* filter types

* ui

* fix bug with maxTs

* stuck with melt

* GenericDropdown

* filters onclick

* iterate

* iter

* add all filters

* Descriptions

* focus position

* stash

* TaggedTextInput works much much better

* placeholder

* currentTag suggestion

* improve

* nit

* Keyboard nav

* buildRunsFilterSearchbarSchema

* nit naming

* assignObjInPlace

* Escaping + pretty dates

* nit empty

* fix cursor

* nit space

* Filter filtering

* escape pasted value

* nit

* escape spaces

* nit undefined

* add space at end if right arrow

* escape all spaces

* arrow skips escape chars

* escape \ too

* delete whole escaped characters

* double space to escape tag

* code refactor

* Ensure cursor visible

* fix keyboard nav

* safety

* filterSchemaRecToZodSchema

* URL Sync

* fix readonly

* fix typing

* start replacing old filter logic

* use new filter impl

* nit

* nit reactivity

* nit fix

* no more localStorage

* Add back status and kind toggles

* Nit fix

* style nit

* focus at end on click

* clearn btn + fixes

* fix broken date uri

* nit

* useSyncedTimeframe

* negative filter button

* negative filters helpers rust

* Negated filters backed

* nit

* highlight

* New useSearchParams

* Accept comma separated list

* nit allowNegative

* openapi update

* Fix trigger kind list/negation not working

* nit oipenpai

* Presets

* DebouncedTempValue

* remove presets from list when already applied

* UI nit improvements

* allowMultiple

* hint

* validateFilterInstance fn

* nit fix

* error highlights

* nit ux selecting negative list

* nit

* on clear btn

* SimpleEditor for JSON

* nit

* flop

* Pass presets as param

* nit delete

* preventCursorMoveOnNextSync

* responsive layout

* Escape \n

* Inline calendar input

* mm/dd or dd/mm depending on US or not

* onClickBehavior

* infiniteRange

* other nits

* Wiring with runs filter

* formatDateRange better

* inits on right page

* style

* min hour support

* Time input

* use our components

* Improve SKILL.md

* dd mm yyyy numeric input

* TimeframeSelect with new date picker

* fixes

* ensure date is in view when value changes externally

* fixes

* nit select all on focus

* select year + nits

* nit layout shift

* nit negative when starting with !

* nit

* SelectDropdown uses GenericDropdown now

* Fix blank select dropdown rendering bug

* icons

* Reset btn + shorter date range formatting

* overflow fix

* unnecessary absolute

* fix clear btn overlap

* Update routes for new filters (assets, schedule, resource, variables)

* update openapi

* Impl for other pages

* ui nits

* nit fixes

* Fix columns filter

* super nits

---------

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-02-23 17:53:09 +00:00
Ruben Fiszel
32c4b474f9 feat: add fileset resource type support
Add a new "fileset" resource type that represents a collection of files
stored as a relpath→content map. This enables resource types to manage
multiple files (e.g., config directories, template sets) instead of just
a single file.

Backend:
- Add is_fileset column to resource_type table
- Update CRUD operations and workspace duplication to handle is_fileset
- Add integration tests for fileset resource types

Frontend:
- Add FilesetEditor component with file explorer + Monaco editor
- Extract shared FileExplorer component from RawAppSidebar (dedup)
- Add fileset toggle to EditableSchemaWrapper
- Show fileset editor in ResourceEditor and ApiConnectForm
- Show folder icon for fileset resource types in IconedResourceType

CLI:
- Support fileset resources in sync pull (expand to .fileset/ directory)
- Support fileset resources in sync push (reconstruct from directory)
- Handle !inline_fileset YAML tag in resource resolution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 17:50:37 +00:00
Guilhem
6ba0da3ee5 truncate long summary and path in SummaryPathDisplay (#8062)
* fix: truncate long summary and path in SummaryPathDisplay

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: allow summary to shrink in flow editor top bar

Add min-w-0 to the SummaryPathDisplay wrapper in FlowBuilder so flex
shrinking works. Remove max-w cap on the wrapper so the summary can use
all available space. Remove w-full and max-w-md from the empty middle
schedule div that was stealing space even when empty.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-23 17:42:50 +00:00
centdix
de6fd160d5 feat(aiagent): handle ai agent as tool (#8031)
* worker: support AIAgent tools in AI executor

* worker: complete nested AIAgent tool execution path

* worker: inline AIAgent tool schema usage

* fix agent action

* frontend: add AI Agent as tool type in flow builder

Add the ability to insert a nested AI Agent as a tool within another
AI Agent step. Includes type definitions, factory function, graph icon,
insert/event wiring, and a dedicated editor component.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: remove AiAgentToolEditor, reuse FlowModuleComponent for AI agent tools

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: populate all input transforms for nested AI agent tools

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: avoid missing v2_job_status error for nested AI agent tools

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* sqlx

* nit

* refactor: cleanup nested AI agent tool implementation

- Add max nesting depth guard (5) on parent chain traversal
- Reject 3+ level nesting explicitly with clear error message
- Remove unnecessary flow_step_id tuple scaffolding in tool dispatch
- Consolidate get_value() calls using borrow in first match
- Replace unsafe `as unknown as FlowModule` casts with agentToolToFlowModule()
- Simplify toolKind ternary chain with .includes() lookup
- Fix leftover over-indentation from tuple removal
- Remove duplicate doc comment on is_completed_input_transform

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: pass flow_step_id and flow_job_id overrides to run_agent for nested AI agents

For nested AI agent tools, job.flow_step_id is None and job.parent_job
points to the parent agent instead of the flow. This caused memory
read/write and flow context resolution to silently fail.

handle_ai_agent_job already computes the correct flow_step_id (via
runnable_path fallback) and flow_job_id (via parent chain traversal).
This change threads those values through run_agent and
ToolExecutionContext so all downstream consumers use the correct IDs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* cleaning

* cleaning

* move const

* fix

* refactor: replace defaultToAi boolean with allowedAiTransforms whitelist

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: propagate root_job at push time, remove flow_job_id_override

Instead of threading flow_job_id_override through run_agent and
get_flow_context, propagate root_job and flow_innermost_root_job
when pushing tool jobs so nested AI agents can find the flow
job naturally via the existing job fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: simplify nested AI agent parent chain walk-up

Replace the generic depth-limited loop with a single-level check since
only flow → agent → nested agent tool is supported. Remove
MAX_AGENT_NESTING_DEPTH constant and flatten the module lookup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: reject 3+ level nested AI agent tools before job creation

Check at the parent agent level whether a nested AIAgent tool contains
AIAgent sub-tools. If so, return a fatal error immediately, preventing
the sub-job from being created and avoiding retry loops.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: resolve deadlock in nested AI agent tool execution

Replace channel forwarding with inline DB writes for tool job
completions. Nested agents used bounded(1) channels where a sub-tool's
forwarded result would fill the parent channel, leaving no room for the
agent's own completion — causing a deadlock. Writing directly via
add_completed_job/add_completed_job_error bypasses the channel entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 15:59:39 +00:00
hugocasa
705e186f3d fix: update git sync init script to hub version 28158 (#8061) 2026-02-23 15:47:02 +00:00
Guilhem
0935bf9fc4 feat: add light mode for navigation sidebar (#8057)
* feat: add light mode support for navigation sidebar

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: use custom selected background for sidebar items

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: darken sidebar section separators in light mode

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: lighten sidebar bg to #F3F3F7 and use border-light for separators

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: use text-secondary for sidebar labels and text-hint for icons

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: extract dark sidebar background color to constant

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 15:25:46 +00:00
HugoCasa
26270d8cd1 port forward nit workmux cursor 2026-02-23 16:23:37 +01:00
hugocasa
9a7a0135f7 Cursor SSH remote integration for workmux worktrees (#8060)
Add wm-cursor (wmc) script that bridges workmux with Cursor SSH remote,
giving each worktree its own Cursor window with an independently-focused
grouped tmux session.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 14:46:32 +00:00
Guilhem
0604600b8b autofocus summary input when opening SummaryPathDisplay popover (#8052)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 13:48:34 +00:00
hugocasa
0aa885db67 fix(backend): use filename instead of content_type to detect file fields in multipart form data (#8054)
String fields with an explicit Content-Type (e.g. text/plain) were
incorrectly treated as file uploads and sent to S3. Per RFC 7578, the
presence of a filename parameter is what distinguishes file fields from
regular form fields.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:05:29 +00:00
hugocasa
9686608355 fix(backend): decimal between 0 and -1 in mssql (#8051) 2026-02-23 09:38:22 +00:00
Ruben Fiszel
f0b7c96d04 cli zsh completions nit 2026-02-23 09:09:16 +00:00
Ruben Fiszel
b60f309a0c chore(main): release 1.642.0 (#8046)
* chore(main): release 1.642.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-23 07:46:35 +00:00
Ruben Fiszel
a00927b300 fix: preserve debouncing settings for flows with preprocessors (#8043)
* fix: preserve debouncing settings for flows with preprocessors

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Revert "fix: preserve debouncing settings for flows with preprocessors"

This reverts commit 3452c1657c.

* feat: add post-preprocessing debounce for flows with preprocessors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* perf: reuse caller tx for push-time debounce and add stress test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add exhaustive edge case tests for debouncing behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* perf: optimize debouncing to reduce DB round-trips

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: replace legacy debounce compat with error logging

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test: add debounce args accumulation tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test: add end-to-end test for maybe_apply_debouncing arg accumulation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: update sqlx offline query cache

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: make workmux pane commands idempotent for replay

Use git rev-parse --show-toplevel to resolve absolute paths instead of
relative cd, so commands work when replayed from within backend/frontend.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add e2e debounce test script for backend API

Comprehensive end-to-end test covering:
- Deploy & run scripts rapidly (no debounce with different args)
- Redeploy without lock in rapid succession
- Debounce with same args (should consolidate)
- Debounce with different args (should not consolidate)
- Custom debounce key behavior
- Git sync debounce + item aggregation (using glob-style ** path filter)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update ee-repo-ref to 0fede4b1086bc1456be9cc55b203228c979c5c5e

This commit updates the EE repository reference after PR #426 was merged in windmill-ee-private.

Previous ee-repo-ref: b5d333370603a6cc7ef70842354cf3be734241b4

New ee-repo-ref: 0fede4b1086bc1456be9cc55b203228c979c5c5e

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-02-22 14:16:52 +00:00
Ruben Fiszel
3c89c28e71 chore: fix flaky agent token test by not splitting on underscore (#8048)
Base64url encoding uses '_' as a valid character, so splitting the JWT
token on '_' would intermittently break the JWT parsing when the encoded
payload or signature contained underscores. Strip the known prefix instead.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 09:20:55 +01:00
Ruben Fiszel
4fedfdfd11 feat(cli): add consistent get/list/new subcommands for all item types (#8047)
* feat(cli): add consistent get/list/new subcommands for all item types

Make the CLI consistent so every item type (script, flow, app, resource,
resource-type, variable, schedule, folder, trigger) supports get/list/new
subcommands, enabling the CLI to be used as a full API client in bash
scripts with jq piping.

- Add --json flag to all list commands for machine-readable output
- Register explicit "list" subcommand alongside default action
- Add "get <path> [--json]" subcommand to fetch single items from API
- Rename "bootstrap" to "new" for script/flow, keep "bootstrap" as alias
- Add "new" subcommand for resource, resource-type, variable, schedule,
  folder, and trigger to create local template YAML files
- Update cli-commands skill documentation for wmill init
- Add integration tests for all new commands

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* all

* feat: install wmill CLI in Docker images and use it for bash variable/resource access

- Install windmill-cli via bun in all Dockerfiles that include bun
- DockerfileCli: switch from node:slim to oven/bun:slim
- CLI: auto-configure from WM_WORKSPACE/WM_TOKEN/BASE_INTERNAL_URL env vars
  as last-resort fallback when no workspace is configured
- Frontend: replace curl-based bash snippets with wmill variable/resource get
- Add backend integration tests for wmill CLI in bash scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): install windmill-cli in backend test workflow

Ensures wmill is available on PATH for bash integration tests
that use `wmill variable get` and `wmill resource get`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(cli): replace @std/* Deno dependencies with Node.js equivalents

Replace @std/log with a lightweight custom logger (core/log.ts),
@std/path with node:path, and @std/yaml with the yaml npm package.
Also fix process hang on exit, add --node option to install_dev.sh,
and add missing hasRequiredPermissions to NpmProvider.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* all

* all

* all

* refactor(cli): replace @ayonli/jsext and @std/encoding with lightweight alternatives

Replace @ayonli/jsext (8.4MB) with tar-stream (32kB) for tar creation,
replace @std/encoding with Node.js Buffer.toString("hex"), and fix
@windmill-labs/shared-utils to use direct npm instead of JSR mirror.
Also resolve merge conflicts in sync.ts and fix pre-existing type errors.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(cli): use singleQuote YAML output and pass yamlOptions in gitsync pull

The yaml library defaults to double quotes, but the codebase (and tests)
expect single-quoted strings. Add singleQuote: true to yamlOptions and
pass yamlOptions to gitsync-settings pull writeFile calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* all

* all

* fix(cli): address code review feedback

- Install CLI from source in backend tests instead of npm
- Fix script bootstrap catch block to re-throw "File already exists"
- Add type-safe local variable after trigger kind validation
- Use created_by instead of policy.on_behalf_of for app get output
- Note --kind is recommended for faster trigger lookup in help text
- Document node symlink purpose in Dockerfiles

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): use /usr/bin for wmill wrapper to ensure it's in PATH

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(ci): install wmill to ~/.local/bin to avoid permission issues

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci(backend): switch to Blacksmith runner and add cargo caching

- Switch from ubicloud-standard-16 to blacksmith-16vcpu-ubuntu-2404 for faster NVMe-backed builds
- Add stickydisk for cargo target directory (persistent NVMe cache across runs)
- Add cache for cargo registry and git dependencies
- Upgrade DuckDB FFI cache from actions/cache@v3 to useblacksmith/cache@v1
- Enable CARGO_INCREMENTAL=1 to benefit from persistent target cache

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix ci

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 07:53:28 +00:00
Ruben Fiszel
a91c532eca fix: make WM_FLOW_PATH available in flow step previews (#8042)
* fix: pass flow path in flow step preview for AI agent modules

JobLoader.runFlowPreview was missing the path parameter, causing
WM_FLOW_PATH to be unavailable when using the Run button on individual
flow steps. Test up to here worked correctly because it uses a
different code path (utils.svelte.ts) that already passed the path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: make WM_FLOW_PATH available for rawscript/script step previews

Inject the flow path as `_flow_path` in the job args when running a
script preview from the flow editor. The SQL pull queries now use
COALESCE to fall back to this arg when no parent runnable path exists,
making WM_FLOW_PATH available for individual step "Run" previews.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: rename _flow_path args key to _FLOW_PATH

Match existing convention used by _ENTRYPOINT_OVERRIDE.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 21:56:32 +00:00
Ruben Fiszel
18b3c1ae5c nit install dev 2026-02-21 21:44:41 +00:00
Ruben Fiszel
a2cefdf0a2 refactor(cli): migrate CLI from Deno to Bun/Node.js (#8041)
* fix: only enable EE features in test backend when license key is available

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: skip EE tests without license key and exclude test-skills from test discovery

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: unskip passing tests and add duplicate (remote, workspaceId) check in addWorkspace

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(cli): migrate from Deno APIs to Node.js/Bun-compatible APIs

Replace Deno-specific APIs with Node.js equivalents across the entire CLI
codebase to enable running on Node.js/Bun. Switch build system from dnt
to bun, update imports from jsr:/npm: prefixed to bare specifiers, and
add package.json/tsconfig.json for the Node.js ecosystem.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* all

* test(cli): expand test coverage with new integration and unit tests

Add standalone_commands.test.ts covering folder list, schedule list,
resource-type list/push/update, script show/run/bootstrap, and user
commands. Add unit tests for filePathExtensionFromContentType and
removeExtensionToPath. Add git_unit, local_encryption_unit,
resource_folders_unit, and settings_unit test files. Fix schedule
cron expressions (6-field format), add includeSchedules flag, improve
test setup with pre-build and auto-cleanup, and support TEST_CLI_RUNTIME=node.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(cli): replace Deno.readFile with node:fs in WASM loaders and add schema parsing tests

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(cli): switch WASM parsers from local files to npm packages

Use published windmill-parser-wasm-* npm packages instead of local
wasm/ files. A loadParser() helper uses createRequire to resolve the
.wasm binary from node_modules and passes it to init() via
readFileSync, avoiding fetch() and Deno.readFile() patches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test(cli): add coverage for --locks-required lint feature

Add 15 tests covering the lock-checking functionality merged from main:
- checkMissingLocks: standalone scripts (python, bun, bash), inline
  lock file resolution (valid, empty, missing), flow inline rawscripts
  (with/without locks, nested forloopflow), app inline scripts, raw
  apps without backend folder
- runLint --locks-required integration: reports issues when locks
  missing, skips checks when flag absent, passes when locks exist

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci(cli): replace Deno with Bun in CI workflows

- cli-tests.yml: remove Deno setup, use `bun test` instead of
  `deno test`, add `bun install` step for dependency installation
- npm_on_release.yml: replace Deno setup with Bun setup for CLI
  publishing
- build.sh: add `bun install` before building so CI has dependencies

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(cli): pre-start backend in test preload and remove Deno test leftovers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(cli): normalize path separators for Windows compatibility

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* more tests + windows

* ci(cli): use Blacksmith runner for Windows tests

Switch test-windows job from windows-latest to blacksmith-16vcpu-windows-2025
for faster CI execution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(cli): fix Windows path separator expectations in unit tests

buildMetadataPath and extractResourceName normalize to forward slashes
internally, so tests should not expect platform-specific separators in
their output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(cli): fix Windows CI test failures for dev_server and script_run

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(cli): set BUN_PATH and NODE_BIN_PATH for backend worker on Windows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ci(cli): add SSH debug step on Windows test failure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(cli): use native path separators for ignore check in dev mode on Windows

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 21:19:04 +00:00
Ruben Fiszel
c4de11a406 chore(main): release 1.641.0 (#8040)
* chore(main): release 1.641.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-21 22:04:35 +01:00
Ruben Fiszel
fd5ebc2fda fix: tag bunnative dependency jobs as bun instead of nativets (#8045)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-21 20:59:48 +00:00
Ruben Fiszel
0d3f956e74 workmux nits 2026-02-21 12:07:22 +00:00
Ruben Fiszel
b330f38889 fix: run substitute_ee_code.sh after creating EE worktree
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 11:37:33 +00:00
Ruben Fiszel
9eb15312f6 feat: add .npmrc support for private npm registries (#8039)
* feat: add .npmrc support for private npm registries

Add a new `npmrc` instance setting that accepts full .npmrc file content
for configuring private npm registries. Works with bun (native .npmrc
support since 1.1.18), deno (native .npmrc support in 2.x), and the npm
proxy (parses default registry + auth token from .npmrc).

Legacy `npm_config_registry` and `bunfig_install_scopes` fields are now
hidden when empty, so new users only see the .npmrc field. Also fixes a
pre-existing race condition where gen_bunfig was called after
start_child_process.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* all

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 05:29:36 +00:00
Ruben Fiszel
e8a13edde7 fix: add created_by ownership check to update/delete saved inputs (#8038)
* fix: add created_by ownership check to update/delete saved inputs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* all

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 04:50:24 +00:00
Ruben Fiszel
d772083573 chore(main): release 1.640.0 (#8013)
* chore(main): release 1.640.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-20 20:44:52 +00:00
Guilhem
ea38419353 add breadcrumb navigation to advanced setup mode (#8010)
* feat: add breadcrumb navigation to advanced setup mode on first-time page

The advanced setup mode on /user/first-time lacked a step indicator,
making navigation disorienting. This adds a 2-step breadcrumb
("Settings" / "Root login & Resource Types") with step-aware navigation
buttons and extracts the account setup UI into a reusable snippet shared
by both wizard and advanced modes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address review issues in advanced setup breadcrumb

- Gate resource type sync by mode to prevent early trigger
- Reset yamlMode when advancing to account setup step
- Allow forward navigation via breadcrumb click
- Use saveAndProceed on Back button for consistency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: show EE license key warning in advanced setup mode

Generalize proceedFromCore to trigger the license key warning when
leaving the settings step in both wizard (step 0) and full mode
(fullStep 0), including the Continue button and breadcrumb forward
navigation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: independent scroll for sidebar and content in advanced setup

Match the superadmin settings drawer pattern: the outer flex container
constrains height without scrolling, while the sidebar and content area
each have h-full overflow-auto for independent scrolling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* format

* fix: simplify breadcrumb onselect to only handle backward navigation

The Breadcrumb component disables forward buttons, so the proceedFromCore
branch was unreachable. Simplify to only handle i < fullStep.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 20:36:10 +00:00
Diego Imbert
87f3de9ae5 feat: Support column detection on S3 objects in DuckDB (#8018)
* Support column detection on S3 objects in DuckDB

* Compilation fix

* support direct s3 path without read_parquet()

* package update

* npm i
2026-02-20 20:34:53 +00:00
Diego Imbert
e3460aba89 Fix duckdb tests (#8035) 2026-02-20 17:34:23 +00:00
hugocasa
37c9acb232 feat: dedicated nativets (#8021)
* feat: dedicated nativets

* review nits

* prewarm isolates

* ref

* chore: update ee-repo-ref to 5f8105b808f3f0186fdf5132d2ee602d8a14aa17

This commit updates the EE repository reference after PR #424 was merged in windmill-ee-private.

Previous ee-repo-ref: b7906acabb8ce359230bbd3e30dbb3bba4c42adb

New ee-repo-ref: 5f8105b808f3f0186fdf5132d2ee602d8a14aa17

Automated by sync-ee-ref workflow.

---------

Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-02-20 16:28:40 +00:00
Ruben Fiszel
9f3dd0bf2b feat: add windmill-ee-private worktree support to workmux (#8034)
* feat: add windmill-ee-private worktree support to workmux

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add EE worktree cleanup on remove and parent-dir lookup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 17:01:49 +01:00
Diego Imbert
ba9960d8db Disable dynamic columnDefs for dbStudio (#8032) 2026-02-20 13:12:25 +00:00
Diego Imbert
f05b00aa8a Nit better test and comment (#8030) 2026-02-20 12:06:05 +00:00
Diego Imbert
ff6c49b43e Fix remove_comments with multi bytes (#8029)
* Fix remove_comments with multi bytes

* change names
2026-02-20 11:53:10 +00:00
Diego Imbert
90b1a7a531 fix: Fix DuckDB incorrect pg password encoding (#8028) 2026-02-20 10:35:27 +00:00
HugoCasa
795abccc19 ignore cf and ellipsis for discord 2026-02-20 09:29:37 +01:00
Ruben Fiszel
3e4cad5f70 exclude schedule-triggered jobs from stale job cancellation (#8025)
* fix: exclude schedule-triggered jobs from stale job cancellation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* all

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 06:49:48 +00:00
Ruben Fiszel
4abe589397 feat(cli): add --locks-required flag to wmill lint and sync push (#8026)
Add a --locks-required flag that fails if scripts or inline scripts
that need locks have no locks. Checks standalone scripts, flow inline
scripts, app inline scripts, and raw app backend scripts.

The flag can be set via CLI (--locks-required) or wmill.yaml config
(locksRequired: true). On sync push, verification runs before any
push operations to fail early.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 06:47:47 +00:00
Alexander Petric
adfd8b4df0 allow devops user to see workers page (#8023) 2026-02-20 05:53:18 +00:00
hugocasa
24d7921bcf fix(frontend): use completed_at instead of created_at for job history (#8022)
* fix(frontend): use completed_at instead of created_at for job history

* lol
2026-02-20 05:52:12 +00:00
hugocasa
ed87e1b08d remove embeds and mentions from PR thread comments 2026-02-19 17:28:55 +01:00
hugocasa
f3697f99d9 forward PR comments to Discord threads (#8020)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 16:24:05 +00:00
Ruben Fiszel
7a59e2b466 workmux nits 2026-02-19 15:31:16 +00:00
Diego Imbert
ad2f81a1bd Fix empty assets array showing up in diff (#8017) 2026-02-19 14:42:16 +01:00
Diego Imbert
e099a9e697 Force text cast on unordarable types (#8016) 2026-02-19 13:27:02 +00:00
Ruben Fiszel
7f8e7cb5f9 workmux setup 2026-02-19 12:09:48 +00:00
Ruben Fiszel
7052a36026 workmux setup 2026-02-19 12:00:51 +00:00
Ruben Fiszel
9ea9f36e03 chore: add workmux config for worktree-based development (#8015)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 10:56:41 +00:00
Ruben Fiszel
99018eca0d .gitignore nit 2026-02-19 09:37:11 +00:00
hugocasa
a1ba10a29e perf: lazy-load JSZip in RawAppEditorHeader (#8012)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 09:36:02 +00:00
Ruben Fiszel
dbec70aedd internal: instant npm run check 2026-02-19 09:33:33 +00:00
Ruben Fiszel
3bb58ebfd9 svelte check nits 2026-02-19 06:50:28 +00:00
Ruben Fiszel
0e23077b34 rhel fixes 2026-02-19 00:57:44 +00:00
Ruben Fiszel
43e74da292 rhel fixes 2026-02-18 22:53:02 +00:00
Guilhem
57ca7dbca0 improve instance settings drawer UX (#8002)
* fix(frontend): prevent false dirty state in instance settings on load

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(frontend): handle undefined python version in select binding

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(frontend): extract SaveButton component and improve drawer header UX

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(frontend): replace inline diff with diff drawer and simplify save flow

Save now saves immediately instead of requiring a two-step confirm flow.
Diff view opens in a separate drawer with split/unified toggle instead of
replacing the form content inline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(frontend): preserve dirty state when toggling YAML mode in instance settings

syncFormToYaml() was setting yamlCodeInitial to the current modified YAML,
causing hasUnsavedChanges to become false when entering YAML mode with
pending form changes. Build yamlCodeInitial from initialValues instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(frontend): clear dirty state after saving in YAML mode

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* reduce save button timeout

* feat(frontend): add review changes button to unsaved changes confirmation modal

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(frontend): address code review issues from PR #8002

Remove unnecessary IIFE wrappers in handleSave/handleSaveAndCloseDiff,
fix stale on:close reference on diff drawer, clip SaveButton overlay with
overflow-hidden, make DiffEditor respond reactively to inlineDiff prop
instead of using {#key} destroy/recreate, and revert normalizeValue
object check to original simpler behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(frontend): remove tab-switch confirmation modal in full settings mode

In full mode, the save button saves all settings across all categories,
so switching tabs cannot lose unsaved changes. Remove the per-category
dirty check, confirmation modal, and unused ConfirmationModal import.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(frontend): prevent SMTP toggles from creating false dirty state

Use getter/setter bind:checked so Toggle reads undefined as false
without writing it back to the store. This prevents visiting the SMTP
tab from mutating smtp_settings and triggering a false unsaved diff.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(frontend): prevent OTEL toggles from creating false dirty state

Same fix as SMTP toggles: use getter/setter bind:checked so Toggle
reads undefined as false without writing it back to the store.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor(frontend): use recursive normalizeValue for dirty state instead of per-component fixes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(frontend): replace save button with always-visible review changes button

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(frontend): address PR review comments on DiffEditor and SaveButton

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:12:20 +00:00
Ruben Fiszel
25701a0639 chore(main): release 1.639.0 (#7997)
* chore(main): release 1.639.0

* Apply automatic changes

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-18 18:24:59 +00:00
Ruben Fiszel
ea4fb64262 cargo update with native-tls pin and benchmark feature propagation (#8009)
Pin native-tls to <0.2.17 to avoid compilation error with
Protocol::Tlsv13 match exhaustiveness, and propagate benchmark
feature to windmill-api-agent-workers to fix argument mismatch
in pull() call.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 17:35:33 +00:00
hugocasa
a9f816a3bf refactor: oauth (#7998)
* refactor: oauth

* chore: update ee-repo-ref to d842747738a2f10fc2fd0cd61f536efffcb45e41

This commit updates the EE repository reference after PR #421 was merged in windmill-ee-private.

Previous ee-repo-ref: d7fa31960f68a3e10915055a66c8d094afd48f40

New ee-repo-ref: d842747738a2f10fc2fd0cd61f536efffcb45e41

Automated by sync-ee-ref workflow.

---------

Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2026-02-18 17:27:15 +00:00
Diego Imbert
ba724250cf app db explorer nit null read (#8008) 2026-02-18 17:26:49 +00:00
Guilhem
4d1d17580b add fuzzy search to instance settings (#8000)
* feat: add fuzzy search to instance settings sidebar

Adds a search input at the top of the superadmin settings sidebar that
uses uFuzzy for fuzzy matching against all setting labels, descriptions,
and categories. Selecting a result navigates to the correct tab and
scrolls to the specific setting card with a brief highlight.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: improve settings search display and description matching

- Extract only the label portion from uFuzzy highlighted text for
  cleaner dropdown display
- Show description only when the match is in the description and NOT
  in the label
- Truncate descriptions to 80 chars in searchable items
- Add maxHeight prop to SelectDropdown for configurable height

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: fix search description truncation and handle undefined marked values

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: remove description from settings search dropdown

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add smooth outline transition for setting highlight animation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: highlight first search result by default for enter-to-select

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* nit

* clean code

* fix: address review feedback - sanitize html, remove max-w-40, document description field

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: constrain search dropdown width to prevent long title overflow

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* perf: add 150ms debounce to settings search filter

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: clean up timeouts on destroy and re-invocation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: extract settings search into reusable SettingsSearchInput component

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: use twMerge for class prop in SettingsSearchInput

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: wrap debounced state write in untrack to prevent re-triggering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 17:12:27 +00:00
centdix
17f9536a76 promote license key to second field in setup wizard and warn on EE (#8001)
* feat: promote license key to second field in setup wizard and warn on EE without key

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: remove screenshots from PR branch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use positive EE check with startsWith instead of negative CE check

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: claude-agent <claude-agent@noreply>
2026-02-18 17:10:15 +00:00
centdix
02e50c915e don't print success message when workspace add is cancelled (#8003)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-18 16:35:49 +00:00
Ruben Fiszel
d2d08f8817 fix: default automate_username_creation to true when setting is missing (#8006)
* fix: default automate_username_creation to true when setting is missing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* latest ref

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 16:33:54 +00:00
Diego Imbert
ede29d0914 New ms sql icon (#8005) 2026-02-18 16:18:56 +00:00
centdix
f6d99dd18c fix: handle raw app folder deletion in sync push without yaml parse error (#7994)
* fix: handle raw app folder deletion in sync push without yaml parse error

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: update ee-repo-ref to 592848d59ca2304926fb2bd85d000668a7f46a77

This commit updates the EE repository reference after PR #420 was merged in windmill-ee-private.

Previous ee-repo-ref: 931813b75b8260faa13ddc07f36a11607b7e3bf6

New ee-repo-ref: 592848d59ca2304926fb2bd85d000668a7f46a77

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-02-18 14:50:27 +00:00
Diego Imbert
858a037435 Fix preview scripts with wrong tag (#7999) 2026-02-18 14:28:35 +00:00
Ruben Fiszel
6bf544f507 refactor: extract object store into dedicated crate with filesystem backend (#7996)
* refactor: extract object store code into windmill-object-store crate with filesystem backend

Consolidate all object_store-dependent code from windmill-common into a new
windmill-object-store crate. Add a filesystem-backed object store implementation
using LocalFileSystem for dev/testing without cloud credentials. Includes 30
comprehensive tests covering render_endpoint, lfs_to_object_store_resource,
duckdb_connection_settings, error mapping, and filesystem-backed integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* all

* all

* all

* all

* fix: fix raw_app hardcoded path, add missing ObjectStoreResource import, and add tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: move S3ModeFormat to windmill-types, make windmill-parser-sql optional, restore debug logs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* all

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 12:24:45 +00:00
Pyra
cd4151a84b perf(cli): skip relock more accurate (#7993)
* perf(cli): skip relock more accurate

Signed-off-by: pyranota <pyra@duck.com>

* Update cli/src/utils/metadata.ts

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* Update cli/src/commands/flow/flow_metadata.ts

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

* fix

Signed-off-by: pyranota <pyra@duck.com>

* use structuredClone for safety

Signed-off-by: pyranota <pyra@duck.com>

* chore: update ee-repo-ref to 592848d59ca2304926fb2bd85d000668a7f46a77

This commit updates the EE repository reference after PR #420 was merged in windmill-ee-private.

Previous ee-repo-ref: 931813b75b8260faa13ddc07f36a11607b7e3bf6

New ee-repo-ref: 592848d59ca2304926fb2bd85d000668a7f46a77

Automated by sync-ee-ref workflow.

* fix ci

Signed-off-by: pyranota <pyra@duck.com>

* add simple tests

Signed-off-by: pyranota <pyra@duck.com>

---------

Signed-off-by: pyranota <pyra@duck.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-02-18 12:17:54 +00:00
Guilhem
db8aa8a083 feat: improve FolderPicker with edit icon pattern (#7995)
* feat: replace native select with custom Select in FolderPicker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: update ee-repo-ref to 592848d59ca2304926fb2bd85d000668a7f46a77

This commit updates the EE repository reference after PR #420 was merged in windmill-ee-private.

Previous ee-repo-ref: 931813b75b8260faa13ddc07f36a11607b7e3bf6

New ee-repo-ref: 592848d59ca2304926fb2bd85d000668a7f46a77

Automated by sync-ee-ref workflow.

* nit

* fix(frontend): edit button in folder picker dropdown should not select the item

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: restore ee-repo-ref.txt to match main

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(frontend): clean up FolderPicker review nits

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Update frontend/src/lib/components/FolderPicker.svelte

Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
2026-02-18 11:06:04 +00:00
Ruben Fiszel
e9f82e9058 chore(main): release 1.638.4 (#7986)
* chore(main): release 1.638.4

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-17 17:23:57 +00:00
Guilhem
6691cde402 await folder list reload before selecting created folder (#7991)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 17:09:29 +00:00
Guilhem
4ea1692ee2 fix(frontend): add folder picker validation, error handling, and loading state (#7987)
* fix(frontend): add folder name validation and error handling to folder picker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(frontend): add loading state to folder picker select

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(frontend): add error toast for folder list loading failure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(frontend): reassign userStore folders array to trigger reactivity

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 17:03:23 +00:00
hugocasa
90fa5b3ced native mode nits (#7981)
* native mode UI nits

* accept flow jobs on native workers

* limit native mode to non-dep jobs + flow tag infobox
2026-02-17 16:32:52 +00:00
Henri Courdent
45b959711e New Raw App tab name (#7984) 2026-02-17 16:32:40 +00:00
Guilhem
a46924a0f2 fix(frontend): improve folder picker with sticky create button and drawer flow (#7985)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 16:32:25 +00:00
Ruben Fiszel
907ed41093 chore(main): release 1.638.3 (#7983)
* chore(main): release 1.638.3

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-17 13:42:25 +00:00
centdix
f387daa2a6 fix: always create guidance files during wmill init (#7974)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 13:39:14 +00:00
Ruben Fiszel
b094649586 fix(frontend): preserve user config when trimming oneOf non-selected keys
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 13:37:28 +00:00
Guilhem
3ed86816fb fix flow rename (#7978)
* fix(frontend): preserve flow settings when updating summary/path from detail page

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(frontend): type builders prop with ReturnType<typeof createDropdownMenu>

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(frontend): extract shared updateItemPathAndSummary utility to deduplicate move/rename logic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(frontend): enable inline summary/path editing on script detail page

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* improve layout

* feat(frontend): add dirty tracking to MoveDrawer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* nit move drawer

* fix(frontend): drop on_behalf_of_email from move/rename and warn user about redeployment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(frontend): hide on_behalf_of warning in MoveDrawer when user is not owner

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(frontend): only reload script when path unchanged in onSaved callback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-17 12:48:58 +00:00
hugocasa
2d5393941c fix(frontend): incorrect job result on the runs page (#7982) 2026-02-17 12:22:14 +00:00
Ruben Fiszel
6d1d1f162b chore(main): release 1.638.2 (#7980)
* chore(main): release 1.638.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-17 09:07:08 +00:00
hugocasa
5b7bb2fb84 fix(backend): gcp private key parsing (#7979) 2026-02-17 09:02:52 +00:00
Ruben Fiszel
71608bf669 fix: yaml settings UI mask rsa_keys and jwt_secret 2026-02-17 08:54:58 +00:00
Ruben Fiszel
47c7fe83f4 chore(main): release 1.638.1 (#7977)
* chore(main): release 1.638.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-17 08:44:00 +00:00
Ruben Fiszel
4b8bb72857 operator nits 2026-02-17 08:40:46 +00:00
Ruben Fiszel
b7bec1a83d fix(operator): improve configmap handling of older license keys 2026-02-17 08:38:12 +00:00
Ruben Fiszel
8971dd660c chore(main): release 1.638.0 (#7973)
* chore(main): release 1.638.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-17 08:01:25 +00:00
Ruben Fiszel
b3eeee4131 feat: show all settings in YAML UI and protect from empty overwrites (#7976)
- Show custom_instance_pg_databases, ducklake_settings, ducklake_user_pg_pwd
  and rsa_keys in frontend YAML editor (remove from excludedKeys)
- Redact sensitive values: add ducklake_user_pg_pwd and rsa_keys to
  sensitiveKeys, add custom_instance_pg_databases.user_pwd to
  nestedSensitiveFields
- Remove rsa_keys from HIDDEN_SETTINGS so it appears in YAML export
- Hide automate_username_creation from export (add to HIDDEN_SETTINGS)
- Add ducklake_user_pg_pwd and rsa_keys to SENSITIVE_SETTINGS for log
  redaction
- Generalize empty/null protection for all PROTECTED_SETTINGS: operator
  diff skips empty values when DB has existing data, direct API rejects
  delete/empty for protected settings

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 08:52:31 +01:00
Pyra
bba319b282 fix: download audit logs (#7965)
* feat: download audit logs

Signed-off-by: pyranota <pyra@duck.com>

* add on hover

Signed-off-by: pyranota <pyra@duck.com>

---------

Signed-off-by: pyranota <pyra@duck.com>
2026-02-17 07:36:59 +00:00
hugocasa
bb03c62c28 fix: add missing google native triggers to triggers panel (#7966) 2026-02-17 07:34:45 +00:00
Ruben Fiszel
2019aecf42 fix: improve operator ConfigMap settings handling (#7975)
* feat: improve operator ConfigMap settings handling

- Protect jwt_secret and min_keep_alive_version from deletion (add to
  PROTECTED_SETTINGS)
- Expose jwt_secret in config exports (remove from HIDDEN_SETTINGS)
- Reject empty/null jwt_secret values with warning
- Clamp retention_period_secs to 30 days max on CE builds
- Improve apply_settings_diff logging: distinguish Created/Updated/Deleted
  with from/to values and unchanged count summary
- Add sensitive value masking in logs with partial redaction (prefix/suffix)
  for top-level secrets and nested sub-field masking for oauths, smtp,
  object_store_cache_config, custom_instance_pg_databases
- Sort global_settings keys alphabetically in YAML export
- Order worker_configs with "default" and "native" first in YAML export
- Add tests for sorted YAML serializer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: address PR review feedback

- Fix redact_string panic on multi-byte UTF-8 by using chars() instead
  of byte-length slicing
- Protect jwt_secret from deletion via direct API
  (set_global_setting_internal rejects empty/null with BadRequest)
- Add code comment documenting jwt_secret visibility trade-off

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 08:34:08 +01:00
Ruben Fiszel
3e313cc4e8 feat: add native_mode as typed field on WorkerGroupConfig
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 00:17:39 +00:00
Ruben Fiszel
c3a76c2cc5 chore(main): release 1.637.0 (#7971)
* chore(main): release 1.637.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-17 00:14:06 +00:00
Guilhem
eb5a8dab74 feat(frontend): inline edit summary & path from header (#7968)
* allow editing flow/script summary

* feat(frontend): wire up edit summary/path on flow detail page

- Fix on:click → onclick (Svelte 5) and add title on Save button
- Make can_write reactive ($state) so onEdit prop updates correctly
- Wire onEdit in flow detail page to call FlowService.updateFlow

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(frontend): use Path component for path editing in detail page header

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(frontend): extract SummaryPathDisplay component with edit popover

Consolidate the summary+path display and edit popover into a reusable
SummaryPathDisplay component, used in both the detail page header and
the flow editor toolbar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(frontend): add size prop to Path/FolderPicker, compact popover

Add size prop ('sm' | 'md') to Path and FolderPicker components,
passed through to ToggleButton, TextInput, and Button children.
Use hideFullPath and size="sm" in the SummaryPathDisplay popover
for a compact inline path editor. Widen popover to 480px.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix add folder in path editor

* fix(frontend): disable focus trap on edit popover for drawer access

Disable melt-ui's focus trap on the SummaryPathDisplay popover so
that inputs inside drawers (e.g. New Folder) can receive focus.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* nit

* feat(frontend): auto-create folder and render drawer above popover

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(frontend): show placeholder and hover-reveal pencil in SummaryPathDisplay

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(frontend): click-to-edit SummaryPathDisplay with inline layout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(frontend): move undo/redo and tutorials into dropdown submenu with notification dot

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(frontend): stack path above summary in SummaryPathDisplay

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(frontend): bind summary/path directly in flow builder popover

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* nit

* chore: add PR screenshots (to be removed before merge)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: remove PR screenshots (moved to release assets)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 00:10:05 +00:00
Ruben Fiszel
f02ef6d03c refactor: switch operator from CRD to ConfigMap (#7972)
* refactor: switch operator from CRD to ConfigMap

Replace the WindmillInstance CRD with a plain ConfigMap for the K8s
operator. This simplifies deployment (no CRD to install/manage, no
ClusterRole for custom API groups) while keeping the same config schema.

- Replace crd_ee.rs with configmap_ee.rs (parses data.spec YAML key)
- Rewrite reconciler_ee.rs: ConfigMap watcher + Event recorder instead
  of CRD Controller + status subresource
- Add license_key preservation: if absent/empty in ConfigMap but present
  in DB, the DB value is kept
- Remove print_crd_yaml() and "operator crd" subcommand
- Drop schemars, chrono, instance_config_schema dependencies
- Delete manifests/crd.yaml
- Update K8s example and README for ConfigMap approach
- RBAC now only needs a namespace-scoped Role (not ClusterRole)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add superadmin YAML export endpoint and remove cache_clear from operator config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 00:06:56 +00:00
hugocasa
535e108cbf feat: native mode (#7939)
* feat: native mode

* improve

* fix build

* review fixes

* tracing nit
2026-02-16 23:36:41 +00:00
Ruben Fiszel
0940d70a2b chore(main): release 1.636.0 (#7962)
* chore(main): release 1.636.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-16 17:09:46 +00:00
centdix
e7b0b00f56 fix: mark base_url as unsaved when using browser fallback (#7964)
* fix: mark base_url as unsaved when using browser fallback in instance settings

When base_url is not set in the database, the frontend silently fills in
window.location.origin but also snapshots it as the initial value. This
makes the dirty-check see no change, so the Save button stays disabled
and the user cannot persist the auto-detected value.

Fix by snapshotting initialValues before applying the fallback, and show
a yellow warning indicating the value is auto-detected and unsaved.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: render base_url fallback warning inside the setting card

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 17:01:18 +00:00
Ruben Fiszel
4587cd5f3e fix sqlx 2026-02-16 17:00:35 +00:00
Ruben Fiszel
7a5719217b fix sqlx 2026-02-16 16:49:42 +00:00
Ruben Fiszel
ec17b29ae2 fix private check issue 2026-02-16 16:39:59 +00:00
Ruben Fiszel
713c39040b fix private check issue 2026-02-16 16:12:02 +00:00
centdix
5db6a405ad fix review comments (#7963) 2026-02-16 15:53:52 +00:00
hugocasa
6f24f1939d feat: google native triggers (#7837)
* feat: google native triggers

* nit skill

* better native trigger abstraction

* use resources for workspace integrations

* better and better

* better tests

* update native trigger skill

* sqlx

* less tx and google update fix

* refactor a bit the external logic

* nits

* fix

* fix google native trigger update

* fix oauth

* review fixes

* sqlx fix

* nit

* chore: update ee-repo-ref to a10eda4251610cceee67fbe05463b8be82ffa9e0

This commit updates the EE repository reference after PR #416 was merged in windmill-ee-private.

Previous ee-repo-ref: bf3696d5f2a39a3cb84dbbee81e092155f2a8c75

New ee-repo-ref: a10eda4251610cceee67fbe05463b8be82ffa9e0

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-02-16 15:52:02 +00:00
centdix
5b8ec502fe feat: allow adding workspace scripts and flows as AI chat context (#7882)
* feat: allow adding workspace scripts and flows as AI chat context

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* cleaning

* cleaning

* cleaning

* better

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-16 15:46:17 +00:00
Ruben Fiszel
b2128a0109 add dynamic tls support for email 2026-02-16 14:57:28 +00:00
Ruben Fiszel
ba80570357 chore(main): release 1.635.1 (#7958)
* chore(main): release 1.635.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <275584+rubenfiszel@users.noreply.github.com>
2026-02-15 17:14:04 +00:00
Ruben Fiszel
714f713a04 only update license key via IaC when new expiry is posterior (#7959)
When using infrastructure-as-code to update settings, if the desired
license key matches the current one (same client ID and signature) but
differs only in the expiration date, only apply the update if the new
key has a later expiry. This prevents accidental downgrades when an
older license key is present in the IaC configuration.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 17:13:37 +00:00
Ruben Fiszel
68f766e1ae fix: pin strum_macros to 0.27 to match strum version (#7957)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 16:56:39 +00:00
703 changed files with 47301 additions and 16305 deletions

View File

@@ -1,30 +0,0 @@
#!/usr/bin/env bash
# Resolve _ee.rs symlinks to actual files so Claude can read them
# This script runs before each user prompt is processed
set -e
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-/home/farhad/windmill}"
MANIFEST_FILE="$PROJECT_DIR/.claude/hooks/.symlink-manifest"
# Find all _ee.rs symlinks and store their targets
find "$PROJECT_DIR" -name "*_ee.rs" -type l 2>/dev/null | while read -r symlink; do
target=$(readlink -f "$symlink" 2>/dev/null) || continue
# Only process if target file exists
if [[ -f "$target" ]]; then
# Store symlink path and target in manifest
echo "$symlink|$target" >> "$MANIFEST_FILE.tmp"
# Replace symlink with actual file content
rm "$symlink"
cp "$target" "$symlink"
fi
done
# Atomically replace manifest
if [[ -f "$MANIFEST_FILE.tmp" ]]; then
mv "$MANIFEST_FILE.tmp" "$MANIFEST_FILE"
fi
exit 0

View File

@@ -1,36 +0,0 @@
#!/usr/bin/env bash
# Restore _ee.rs symlinks after Claude finishes processing
# This script runs when Claude stops
# IMPORTANT: Copies any modifications back to the target before restoring symlinks
set -e
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-/home/farhad/windmill}"
MANIFEST_FILE="$PROJECT_DIR/.claude/hooks/.symlink-manifest"
# Check if manifest exists
if [[ ! -f "$MANIFEST_FILE" ]]; then
exit 0
fi
# Read manifest and restore symlinks
while IFS='|' read -r symlink target; do
if [[ -n "$symlink" && -n "$target" ]]; then
# If the file exists (not a symlink) and target exists, copy changes back
if [[ -f "$symlink" && ! -L "$symlink" && -e "$target" ]]; then
# Copy the potentially modified file back to the target
cp "$symlink" "$target"
fi
# Remove the regular file (which was a copy)
rm -f "$symlink" 2>/dev/null || true
# Recreate the symlink
ln -s "$target" "$symlink" 2>/dev/null || true
fi
done < "$MANIFEST_FILE"
# Clean up manifest
rm -f "$MANIFEST_FILE"
exit 0

View File

@@ -1,5 +1,8 @@
{
"permissions": {
"additionalDirectories": [
"../windmill-ee-private"
],
"allow": [
"Bash(ls:*)",
"Bash(grep:*)",
@@ -63,39 +66,6 @@
},
"enableAllProjectMcpServers": true,
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/resolve-symlinks.sh",
"timeout": 30
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/restore-symlinks.sh",
"timeout": 30
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/restore-symlinks.sh",
"timeout": 30
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
@@ -130,4 +100,4 @@
"typescript-lsp@claude-plugins-official": true,
"code-review@claude-plugins-official": true
}
}
}

View File

@@ -0,0 +1,777 @@
# Skill: Adding Native Trigger Services
This skill provides comprehensive guidance for adding new native trigger services to Windmill. Native triggers allow external services (like Nextcloud, Google Drive, etc.) to trigger Windmill scripts/flows via webhooks or push notifications.
## Architecture Overview
The native trigger system consists of:
1. **Database Layer** - PostgreSQL tables and enum types
2. **Backend Rust Implementation** - Core trait, handlers, and service modules in the `windmill-native-triggers` crate
3. **Frontend Svelte Components** - Configuration forms and UI components
### Key Files
| Component | Path |
|-----------|------|
| Core module with `External` trait | `backend/windmill-native-triggers/src/lib.rs` |
| Generic CRUD handlers | `backend/windmill-native-triggers/src/handler.rs` |
| Background sync logic | `backend/windmill-native-triggers/src/sync.rs` |
| OAuth/workspace integration | `backend/windmill-native-triggers/src/workspace_integrations.rs` |
| Re-export shim (windmill-api) | `backend/windmill-api/src/native_triggers/mod.rs` |
| TriggerKind enum | `backend/windmill-common/src/triggers.rs` |
| JobTriggerKind enum | `backend/windmill-common/src/jobs.rs` |
| Frontend service registry | `frontend/src/lib/components/triggers/native/utils.ts` |
| Frontend trigger utilities | `frontend/src/lib/components/triggers/utils.ts` |
| Trigger badges (icons + counts) | `frontend/src/lib/components/graph/renderers/triggers/TriggersBadge.svelte` |
| Workspace integrations UI | `frontend/src/lib/components/workspaceSettings/WorkspaceIntegrations.svelte` |
| OAuth config form component | `frontend/src/lib/components/workspaceSettings/OAuthClientConfig.svelte` |
| OpenAPI spec | `backend/windmill-api/openapi.yaml` |
| Reference: Nextcloud module | `backend/windmill-native-triggers/src/nextcloud/` |
| Reference: Google module | `backend/windmill-native-triggers/src/google/` |
### Crate Structure
The native trigger code lives in the `windmill-native-triggers` crate (`backend/windmill-native-triggers/`). The `windmill-api` crate re-exports everything via a shim:
```rust
// backend/windmill-api/src/native_triggers/mod.rs
pub use windmill_native_triggers::*;
```
All new service modules go in `backend/windmill-native-triggers/src/`.
---
## Core Concepts
### The `External` Trait
Every native trigger service implements the `External` trait defined in `lib.rs`:
```rust
#[async_trait]
pub trait External: Send + Sync + 'static {
// Associated types:
type ServiceConfig: Debug + DeserializeOwned + Serialize + Send + Sync;
type TriggerData: Debug + Serialize + Send + Sync;
type OAuthData: DeserializeOwned + Serialize + Clone + Send + Sync;
type CreateResponse: DeserializeOwned + Send + Sync;
// Constants:
const SUPPORT_WEBHOOK: bool;
const SERVICE_NAME: ServiceName;
const DISPLAY_NAME: &'static str;
const TOKEN_ENDPOINT: &'static str;
const REFRESH_ENDPOINT: &'static str;
const AUTH_ENDPOINT: &'static str;
// Required methods:
async fn create(&self, w_id, oauth_data, webhook_token, data, db, tx) -> Result<Self::CreateResponse>;
async fn update(&self, w_id, oauth_data, external_id, webhook_token, data, db, tx) -> Result<serde_json::Value>;
async fn get(&self, w_id, oauth_data, external_id, db, tx) -> Result<Self::TriggerData>;
async fn delete(&self, w_id, oauth_data, external_id, db, tx) -> Result<()>;
async fn exists(&self, w_id, oauth_data, external_id, db, tx) -> Result<bool>;
async fn maintain_triggers(&self, db, workspace_id, triggers, oauth_data, synced, errors);
fn external_id_and_metadata_from_response(&self, resp) -> (String, Option<serde_json::Value>);
// Methods with defaults:
async fn prepare_webhook(&self, db, w_id, headers, body, script_path, is_flow) -> Result<PushArgsOwned>;
fn service_config_from_create_response(&self, data, resp) -> Option<serde_json::Value>;
fn additional_routes(&self) -> axum::Router;
async fn http_client_request<T, B>(&self, url, method, workspace_id, tx, db, headers, body) -> Result<T>;
}
```
Key design points:
- **`update()` returns `serde_json::Value`** - the resolved service_config to store. Each service is responsible for building the final config.
- **`maintain_triggers()`** - periodic background maintenance. Each service implements its own strategy (Nextcloud: reconcile with external state; Google: renew expiring channels).
- **No `list_all()` in the trait** - services that need it (Nextcloud) implement it privately; services that don't (Google) use different maintenance strategies.
- **No `get_external_id_from_trigger_data()` or `extract_service_config_from_trigger_data()`** - removed in favor of the `maintain_triggers` pattern.
### Create Lifecycle: Two Paths
The `create_native_trigger` handler in `handler.rs` supports two creation flows, controlled by `service_config_from_create_response()`:
**Path A: Short (Google pattern)** - `service_config_from_create_response()` returns `Some(config)`:
1. `create()` registers on external service
2. `external_id_and_metadata_from_response()` extracts the ID
3. `service_config_from_create_response()` builds the config directly from input data + response metadata
4. Stores trigger in DB -- done, no extra round-trip
Use this when the external_id is known before the create call (e.g., Google generates the channel_id as a UUID upfront and includes it in the webhook URL).
**Path B: Long (Nextcloud pattern)** - `service_config_from_create_response()` returns `None` (default):
1. `create()` registers on external service (webhook URL has no external_id yet)
2. `external_id_and_metadata_from_response()` extracts the ID
3. `update()` is called to fix the webhook URL with the now-known external_id
4. `update()` returns the resolved service_config
5. Stores trigger in DB
Use this when the external_id is assigned by the remote service and the webhook URL needs to be corrected after creation.
### OAuth Token Storage (Three-Table Pattern)
OAuth tokens are stored across three tables, NOT in `workspace_integrations.oauth_data` directly:
| Table | What's Stored |
|-------|---------------|
| `workspace_integrations` | `oauth_data` JSON with `base_url`, `client_id`, `client_secret`, `instance_shared` flag; `resource_path` pointing to the variable |
| `variable` | Encrypted `access_token` (at the path stored in `resource_path`), linked to `account` via `account` column |
| `account` | `refresh_token`, keyed by `workspace_id` + `client` (service name) + `is_workspace_integration = true` |
The `decrypt_oauth_data()` function in `lib.rs` assembles these into a unified struct:
```rust
pub struct OAuthConfig {
pub base_url: String,
pub access_token: String, // decrypted from variable
pub refresh_token: Option<String>, // from account table
pub client_id: String, // from oauth_data or instance settings
pub client_secret: String, // from oauth_data or instance settings
}
```
Instance-level sharing: when `oauth_data.instance_shared == true`, `client_id` and `client_secret` are read from global settings instead of workspace_integrations.
### URL Resolution
The `resolve_endpoint()` helper handles both absolute and relative OAuth URLs:
```rust
pub fn resolve_endpoint(base_url: &str, endpoint: &str) -> String {
if endpoint.starts_with("http://") || endpoint.starts_with("https://") {
endpoint.to_string() // Google: absolute URLs
} else {
format!("{}{}", base_url, endpoint) // Nextcloud: relative paths
}
}
```
### ServiceName Methods
`ServiceName` is the central registry enum. Each variant must implement these match arms:
| Method | Purpose |
|--------|---------|
| `as_str()` | Lowercase identifier (e.g., `"google"`) |
| `as_trigger_kind()` | Maps to `TriggerKind` enum |
| `as_job_trigger_kind()` | Maps to `JobTriggerKind` enum |
| `token_endpoint()` | OAuth token endpoint (relative or absolute) |
| `auth_endpoint()` | OAuth authorization endpoint |
| `oauth_scopes()` | Space-separated OAuth scopes |
| `resource_type()` | Resource type for token storage (e.g., `"gworkspace"`) |
| `extra_auth_params()` | Extra OAuth params (e.g., Google needs `access_type=offline`, `prompt=consent`) |
| `integration_service()` | Maps to the workspace integration service (usually `*self`) |
| `TryFrom<String>` | Parse from string |
| `Display` | Delegates to `as_str()` |
---
## Step-by-Step Implementation Guide
### Step 1: Database Migration
Create a new migration file: `backend/migrations/YYYYMMDDHHMMSS_newservice_trigger.up.sql`
```sql
-- Add the service to the native_trigger_service enum
ALTER TYPE native_trigger_service ADD VALUE IF NOT EXISTS 'newservice';
-- Add to TRIGGER_KIND enum (used for trigger tracking)
ALTER TYPE TRIGGER_KIND ADD VALUE IF NOT EXISTS 'newservice';
-- Add to job_trigger_kind enum (used for job tracking)
ALTER TYPE job_trigger_kind ADD VALUE IF NOT EXISTS 'newservice';
```
Also create the corresponding down migration.
### Step 2: Update windmill-common Enums
#### `backend/windmill-common/src/triggers.rs`
Add variant to `TriggerKind` enum, and update `to_key()` and `fmt()` implementations.
#### `backend/windmill-common/src/jobs.rs`
Add variant to `JobTriggerKind` enum and update the `Display` implementation.
### Step 3: Backend Service Module
Create a new directory: `backend/windmill-native-triggers/src/newservice/`
#### `mod.rs` - Type Definitions
```rust
use serde::{Deserialize, Serialize};
pub mod external;
// pub mod routes; // Only if you need additional service-specific routes
/// OAuth data deserialized from the three-table pattern.
/// The actual structure is built by decrypt_oauth_data() from variable + account + workspace_integrations.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct NewServiceOAuthData {
pub base_url: String, // from workspace_integrations.oauth_data
pub access_token: String, // decrypted from variable table
pub refresh_token: Option<String>, // from account table
// Note: client_id and client_secret are in OAuthConfig, not here
// unless the service needs them at runtime for API calls
}
/// Configuration provided by user when creating/updating a trigger.
/// Stored as JSON in native_trigger.service_config.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NewServiceConfig {
// Service-specific configuration fields
pub folder_path: String,
pub file_filter: Option<String>,
}
/// Data retrieved from the external service about a trigger.
/// Returned by the get() method and shown in the UI.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NewServiceTriggerData {
pub folder_path: String,
pub file_filter: Option<String>,
// Fields that shouldn't affect service_config comparison should use #[serde(skip_serializing)]
}
/// Response from external service when creating a trigger/webhook.
#[derive(Debug, Deserialize)]
pub struct CreateTriggerResponse {
pub id: String,
}
/// Handler struct (stateless, used for routing)
#[derive(Copy, Clone)]
pub struct NewService;
```
#### `external.rs` - External Trait Implementation
```rust
use async_trait::async_trait;
use reqwest::Method;
use sqlx::PgConnection;
use std::collections::HashMap;
use windmill_common::{
error::{Error, Result},
BASE_URL, DB,
};
use crate::{
generate_webhook_service_url, External, NativeTrigger, NativeTriggerData, ServiceName,
sync::{SyncError, TriggerSyncInfo},
};
use super::{NewService, NewServiceConfig, NewServiceOAuthData, NewServiceTriggerData, CreateTriggerResponse};
#[async_trait]
impl External for NewService {
type ServiceConfig = NewServiceConfig;
type TriggerData = NewServiceTriggerData;
type OAuthData = NewServiceOAuthData;
type CreateResponse = CreateTriggerResponse;
const SERVICE_NAME: ServiceName = ServiceName::NewService;
const DISPLAY_NAME: &'static str = "New Service";
const SUPPORT_WEBHOOK: bool = true;
const TOKEN_ENDPOINT: &'static str = "/oauth/token";
const REFRESH_ENDPOINT: &'static str = "/oauth/token";
const AUTH_ENDPOINT: &'static str = "/oauth/authorize";
async fn create(
&self,
w_id: &str,
oauth_data: &Self::OAuthData,
webhook_token: &str,
data: &NativeTriggerData<Self::ServiceConfig>,
db: &DB,
tx: &mut PgConnection,
) -> Result<Self::CreateResponse> {
let base_url = &*BASE_URL.read().await;
// external_id is None during create (we get it from the response)
let webhook_url = generate_webhook_service_url(
base_url, w_id, &data.script_path, data.is_flow,
None, Self::SERVICE_NAME, webhook_token,
);
let url = format!("{}/api/webhooks/create", oauth_data.base_url);
let payload = serde_json::json!({
"callback_url": webhook_url,
"folder_path": data.service_config.folder_path,
});
let response: CreateTriggerResponse = self
.http_client_request(&url, Method::POST, w_id, tx, db, None, Some(&payload))
.await?;
Ok(response)
}
/// Update returns the resolved service_config as JSON.
/// For services using the update+get pattern, call self.get() and serialize.
async fn update(
&self,
w_id: &str,
oauth_data: &Self::OAuthData,
external_id: &str,
webhook_token: &str,
data: &NativeTriggerData<Self::ServiceConfig>,
db: &DB,
tx: &mut PgConnection,
) -> Result<serde_json::Value> {
let base_url = &*BASE_URL.read().await;
let webhook_url = generate_webhook_service_url(
base_url, w_id, &data.script_path, data.is_flow,
Some(external_id), Self::SERVICE_NAME, webhook_token,
);
let url = format!("{}/api/webhooks/{}", oauth_data.base_url, external_id);
let payload = serde_json::json!({
"callback_url": webhook_url,
"folder_path": data.service_config.folder_path,
});
let _: serde_json::Value = self
.http_client_request(&url, Method::PUT, w_id, tx, db, None, Some(&payload))
.await?;
// Fetch back the updated state to get the resolved config
let trigger_data = self.get(w_id, oauth_data, external_id, db, tx).await?;
serde_json::to_value(&trigger_data)
.map_err(|e| Error::InternalErr(format!("Failed to serialize trigger data: {}", e)))
}
async fn get(
&self,
w_id: &str,
oauth_data: &Self::OAuthData,
external_id: &str,
db: &DB,
tx: &mut PgConnection,
) -> Result<Self::TriggerData> {
let url = format!("{}/api/webhooks/{}", oauth_data.base_url, external_id);
self.http_client_request::<_, ()>(&url, Method::GET, w_id, tx, db, None, None).await
}
async fn delete(
&self,
w_id: &str,
oauth_data: &Self::OAuthData,
external_id: &str,
db: &DB,
tx: &mut PgConnection,
) -> Result<()> {
let url = format!("{}/api/webhooks/{}", oauth_data.base_url, external_id);
let _: serde_json::Value = self
.http_client_request::<_, ()>(&url, Method::DELETE, w_id, tx, db, None, None)
.await
.or_else(|e| match &e {
Error::InternalErr(msg) if msg.contains("404") => Ok(serde_json::Value::Null),
_ => Err(e),
})?;
Ok(())
}
async fn exists(
&self,
w_id: &str,
oauth_data: &Self::OAuthData,
external_id: &str,
db: &DB,
tx: &mut PgConnection,
) -> Result<bool> {
match self.get(w_id, oauth_data, external_id, db, tx).await {
Ok(_) => Ok(true),
Err(Error::NotFound(_)) => Ok(false),
Err(e) => Err(e),
}
}
/// Background maintenance. Choose the right pattern for your service:
/// - For services with queryable external state: use reconcile_with_external_state()
/// - For channel-based services with expiration: implement renewal logic
async fn maintain_triggers(
&self,
db: &DB,
workspace_id: &str,
triggers: &[NativeTrigger],
oauth_data: &Self::OAuthData,
synced: &mut Vec<TriggerSyncInfo>,
errors: &mut Vec<SyncError>,
) {
// Option A: Reconcile with external state (Nextcloud pattern)
// Fetch all triggers from external service and compare with DB
let external_triggers = match self.list_all(workspace_id, oauth_data, db).await {
Ok(triggers) => triggers,
Err(e) => {
errors.push(SyncError {
resource_path: format!("workspace:{}", workspace_id),
error_message: format!("Failed to list triggers: {}", e),
error_type: "api_error".to_string(),
});
return;
}
};
// Convert to (external_id, config_json) pairs
let external_pairs: Vec<(String, serde_json::Value)> = external_triggers
.into_iter()
.map(|t| (t.id.clone(), serde_json::to_value(&t).unwrap_or_default()))
.collect();
crate::sync::reconcile_with_external_state(
db, workspace_id, Self::SERVICE_NAME, triggers, &external_pairs, synced, errors,
).await;
}
fn external_id_and_metadata_from_response(
&self,
resp: &Self::CreateResponse,
) -> (String, Option<serde_json::Value>) {
(resp.id.clone(), None)
}
// service_config_from_create_response: NOT overridden (returns None).
// This means the handler uses the update+get pattern after create.
// Override and return Some(...) to skip the update+get cycle (Google pattern).
}
impl NewService {
/// Private helper to list all triggers from the external service.
async fn list_all(
&self,
w_id: &str,
oauth_data: &<Self as External>::OAuthData,
db: &DB,
) -> Result<Vec<<Self as External>::TriggerData>> {
// Implementation depends on the external service's API
todo!()
}
}
```
### Step 4: Update lib.rs Registry
In `backend/windmill-native-triggers/src/lib.rs`:
```rust
// Service modules - add new services here:
#[cfg(feature = "native_trigger")]
pub mod newservice; // <-- Add this
// ServiceName enum - add variant:
pub enum ServiceName {
Nextcloud,
Google,
NewService, // <-- Add this
}
// Then add match arms in ALL ServiceName methods:
// as_str(), as_trigger_kind(), as_job_trigger_kind(), token_endpoint(),
// auth_endpoint(), oauth_scopes(), resource_type(), extra_auth_params(),
// integration_service(), TryFrom<String>, Display
```
### Step 5: Update handler.rs Routes
In `backend/windmill-native-triggers/src/handler.rs`:
```rust
pub fn generate_native_trigger_routers() -> Router {
// ...
#[cfg(feature = "native_trigger")]
{
use crate::newservice::NewService;
return router
.nest("/nextcloud", service_routes(NextCloud))
.nest("/google", service_routes(Google))
.nest("/newservice", service_routes(NewService)); // <-- Add this
}
// ...
}
```
### Step 6: Update sync.rs
In `backend/windmill-native-triggers/src/sync.rs`:
```rust
pub async fn sync_all_triggers(db: &DB) -> Result<BackgroundSyncResult> {
// ...
#[cfg(feature = "native_trigger")]
{
use crate::newservice::NewService;
// ... existing service syncs ...
// New service sync
let (service_name, result) = sync_service_triggers(db, NewService).await;
total_synced += result.synced_triggers.len();
total_errors += result.errors.len();
service_results.insert(service_name, result);
}
// ...
}
```
### Step 7: Frontend Service Registry
In `frontend/src/lib/components/triggers/native/utils.ts`:
Add to `NATIVE_TRIGGER_SERVICES`, `getTriggerIconName()`, and `getServiceIcon()`.
### Step 8: Frontend Trigger Form Component
Create: `frontend/src/lib/components/triggers/native/services/newservice/NewServiceTriggerForm.svelte`
### Step 9: Frontend Icon Component
Create: `frontend/src/lib/components/icons/NewServiceIcon.svelte`
### Step 10: Update NativeTriggerEditor
Check `frontend/src/lib/components/triggers/native/NativeTriggerEditor.svelte` to ensure it dynamically loads form components based on service name.
### Step 11: Workspace Integration UI
Add your service to the `supportedServices` map in `frontend/src/lib/components/workspaceSettings/WorkspaceIntegrations.svelte`:
```typescript
const supportedServices: Record<string, ServiceConfig> = {
// ... existing services ...
newservice: {
name: 'newservice',
displayName: 'New Service',
description: 'Connect to New Service for triggers',
icon: NewServiceIcon,
docsUrl: 'https://www.windmill.dev/docs/integrations/newservice',
requiresBaseUrl: false, // false for cloud services, true for self-hosted
setupInstructions: [
'Step 1: Create an OAuth app on the service',
'Step 2: Configure the redirect URI shown below',
'Step 3: Enter the client credentials below'
]
}
}
```
### Step 12: Update `frontend/src/lib/components/triggers/utils.ts`
Update ALL of these maps/functions:
1. `triggerIconMap` - import and add icon
2. `triggerDisplayNamesMap` - add display name
3. `triggerTypeOrder` in `sortTriggers()` - add type
4. `getLightConfig()` - add case for your service
5. `getTriggerLabel()` - add case for your service
6. `jobTriggerKinds` - add to array
7. `countPropertyMap` - add count property
8. `triggerSaveFunctions` - add save function
### Step 13: Update TriggersBadge Component
In `frontend/src/lib/components/graph/renderers/triggers/TriggersBadge.svelte`:
1. Import the icon
2. Add to `baseConfig` with `countKey` (the dynamic `availableNativeServices` loop does NOT set `countKey`)
3. Add to the `allTypes` array
### Step 14: Update TriggersWrapper.svelte
In `frontend/src/lib/components/triggers/TriggersWrapper.svelte`:
Add a `{:else if selectedTrigger.type === 'yourservice'}` case that renders `<NativeTriggersPanel service="yourservice" ...>` with the same props pattern as the existing native trigger cases (e.g., `nextcloud`).
### Step 15: Update AddTriggersButton.svelte
In `frontend/src/lib/components/triggers/AddTriggersButton.svelte`:
1. Add `yourserviceAvailable` state variable
2. Add `setYourserviceState()` async function using `isServiceAvailable('yourservice', $workspaceStore!)`
3. Call it at module level
4. Add a dropdown entry to `addTriggerItems` with `hidden: !yourserviceAvailable`
### Step 16: Update TriggersEditor.svelte Delete Handling
In `frontend/src/lib/components/triggers/TriggersEditor.svelte`:
Add your service to the `nativeTriggerServices` map in `deleteDeployedTrigger()`. Native triggers use `NativeTriggerService.deleteNativeTrigger({ workspace, serviceName, externalId })` instead of the standard `path`-based delete.
### Step 17: Update OpenAPI Spec and Regenerate Types
Add to `JobTriggerKind` enum in `backend/windmill-api/openapi.yaml`, then:
```bash
cd frontend && npm run generate-backend-client
```
---
## Special Patterns
### Unified Service with `trigger_type` (Google Pattern)
When a single service handles multiple trigger types (e.g., Google Drive + Calendar share OAuth and API patterns), use a single `ServiceName` variant with a discriminator field:
```rust
pub enum GoogleTriggerType { Drive, Calendar }
pub struct GoogleServiceConfig {
pub trigger_type: GoogleTriggerType,
// Drive-specific fields (only used when trigger_type = Drive)
pub resource_id: Option<String>,
pub resource_name: Option<String>,
// Calendar-specific fields (only used when trigger_type = Calendar)
pub calendar_id: Option<String>,
pub calendar_name: Option<String>,
// Metadata set after creation
pub google_resource_id: Option<String>,
pub expiration: Option<String>,
}
```
Branch in trait methods based on `trigger_type`. Frontend uses a `ToggleButtonGroup` to switch between types. This keeps the codebase simpler (one service, one OAuth flow, one set of routes).
See `backend/windmill-native-triggers/src/google/` for the reference implementation.
### Skipping update+get After Create (Google Pattern)
Override `service_config_from_create_response()` to return `Some(config)` when the external_id is known before the create call:
```rust
fn service_config_from_create_response(
&self,
data: &NativeTriggerData<Self::ServiceConfig>,
resp: &Self::CreateResponse,
) -> Option<serde_json::Value> {
// Clone input config, add metadata from response
let mut config = data.service_config.clone();
config.google_resource_id = Some(resp.resource_id.clone());
config.expiration = Some(resp.expiration.clone());
Some(serde_json::to_value(&config).unwrap())
}
```
### Services with Absolute OAuth Endpoints (Google)
Unlike self-hosted services where OAuth endpoints are relative paths appended to `base_url`, services like Google have absolute URLs:
```rust
// Nextcloud: relative paths
ServiceName::Nextcloud => "/apps/oauth2/api/v1/token",
// Google: absolute URLs
ServiceName::Google => "https://oauth2.googleapis.com/token",
```
The `resolve_endpoint()` function handles both. For services with absolute endpoints:
- `base_url` can be empty
- `requiresBaseUrl: false` in the frontend workspace integration config
- Add `extra_auth_params()` if needed (Google requires `access_type=offline` and `prompt=consent`)
### Channel-Based Push Notifications with Renewal (Google Pattern)
For services using expiring watch channels instead of persistent webhooks:
1. Store expiration in `service_config` (as part of `ServiceConfig`)
2. In `maintain_triggers()`, implement renewal logic instead of using `reconcile_with_external_state()`:
```rust
async fn maintain_triggers(&self, db, workspace_id, triggers, oauth_data, synced, errors) {
for trigger in triggers {
if should_renew_channel(trigger) {
self.renew_channel(db, trigger, oauth_data).await;
}
}
}
```
3. Renewal: best-effort stop old channel, create new one with same external_id, update service_config with new expiration
4. Google example: Drive channels expire in 24h (renew when <1h left), Calendar channels expire in 7 days (renew when <1 day left)
### reconcile_with_external_state (Nextcloud Pattern)
The reusable function in `sync.rs` compares external triggers with DB state:
- Triggers missing externally: sets error "Trigger no longer exists on external service"
- Triggers present externally: clears errors, updates service_config if it differs
Usage in `maintain_triggers()`:
```rust
let external_pairs: Vec<(String, serde_json::Value)> = /* fetch from external */;
crate::sync::reconcile_with_external_state(
db, workspace_id, Self::SERVICE_NAME, triggers, &external_pairs, synced, errors,
).await;
```
### Webhook Payload Processing
Override `prepare_webhook()` to parse service-specific payloads into script/flow args:
```rust
async fn prepare_webhook(&self, db, w_id, headers, body, script_path, is_flow) -> Result<PushArgsOwned> {
let mut args = HashMap::new();
args.insert("event_type".to_string(), Box::new(headers.get("x-event-type").cloned()) as _);
args.insert("payload".to_string(), Box::new(serde_json::from_str::<serde_json::Value>(&body)?) as _);
Ok(PushArgsOwned { extra: None, args })
}
```
Then register in `prepare_native_trigger_args()` in `lib.rs`:
```rust
pub async fn prepare_native_trigger_args(service_name, db, w_id, headers, body) -> Result<Option<PushArgsOwned>> {
match service_name {
ServiceName::Google => { /* ... */ Ok(Some(args)) }
ServiceName::NewService => { /* ... */ Ok(Some(args)) }
ServiceName::Nextcloud => Ok(None), // Uses default body parsing
}
}
```
### Instance-Level OAuth Credentials
When `workspace_integrations.oauth_data.instance_shared == true`, `decrypt_oauth_data()` reads `client_id` and `client_secret` from instance-level global settings instead of workspace-level. This allows admins to share OAuth app credentials across workspaces.
The frontend handles this via the `generate_instance_connect_url` endpoint in `workspace_integrations.rs`.
---
## Testing Checklist
- [ ] Database migration runs successfully
- [ ] `cargo check -p windmill-native-triggers --features native_trigger` passes
- [ ] `npx svelte-check --threshold error` passes (in frontend/)
- [ ] Service appears in workspace integrations list
- [ ] OAuth flow completes successfully
- [ ] Can create a new trigger
- [ ] Can view trigger details
- [ ] Can update trigger configuration
- [ ] Can delete trigger
- [ ] Webhook receives and processes payloads
- [ ] Background sync works correctly (reconciliation or channel renewal)
- [ ] Error handling works (expired tokens, service unavailable)
---
## Reference Implementations
### Nextcloud (Self-Hosted, Update+Get Pattern)
| File | Purpose |
|------|---------|
| `nextcloud/mod.rs` | Types: NextCloudOAuthData, NextcloudServiceConfig, NextCloudTriggerData |
| `nextcloud/external.rs` | External trait: uses update+get pattern, reconcile_with_external_state for sync |
| `nextcloud/routes.rs` | Additional route: `GET /events` |
Key patterns: relative OAuth endpoints, base_url required, list_all + reconcile for sync, update returns JSON from get().
### Google (Cloud, Unified Service, Short Create)
| File | Purpose |
|------|---------|
| `google/mod.rs` | Types: GoogleServiceConfig with trigger_type discriminator, GoogleTriggerType enum |
| `google/external.rs` | External trait: overrides service_config_from_create_response, channel renewal for sync |
| `google/routes.rs` | Additional routes: `GET /calendars`, `GET /drive/files`, `GET /drive/shared_drives` |
Key patterns: absolute OAuth endpoints, empty base_url, trigger_type for Drive/Calendar, expiring watch channels with renewal, service_config_from_create_response skips update+get, get() reconstructs data from stored service_config (no external "get channel" API).

View File

@@ -226,4 +226,93 @@ When generating Svelte 5 code, prioritize frontend performance by applying the f
Hello
</div>
```
5. **Stay Updated**: Keep Svelte and its related packages up to date to benefit from the latest features, performance improvements, and security fixes.
5. **Stay Updated**: Keep Svelte and its related packages up to date to benefit from the latest features, performance improvements, and security fixes.
## Windmill UI Component Rules (MUST follow)
Always use Windmill's own design-system components instead of raw HTML elements. Using raw HTML elements produces inconsistent styling and breaks the design language.
### Icons — use `lucide-svelte`
**Never** write inline SVGs. Import icons from `lucide-svelte`.
```svelte
<script>
import { ChevronLeft, ChevronRight, X } from 'lucide-svelte'
</script>
<ChevronLeft size={16} />
```
### Buttons — use `<Button>`
**Never** use `<button>`. Import and use `Button` from `$lib/components/common`.
```svelte
<script>
import { Button } from '$lib/components/common'
import { ChevronLeft, ChevronRight } from 'lucide-svelte'
</script>
<!-- Regular button -->
<Button variant="default" onclick={handleClick}>Label</Button>
<!-- Icon-only button (no label) -->
<Button startIcon={{ icon: ChevronLeft }} iconOnly onclick={prevMonth} />
<Button startIcon={{ icon: ChevronRight }} iconOnly onclick={nextMonth} />
```
Key `Button` props:
- `variant?: 'accent' | 'accent-secondary' | 'default' | 'subtle'`
- `unifiedSize?: 'sm' | 'md' | 'lg'`
- `startIcon?: { icon: SvelteComponent }` — renders an icon before the label
- `iconOnly?: boolean` — renders icon with no surrounding label text
- `disabled?: boolean`
### Text inputs — use `<TextInput>`
**Never** use `<input>`. Import and use `TextInput` from `$lib/components/common`.
```svelte
<script>
import { TextInput } from '$lib/components/common'
let val = $state('')
</script>
<TextInput bind:value={val} placeholder="Enter value" />
```
Key `TextInput` props:
- `value?: string | number` (bindable)
- `placeholder?: string`
- `disabled?: boolean`
- `error?: string | boolean`
- `size?: 'sm' | 'md' | 'lg'`
- `inputProps?` — forwarded to the underlying `<input>`
### Selects — use `<Select>`
**Never** use `<select>`. Import and use `Select` from `$lib/components/select/Select.svelte`.
```svelte
<script>
import Select from '$lib/components/select/Select.svelte'
const monthItems = [
{ label: 'January', value: 1 },
{ label: 'February', value: 2 },
// ...
]
let selectedMonth = $state(1)
</script>
<Select items={monthItems} bind:value={selectedMonth} />
```
Key `Select` props:
- `items?: Array<{ label?: string; value: any; subtitle?: string; disabled?: boolean }>`
- `value` (bindable) — the currently selected `.value`
- `placeholder?: string`
- `clearable?: boolean`
- `disabled?: boolean`
- `size?: 'sm' | 'md' | 'lg'`

View File

@@ -44,6 +44,10 @@ RUN /usr/local/bin/python3 -m pip install pip-tools
# Bun
COPY --from=oven/bun:1.3.8 /usr/local/bin/bun /usr/bin/bun
# Install windmill CLI
RUN bun install -g windmill-cli \
&& ln -s $(bun pm bin -g)/wmill /usr/bin/wmill
ARG TARGETPLATFORM
# Deno

View File

@@ -70,13 +70,23 @@ jobs:
with:
ruby-version: "3.3"
bundler-cache: false
- name: Install windmill CLI from source
run: |
cd $GITHUB_WORKSPACE/cli
bash gen_wm_client.sh
bun install
mkdir -p "$HOME/.local/bin"
printf '#!/bin/sh\nexec bun run "%s/cli/src/main.ts" "$@"\n' "$GITHUB_WORKSPACE" > "$HOME/.local/bin/wmill"
chmod +x "$HOME/.local/bin/wmill"
echo "$HOME/.local/bin" >> $GITHUB_PATH
working-directory: /
- name: Install PowerShell, mold and clang
run: |
sudo apt-get update && sudo apt-get install -y powershell mold clang libcurl4-openssl-dev
working-directory: /
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false
cache-workspaces: backend
toolchain: 1.93.0
- name: Read EE repo commit hash
run: |
@@ -165,6 +175,12 @@ jobs:
fi
echo "NPM_TOKEN=${NPM_TOKEN}" >> $GITHUB_ENV
{
echo "TEST_NPMRC<<NPMRC_EOF"
echo "@windmill-test:registry=http://localhost:4873/"
echo "//localhost:4873/:_authToken=${NPM_TOKEN}"
echo "NPMRC_EOF"
} >> $GITHUB_ENV
echo "Got NPM token successfully: ${NPM_TOKEN:0:10}..."
# Configure npm globally with the auth token

View File

@@ -9,7 +9,7 @@ permissions: write-all
jobs:
build_ee:
runs-on: ubicloud
runs-on: ubicloud-standard-4
steps:
- uses: actions/checkout@v4
with:

View File

@@ -9,7 +9,7 @@ permissions: write-all
jobs:
build_ee:
runs-on: ubicloud
runs-on: ubicloud-standard-4
steps:
- uses: actions/checkout@v4
with:

View File

@@ -23,16 +23,16 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Generate Windmill client
working-directory: cli
run: ./gen_wm_client.sh
@@ -69,11 +69,6 @@ jobs:
cache: true
cache-workspaces: backend
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Setup Node.js
uses: actions/setup-node@v4
with:
@@ -90,6 +85,10 @@ jobs:
- name: Symlink Node to /usr/bin/node
run: sudo ln -sf $(which node) /usr/bin/node
- name: Install dependencies
working-directory: cli
run: bun install
- name: Generate Windmill clients
working-directory: cli
run: |
@@ -101,12 +100,10 @@ jobs:
env:
DATABASE_URL: postgres://postgres:changeme@localhost:5432
CI_MINIMAL_FEATURES: "true"
run: |
deno test --no-check --allow-all test/ \
--ignore=test/cargo_backend_example.test.ts
run: bun test --timeout 120000 test/
test-windows:
runs-on: windows-latest
runs-on: blacksmith-16vcpu-windows-2025
steps:
- name: Checkout code
@@ -126,11 +123,6 @@ jobs:
cache: true
cache-workspaces: backend
- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- name: Setup Node.js
uses: actions/setup-node@v4
with:
@@ -150,6 +142,10 @@ jobs:
echo "BUN_PATH=$bunPath" >> $env:GITHUB_OUTPUT
echo "NODE_BIN_PATH=$nodePath" >> $env:GITHUB_OUTPUT
- name: Install dependencies
working-directory: cli
run: bun install
- name: Generate Windmill clients
working-directory: cli
shell: bash
@@ -165,9 +161,12 @@ jobs:
CI_MINIMAL_FEATURES: "true"
BUN_PATH: ${{ steps.runtime-paths.outputs.BUN_PATH }}
NODE_BIN_PATH: ${{ steps.runtime-paths.outputs.NODE_BIN_PATH }}
run: |
deno test --no-check --allow-all test/ `
--ignore=test/cargo_backend_example.test.ts
run: bun test --timeout 120000 test/
- name: Keep runner alive for SSH debug
if: failure()
shell: pwsh
run: Start-Sleep -Seconds 3600
# Combined summary job for branch protection
test-summary:

View File

@@ -6,6 +6,12 @@ on:
- opened
- ready_for_review
- closed
issue_comment:
types:
- created
pull_request_review_comment:
types:
- created
jobs:
notify_discord_when_pr_opened:
@@ -33,3 +39,38 @@ jobs:
PR_NUMBER: ${{ github.event.pull_request.number }}
secrets:
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}
notify_discord_on_comment:
if: >
github.event_name == 'issue_comment'
&& github.event.issue.pull_request
&& github.event.comment.user.login != 'cloudflare-workers-and-pages[bot]'
&& github.event.comment.user.login != 'ellipsis-dev[bot]'
uses: ./.github/workflows/shareable-discord-notification.yml
with:
PR_STATUS: "comment"
PR_NUMBER: ${{ github.event.issue.number }}
COMMENT_BODY: ${{ github.event.comment.body }}
COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
COMMENT_URL: ${{ github.event.comment.html_url }}
DISCORD_CHANNEL_ID: "1372204995868491786"
DISCORD_GUILD_ID: "930051556043276338"
secrets:
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}
notify_discord_on_review_comment:
if: >
github.event_name == 'pull_request_review_comment'
&& github.event.comment.user.login != 'cloudflare-workers-and-pages[bot]'
&& github.event.comment.user.login != 'ellipsis-dev[bot]'
uses: ./.github/workflows/shareable-discord-notification.yml
with:
PR_STATUS: "comment"
PR_NUMBER: ${{ github.event.pull_request.number }}
COMMENT_BODY: ${{ github.event.comment.body }}
COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
COMMENT_URL: ${{ github.event.comment.html_url }}
DISCORD_CHANNEL_ID: "1372204995868491786"
DISCORD_GUILD_ID: "930051556043276338"
secrets:
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_AI_BOT_TOKEN }}

View File

@@ -25,9 +25,9 @@ jobs:
with:
node-version: "20.x"
registry-url: "https://registry.npmjs.org"
- uses: denoland/setup-deno@v2
- uses: oven-sh/setup-bun@v2
with:
deno-version: v2.x
bun-version: latest
- run: cd cli && ./build.sh && cd npm && npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@@ -24,9 +24,22 @@ on:
DISCORD_GUILD_ID:
description: "The Discord guild ID"
type: string
COMMENT_BODY:
description: "The comment body"
type: string
default: ""
COMMENT_AUTHOR:
description: "The comment author"
type: string
default: ""
COMMENT_URL:
description: "The comment URL"
type: string
default: ""
secrets:
DISCORD_WEBHOOK_URL:
description: "Discord Webhook URL"
required: false
DISCORD_BOT_TOKEN:
description: "Discord Bot Token"
@@ -117,3 +130,54 @@ jobs:
curl -X PUT \
-H "Authorization: Bot $BOT_TOKEN" \
"https://discord.com/api/v10/channels/$thread_id/messages/$message_id/reactions/%E2%9C%85/@me"
post_comment:
runs-on: ubuntu-latest
if: ${{ inputs.PR_STATUS == 'comment' }}
steps:
- name: Post comment to Discord thread
env:
BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
CHANNEL_ID: ${{ inputs.DISCORD_CHANNEL_ID }}
GUILD_ID: ${{ inputs.DISCORD_GUILD_ID }}
PR_NUMBER: ${{ inputs.PR_NUMBER }}
COMMENT_BODY: ${{ inputs.COMMENT_BODY }}
COMMENT_AUTHOR: ${{ inputs.COMMENT_AUTHOR }}
COMMENT_URL: ${{ inputs.COMMENT_URL }}
run: |
# 1) Find the thread by PR number
threads=$(curl -s -H "Authorization: Bot $BOT_TOKEN" \
"https://discord.com/api/v10/guilds/${GUILD_ID}/threads/active")
thread_id=$(echo "$threads" | jq -r \
--arg cid "$CHANNEL_ID" \
--arg pref "#${PR_NUMBER}:" \
'.threads[] | select(.parent_id == $cid and (.name | startswith($pref))) | .id')
if [ -z "$thread_id" ]; then
echo "Thread not found for PR #${PR_NUMBER}, skipping"
exit 0
fi
# 2) Truncate comment body to fit Discord's 2000 char limit
# Reserve space for the author line + link (~100 chars)
max_body=1800
if [ ${#COMMENT_BODY} -gt $max_body ]; then
# For bot comments, show the tail (conclusions/code tend to be at the end)
if [[ "$COMMENT_AUTHOR" == *"[bot]"* ]] || [[ "$COMMENT_AUTHOR" == *"-bot"* ]]; then
truncated_body="...${COMMENT_BODY: -$max_body}"
else
truncated_body="${COMMENT_BODY:0:$max_body}..."
fi
else
truncated_body="$COMMENT_BODY"
fi
# 3) Post the comment to the thread
message=$(printf '**%s** [commented](%s):\n%s' "$COMMENT_AUTHOR" "$COMMENT_URL" "$truncated_body")
payload=$(jq -n --arg content "$message" '{content: $content, flags: 4, allowed_mentions: {parse: []}}')
curl -s -X POST \
-H "Authorization: Bot $BOT_TOKEN" \
-H "Content-Type: application/json" \
-d "$payload" \
"https://discord.com/api/v10/channels/${thread_id}/messages"

7
.gitignore vendored
View File

@@ -14,9 +14,16 @@ backend/.minio-data
!.aiderignore
rust-client/Cargo.toml
# Worktree-generated port isolation
.env.local
# Worktree-specific Claude Code settings (generated by scripts/worktree-env)
.claude/settings.local.json
# Symlinked cache directories (for git worktrees)
backend/target
frontend/node_modules
typescript-client/node_modules
frontend/.svelte-kit
backend/chrome_profiler.json
.fast-check/

View File

@@ -3,10 +3,6 @@
"svelte": {
"type": "http",
"url": "https://mcp.svelte.dev/mcp"
},
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@latest"]
}
}
}

79
.wmdev.yaml Normal file
View File

@@ -0,0 +1,79 @@
services:
- name: BE
portEnv: BACKEND_PORT
- name: FE
portEnv: FRONTEND_PORT
profiles:
default:
name: default
sandbox:
name: sandbox
image: windmill-sandbox
envPassthrough:
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- R2_ENDPOINT
- R2_BUCKET
- R2_PUBLIC_URL
extraMounts:
- hostPath: ~/.codex
guestPath: /root/.codex
writable: true
- hostPath: ~/windmill-ee-private
writable: true
- hostPath: ~/windmill-ee-private__worktrees
writable: true
systemPrompt: >
You are running inside a sandboxed container with full permissions.
This worktree is configured with the following ports:
- Backend: port ${BACKEND_PORT}.
Start with: cd backend && PORT=${BACKEND_PORT}
DATABASE_URL=postgres://postgres:changeme@localhost:5432/windmill
cargo watch -x run
- Frontend: port ${FRONTEND_PORT}.
Start with: cd frontend && REMOTE=http://localhost:${BACKEND_PORT}
npm run dev -- --port ${FRONTEND_PORT} --host 0.0.0.0
--- Screenshots ---
You can take screenshots of the frontend UI and upload them to R2
for use in PR descriptions.
1) Take a screenshot:
bunx playwright screenshot --browser chromium
http://localhost:${FRONTEND_PORT}/path/to/page /tmp/screenshot.png
2) Upload to R2:
aws s3 cp /tmp/screenshot.png
"s3://$(printenv R2_BUCKET)/$(git rev-parse --abbrev-ref HEAD)/screenshot.png"
--endpoint-url "$(printenv R2_ENDPOINT)"
3) The public URL will be:
$(printenv R2_PUBLIC_URL)/<branch>/screenshot.png
4) Include screenshots in PR descriptions as markdown images:
![description]($(printenv R2_PUBLIC_URL)/<branch>/screenshot.png)
--- Terminal Recordings (asciinema) ---
You can record terminal sessions and upload them for sharing.
asciinema is pre-installed at /usr/local/bin/asciinema.
1) Write a shell script with the commands to demo. Add sleep
delays for readable pacing:
- 0.5s after printing a "$ command" line (lets viewer read it)
- 1.5-2s after command output (lets viewer absorb the result)
- Set GIT_PAGER=cat and PAGER=cat to prevent pager hangs
2) Record headlessly:
asciinema rec --headless --overwrite \
-c "bash /tmp/demo.sh" \
--window-size 120x50 \
--title "Description of demo" \
/tmp/demo.cast
3) Upload to asciinema.org:
XDG_DATA_HOME=/tmp/.local/share \
asciinema upload --server-url https://asciinema.org /tmp/demo.cast
linkedRepos:
- repo: windmill-labs/windmill-ee-private
alias: ee

72
.workmux.yaml Normal file
View File

@@ -0,0 +1,72 @@
main_branch: main
merge_strategy: rebase
# worktree_dir: .worktrees
worktree_naming: basename
worktree_prefix: ""
# Default: "wm-"
window_prefix: "wm-"
auto_name:
model: "gemini-2.5-flash-lite"
system_prompt: |
Generate a concise git branch name based on the task description.
Rules:
- Use kebab-case (lowercase with hyphens)
- Keep it short: 1-3 words, max 4 if necessary
- Focus on the core task/feature, not implementation details
- No prefixes like feat/, fix/, chore/
Examples of good branch names:
- "Add dark mode toggle" → dark-mode
- "Fix the search results not showing" → fix-search
- "Refactor the authentication module" → auth-refactor
- "Add CSV export to reports" → export-csv
- "Shell completion is broken" → shell-completion
Output ONLY the branch name, nothing else.
background: true
# Commands to run in new worktree before tmux window opens.
# These block window creation - use for short tasks only.
# Use "<global>" to inherit from global config.
# Set to empty list to disable: `post_create: []`
# post_create:
# - "<global>"
# - mise use
post_create:
- ./scripts/worktree-env
pre_remove:
- ./scripts/worktree-cleanup
panes:
- command: >-
claude --dangerously-skip-permissions --append-system-prompt
"You are running inside a tmux session with other panes running services.\n
Pane layout (current window):\n
- Pane 0: this pane (claude agent)\n
- Pane 1: backend (cargo watch -x run)\n
- Pane 2: frontend (npm run dev)\n\n
To check logs, use: \`tmux capture-pane -t .1 -p -S -50\` (backend) or \`tmux capture-pane -t .2 -p -S -50\` (frontend).\n
When restarting backend or frontend, make sure to use the ports listed in .env.local.\n
Because we are running backend with cargo watch, to verify your changes, just check the logs in the backend pane. No need for cargo check."
focus: true
- command: 'ROOT="$(git rev-parse --show-toplevel)"; [ -f "$ROOT/.env.local" ] && source "$ROOT/.env.local"; cd "$ROOT/backend" && PORT=${BACKEND_PORT:-8000} cargo watch -x "run ${CARGO_FEATURES:+--features $CARGO_FEATURES}"'
split: horizontal
- command: 'ROOT="$(git rev-parse --show-toplevel)"; [ -f "$ROOT/.env.local" ] && source "$ROOT/.env.local"; cd "$ROOT/frontend" && npm run generate-backend-client && REMOTE=${REMOTE:-http://localhost:${BACKEND_PORT:-8000}} npm run dev -- --port ${FRONTEND_PORT:-3000} --host 0.0.0.0'
split: vertical
files:
copy:
- backend/.env
- scripts/
sandbox:
enabled: false
toolchain: off

View File

@@ -1,5 +1,185 @@
# Changelog
## [1.644.0](https://github.com/windmill-labs/windmill/compare/v1.643.0...v1.644.0) (2026-02-24)
### Features
* **cli:** detect missing folders on sync push and add 'wmill folder add-missing' ([#8011](https://github.com/windmill-labs/windmill/issues/8011)) ([835db5d](https://github.com/windmill-labs/windmill/commit/835db5d290a151f38f4e879ed7ffbda5d1c4b24f))
### Bug Fixes
* prevent concurrent index migrations from re-running on every startup ([#8069](https://github.com/windmill-labs/windmill/issues/8069)) ([8ff2340](https://github.com/windmill-labs/windmill/commit/8ff2340c0c08ce49a809c8958a9862ffb1681642))
## [1.643.0](https://github.com/windmill-labs/windmill/compare/v1.642.0...v1.643.0) (2026-02-24)
### Features
* add fileset resource type support ([32c4b47](https://github.com/windmill-labs/windmill/commit/32c4b474f92f3dbbd2077fab70bdf9e407581626))
* add fileset resource type support ([#8063](https://github.com/windmill-labs/windmill/issues/8063)) ([c15b9ab](https://github.com/windmill-labs/windmill/commit/c15b9abe5eb2a1566a7ce4b18784c961d178a669))
* add light mode for navigation sidebar ([#8057](https://github.com/windmill-labs/windmill/issues/8057)) ([0935bf9](https://github.com/windmill-labs/windmill/commit/0935bf9fc460c03c6d8469b93036e43714517ef2))
* **aiagent:** handle ai agent as tool ([#8031](https://github.com/windmill-labs/windmill/issues/8031)) ([de6fd16](https://github.com/windmill-labs/windmill/commit/de6fd160d56c1037adbbe785f195483c25982e1c))
* Unified filters and new runs page ([#8027](https://github.com/windmill-labs/windmill/issues/8027)) ([9b28c85](https://github.com/windmill-labs/windmill/commit/9b28c85469d6b2a8590810b313b030d9f00ee9e3))
### Bug Fixes
* address code review findings for fileset feature ([1b4489a](https://github.com/windmill-labs/windmill/commit/1b4489acac3b050f0a783548bacfc9bdf33ee593))
* address second round of review findings ([753c05a](https://github.com/windmill-labs/windmill/commit/753c05a03089b95b4ade68d3bf61c8818de422ce))
* **backend:** decimal between 0 and -1 in mssql ([#8051](https://github.com/windmill-labs/windmill/issues/8051)) ([9686608](https://github.com/windmill-labs/windmill/commit/9686608355615a50c8395f6e2fd51dcc25498226))
* **backend:** use filename instead of content_type to detect file fields in multipart form data ([#8054](https://github.com/windmill-labs/windmill/issues/8054)) ([0aa885d](https://github.com/windmill-labs/windmill/commit/0aa885db67d77202205fc1609e841b8ffd9a8121))
* exclude app_theme resources from workspace tab ([9c513b2](https://github.com/windmill-labs/windmill/commit/9c513b2c62acc369179fb9e404e1f4007cd854c6))
* fileset editor takes full height with matching header ([9ac0789](https://github.com/windmill-labs/windmill/commit/9ac07897cf99f3af27801e435c7376a46ef760c9))
* prevent iframe from overriding file selection after file creation ([7f3ddd7](https://github.com/windmill-labs/windmill/commit/7f3ddd7edd3ea993642aadd55cdba0ac2ea1eb9f))
* resolve svelte warnings and type error in fileset components ([4c06d74](https://github.com/windmill-labs/windmill/commit/4c06d74bd01ca2dda848be421d70dd5268520992))
* restore full-width file tree items in raw app sidebar ([5bac8b0](https://github.com/windmill-labs/windmill/commit/5bac8b093dbe913a563b02573959c64dd405ff61))
* suppress iframe setActiveDocument during file population ([1abfeea](https://github.com/windmill-labs/windmill/commit/1abfeea81a645c59934d62257ad869ed7b475634))
* update git sync init script to hub version 28158 ([#8061](https://github.com/windmill-labs/windmill/issues/8061)) ([705e186](https://github.com/windmill-labs/windmill/commit/705e186f3d4c7d8f8a88fc84b379ed9fe800a6b2))
* use correct column name completed_at instead of ended_at in count_completed_jobs_detail ([#8066](https://github.com/windmill-labs/windmill/issues/8066)) ([3aba0ed](https://github.com/windmill-labs/windmill/commit/3aba0ed2508debdc78a6631e49b074a97635f21d))
## [1.642.0](https://github.com/windmill-labs/windmill/compare/v1.641.0...v1.642.0) (2026-02-22)
### Features
* **cli:** add consistent get/list/new subcommands for all item types ([#8047](https://github.com/windmill-labs/windmill/issues/8047)) ([4fedfdf](https://github.com/windmill-labs/windmill/commit/4fedfdfd11aa8ca7fff6f7aed5ae2b313888f878))
### Bug Fixes
* make WM_FLOW_PATH available in flow step previews ([#8042](https://github.com/windmill-labs/windmill/issues/8042)) ([a91c532](https://github.com/windmill-labs/windmill/commit/a91c532ecadce63cea965c497351fa1a6f39697a))
* preserve debouncing settings for flows with preprocessors ([#8043](https://github.com/windmill-labs/windmill/issues/8043)) ([a00927b](https://github.com/windmill-labs/windmill/commit/a00927b3008a2d953fde1d461723a3c92f375eb4))
## [1.641.0](https://github.com/windmill-labs/windmill/compare/v1.640.0...v1.641.0) (2026-02-21)
### Features
* add .npmrc support for private npm registries ([#8039](https://github.com/windmill-labs/windmill/issues/8039)) ([9eb1531](https://github.com/windmill-labs/windmill/commit/9eb15312f663aa6d700e8ac562d7b5c75c2221f7))
### Bug Fixes
* add created_by ownership check to update/delete saved inputs ([#8038](https://github.com/windmill-labs/windmill/issues/8038)) ([e8a13ed](https://github.com/windmill-labs/windmill/commit/e8a13edde7c0ba2ef80344ab7c7288e7bb2eb6b5))
* run substitute_ee_code.sh after creating EE worktree ([b330f38](https://github.com/windmill-labs/windmill/commit/b330f388894ecd9cc6b64297420ac6f032d32f72))
* tag bunnative dependency jobs as bun instead of nativets ([#8045](https://github.com/windmill-labs/windmill/issues/8045)) ([fd5ebc2](https://github.com/windmill-labs/windmill/commit/fd5ebc2fda589c022074c3bb4dcdb447c7f86cf0))
## [1.640.0](https://github.com/windmill-labs/windmill/compare/v1.639.0...v1.640.0) (2026-02-20)
### Features
* add windmill-ee-private worktree support to workmux ([#8034](https://github.com/windmill-labs/windmill/issues/8034)) ([9f3dd0b](https://github.com/windmill-labs/windmill/commit/9f3dd0bf2b2ba7c622093c54b7b6b5e7ebb26b74))
* **cli:** add --locks-required flag to wmill lint and sync push ([#8026](https://github.com/windmill-labs/windmill/issues/8026)) ([4abe589](https://github.com/windmill-labs/windmill/commit/4abe58939787f375ccfef5b2dbcfbd7e86cff076))
* dedicated nativets ([#8021](https://github.com/windmill-labs/windmill/issues/8021)) ([37c9acb](https://github.com/windmill-labs/windmill/commit/37c9acb232c64c98ecfb64754f5b69b31047c625))
* Support column detection on S3 objects in DuckDB ([#8018](https://github.com/windmill-labs/windmill/issues/8018)) ([87f3de9](https://github.com/windmill-labs/windmill/commit/87f3de9ae5975c88b6748e297f84a539aec4c0ca))
### Bug Fixes
* Fix DuckDB incorrect pg password encoding ([#8028](https://github.com/windmill-labs/windmill/issues/8028)) ([90b1a7a](https://github.com/windmill-labs/windmill/commit/90b1a7a531bce5621ea4de4792a8c9d3d3beec3d))
* **frontend:** use completed_at instead of created_at for job history ([#8022](https://github.com/windmill-labs/windmill/issues/8022)) ([24d7921](https://github.com/windmill-labs/windmill/commit/24d7921bcf23543759719ffd2463959c627b61b8))
### Performance Improvements
* lazy-load JSZip in RawAppEditorHeader ([#8012](https://github.com/windmill-labs/windmill/issues/8012)) ([a1ba10a](https://github.com/windmill-labs/windmill/commit/a1ba10a29e12ab5f553bd9aad74067cc5b3ead9e))
## [1.639.0](https://github.com/windmill-labs/windmill/compare/v1.638.4...v1.639.0) (2026-02-18)
### Features
* improve FolderPicker with edit icon pattern ([#7995](https://github.com/windmill-labs/windmill/issues/7995)) ([db8aa8a](https://github.com/windmill-labs/windmill/commit/db8aa8a0839b5729f0bb847e7a71766c7883ff36))
### Bug Fixes
* default automate_username_creation to true when setting is missing ([#8006](https://github.com/windmill-labs/windmill/issues/8006)) ([d2d08f8](https://github.com/windmill-labs/windmill/commit/d2d08f8817e6e7818eb4b6f092e66ae039f0c756))
* handle raw app folder deletion in sync push without yaml parse error ([#7994](https://github.com/windmill-labs/windmill/issues/7994)) ([f6d99dd](https://github.com/windmill-labs/windmill/commit/f6d99dd18c06a7f5aea93122276dd68c45772b43))
### Performance Improvements
* **cli:** skip relock more accurate ([#7993](https://github.com/windmill-labs/windmill/issues/7993)) ([cd4151a](https://github.com/windmill-labs/windmill/commit/cd4151a84b2c1e0f2e616079091d0429bf469f4e))
## [1.638.4](https://github.com/windmill-labs/windmill/compare/v1.638.3...v1.638.4) (2026-02-17)
### Bug Fixes
* **frontend:** add folder picker validation, error handling, and loading state ([#7987](https://github.com/windmill-labs/windmill/issues/7987)) ([4ea1692](https://github.com/windmill-labs/windmill/commit/4ea1692ee27adbba583d8ead753fa8a19099183f))
* **frontend:** improve folder picker with sticky create button and drawer flow ([#7985](https://github.com/windmill-labs/windmill/issues/7985)) ([a46924a](https://github.com/windmill-labs/windmill/commit/a46924a0f21314826c00fa4ac61885bdf3700421))
## [1.638.3](https://github.com/windmill-labs/windmill/compare/v1.638.2...v1.638.3) (2026-02-17)
### Bug Fixes
* always create guidance files during wmill init ([#7974](https://github.com/windmill-labs/windmill/issues/7974)) ([f387daa](https://github.com/windmill-labs/windmill/commit/f387daa2a6c7eb260981a19c58374062f652fca6))
* **frontend:** incorrect job result on the runs page ([#7982](https://github.com/windmill-labs/windmill/issues/7982)) ([2d53939](https://github.com/windmill-labs/windmill/commit/2d5393941cf17d45d1d4ff840766f07bd482f70b))
* **frontend:** preserve user config when trimming oneOf non-selected keys ([b094649](https://github.com/windmill-labs/windmill/commit/b0946495863e206d12922536d2cae24cb78b55fc))
## [1.638.2](https://github.com/windmill-labs/windmill/compare/v1.638.1...v1.638.2) (2026-02-17)
### Bug Fixes
* **backend:** gcp private key parsing ([#7979](https://github.com/windmill-labs/windmill/issues/7979)) ([5b7bb2f](https://github.com/windmill-labs/windmill/commit/5b7bb2fb84a12433c48f1cdfc022edff0cbc88ea))
* yaml settings UI mask rsa_keys and jwt_secret ([71608bf](https://github.com/windmill-labs/windmill/commit/71608bf669658241b4ce4e1da3a83f1045dea1f6))
## [1.638.1](https://github.com/windmill-labs/windmill/compare/v1.638.0...v1.638.1) (2026-02-17)
### Bug Fixes
* **operator:** improve configmap handling of older license keys ([b7bec1a](https://github.com/windmill-labs/windmill/commit/b7bec1a83d97a823ff6fc7d7fa549b975f848066))
## [1.638.0](https://github.com/windmill-labs/windmill/compare/v1.637.0...v1.638.0) (2026-02-17)
### Features
* add native_mode as typed field on WorkerGroupConfig ([3e313cc](https://github.com/windmill-labs/windmill/commit/3e313cc4e864108d7dee866e784dff428883cadf))
* show all settings in YAML UI and protect from empty overwrites ([#7976](https://github.com/windmill-labs/windmill/issues/7976)) ([b3eeee4](https://github.com/windmill-labs/windmill/commit/b3eeee413114cb54b5932542b14d8904a3c6c93c))
### Bug Fixes
* add missing google native triggers to triggers panel ([#7966](https://github.com/windmill-labs/windmill/issues/7966)) ([bb03c62](https://github.com/windmill-labs/windmill/commit/bb03c62c2819d40acd676d10cc586958f4117b5d))
* download audit logs ([#7965](https://github.com/windmill-labs/windmill/issues/7965)) ([bba319b](https://github.com/windmill-labs/windmill/commit/bba319b2826f4d264ecebef3258d3c3f16237cc5))
* improve operator ConfigMap settings handling ([#7975](https://github.com/windmill-labs/windmill/issues/7975)) ([2019aec](https://github.com/windmill-labs/windmill/commit/2019aecf4253edcf7b33e30862f642b303948440))
## [1.637.0](https://github.com/windmill-labs/windmill/compare/v1.636.0...v1.637.0) (2026-02-17)
### Features
* **frontend:** inline edit summary & path from header ([#7968](https://github.com/windmill-labs/windmill/issues/7968)) ([eb5a8da](https://github.com/windmill-labs/windmill/commit/eb5a8dab74822eb3e43557cf1c85bf14d6e1910f))
* native mode ([#7939](https://github.com/windmill-labs/windmill/issues/7939)) ([535e108](https://github.com/windmill-labs/windmill/commit/535e108cbf5070a6a23183389007db63fb07a58f))
## [1.636.0](https://github.com/windmill-labs/windmill/compare/v1.635.1...v1.636.0) (2026-02-16)
### Features
* allow adding workspace scripts and flows as AI chat context ([#7882](https://github.com/windmill-labs/windmill/issues/7882)) ([5b8ec50](https://github.com/windmill-labs/windmill/commit/5b8ec502fef8fb439200e18b8c610d0f5998b6df))
* google native triggers ([#7837](https://github.com/windmill-labs/windmill/issues/7837)) ([6f24f19](https://github.com/windmill-labs/windmill/commit/6f24f1939d75a597acc74c1589794d511e041baa))
### Bug Fixes
* mark base_url as unsaved when using browser fallback ([#7964](https://github.com/windmill-labs/windmill/issues/7964)) ([e7b0b00](https://github.com/windmill-labs/windmill/commit/e7b0b00f5696828dec094155298d0c9dc033b355))
## [1.635.1](https://github.com/windmill-labs/windmill/compare/v1.635.0...v1.635.1) (2026-02-15)
### Bug Fixes
* pin strum_macros to 0.27 to match strum version ([#7957](https://github.com/windmill-labs/windmill/issues/7957)) ([68f766e](https://github.com/windmill-labs/windmill/commit/68f766e1ae54dbe2fe42769559d81d4d76a409ef))
## [1.635.0](https://github.com/windmill-labs/windmill/compare/v1.634.6...v1.635.0) (2026-02-15)

View File

@@ -258,6 +258,10 @@ COPY --from=denoland/deno:2.2.1 --chmod=755 /usr/bin/deno /usr/bin/deno
COPY --from=oven/bun:1.3.8 /usr/local/bin/bun /usr/bin/bun
# Install windmill CLI
RUN bun install -g windmill-cli \
&& ln -s $(bun pm bin -g)/wmill /usr/bin/wmill
COPY --from=php:8.3.7-cli /usr/local/bin/php /usr/bin/php
COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer

238
Dockerfile.sandbox Normal file
View File

@@ -0,0 +1,238 @@
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
ca-certificates \
git \
iptables \
gosu \
sudo \
unzip \
# Rust native build deps (for cargo check)
pkg-config \
cmake \
clang \
mold \
libtool \
libssl-dev \
libxml2-dev \
libxmlsec1-dev \
libxslt1-dev \
libffi-dev \
zlib1g-dev \
libcurl4-openssl-dev \
libclang-dev \
libkrb5-dev \
libsasl2-dev \
# PostgreSQL (for local DB during development)
postgresql \
postgresql-client \
# Node.js 22 (for npm run check / frontend dev)
&& curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& rm -rf /var/lib/apt/lists/* \
# Container runs as arbitrary UIDs (--user uid:gid). These three lines make
# sudo work for any UID:
# 1) NOPASSWD rule so sudo never prompts for a password
# 2) Writable passwd/group so the entrypoint can register the dynamic UID
# 3) Writable shadow so unix_chkpwd can validate the account (without this,
# sudo fails with "account validation failure, is your account locked?")
&& echo "ALL ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/sandbox \
&& chmod 0440 /etc/sudoers.d/sandbox \
&& chmod 666 /etc/passwd /etc/group /etc/shadow
# ── GitHub CLI (for PR creation) ──────────────────────────────────────────────
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
-o /usr/share/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
> /etc/apt/sources.list.d/github-cli.list \
&& apt-get update && apt-get install -y --no-install-recommends gh \
&& rm -rf /var/lib/apt/lists/*
# ── Rust toolchain ────────────────────────────────────────────────────────────
# Install under /usr/local/lib/ so bins are world-readable with default umask.
# CARGO_HOME is overridden to /tmp/.cargo at the end for mutable runtime state.
ENV RUSTUP_HOME=/usr/local/lib/rustup CARGO_HOME=/usr/local/lib/cargo
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
sh -s -- -y --default-toolchain stable --profile minimal && \
ln -s /usr/local/lib/cargo/bin/* /usr/local/bin/
RUN cargo install sqlx-cli --no-default-features --features native-tls,postgres && \
cargo install cargo-watch && \
cargo install --locked --git https://github.com/asciinema/asciinema && \
ln -sf /usr/local/lib/cargo/bin/sqlx /usr/local/bin/sqlx && \
ln -sf /usr/local/lib/cargo/bin/cargo-watch /usr/local/bin/cargo-watch && \
ln -sf /usr/local/lib/cargo/bin/asciinema /usr/local/bin/asciinema
# ── Register dynamic runtime users ───────────────────────────────────────────
RUN cat <<'SCRIPT' > /usr/local/bin/register-dynamic-user.sh
#!/bin/sh
set -eu
uid="${1:-}"
gid="${2:-}"
if [ -z "$uid" ] || [ -z "$gid" ]; then
echo "register-dynamic-user: usage: register-dynamic-user <uid> <gid>" >&2
exit 1
fi
if ! getent group "$gid" >/dev/null 2>&1; then
echo "sandbox:x:${gid}:" >> /etc/group
fi
if ! getent passwd "$uid" >/dev/null 2>&1; then
echo "sandbox:x:${uid}:${gid}:sandbox:/tmp:/bin/sh" >> /etc/passwd
fi
# Add a shadow entry ("*" = no password) so unix_chkpwd doesn't reject sudo.
if ! grep -q "^sandbox:" /etc/shadow 2>/dev/null; then
echo "sandbox:*:19000:0:99999:7:::" >> /etc/shadow
fi
SCRIPT
RUN chmod +x /usr/local/bin/register-dynamic-user.sh
# ── Network init script (iptables firewall + privilege drop) ──────────────────
RUN cat <<'SCRIPT' > /usr/local/bin/network-init.sh
#!/bin/bash
set -euo pipefail
if [ -n "${WM_PROXY_HOST:-}" ] && [ -n "${WM_PROXY_PORT:-}" ]; then
# Resolve hostnames to ALL IPs (multi-A records, round-robin DNS)
PROXY_IPS=$(getent ahostsv4 "$WM_PROXY_HOST" | awk '{print $1}' | sort -u)
RPC_HOST="${WM_RPC_HOST:-$WM_PROXY_HOST}"
RPC_IPS=$(getent ahostsv4 "$RPC_HOST" | awk '{print $1}' | sort -u)
if [ -z "$PROXY_IPS" ] || [ -z "$RPC_IPS" ]; then
echo "network-init: failed to resolve proxy/RPC host" >&2
exit 1
fi
# IPv4: default deny outbound
iptables -P OUTPUT DROP
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow DNS (UDP/TCP 53) to configured nameservers.
if [ -f /etc/resolv.conf ]; then
grep '^nameserver' /etc/resolv.conf | awk '{print $2}' | while read -r ns; do
iptables -A OUTPUT -d "$ns" -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -d "$ns" -p tcp --dport 53 -j ACCEPT
done
fi
# Allow ALL resolved proxy IPs (handles multi-A DNS)
for ip in $PROXY_IPS; do
iptables -A OUTPUT -d "$ip" -p tcp --dport "$WM_PROXY_PORT" -j ACCEPT
done
# Allow ALL resolved RPC IPs
if [ -n "${WM_RPC_PORT:-}" ]; then
for ip in $RPC_IPS; do
iptables -A OUTPUT -d "$ip" -p tcp --dport "$WM_RPC_PORT" -j ACCEPT
done
fi
# Reject (not drop) everything else to fail fast instead of hanging
iptables -A OUTPUT -j REJECT
# IPv6: block entirely to prevent leaks (fail closed)
if ip6tables -L -n >/dev/null 2>&1; then
ip6tables -P OUTPUT DROP
ip6tables -A OUTPUT -o lo -j ACCEPT
ip6tables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
ip6tables -A OUTPUT -j REJECT
else
if ! sysctl -w net.ipv6.conf.all.disable_ipv6=1 2>/dev/null; then
echo "network-init: failed to block IPv6 (neither ip6tables nor sysctl available)" >&2
exit 1
fi
fi
fi
# Add sandbox user/group so sudo works after dropping privileges.
if [ -z "${WM_TARGET_UID:-}" ] || [ -z "${WM_TARGET_GID:-}" ]; then
echo "network-init: WM_TARGET_UID and WM_TARGET_GID are required" >&2
exit 1
fi
/usr/local/bin/register-dynamic-user.sh "${WM_TARGET_UID}" "${WM_TARGET_GID}"
# Fix PTY ownership so the unprivileged user can read/write the terminal.
if [ -t 0 ]; then
chown "${WM_TARGET_UID}:${WM_TARGET_GID}" "$(tty)"
fi
# Drop privileges and exec the user command.
exec gosu "${WM_TARGET_UID}:${WM_TARGET_GID}" env HOME=/tmp "$@"
SCRIPT
RUN chmod +x /usr/local/bin/network-init.sh
# ── workmux (sandbox RPC) ────────────────────────────────────────────────────
RUN curl -fsSL https://raw.githubusercontent.com/raine/workmux/main/scripts/install.sh | bash
# ── Claude Code ───────────────────────────────────────────────────────────────
RUN curl -fsSL https://claude.ai/install.sh | bash && \
target="$(readlink -f /root/.local/bin/claude)" && \
mv /root/.local/share/claude /usr/local/lib/claude && \
ln -s "/usr/local/lib/claude/versions/$(basename "$target")" /usr/local/bin/claude && \
mkdir -p /tmp/.local/bin && \
ln -s /usr/local/bin/claude /tmp/.local/bin/claude && \
chmod -R a+rwX /tmp/.local
# ── Codex ─────────────────────────────────────────────────────────────────────
RUN npm i -g @openai/codex
# ── Bun ───────────────────────────────────────────────────────────────────────
ENV BUN_INSTALL=/usr/local/lib/bun
RUN curl -fsSL https://bun.sh/install | bash && \
ln -s /usr/local/lib/bun/bin/bun /usr/local/bin/bun && \
ln -s /usr/local/lib/bun/bin/bunx /usr/local/bin/bunx
# ── Playwright + Chromium (for screenshots) ──────────────────────────────────
ENV PLAYWRIGHT_BROWSERS_PATH=/usr/local/lib/playwright-browsers
RUN bun add -g @playwright/test \
&& bunx playwright install chromium --with-deps \
&& chmod -R a+rwX /usr/local/lib/playwright-browsers \
&& chmod -R a+rwX /usr/local/lib/bun/install \
&& rm -rf /var/lib/apt/lists/* /tmp/bunx-*
# ── AWS CLI (for S3-compatible uploads to R2) ─────────────────────────────────
RUN curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip \
&& unzip -q /tmp/awscliv2.zip -d /tmp \
&& /tmp/aws/install \
&& rm -rf /tmp/aws /tmp/awscliv2.zip
ENV AWS_DEFAULT_REGION=auto
# ── Runtime env for arbitrary UID ─────────────────────────────────────────────
# Mutable state goes to /tmp (writable by any UID). Toolchains stay read-only.
ENV CARGO_HOME=/tmp/.cargo BUN_TMPDIR=/tmp
# ── Entrypoint ────────────────────────────────────────────────────────────────
RUN cat <<'ENTRY' > /usr/local/bin/entrypoint.sh
#!/bin/sh
/usr/local/bin/register-dynamic-user.sh "$(id -u)" "$(id -g)"
# Start PostgreSQL (unix socket in /tmp, owned by postgres user)
mkdir -p /tmp/pgdata && sudo chown postgres:postgres /tmp/pgdata
if [ ! -f /tmp/pgdata/PG_VERSION ]; then
sudo -u postgres /usr/lib/postgresql/15/bin/initdb -D /tmp/pgdata --auth=trust
fi
sudo -u postgres /usr/lib/postgresql/15/bin/pg_ctl -D /tmp/pgdata -l /tmp/pg.log start -o "-k /tmp"
sudo -u postgres psql -h /tmp -c "CREATE ROLE sandbox SUPERUSER LOGIN" 2>/dev/null || true
sudo -u postgres createdb -h /tmp windmill 2>/dev/null || true
# Run database migrations so sqlx compile-time checks work
if [ -d "$PWD/backend/migrations" ]; then
DATABASE_URL="postgres://sandbox@localhost/windmill?host=/tmp" \
sqlx migrate run --source "$PWD/backend/migrations" 2>/dev/null || true
fi
# Install frontend dependencies and generate backend client
if [ -d "$PWD/frontend" ]; then
(cd "$PWD/frontend" && npm install && npm run generate-backend-client) 2>/dev/null || true
fi
exec "$@"
ENTRY
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

View File

@@ -257,6 +257,7 @@ On self-hosted instances, you might want to import all the approved resource typ
| BASE_URL | http://localhost:8000 | The base url that is exposed publicly to access your instance. Is overriden by the instance settings if any. | Server |
| ZOMBIE_JOB_TIMEOUT | 30 | The timeout after which a job is considered to be zombie if the worker did not send pings about processing the job (every server check for zombie jobs every 30s) | Server |
| RESTART_ZOMBIE_JOBS | true | If true then a zombie job is restarted (in-place with the same uuid and some logs), if false the zombie job is failed | Server |
| NATIVE_MODE | false | Enable native mode: sets NUM_WORKERS=8, rejects non-native jobs (nativets, postgresql, mysql, etc.) | Worker |
| SLEEP_QUEUE | 50 | The number of ms to sleep in between the last check for new jobs in the DB. It is multiplied by NUM_WORKERS such that in average, for one worker instance, there is one pull every SLEEP_QUEUE ms. | Worker |
| KEEP_JOB_DIR | false | Keep the job directory after the job is done. Useful for debugging. | Worker |
| LICENSE_KEY (EE only) | None | License key checked at startup for the Enterprise Edition of Windmill | Worker |

289
README_WORKMUX_DEV.md Normal file
View File

@@ -0,0 +1,289 @@
# Windmill Development with workmux
This guide covers the workmux-based development setup for Windmill. Each worktree gets its own tmux window with a Claude Code agent, a backend server (with auto-reload), and a frontend dev server — all on isolated ports.
## Prerequisites
- tmux
- Rust toolchain (rustup)
- Node.js + npm
- PostgreSQL running locally (see `backend/.env`)
## Installation
### 1. Install workmux
```bash
cargo install workmux
```
### 2. Install the Claude Code plugin
```bash
workmux claude install
```
This lets workmux manage Claude Code agents in worktree panes.
### 3. Install cargo-watch
Used for auto-recompiling the backend on file changes:
```bash
cargo install cargo-watch
```
### 4. Install llm CLI (required for auto branch naming)
workmux uses the `llm` CLI to automatically generate branch names from prompts. Install it with:
```bash
uv tool install llm
llm install llm-anthropic
```
Then set your Anthropic API key:
```bash
llm keys set anthropic
# paste your API key when prompted
```
### 5. Recommended: shell alias and autocomplete
Set up a `wm` alias for convenience:
```bash
# Add to your ~/.zshrc
alias wm="workmux"
```
Setting up zsh autocomplete is also recommended — see the [workmux docs](https://github.com/rubenfiszel/workmux) for instructions.
## Port Slot System
Each worktree is assigned a **slot** that determines its ports:
| Slot | Backend | Frontend |
| ---- | ------- | -------- |
| 0 | 8000 | 3000 |
| 1 | 8010 | 3010 |
| 2 | 8020 | 3020 |
| 3 | 8030 | 3030 |
| ... | ... | ... |
- **Slot 0** is reserved for the main worktree (default `cargo run` / `npm run dev`).
- Without `WM_SLOT`, the script auto-assigns the first available slot (starting from 1) and prints it.
- With `WM_SLOT=N`, it uses that slot and errors if the ports are taken.
## SSH Port Forwarding
If you develop over SSH, add this to `~/.ssh/config` on your **local machine** to pre-configure tunnels for each slot:
```
Host windmill-dev
HostName <remote-ip>
User <username>
# Slot 0 (main worktree)
LocalForward 8000 localhost:8000
LocalForward 3000 localhost:3000
# Slot 1
LocalForward 8010 localhost:8010
LocalForward 3010 localhost:3010
# Slot 2
LocalForward 8020 localhost:8020
LocalForward 3020 localhost:3020
# Slot 3
LocalForward 8030 localhost:8030
LocalForward 3030 localhost:3030
```
Then connect once and all tunnels are active:
```bash
ssh windmill-dev
```
Access the frontend at `http://localhost:<frontend-port>` in your local browser.
## Quickstart
```bash
# Create a new worktree (auto-assigns slot, prints ports)
workmux add my-feature
# Or with an explicit slot
WM_SLOT=2 workmux add my-feature
# Create a worktree and immediately send a prompt to the agent
workmux add -A -p "fix the login bug in auth.rs"
```
The `add` command creates the worktree but does **not** open it. To open the tmux window and start working:
```bash
workmux open my-feature
```
This will open a tmux window with three panes:
- **Claude Code agent** (focused)
- **Backend**: `cargo watch -x run` on the assigned port (auto-reloads on save)
- **Frontend**: `npm run dev` proxying to the backend
When using `-A` with `add`, the worktree is created and opened automatically, and the prompt is sent to the agent right away.
Check which ports were assigned:
```bash
cat <worktree-path>/.env.local
```
### Sending work to the agent
```bash
# Send a prompt to the agent in a worktree
workmux send my-feature "fix the login bug in auth.rs"
# Check agent status
workmux status
```
### Merging and cleaning up
We never merge worktrees directly — always create a PR on GitHub and let it be merged there. Once the PR is merged, clean up the worktree:
```bash
# Close the tmux window but keep the worktree
workmux close my-feature
# After your PR is merged, remove the worktree, branch, and tmux window
workmux rm my-feature
```
> **Note**: Do not use `workmux merge`. Always go through a PR to get your changes into main. You can ask the Claude Code agent in the worktree to create the PR for you.
## Configuration
The setup is defined in `.workmux.yaml` at the repo root. Key sections:
- **`post_create`**: Runs `scripts/worktree-env` to generate `.env.local` with port assignments
- **`panes`**: Defines the tmux layout (agent, backend, frontend)
- **`files.copy`**: Copies `backend/.env` and `scripts/` into each worktree
The `post_create` hook also copies `frontend/node_modules` using `cp -a` (preserves `.bin/` symlinks that `cp -r` would dereference).
## Enterprise (EE) Code Access
The enterprise source code lives in the `windmill-ee-private` repository (sibling to this repo). When you create a worktree, `scripts/worktree-env` automatically creates a matching EE worktree on the same branch and configures Claude Code's `additionalDirectories` to grant access.
### Sandbox setup
When using sandbox mode, the container needs explicit mounts to access the EE repo. Add the following to your global workmux config (`~/.config/workmux/config.yaml`):
```yaml
sandbox:
extra_mounts:
- host_path: ~/windmill-ee-private
writable: true
- host_path: ~/windmill-ee-private__worktrees
writable: true
```
This mounts both the main EE repo (used by the main worktree) and the EE worktrees directory (used by feature worktrees) into every sandbox container.
## Cursor SSH Integration (`wmc`)
`wm-cursor` (aliased as `wmc`) gives each worktree its own Cursor SSH remote window with an independently-focused tmux session. All windows are visible in the status bar across all Cursor terminals, but each one is focused on its own worktree.
This uses **grouped tmux sessions** — multiple sessions that share the same window list but track focus independently:
```
tmux session: main <-- your main Cursor terminal
tmux session: cursor-feat-a <-- Cursor window for feat-a (focused on wm-feat-a)
tmux session: cursor-feat-b <-- Cursor window for feat-b (focused on wm-feat-b)
\__ all three share the same windows in the status bar
```
### Setup
Run once from inside tmux on the remote:
```bash
./scripts/wm-cursor setup /home/hugo/projects/windmill
```
This:
1. **Merges `.vscode/settings.json`** — adds the `wm-tmux` terminal profile (auto-attaches to the `main` tmux session), disables auto port forwarding, configures forwarding for ports 8000/3000/5432, and stops rust-analyzer from auto-starting. Existing settings are preserved.
2. **Creates `.vscode/tasks.json`** — auto-starts the dev database (`start-dev-db.sh`) when the folder opens.
3. **Adds `wmc` alias to `~/.zshrc`** — so you can use `wmc` from any tmux window.
4. **Adds `eval "$(wmc completions)"`** to `~/.zshrc` — provides tab-completion for subcommands and worktree names (for `open`, `open-ee`, and `close`).
After setup, reopen Cursor's terminal to pick up the new profile.
### Usage
All commands run from inside a tmux session (i.e., from Cursor's integrated terminal after setup).
**Create a new worktree + open Cursor:**
```bash
wmc add -A -p "implement feature X"
```
This runs `workmux add`, creates a grouped tmux session, writes `.vscode/settings.json` in the worktree (with port forwarding matching the worktree's assigned ports), and opens a new Cursor window.
**Open Cursor for an existing worktree:**
```bash
wmc open my-feature
```
**Open the EE worktree in Cursor (no tmux session):**
```bash
wmc open-ee my-feature
```
This finds the matching `windmill-ee-private__worktrees/<name>` directory and opens it in a new Cursor window.
**Close a worktree's Cursor window and tmux window (keeps the worktree):**
```bash
wmc close my-feature
```
This kills the grouped tmux session and calls `workmux close` to close the tmux window. The worktree and branch are preserved. Grouped sessions are also automatically cleaned up when you `workmux rm` a worktree (via `scripts/worktree-cleanup`).
## Cargo Features
To build the backend with specific Cargo features (e.g., `enterprise`, `parquet`), pass them via `CARGO_FEATURES`. The backend pane reads this from `.env.local` and appends `--features <value>` to the `cargo watch` command.
**With `wm` (workmux):**
Set `CARGO_FEATURES` as an environment variable before creating the worktree:
```bash
CARGO_FEATURES="enterprise,parquet" wm add my-feature
```
This gets written to `.env.local` by the `post_create` hook (`scripts/worktree-env`), and the backend pane picks it up automatically.
**With `wmc` (wm-cursor):**
Use the `--features` flag:
```bash
# Create a new worktree with features
wmc add --features "enterprise,parquet" -A -p "implement feature X"
# Open an existing worktree with different features
wmc open my-feature --features "enterprise,parquet"
```
The `--features` flag exports `CARGO_FEATURES` so the `post_create` hook writes it to `.env.local`. When using `wmc open`, it updates the existing `.env.local` with the new features.
## Login
Default credentials: `admin@windmill.dev` / `changeme`

View File

@@ -1,11 +1,11 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n oauth_data as \"oauth_data!: sqlx::types::Json<WorkspaceOAuthConfig>\",\n service_name as \"service_name!: ServiceName\"\n FROM\n workspace_integrations\n WHERE\n workspace_id = $1\n ",
"query": "\n SELECT\n oauth_data as \"oauth_data: sqlx::types::Json<WorkspaceOAuthConfig>\",\n service_name as \"service_name!: ServiceName\",\n resource_path\n FROM\n workspace_integrations\n WHERE\n workspace_id = $1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "oauth_data!: sqlx::types::Json<WorkspaceOAuthConfig>",
"name": "oauth_data: sqlx::types::Json<WorkspaceOAuthConfig>",
"type_info": "Jsonb"
},
{
@@ -22,6 +22,11 @@
}
}
}
},
{
"ordinal": 2,
"name": "resource_path",
"type_info": "Text"
}
],
"parameters": {
@@ -30,9 +35,10 @@
]
},
"nullable": [
true,
false,
false
true
]
},
"hash": "5368683c19f8d6744d5dbc53e5b2ab0f2348646d79f5306c6868e2c3a8f389ee"
"hash": "0010ef26da16facd1c2c832601ac687c4c27de46a90f45496b8446af1a9d0578"
}

View File

@@ -37,6 +37,11 @@
"ordinal": 6,
"name": "format_extension",
"type_info": "Varchar"
},
{
"ordinal": 7,
"name": "is_fileset",
"type_info": "Bool"
}
],
"parameters": {
@@ -52,7 +57,8 @@
true,
true,
true,
true
true,
false
]
},
"hash": "03d63d2e64b012f624d2731b5bcb8849c74a9474777be61edf0ed43ddda07ef3"

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM resource WHERE workspace_id = $1 AND path = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "05e05a9b979941c7a11cd881da652f459e4a0444d63a96deba4a879fbe1124ff"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag)\n SELECT unnest($1::uuid[]), 'test-workspace', now(), 'flow'",
"describe": {
"columns": [],
"parameters": {
"Left": [
"UuidArray"
]
},
"nullable": []
},
"hash": "0681b850c033619e1b9498376263681f875a5aba22170ca50ec8b578f7fa478b"
}

View File

@@ -46,11 +46,11 @@
]
},
"nullable": [
false,
false,
false,
false,
false,
true,
true,
true,
true,
true,
true,
true
]

View File

@@ -1,28 +1,29 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT name, format_extension FROM resource_type WHERE format_extension IS NOT NULL AND (workspace_id = $1 OR workspace_id = 'admins')",
"query": "SELECT email, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "name",
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "format_extension",
"name": "edited_by",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
true
false
]
},
"hash": "cf1cef7e0fe2e7e3db96b0ec005360361b9eec023a6fc2a4a7a917f59d86af4d"
"hash": "075d4749299af2cb81162bf396bec6aa89de43ec201c911196763e03e644ca7a"
}

View File

@@ -30,7 +30,8 @@
"sqs",
"gcp",
"mqtt",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -24,7 +24,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -122,7 +122,8 @@
"sqs",
"gcp",
"mqtt",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -0,0 +1,26 @@
{
"db_name": "PostgreSQL",
"query": "\n WITH completed AS (\n INSERT INTO v2_job_completed\n (workspace_id, id, started_at, duration_ms, result,\n flow_status, workflow_as_code_status, status, worker)\n SELECT\n q.workspace_id, q.id, q.started_at,\n (EXTRACT('epoch' FROM now()) - EXTRACT('epoch' FROM COALESCE(q.started_at, now()))) * 1000,\n CASE WHEN q.running\n THEN $3::text::jsonb\n ELSE $4::text::jsonb\n END,\n s.flow_status,\n s.workflow_as_code_status,\n 'skipped'::job_status,\n q.worker\n FROM v2_job_queue q\n LEFT JOIN v2_job_status s ON s.id = q.id\n WHERE q.id = $1\n ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status, result = EXCLUDED.result\n RETURNING 1 AS x\n ), _deleted AS (\n DELETE FROM v2_job_queue WHERE id = $1\n ), _logged AS (\n INSERT INTO job_logs (logs, job_id, workspace_id)\n VALUES ($5, $1, $2)\n ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, EXCLUDED.logs)\n )\n SELECT x FROM completed\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "x",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Uuid",
"Varchar",
"Text",
"Text",
"Text"
]
},
"nullable": [
null
]
},
"hash": "1437b432d2c23e30eb05443e83069cdb049f65ec299b0778ce14677728cf6346"
}

View File

@@ -24,7 +24,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -0,0 +1,29 @@
{
"db_name": "PostgreSQL",
"query": "SELECT email, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "edited_by",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false
]
},
"hash": "17aafb72843659df9594d6d2466d2afaf26e666ffe52e0ea85792ea31b63410c"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT result::text FROM v2_job_completed WHERE id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "result",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
null
]
},
"hash": "18b6262a60400f2b58ab26615466c23b4c1a7805c66b70b0fcfb7d33b122a7bf"
}

View File

@@ -40,7 +40,8 @@
"sqs",
"gcp",
"mqtt",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -1,11 +1,10 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE workspace_integrations\n SET oauth_data = $1, updated_at = now()\n WHERE workspace_id = $2 AND service_name = $3\n ",
"query": "DELETE FROM native_trigger WHERE workspace_id = $1 AND service_name = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb",
"Text",
{
"Custom": {
@@ -22,5 +21,5 @@
},
"nullable": []
},
"hash": "3b3f60623126626b52ca0a4a188655ddf728cd3f21ee308db7393694ccc5c7b3"
"hash": "1af48c42255f1c973b4a9c9a58050bf5ec1ee6f93f0a90c1c7d0c0fcd816702d"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag)\n VALUES ($1, $2, now(), 'flow')",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Varchar"
]
},
"nullable": []
},
"hash": "1af6885dbc5055281acb82b3e57f7dba2e4b04d9535058fab695660a14bf8890"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource_type (workspace_id, name, schema, description, edited_at, created_by, format_extension, is_fileset)\n SELECT $2, name, schema, description, edited_at, created_by, format_extension, is_fileset\n FROM resource_type\n WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Varchar"
]
},
"nullable": []
},
"hash": "1c2157ce14e90f0751d7f0a9f2dbb3c5a5789a32423e75260098a5300a4af986"
}

View File

@@ -34,7 +34,8 @@
"sqs",
"gcp",
"mqtt",
"nextcloud"
"nextcloud",
"google"
]
}
}
@@ -67,7 +68,8 @@
"sqs",
"gcp",
"mqtt",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -24,7 +24,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -40,7 +40,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -0,0 +1,25 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO account (workspace_id, client, expires_at, refresh_token, is_workspace_integration)\n VALUES ($1, $2, now() + ($3 || ' seconds')::interval, $4, true)\n RETURNING id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Text",
"Varchar"
]
},
"nullable": [
false
]
},
"hash": "26beff5e94b68703ad81ef9dd2d08869eb3bb7659efd9bac04cdf98ae963063d"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM account WHERE workspace_id = $1 AND client = $2 AND is_workspace_integration = true RETURNING id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "27065225c6affd26f1533dacffe1c38321511b5a7dd2a7e9435c04868188fd44"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT schema, description, format_extension\n FROM resource_type\n WHERE workspace_id = $1 AND name = $2",
"query": "SELECT schema, description, format_extension, is_fileset\n FROM resource_type\n WHERE workspace_id = $1 AND name = $2",
"describe": {
"columns": [
{
@@ -17,6 +17,11 @@
"ordinal": 2,
"name": "format_extension",
"type_info": "Varchar"
},
{
"ordinal": 3,
"name": "is_fileset",
"type_info": "Bool"
}
],
"parameters": {
@@ -28,8 +33,9 @@
"nullable": [
true,
true,
true
true,
false
]
},
"hash": "7bc9fc05dbd162866bef1fdd3e7faeb50429881ed1bc962903f06e4b3d5f8d44"
"hash": "2768622b76ad92c05f4f44d997aff285707e1a43ce85e5bb8e87849d78a0637f"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job (id, kind, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, runnable_path)\n VALUES ($1, 'flow', 'flow', 'test-user', 'u/test-user', 'test@windmill.dev', $2, $3)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Varchar",
"Varchar"
]
},
"nullable": []
},
"hash": "27f70ebe788cca2e88732d8bf978883037bebca4cf75ba459858e4fb197f940b"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO worker_ping (worker_instance, worker, ip, custom_tags, worker_group, dedicated_worker, dedicated_workers, wm_version, vcpus, memory, job_isolation) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) ON CONFLICT (worker)\n DO UPDATE set ip = EXCLUDED.ip, custom_tags = EXCLUDED.custom_tags, worker_group = EXCLUDED.worker_group, dedicated_workers = EXCLUDED.dedicated_workers",
"query": "INSERT INTO worker_ping (worker_instance, worker, ip, custom_tags, worker_group, dedicated_worker, dedicated_workers, wm_version, vcpus, memory, job_isolation, native_mode) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) ON CONFLICT (worker)\n DO UPDATE set ip = EXCLUDED.ip, custom_tags = EXCLUDED.custom_tags, worker_group = EXCLUDED.worker_group, dedicated_workers = EXCLUDED.dedicated_workers, native_mode = EXCLUDED.native_mode",
"describe": {
"columns": [],
"parameters": {
@@ -15,10 +15,11 @@
"Varchar",
"Int8",
"Int8",
"Text"
"Text",
"Bool"
]
},
"nullable": []
},
"hash": "97c61b6a9a5112ea484565236959a544511d5d501fb737da8110a8725b883465"
"hash": "298fa4f8eb05b4c3f33b608b0cdb6ed918af2df012de33acb3befd3fcccbc257"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT debounce_batch FROM v2_job_debounce_batch WHERE id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "debounce_batch",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
false
]
},
"hash": "2a95f18e80c55a7e8178a4bd2b781d41fa47efd4da5bb9bc2d72b9aa1e33617f"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM token WHERE workspace_id = $1 AND label IS DISTINCT FROM 'session' RETURNING token",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false
]
},
"hash": "2d6607b3c38fe72b5663c32de58dacbabed4c5ae28101e3ae2694f96fd055a91"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "SELECT count(*) FROM native_trigger WHERE workspace_id = 'test-workspace' AND service_name = 'google'",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": []
},
"nullable": [
null
]
},
"hash": "2e5dd992b0bfd7550d6f4cb5424a1c14352527b98249bce286790641bf56491e"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job (id, kind, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, runnable_path)\n VALUES ($1, 'flow', 'flow', 'test-user', 'u/test-user', 'test@windmill.dev', 'ws2', 'f/test/flow')",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": []
},
"hash": "4010328a9f1611064f497726b69c08625a55a4dab25c3d9b5ece07e44d14915b"
}

View File

@@ -24,7 +24,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -30,7 +30,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -1,35 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n INSERT INTO debounce_key (job_id, key)\n VALUES ($1, $2)\n ON CONFLICT (key)\n DO UPDATE SET\n previous_job_id = debounce_key.job_id,\n job_id = EXCLUDED.job_id, -- replace current job with new one \n debounced_times = debounce_key.debounced_times + 1 -- evaluated only if conflict,\n -- conflict means there is already existing value,\n -- which means overriding it will also imply adding new entry to v2_job_debounce_batch and thus debouncing the job\n -- so the counter should be incremented\n RETURNING\n debounced_times,\n first_started_at,\n previous_job_id AS job_id_to_debounce\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "debounced_times",
"type_info": "Int4"
},
{
"ordinal": 1,
"name": "first_started_at",
"type_info": "Timestamptz"
},
{
"ordinal": 2,
"name": "job_id_to_debounce",
"type_info": "Uuid"
}
],
"parameters": {
"Left": [
"Uuid",
"Varchar"
]
},
"nullable": [
false,
false,
true
]
},
"hash": "454ace9ce391725ef4f4c129cd66e4c12a5c40f512b70551958178c8b4d6c183"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "WITH ids AS (\n SELECT id as job_id FROM v2_job_debounce_batch WHERE debounce_batch = (\n SELECT debounce_batch FROM v2_job_debounce_batch WHERE id = $1\n )\n ) SELECT args->>'items' FROM ids LEFT JOIN v2_job ON v2_job.id = ids.job_id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "?column?",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
null
]
},
"hash": "48536968f4173715d4ef8293683c2a3eb4bd22fbe18c34890a3dc4e96e4e6133"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO global_settings (name, value) VALUES ('oauths', $1)\n ON CONFLICT (name) DO UPDATE SET value = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Jsonb"
]
},
"nullable": []
},
"hash": "4be53f0b801ebc1a33a184556fd138fdec8082f31f56d7023cf8c6311964f3b0"
}

View File

@@ -37,7 +37,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -32,7 +32,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}
@@ -70,7 +71,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT v2_job_queue.id, v2_job.tag, v2_job_queue.scheduled_for, v2_job_queue.workspace_id FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id WHERE running = false AND scheduled_for < now() - ($1 || ' minutes')::interval",
"query": "SELECT v2_job_queue.id, v2_job.tag, v2_job_queue.scheduled_for, v2_job_queue.workspace_id FROM v2_job_queue LEFT JOIN v2_job ON v2_job_queue.id = v2_job.id WHERE running = false AND scheduled_for < now() - ($1 || ' minutes')::interval AND v2_job.trigger_kind IS DISTINCT FROM 'schedule'::job_trigger_kind",
"describe": {
"columns": [
{
@@ -36,5 +36,5 @@
false
]
},
"hash": "b45e17ad532a23b394226c9a5d7ab5a21e20202dbbf9c67831cc62eb067cd2ba"
"hash": "53648c069749df45c0459d733b3e429af20c69c841fb0c3bceafe3ea6c3f5329"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag)\n SELECT unnest($1::uuid[]), 'test-workspace', now(), 'deno'",
"describe": {
"columns": [],
"parameters": {
"Left": [
"UuidArray"
]
},
"nullable": []
},
"hash": "539d661500254e2e346490710f5772cb88a1ab6bbddd97a77e06644ac0f61762"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource_type\n (workspace_id, name, schema, description, created_by, format_extension, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, now())",
"query": "INSERT INTO resource_type\n (workspace_id, name, schema, description, created_by, format_extension, is_fileset, edited_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7, now())",
"describe": {
"columns": [],
"parameters": {
@@ -10,10 +10,11 @@
"Jsonb",
"Text",
"Varchar",
"Varchar"
"Varchar",
"Bool"
]
},
"nullable": []
},
"hash": "ffedbb3a2676a6d7b71f81f89109a02a8dba90d40144e942527f8a3fc36dfbc1"
"hash": "5899c7614f195fdd23e38389e52b004f957aafa2201b80638b5f87a625373f00"
}

View File

@@ -1,18 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "WITH job_result AS (\n SELECT result\n FROM v2_job_completed\n WHERE id = $1\n ),\n updated_queue AS (\n UPDATE v2_job_queue\n SET running = false,\n tag = COALESCE($3, tag)\n WHERE id = $2\n )\n UPDATE v2_job\n SET\n tag = COALESCE($3, tag),\n concurrent_limit = COALESCE($4, concurrent_limit),\n concurrency_time_window_s = COALESCE($5, concurrency_time_window_s),\n args = COALESCE(\n CASE\n WHEN job_result.result IS NULL THEN NULL\n WHEN jsonb_typeof(job_result.result) = 'object'\n THEN job_result.result\n WHEN jsonb_typeof(job_result.result) = 'null'\n THEN NULL\n ELSE jsonb_build_object('value', job_result.result)\n END,\n '{}'::jsonb\n ),\n preprocessed = TRUE\n FROM job_result\n WHERE v2_job.id = $2;\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Uuid",
"Varchar",
"Int4",
"Int4"
]
},
"nullable": []
},
"hash": "5b8c1803f0ccead11517fbc8a9bdc0227dc3922217fa18f0b71ff0484d65838c"
}

View File

@@ -245,7 +245,8 @@
"sqs",
"gcp",
"mqtt",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT count(*) FROM variable WHERE workspace_id = 'test-workspace' AND path = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "count",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
null
]
},
"hash": "607c13333627e79557d3b6f6f68eee0a5dbe7cd4643e4bf99a592eb1bb82580c"
}

View File

@@ -1,16 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n WITH prev_sd AS (\n DELETE FROM debounce_stale_data WHERE job_id = $1 RETURNING to_relock\n ) INSERT INTO debounce_stale_data (job_id, to_relock)\n VALUES ($2, array_cat((SELECT to_relock FROM prev_sd), $3))\n ON CONFLICT (job_id) DO UPDATE SET to_relock = EXCLUDED.to_relock\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Uuid",
"TextArray"
]
},
"nullable": []
},
"hash": "61b37cb4db6e60c2d35f7d23db5afbe04e040a8dcd1d93afaaaa320665c8779a"
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE debounce_key SET first_started_at = now() - interval '20 seconds' WHERE key = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text"
]
},
"nullable": []
},
"hash": "66342c32f7ae0238803cb1896d9f23a74b64573f77dd32189a25b6e8369f147b"
}

View File

@@ -35,7 +35,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job (id, kind, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, runnable_path)\n SELECT unnest($1::uuid[]), 'flow', 'flow', 'test-user', 'u/test-user', 'test@windmill.dev', 'test-workspace', 'f/test/flow'",
"describe": {
"columns": [],
"parameters": {
"Left": [
"UuidArray"
]
},
"nullable": []
},
"hash": "66faba2137791e0cb1353545c06f9f7c23a1559e7a761db7c2195736b8b30709"
}

View File

@@ -0,0 +1,19 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource (workspace_id, path, value, resource_type, description, created_by)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (workspace_id, path) DO UPDATE\n SET value = EXCLUDED.value, resource_type = EXCLUDED.resource_type",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Jsonb",
"Varchar",
"Text",
"Varchar"
]
},
"nullable": []
},
"hash": "6868520d496afe306bbd93293076ea4bb155097d1e8d3ffe5b75dd80ced735de"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT worker, worker_instance, EXTRACT(EPOCH FROM (now() - ping_at))::integer as last_ping, started_at, ip, jobs_executed,\n CASE WHEN $4 IS TRUE THEN current_job_id ELSE NULL END as last_job_id, CASE WHEN $4 IS TRUE THEN current_job_workspace_id ELSE NULL END as last_job_workspace_id,\n custom_tags, worker_group, wm_version, occupancy_rate, occupancy_rate_15s, occupancy_rate_5m, occupancy_rate_30m, memory, vcpus, memory_usage, wm_memory_usage, job_isolation\n FROM worker_ping\n WHERE ($1::integer IS NULL AND ping_at > now() - interval '5 minute') OR (ping_at > now() - ($1 || ' seconds')::interval)\n ORDER BY ping_at desc LIMIT $2 OFFSET $3",
"query": "SELECT worker, worker_instance, EXTRACT(EPOCH FROM (now() - ping_at))::integer as last_ping, started_at, ip, jobs_executed,\n CASE WHEN $4 IS TRUE THEN current_job_id ELSE NULL END as last_job_id, CASE WHEN $4 IS TRUE THEN current_job_workspace_id ELSE NULL END as last_job_workspace_id,\n custom_tags, worker_group, wm_version, occupancy_rate, occupancy_rate_15s, occupancy_rate_5m, occupancy_rate_30m, memory, vcpus, memory_usage, wm_memory_usage, job_isolation, native_mode\n FROM worker_ping\n WHERE ($1::integer IS NULL AND ping_at > now() - interval '5 minute') OR (ping_at > now() - ($1 || ' seconds')::interval)\n ORDER BY ping_at desc LIMIT $2 OFFSET $3",
"describe": {
"columns": [
{
@@ -102,6 +102,11 @@
"ordinal": 19,
"name": "job_isolation",
"type_info": "Text"
},
{
"ordinal": 20,
"name": "native_mode",
"type_info": "Bool"
}
],
"parameters": {
@@ -132,8 +137,9 @@
true,
true,
true,
true
true,
false
]
},
"hash": "771a858a4b7ca41b6787e61f5a4a5c9c4d48fd213852e2f997cd4b2420580d30"
"hash": "68bca8f839e47705b11d312ee874eceaa3d1d24d9053ad4aea94b9f8465585ca"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT refresh_token FROM account WHERE workspace_id = $1 AND id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "refresh_token",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Int4"
]
},
"nullable": [
false
]
},
"hash": "6b4d48527af6f1411dc5e03f9144fb127488a79ac53a154d71253628320b1084"
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE account SET\n refresh_token = $1,\n expires_at = now() + interval '1 hour',\n refresh_error = NULL\n WHERE workspace_id = $2 AND client = $3 AND is_workspace_integration = true",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "6e83478011a8f65bf294ad7886139369e386f6a552c6626be48ecaa0e5ab78a7"
}

View File

@@ -29,7 +29,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -24,7 +24,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT\n workspace_id,\n service_name AS \"service_name!: ServiceName\",\n oauth_data,\n created_at,\n updated_at,\n created_by\n FROM\n workspace_integrations\n WHERE\n workspace_id = $1\n AND service_name = $2\n ",
"query": "\n SELECT\n workspace_id,\n service_name AS \"service_name!: ServiceName\",\n oauth_data,\n resource_path,\n created_at,\n updated_at,\n created_by\n FROM\n workspace_integrations\n WHERE\n workspace_id = $1\n AND service_name = $2\n ",
"describe": {
"columns": [
{
@@ -30,16 +30,21 @@
},
{
"ordinal": 3,
"name": "resource_path",
"type_info": "Text"
},
{
"ordinal": 4,
"name": "created_at",
"type_info": "Timestamptz"
},
{
"ordinal": 4,
"ordinal": 5,
"name": "updated_at",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
"ordinal": 6,
"name": "created_by",
"type_info": "Varchar"
}
@@ -63,11 +68,12 @@
"nullable": [
false,
false,
false,
true,
true,
false,
false,
false
]
},
"hash": "3e5bdc2e071fc2f1e3c7971736272f20bb5a0aa921a614bd02898d3f162660c2"
"hash": "7443ba1f922e190bb9a0ca313847f8d27c35a6ee3aff20157d6285e73aa923ef"
}

View File

@@ -40,7 +40,8 @@
"sqs",
"gcp",
"mqtt",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -0,0 +1,19 @@
{
"db_name": "PostgreSQL",
"query": "WITH job_result AS (\n SELECT result\n FROM v2_job_completed\n WHERE id = $1\n ),\n updated_queue AS (\n UPDATE v2_job_queue\n SET running = false,\n tag = COALESCE($3, tag),\n scheduled_for = COALESCE($6, scheduled_for)\n WHERE id = $2\n )\n UPDATE v2_job\n SET\n tag = COALESCE($3, tag),\n concurrent_limit = COALESCE($4, concurrent_limit),\n concurrency_time_window_s = COALESCE($5, concurrency_time_window_s),\n args = COALESCE(\n CASE\n WHEN job_result.result IS NULL THEN NULL\n WHEN jsonb_typeof(job_result.result) = 'object'\n THEN job_result.result\n WHEN jsonb_typeof(job_result.result) = 'null'\n THEN NULL\n ELSE jsonb_build_object('value', job_result.result)\n END,\n '{}'::jsonb\n ),\n preprocessed = TRUE\n FROM job_result\n WHERE v2_job.id = $2;\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Uuid",
"Varchar",
"Int4",
"Int4",
"Timestamptz"
]
},
"nullable": []
},
"hash": "79b437ad31ddab94310989b8fb6a1c130b9be1ab4b6a100fffffd687677b9c92"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO account (workspace_id, client, expires_at, refresh_token, is_workspace_integration)\n VALUES ('test-workspace', $1, now() + interval '1 hour', $2, true)\n RETURNING id",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "id",
"type_info": "Int4"
}
],
"parameters": {
"Left": [
"Varchar",
"Varchar"
]
},
"nullable": [
false
]
},
"hash": "7a57f58e809e482a599722d3887fb7e115506ff1e5ec9cf6dd2af84ed9a78632"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "SELECT runnable_id as \"runnable_id: ScriptHash\", raw_flow as \"raw_flow: _\", kind as \"kind: _\" FROM v2_job WHERE id = $1",
"query": "SELECT runnable_id as \"runnable_id: ScriptHash\", raw_flow as \"raw_flow: _\", kind as \"kind: _\", parent_job, flow_step_id FROM v2_job WHERE id = $1",
"describe": {
"columns": [
{
@@ -47,6 +47,16 @@
}
}
}
},
{
"ordinal": 3,
"name": "parent_job",
"type_info": "Uuid"
},
{
"ordinal": 4,
"name": "flow_step_id",
"type_info": "Varchar"
}
],
"parameters": {
@@ -57,8 +67,10 @@
"nullable": [
true,
true,
false
false,
true,
true
]
},
"hash": "805d633de90fee335f1726284eda0dbc200d45960fb8dea867492c8c7dd096d5"
"hash": "7aaa5b0bd873c2029e2201d287ea0aaae04678ac105374bbe387e534a6cb6333"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO resource_type (workspace_id, name, schema, description, edited_at, created_by, format_extension)\n SELECT $2, name, schema, description, edited_at, created_by, format_extension\n FROM resource_type\n WHERE workspace_id = $1",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Varchar"
]
},
"nullable": []
},
"hash": "7abd579d3ec97853ac36cc8dad29013eb133a28cd848bf8fdf9571b2ee402a3e"
}

View File

@@ -37,6 +37,11 @@
"ordinal": 6,
"name": "format_extension",
"type_info": "Varchar"
},
{
"ordinal": 7,
"name": "is_fileset",
"type_info": "Bool"
}
],
"parameters": {
@@ -51,7 +56,8 @@
true,
true,
true,
true
true,
false
]
},
"hash": "7b1239ad6460e8f5fb41bfe12f662a779528784ec8cf3f6dcce5545ab90bf234"

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE account SET\n expires_at = now() + interval '1 hour',\n refresh_error = NULL\n WHERE workspace_id = $1 AND client = $2 AND is_workspace_integration = true",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "7b1d29170c9c4ad4e3a15c4e8acbeb6769dc6fc97269beee607a5625a495a121"
}

View File

@@ -0,0 +1,22 @@
{
"db_name": "PostgreSQL",
"query": "SELECT logs as \"logs!\" FROM job_logs WHERE job_id = $1",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "logs!",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Uuid"
]
},
"nullable": [
true
]
},
"hash": "7ca599330c9913c7e66b27e2ffcfa18d53cbdd16e179749f0aea7980a901b23c"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag)\n VALUES ($1, $2, now(), 'deno')",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Varchar"
]
},
"nullable": []
},
"hash": "7ca7dabfe360845a5b57552b0d02267d5dbbc488bc7ab990c0bda1594bf5ef3a"
}

View File

@@ -27,7 +27,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT EXISTS (\n SELECT 1\n FROM workspace_integrations\n WHERE workspace_id = $1\n AND service_name = $2\n AND oauth_data IS NOT NULL\n )\n ",
"query": "\n SELECT EXISTS (\n SELECT 1\n FROM workspace_integrations wi\n WHERE wi.workspace_id = $1\n AND wi.service_name = $2\n AND wi.oauth_data IS NOT NULL\n )\n ",
"describe": {
"columns": [
{
@@ -29,5 +29,5 @@
null
]
},
"hash": "95c57fb921a2e3725b92cbafac6e3dc360b88429f03dd1e2b1b55cfabe208cb7"
"hash": "823fc5f998fe747ec8537752d9eb7ef548b2fd9ee1f5380084f27796c2bcc8ad"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM variable WHERE workspace_id = $1 AND account = ANY($2)",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Int4Array"
]
},
"nullable": []
},
"hash": "826a4216830f6a930c382209a20bc7f8b460064480e080b989e31df3d6a30e31"
}

View File

@@ -0,0 +1,29 @@
{
"db_name": "PostgreSQL",
"query": "SELECT email, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "email",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "edited_by",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false
]
},
"hash": "8311a553c44221751ffdbbe6a997d6feba8d43292daf6c5433b66bd8450e8854"
}

View File

@@ -1,15 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO v2_job_debounce_batch (id, debounce_batch)\n -- if it the first one, nextval will be evaluated, otherwise take from the job we will debounce\n SELECT\n $2,\n COALESCE(\n (\n SELECT debounce_batch\n FROM v2_job_debounce_batch\n WHERE id = $1\n LIMIT 1\n ), -- maybe use current batch\n nextval('debounce_batch_seq')\n )\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Uuid",
"Uuid"
]
},
"nullable": []
},
"hash": "8360ab72d60f07dde6ecae599e6531b5b86862029ab51fdbdd44ec16239108e2"
}

View File

@@ -24,7 +24,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -0,0 +1,39 @@
{
"db_name": "PostgreSQL",
"query": "SELECT external_id, webhook_token_prefix FROM native_trigger WHERE workspace_id = $1 AND service_name = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "external_id",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "webhook_token_prefix",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
{
"Custom": {
"name": "native_trigger_service",
"kind": {
"Enum": [
"nextcloud",
"google"
]
}
}
}
]
},
"nullable": [
false,
false
]
},
"hash": "83d6e371ca84903e9f487afc065353a9f7be86ff752612909587ec3cb770cb75"
}

View File

@@ -0,0 +1,34 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT name, format_extension, is_fileset FROM resource_type WHERE (format_extension IS NOT NULL OR is_fileset = true) AND (workspace_id = $1 OR workspace_id = 'admins')",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "name",
"type_info": "Varchar"
},
{
"ordinal": 1,
"name": "format_extension",
"type_info": "Varchar"
},
{
"ordinal": 2,
"name": "is_fileset",
"type_info": "Bool"
}
],
"parameters": {
"Left": [
"Text"
]
},
"nullable": [
false,
true,
false
]
},
"hash": "842775bcf91d747abb11ffe9c98fa1208595e012590606ef6667ea3a78105883"
}

View File

@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT on_behalf_of_email FROM flow WHERE path = $1 AND workspace_id = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "on_behalf_of_email",
"type_info": "Text"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
true
]
},
"hash": "85a6a85fd126a8bfedd65d6b38d22c65911ab9cf0414c33a3321a1d43af49795"
}

View File

@@ -35,7 +35,8 @@
"mqtt",
"gcp",
"default_email",
"nextcloud"
"nextcloud",
"google"
]
}
}

View File

@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE workspace_integrations SET resource_path = REGEXP_REPLACE(resource_path, 'u/' || $2 || '/(.*)', 'u/' || $1 || '/\\1') WHERE resource_path LIKE ('u/' || $2 || '/%') AND workspace_id = $3",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Text"
]
},
"nullable": []
},
"hash": "89791ad1a0862b6475ebfdeb54b0101e124fcf9d12e93d84b44457c72c7604a5"
}

View File

@@ -1,12 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "\n SELECT EXISTS(\n SELECT 1\n FROM native_trigger\n WHERE\n workspace_id = $1 AND\n service_name = $2 AND\n external_id = $3\n )\n ",
"query": "\n SELECT service_config\n FROM native_trigger\n WHERE external_id = $1 AND service_name = $2 AND workspace_id = $3\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "exists",
"type_info": "Bool"
"name": "service_config",
"type_info": "Jsonb"
}
],
"parameters": {
@@ -27,8 +27,8 @@
]
},
"nullable": [
null
true
]
},
"hash": "06072cfe26abe58629623a8b38382b33947c5a5c702ce586e6e6ea51430380bf"
"hash": "8a4d42e373043bc509985ab320894bf3e6afa7b2019cc4739baac9165f7ead9e"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "PostgreSQL",
"query": "INSERT INTO workspace_settings (workspace_id) VALUES ('ws2')",
"describe": {
"columns": [],
"parameters": {
"Left": []
},
"nullable": []
},
"hash": "8cf5af21cde4e4de45f995efa2a9b56ce20c26869ca78d7e17b3504b92ae85b1"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM _sqlx_migrations WHERE\n version=20250131115248 OR version=20250902085503 OR version=20250201145630 OR\n version=20250201145631 OR version=20250201145632 OR version=20251006143821 OR\n version=20260207000001 OR version=20260207000002 OR version=20260207000003 OR version=20260207000004",
"query": "DELETE FROM _sqlx_migrations WHERE\n version=20250131115248 OR version=20250902085503 OR version=20250201145630 OR\n version=20250201145631 OR version=20250201145632 OR version=20251006143821",
"describe": {
"columns": [],
"parameters": {
@@ -8,5 +8,5 @@
},
"nullable": []
},
"hash": "c6bcf0d9e211bc03e3338682295f4995e1d622917367c478742addd073245ad5"
"hash": "8d4ad4ee75fb149c36a9f6a0c4cf5fd981473f45d1b71b4b8236e021f7c8682d"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE account SET refresh_token = $1 WHERE workspace_id = 'test-workspace' AND id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Varchar",
"Int4"
]
},
"nullable": []
},
"hash": "8e5881225f4bf7243bd40397ac8b8708fb6ce6c0ba0d263bb7eeb404f5dd62ff"
}

View File

@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "DELETE FROM resource WHERE workspace_id = $1 AND resource_type = $2 AND path LIKE 'u/%/native_%'",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": []
},
"hash": "8f33846f6c25a78267a5c8143b414f033280ebf57b7b9568dc2fe31bc625020d"
}

Some files were not shown because too many files have changed in this diff Show More