diff --git a/.github/workflows/check-org-membership.yml b/.github/workflows/check-org-membership.yml index 910c39c7ba..d8d648e659 100644 --- a/.github/workflows/check-org-membership.yml +++ b/.github/workflows/check-org-membership.yml @@ -46,7 +46,13 @@ jobs: exit 0 fi - # 2. Otherwise fall back to the org-membership check + # 2. Disallow other bots + if [[ "${COMMENTER}" =~ \[bot\]$ ]]; then + echo "is_member=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # 3. Otherwise check if the user is a member of the organization STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ -H "Authorization: token $ORG_ACCESS_TOKEN" \ -H "Accept: application/vnd.github+json" \ diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 4307db0915..41d7a9bed1 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -98,6 +98,7 @@ jobs: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} timeout_minutes: "60" allowed_tools: "mcp__github__create_pull_request,Bash" + allowed_bots: "windmill-internal-app[bot]" custom_instructions: | ## IMPORTANT INSTRUCTIONS - Your branch name should be a short description of the requested changes. diff --git a/.github/workflows/rust-client-check.yml b/.github/workflows/rust-client-check.yml new file mode 100644 index 0000000000..4ee5575071 --- /dev/null +++ b/.github/workflows/rust-client-check.yml @@ -0,0 +1,27 @@ +name: Rust Client Check +on: + workflow_run: + workflows: ["Change versions"] + types: + - completed + push: + paths: + - "rust-client/**" + - "backend/**/*.rs" + - "backend/windmill-api/openapi.yaml" + - "version.txt" + - "flake.nix" + - ".github/workflows/rust-client-check.yml" + +jobs: + check_rust_client: + runs-on: ubicloud-standard-8 + steps: + - uses: actions/checkout@v4 + - uses: cachix/install-nix-action@v20 + with: + extra_nix_config: | + experimental-features = nix-command flakes + - name: Check rust client builds + run: cd rust-client && nix develop ../ --command ./dev.nu --check + timeout-minutes: 16 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 057a70bd15..f29b7b8ae8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,176 @@ # Changelog +## [1.527.1](https://github.com/windmill-labs/windmill/compare/v1.527.0...v1.527.1) (2025-08-16) + + +### Bug Fixes + +* **cli:** module not found ../ruby/../wasm.js ([#6399](https://github.com/windmill-labs/windmill/issues/6399)) ([f4851e7](https://github.com/windmill-labs/windmill/commit/f4851e7747c057ea093d55b122a976a1eeec8d98)) +* fix inlinecompletion errors ([03c82af](https://github.com/windmill-labs/windmill/commit/03c82af00a113378bb7e00d8018c054856eb2314)) + +## [1.527.0](https://github.com/windmill-labs/windmill/compare/v1.526.1...v1.527.0) (2025-08-15) + + +### Features + +* add ruby support ([#5939](https://github.com/windmill-labs/windmill/issues/5939)) ([11dd411](https://github.com/windmill-labs/windmill/commit/11dd4118ce43136da900601401ee0d04e60c246b)) + + +### Bug Fixes + +* improve flow layout for more complex flow ([eae0c09](https://github.com/windmill-labs/windmill/commit/eae0c099790aedb3fd5ac69b551cafc5efc1bd62)) + +## [1.526.1](https://github.com/windmill-labs/windmill/compare/v1.526.0...v1.526.1) (2025-08-14) + + +### Bug Fixes + +* add timeouts to more queries to prevent some rare deadlocks scnarios ([65bcc00](https://github.com/windmill-labs/windmill/commit/65bcc00cd9b289193e27a6f74b053e71f90be698)) + +## [1.526.0](https://github.com/windmill-labs/windmill/compare/v1.525.0...v1.526.0) (2025-08-14) + + +### Features + +* instance groups workspace ([#6380](https://github.com/windmill-labs/windmill/issues/6380)) ([58975b5](https://github.com/windmill-labs/windmill/commit/58975b58dc7ce665000a46873a145263c5d8a38d)) + + +### Bug Fixes + +* **aichat:** better placeholders based on mode ([#6378](https://github.com/windmill-labs/windmill/issues/6378)) ([f149203](https://github.com/windmill-labs/windmill/commit/f1492036a7c75dcfda4b1e7e0de8c09c29ad4436)) +* **aichat:** fix usage with gpt models + adapt test flow tool schema ([#6390](https://github.com/windmill-labs/windmill/issues/6390)) ([a530589](https://github.com/windmill-labs/windmill/commit/a5305897c5559e868f1abac945ef1680439d41e0)) +* better gcp pubsub error status code ([#6385](https://github.com/windmill-labs/windmill/issues/6385)) ([80d1242](https://github.com/windmill-labs/windmill/commit/80d12426f67284495c0c7446c6b91d33311141b4)) +* **flowEditor:** add diff mode action buttons to inline script editor ([#6379](https://github.com/windmill-labs/windmill/issues/6379)) ([4398013](https://github.com/windmill-labs/windmill/commit/4398013e8107a3085c3482baa7a982665aa873a1)) +* improve schemaeditor for nested oneOfs ([4bf4531](https://github.com/windmill-labs/windmill/commit/4bf4531fba65da92ae7acbea22b78187e24d7d75)) + +## [1.525.0](https://github.com/windmill-labs/windmill/compare/v1.524.0...v1.525.0) (2025-08-14) + + +### Features + +* **aichat:** add test tool to script and flow mode ([#6367](https://github.com/windmill-labs/windmill/issues/6367)) ([34773f2](https://github.com/windmill-labs/windmill/commit/34773f2614450d0e82b190c04bb446dec74f84dc)) +* **cli:** add better error handling with path logging for JSON parsing failures ([#6370](https://github.com/windmill-labs/windmill/issues/6370)) ([f03a8d6](https://github.com/windmill-labs/windmill/commit/f03a8d69c017e5ac8bb34cabdfd5c634dc126f3f)) +* **frontend:** add flow log view ([#6330](https://github.com/windmill-labs/windmill/issues/6330)) ([4ec1dce](https://github.com/windmill-labs/windmill/commit/4ec1dce5313177079177b9d90558e8085599d19d)) + + +### Bug Fixes + +* fix csharp build hanging ([ef14290](https://github.com/windmill-labs/windmill/commit/ef14290265eaf327d3e42b7f2fbb9dfd9eb3a873)) +* fix resource type search when adding resources ([e1629f7](https://github.com/windmill-labs/windmill/commit/e1629f799d60b4ca5db1e469cac57cc6cfc7d83f)) +* **frontend:** do not open popup when clicking on wand in flow inline script editor ([#6374](https://github.com/windmill-labs/windmill/issues/6374)) ([2b37281](https://github.com/windmill-labs/windmill/commit/2b372810844cd28019145c1a825dd0ac6e924292)) +* **frontend:** fix minor issues in the UI ([#6382](https://github.com/windmill-labs/windmill/issues/6382)) ([a41edd2](https://github.com/windmill-labs/windmill/commit/a41edd236bdd3196468cdf2586c95ff0a4c1abf5)) + +## [1.524.0](https://github.com/windmill-labs/windmill/compare/v1.523.0...v1.524.0) (2025-08-12) + + +### Features + +* **mcp:** allow filtering by folder ([#6366](https://github.com/windmill-labs/windmill/issues/6366)) ([8ec4d61](https://github.com/windmill-labs/windmill/commit/8ec4d615d251a0a2ed26f3b1907d6f813d91f43c)) + + +### Bug Fixes + +* **app:** improve copy paste of tables with sub-components ([0dc8425](https://github.com/windmill-labs/windmill/commit/0dc84254fc152df82ffbd137f80ed01225c00043)) +* fix preprocessor usage in python ([85a9c91](https://github.com/windmill-labs/windmill/commit/85a9c91895d0460e5e7d2d9ff0e53d05e2354386)) +* fix v1.523.0 rust sdk build ([#6363](https://github.com/windmill-labs/windmill/issues/6363)) ([0893ce1](https://github.com/windmill-labs/windmill/commit/0893ce103ffcfbcb037c9e2ab851e480b61b3735)) + +## [1.523.0](https://github.com/windmill-labs/windmill/compare/v1.522.1...v1.523.0) (2025-08-11) + + +### Features + +* **aichat:** add api mode to call api endpoints ([#6343](https://github.com/windmill-labs/windmill/issues/6343)) ([2471c7a](https://github.com/windmill-labs/windmill/commit/2471c7acad3404dd37649ed661ab8793607b5a97)) +* **aichat:** add gpt5 compatibility ([#6358](https://github.com/windmill-labs/windmill/issues/6358)) ([49f6a3d](https://github.com/windmill-labs/windmill/commit/49f6a3d979ab8933de197dc0065c2720131fd597)) + + +### Bug Fixes + +* add extra query params for token request for client credentials ([#6360](https://github.com/windmill-labs/windmill/issues/6360)) ([1112de8](https://github.com/windmill-labs/windmill/commit/1112de87d440db5c8d0ddab6e3812dc432a58705)) +* **app:** improve carousel list recursive error ([a47463e](https://github.com/windmill-labs/windmill/commit/a47463e05398e4ebe0f2d09ee2eba462bcef217b)) +* improve app component loading speed ([13bf33f](https://github.com/windmill-labs/windmill/commit/13bf33f83c6660e05b78a8c941c2adc1b486f810)) +* improve app decision tree behavior ([322f680](https://github.com/windmill-labs/windmill/commit/322f68053a238d6ef7c0116ccfb1a49a19e08f76)) +* remove spurrious error log for apps ([c27df6a](https://github.com/windmill-labs/windmill/commit/c27df6a917d8a585593de0183f7ff1c5bb2a0321)) + +## [1.522.1](https://github.com/windmill-labs/windmill/compare/v1.522.0...v1.522.1) (2025-08-11) + + +### Bug Fixes + +* **app:** handle inline script of components with underscore in apps ([2648520](https://github.com/windmill-labs/windmill/commit/2648520b53925616b02ecab060e4d2d6db8c2e34)) +* **app:** improve id handling for transformers ([47c6386](https://github.com/windmill-labs/windmill/commit/47c6386d0ff6d59367ee38e0704d0e98802e1bff)) +* improve validate ID for id editors ([ea2f71d](https://github.com/windmill-labs/windmill/commit/ea2f71d8be424fe13772ec1b7eba85d55bc4eae4)) + +## [1.522.0](https://github.com/windmill-labs/windmill/compare/v1.521.0...v1.522.0) (2025-08-08) + + +### Features + +* add configurable stale jobs detection and cancellation ([147e697](https://github.com/windmill-labs/windmill/commit/147e6975c4b1e6c63e7b6b77c6645f8c88f0f78b)) + +## [1.521.0](https://github.com/windmill-labs/windmill/compare/v1.520.1...v1.521.0) (2025-08-08) + + +### Features + +* add instance-wide workspace prefix option for custom app ([#6180](https://github.com/windmill-labs/windmill/issues/6180)) ([414f099](https://github.com/windmill-labs/windmill/commit/414f09918856eb1d577eb7776273b6697d11e848)) +* nextcloud oauth ([#6341](https://github.com/windmill-labs/windmill/issues/6341)) ([755e334](https://github.com/windmill-labs/windmill/commit/755e3343035402b5993a516e58d03c10c47c3a00)) +* togglable manual acknowledgement for gcp trigger ([#6321](https://github.com/windmill-labs/windmill/issues/6321)) ([852bf06](https://github.com/windmill-labs/windmill/commit/852bf064dc4f640dab4248082afade6eae8bb2cc)) + + +### Bug Fixes + +* display if tag has an active workers attached to it in tag select ([2a64246](https://github.com/windmill-labs/windmill/commit/2a6424672b5ed6adb1a408ddc8acbf7d7b2221ac)) +* do not save license key when renewing if the expiry date is earlier than that of the current key ([#6346](https://github.com/windmill-labs/windmill/issues/6346)) ([5a97258](https://github.com/windmill-labs/windmill/commit/5a97258375d76164ed17f7b258fa9b3222459fe1)) + +## [1.520.1](https://github.com/windmill-labs/windmill/compare/v1.520.0...v1.520.1) (2025-08-07) + + +### Bug Fixes + +* fix oss build ([32b8e69](https://github.com/windmill-labs/windmill/commit/32b8e692b1e8ba549b75a72ff17300aca6f38f8e)) + +## [1.520.0](https://github.com/windmill-labs/windmill/compare/v1.519.2...v1.520.0) (2025-08-07) + + +### Features + +* add email workspace error handler if smtp is configured ([#6267](https://github.com/windmill-labs/windmill/issues/6267)) ([9fbb199](https://github.com/windmill-labs/windmill/commit/9fbb1992e505d47cbb3750db9c672e69b0d5f8e7)) +* **mcp:** add api endpoints as tools ([#6329](https://github.com/windmill-labs/windmill/issues/6329)) ([4d37463](https://github.com/windmill-labs/windmill/commit/4d3746335e5f9056dafdb0557169f8fae5449005)) + + +### Bug Fixes + +* improve result stream query efficiency ([9e5a1ce](https://github.com/windmill-labs/windmill/commit/9e5a1cee0cc833d33d3b654cf330723295c3f31a)) + +## [1.519.2](https://github.com/windmill-labs/windmill/compare/v1.519.1...v1.519.2) (2025-08-07) + + +### Bug Fixes + +* native text response streaming in agent workers ([545d3ce](https://github.com/windmill-labs/windmill/commit/545d3ce74c1edcf588289dc67aa4c365dd48ed10)) + +## [1.519.1](https://github.com/windmill-labs/windmill/compare/v1.519.0...v1.519.1) (2025-08-06) + + +### Bug Fixes + +* fix python-client f formatting error ([fb9d7d4](https://github.com/windmill-labs/windmill/commit/fb9d7d4c95601965a0c00fe07458ea1b77a0fe69)) + +## [1.519.0](https://github.com/windmill-labs/windmill/compare/v1.518.2...v1.519.0) (2025-08-06) + + +### Features + +* add native result streaming ([#6242](https://github.com/windmill-labs/windmill/issues/6242)) ([161dbbc](https://github.com/windmill-labs/windmill/commit/161dbbc6d60ad8b4e7f1ea7afdcae35b2128eeda)) +* Ducklake native support ([#6268](https://github.com/windmill-labs/windmill/issues/6268)) ([e6f1211](https://github.com/windmill-labs/windmill/commit/e6f1211d317ca5c5b33ced324bf914b080d3ce2c)) +* dynamic select in flow ([#6315](https://github.com/windmill-labs/windmill/issues/6315)) ([1bcb043](https://github.com/windmill-labs/windmill/commit/1bcb0431c44be5cc7b99e2131f73f456c1bd91ac)) +* git sync v2 + cli git_branches support ([#6327](https://github.com/windmill-labs/windmill/issues/6327)) ([cb649b2](https://github.com/windmill-labs/windmill/commit/cb649b26893d8829aae0ed8cdf896607a3331804)) + + +### Bug Fixes + +* **rust-sdk:** revert `openapi-generator-cli` to `7.10.0` ([#6325](https://github.com/windmill-labs/windmill/issues/6325)) ([83aa0d0](https://github.com/windmill-labs/windmill/commit/83aa0d0267fbf2c467c050d21583ddcb49909a8f)) + ## [1.518.2](https://github.com/windmill-labs/windmill/compare/v1.518.1...v1.518.2) (2025-08-05) diff --git a/backend/.gitignore b/backend/.gitignore index 2a3262acac..f96e7e106d 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -7,4 +7,6 @@ heaptrack* index/ windmill-api/openapi-*.* .duckdb/* -*ee.rs \ No newline at end of file +*ee.rs +generate_mcp_endpoints_tools/venv +bacon.toml \ No newline at end of file diff --git a/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json b/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json index 0ef59c340c..fac1d666f7 100644 --- a/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json +++ b/backend/.sqlx/query-08f288d2781d823e109a9e5b8848234ca7d1efeee9661f3901f298da375e73f7.json @@ -147,6 +147,16 @@ "ordinal": 28, "name": "ducklake", "type_info": "Jsonb" + }, + { + "ordinal": 29, + "name": "auto_add_instance_groups", + "type_info": "TextArray" + }, + { + "ordinal": 30, + "name": "auto_add_instance_groups_roles", + "type_info": "Jsonb" } ], "parameters": { @@ -183,6 +193,8 @@ true, true, false, + true, + true, true ] }, diff --git a/backend/.sqlx/query-0937e25e89959447e7cb1816c112bbc4718cbb8ad6e2f13eae6b14f129d12936.json b/backend/.sqlx/query-0937e25e89959447e7cb1816c112bbc4718cbb8ad6e2f13eae6b14f129d12936.json index 152ae38fdc..60d2cfdc01 100644 --- a/backend/.sqlx/query-0937e25e89959447e7cb1816c112bbc4718cbb8ad6e2f13eae6b14f129d12936.json +++ b/backend/.sqlx/query-0937e25e89959447e7cb1816c112bbc4718cbb8ad6e2f13eae6b14f129d12936.json @@ -62,7 +62,8 @@ "oracledb", "nu", "java", - "duckdb" + "duckdb", + "ruby" ] } } diff --git a/backend/.sqlx/query-0a56301b5aaf57339cb2904c8f617366b74e891034d32f2867ccb019da869fc8.json b/backend/.sqlx/query-0a56301b5aaf57339cb2904c8f617366b74e891034d32f2867ccb019da869fc8.json deleted file mode 100644 index 159789f439..0000000000 --- a/backend/.sqlx/query-0a56301b5aaf57339cb2904c8f617366b74e891034d32f2867ccb019da869fc8.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO flow\n (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at) \n SELECT workspace_id, REGEXP_REPLACE(path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1'), summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at\n FROM flow \n WHERE path LIKE ('u/' || $2 || '/%') AND workspace_id = $3", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text", - "Text", - "Text" - ] - }, - "nullable": [] - }, - "hash": "0a56301b5aaf57339cb2904c8f617366b74e891034d32f2867ccb019da869fc8" -} diff --git a/backend/.sqlx/query-e822d186203fe809b764007ad7c02870a3b7d93ae43b40ac2cd3181dffab0837.json b/backend/.sqlx/query-0d7ce0397ef15c9d6cdaeaa2730a9e27fb7387ca24980df1481e6a94622ef006.json similarity index 57% rename from backend/.sqlx/query-e822d186203fe809b764007ad7c02870a3b7d93ae43b40ac2cd3181dffab0837.json rename to backend/.sqlx/query-0d7ce0397ef15c9d6cdaeaa2730a9e27fb7387ca24980df1481e6a94622ef006.json index 8974494ddd..1c8c6bb12c 100644 --- a/backend/.sqlx/query-e822d186203fe809b764007ad7c02870a3b7d93ae43b40ac2cd3181dffab0837.json +++ b/backend/.sqlx/query-0d7ce0397ef15c9d6cdaeaa2730a9e27fb7387ca24980df1481e6a94622ef006.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO usr (workspace_id, username, email, is_admin, operator) VALUES ($1, $2, $3, false, $4) ON CONFLICT DO NOTHING", + "query": "INSERT INTO usr (workspace_id, username, email, is_admin, operator, added_via) VALUES ($1, $2, $3, false, $4, $5) ON CONFLICT DO NOTHING", "describe": { "columns": [], "parameters": { @@ -8,10 +8,11 @@ "Varchar", "Varchar", "Varchar", - "Bool" + "Bool", + "Jsonb" ] }, "nullable": [] }, - "hash": "e822d186203fe809b764007ad7c02870a3b7d93ae43b40ac2cd3181dffab0837" + "hash": "0d7ce0397ef15c9d6cdaeaa2730a9e27fb7387ca24980df1481e6a94622ef006" } diff --git a/backend/.sqlx/query-0d8415a79d8312db4d46da46fdf348ab17dee7b8358b6719fa399511962bc3e9.json b/backend/.sqlx/query-0d8415a79d8312db4d46da46fdf348ab17dee7b8358b6719fa399511962bc3e9.json new file mode 100644 index 0000000000..2040b0ed88 --- /dev/null +++ b/backend/.sqlx/query-0d8415a79d8312db4d46da46fdf348ab17dee7b8358b6719fa399511962bc3e9.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n nats_trigger\n SET\n nats_resource_path = $1,\n subjects = $2,\n stream_name = $3,\n consumer_name = $4,\n use_jetstream = $5,\n script_path = $6,\n path = $7,\n is_flow = $8,\n edited_by = $9,\n email = $10,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $13,\n error_handler_args = $14,\n retry = $15\n WHERE\n workspace_id = $11 AND path = $12\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "VarcharArray", + "Varchar", + "Varchar", + "Bool", + "Varchar", + "Varchar", + "Bool", + "Varchar", + "Varchar", + "Text", + "Text", + "Varchar", + "Jsonb", + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "0d8415a79d8312db4d46da46fdf348ab17dee7b8358b6719fa399511962bc3e9" +} diff --git a/backend/.sqlx/query-0fa105c49c8345916716514444bd3616ae4d114216c659233fbbc3c047e6b30a.json b/backend/.sqlx/query-0fa105c49c8345916716514444bd3616ae4d114216c659233fbbc3c047e6b30a.json new file mode 100644 index 0000000000..c604c19f04 --- /dev/null +++ b/backend/.sqlx/query-0fa105c49c8345916716514444bd3616ae4d114216c659233fbbc3c047e6b30a.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE\n workspace_settings\n SET\n error_handler = NULL,\n error_handler_extra_args = NULL,\n error_handler_muted_on_cancel = NULL\n WHERE\n workspace_id = $1\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "0fa105c49c8345916716514444bd3616ae4d114216c659233fbbc3c047e6b30a" +} diff --git a/backend/.sqlx/query-10f6d3ffd7406146572b1becdce5c8da5242b58f6ce46ab10296cff9d6a3a6c4.json b/backend/.sqlx/query-10f6d3ffd7406146572b1becdce5c8da5242b58f6ce46ab10296cff9d6a3a6c4.json new file mode 100644 index 0000000000..98bafc734c --- /dev/null +++ b/backend/.sqlx/query-10f6d3ffd7406146572b1becdce5c8da5242b58f6ce46ab10296cff9d6a3a6c4.json @@ -0,0 +1,32 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT name, summary, array_remove(array_agg(email_to_igroup.email), null) as emails FROM email_to_igroup RIGHT JOIN instance_group ON instance_group.name = email_to_igroup.igroup GROUP BY name, summary", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "summary", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "emails", + "type_info": "VarcharArray" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + true, + null + ] + }, + "hash": "10f6d3ffd7406146572b1becdce5c8da5242b58f6ce46ab10296cff9d6a3a6c4" +} diff --git a/backend/.sqlx/query-11e24f758a70cd5f3a240bc81a05f40754826db0ee1194409227597a98603e92.json b/backend/.sqlx/query-11e24f758a70cd5f3a240bc81a05f40754826db0ee1194409227597a98603e92.json new file mode 100644 index 0000000000..4ed5cb835e --- /dev/null +++ b/backend/.sqlx/query-11e24f758a70cd5f3a240bc81a05f40754826db0ee1194409227597a98603e92.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n path,\n custom_path\n FROM \n app\n WHERE \n custom_path IN (\n SELECT \n custom_path\n FROM \n app\n GROUP \n BY custom_path\n HAVING COUNT(*) > 1\n )\n ORDER BY custom_path\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "path", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "custom_path", + "type_info": "Text" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + true + ] + }, + "hash": "11e24f758a70cd5f3a240bc81a05f40754826db0ee1194409227597a98603e92" +} diff --git a/backend/.sqlx/query-18ca698813b58c7f93139b12818681f9e36e8bb69b7a37755dc9b90c5e1af4cf.json b/backend/.sqlx/query-18ca698813b58c7f93139b12818681f9e36e8bb69b7a37755dc9b90c5e1af4cf.json new file mode 100644 index 0000000000..6b35c38a4d --- /dev/null +++ b/backend/.sqlx/query-18ca698813b58c7f93139b12818681f9e36e8bb69b7a37755dc9b90c5e1af4cf.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT tag::text, EXISTS(SELECT 1 FROM worker_ping WHERE custom_tags @> ARRAY[tag] AND ping_at > now() - interval '1 minute') as exists\n FROM unnest($1::text[]) as tag", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "tag", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "exists", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "TextArray" + ] + }, + "nullable": [ + null, + null + ] + }, + "hash": "18ca698813b58c7f93139b12818681f9e36e8bb69b7a37755dc9b90c5e1af4cf" +} diff --git a/backend/.sqlx/query-18e550f4ec23d465632449b88c4b25931f145f771b93828e8e6dfcc1f906443d.json b/backend/.sqlx/query-18e550f4ec23d465632449b88c4b25931f145f771b93828e8e6dfcc1f906443d.json new file mode 100644 index 0000000000..bfc9587a1e --- /dev/null +++ b/backend/.sqlx/query-18e550f4ec23d465632449b88c4b25931f145f771b93828e8e6dfcc1f906443d.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COALESCE(auto_add_instance_groups, '{}') FROM workspace_settings WHERE workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "coalesce", + "type_info": "TextArray" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "18e550f4ec23d465632449b88c4b25931f145f771b93828e8e6dfcc1f906443d" +} diff --git a/backend/.sqlx/query-1a54356c1e1353950bf6ab1d25ab21270131e9e93ca10d195664e7e5a774fe9e.json b/backend/.sqlx/query-1a54356c1e1353950bf6ab1d25ab21270131e9e93ca10d195664e7e5a774fe9e.json index e8b39caef7..9462f9b48b 100644 --- a/backend/.sqlx/query-1a54356c1e1353950bf6ab1d25ab21270131e9e93ca10d195664e7e5a774fe9e.json +++ b/backend/.sqlx/query-1a54356c1e1353950bf6ab1d25ab21270131e9e93ca10d195664e7e5a774fe9e.json @@ -43,7 +43,8 @@ "oracledb", "nu", "java", - "duckdb" + "duckdb", + "ruby" ] } } diff --git a/backend/.sqlx/query-566823041170df3a6379ce58d321895746b0cd8a6891b05d51abeb7079ded5d7.json b/backend/.sqlx/query-1a9ba16c90d3d65c4ff39aaddb3079009e03af711e7f6b53332537cf4cb0e8dd.json similarity index 80% rename from backend/.sqlx/query-566823041170df3a6379ce58d321895746b0cd8a6891b05d51abeb7079ded5d7.json rename to backend/.sqlx/query-1a9ba16c90d3d65c4ff39aaddb3079009e03af711e7f6b53332537cf4cb0e8dd.json index 2c7d846d8f..141f92e271 100644 --- a/backend/.sqlx/query-566823041170df3a6379ce58d321895746b0cd8a6891b05d51abeb7079ded5d7.json +++ b/backend/.sqlx/query-1a9ba16c90d3d65c4ff39aaddb3079009e03af711e7f6b53332537cf4cb0e8dd.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE \n gcp_trigger \n SET \n gcp_resource_path = $1,\n subscription_id = $2,\n topic_id = $3,\n delivery_type = $4,\n delivery_config = $5,\n is_flow = $6, \n edited_by = $7, \n email = $8,\n script_path = $9,\n path = $10,\n enabled = $11,\n edited_at = now(), \n error = NULL,\n server_id = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16\n WHERE \n workspace_id = $12 AND \n path = $13\n ", + "query": "\n UPDATE \n gcp_trigger \n SET \n gcp_resource_path = $1,\n subscription_id = $2,\n topic_id = $3,\n delivery_type = $4,\n delivery_config = $5,\n is_flow = $6, \n edited_by = $7, \n email = $8,\n script_path = $9,\n path = $10,\n enabled = $11,\n edited_at = now(), \n error = NULL,\n server_id = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16,\n auto_acknowledge_msg = $17\n WHERE \n workspace_id = $12 AND \n path = $13\n ", "describe": { "columns": [], "parameters": { @@ -30,10 +30,11 @@ "Text", "Varchar", "Jsonb", - "Jsonb" + "Jsonb", + "Bool" ] }, "nullable": [] }, - "hash": "566823041170df3a6379ce58d321895746b0cd8a6891b05d51abeb7079ded5d7" + "hash": "1a9ba16c90d3d65c4ff39aaddb3079009e03af711e7f6b53332537cf4cb0e8dd" } diff --git a/backend/.sqlx/query-1e6c125c884002a1565b11a2c308ce89f9726d21bbb3cc712ad0b6450cbb44e6.json b/backend/.sqlx/query-1e6c125c884002a1565b11a2c308ce89f9726d21bbb3cc712ad0b6450cbb44e6.json new file mode 100644 index 0000000000..2a4c87da6a --- /dev/null +++ b/backend/.sqlx/query-1e6c125c884002a1565b11a2c308ce89f9726d21bbb3cc712ad0b6450cbb44e6.json @@ -0,0 +1,38 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n ig.name as group_name,\n ws.workspace_id,\n w.name as workspace_name,\n ws.auto_add_instance_groups_roles->ig.name as role\n FROM instance_group ig\n INNER JOIN workspace_settings ws ON ws.auto_add_instance_groups IS NOT NULL\n AND ig.name = ANY(ws.auto_add_instance_groups)\n INNER JOIN workspace w ON w.id = ws.workspace_id AND w.deleted = false\n ORDER BY ig.name, ws.workspace_id\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "group_name", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "workspace_name", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "role", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + null + ] + }, + "hash": "1e6c125c884002a1565b11a2c308ce89f9726d21bbb3cc712ad0b6450cbb44e6" +} diff --git a/backend/.sqlx/query-21099fabde943edc90d3a0125e8490691b2701d11f918da3bb5ae610d5c023a0.json b/backend/.sqlx/query-21099fabde943edc90d3a0125e8490691b2701d11f918da3bb5ae610d5c023a0.json new file mode 100644 index 0000000000..ccfddce0ff --- /dev/null +++ b/backend/.sqlx/query-21099fabde943edc90d3a0125e8490691b2701d11f918da3bb5ae610d5c023a0.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT email FROM email_to_igroup WHERE igroup = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "email", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "21099fabde943edc90d3a0125e8490691b2701d11f918da3bb5ae610d5c023a0" +} diff --git a/backend/.sqlx/query-2132a715995f2775917c59c01ba66f2e472a2347f25cc7109f36a805806ee6e2.json b/backend/.sqlx/query-2132a715995f2775917c59c01ba66f2e472a2347f25cc7109f36a805806ee6e2.json new file mode 100644 index 0000000000..673b8f4574 --- /dev/null +++ b/backend/.sqlx/query-2132a715995f2775917c59c01ba66f2e472a2347f25cc7109f36a805806ee6e2.json @@ -0,0 +1,29 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT username, added_via\n FROM usr\n WHERE workspace_id = $1 AND email = $2\n AND added_via->>'source' = 'instance_group'\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "username", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "added_via", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + true + ] + }, + "hash": "2132a715995f2775917c59c01ba66f2e472a2347f25cc7109f36a805806ee6e2" +} diff --git a/backend/.sqlx/query-278bc6b4f149f824b5db32dacfaa714ee3852dc2ddf2d661dfdd5a986a9bb62b.json b/backend/.sqlx/query-278bc6b4f149f824b5db32dacfaa714ee3852dc2ddf2d661dfdd5a986a9bb62b.json deleted file mode 100644 index 1a18c37a2e..0000000000 --- a/backend/.sqlx/query-278bc6b4f149f824b5db32dacfaa714ee3852dc2ddf2d661dfdd5a986a9bb62b.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT\n c.id IS NOT NULL AS completed,\n CASE \n WHEN q.id IS NOT NULL THEN (CASE WHEN NOT $5 AND q.running THEN true ELSE null END)\n ELSE false\n END AS running,\n SUBSTR(logs, GREATEST($1 - log_offset, 0)) AS logs,\n COALESCE(r.memory_peak, c.memory_peak) AS mem_peak,\n COALESCE(c.flow_status, f.flow_status) AS \"flow_status: sqlx::types::Json>\",\n COALESCE(c.workflow_as_code_status, f.workflow_as_code_status) AS \"workflow_as_code_status: sqlx::types::Json>\",\n job_logs.log_offset + CHAR_LENGTH(job_logs.logs) + 1 AS log_offset,\n created_by AS \"created_by!\",\n CASE WHEN $4::BOOLEAN THEN (\n SELECT scalar_int FROM job_stats WHERE job_id = $3 AND metric_id = 'progress_perc'\n ) END AS progress\n FROM v2_job j\n LEFT JOIN v2_job_queue q USING (id)\n LEFT JOIN v2_job_runtime r USING (id)\n LEFT JOIN v2_job_status f USING (id)\n LEFT JOIN v2_job_completed c USING (id)\n LEFT JOIN job_logs ON job_logs.job_id = $3\n WHERE j.workspace_id = $2 AND j.id = $3\n AND ($6::text[] IS NULL OR j.tag = ANY($6))", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "completed", - "type_info": "Bool" - }, - { - "ordinal": 1, - "name": "running", - "type_info": "Bool" - }, - { - "ordinal": 2, - "name": "logs", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "mem_peak", - "type_info": "Int4" - }, - { - "ordinal": 4, - "name": "flow_status: sqlx::types::Json>", - "type_info": "Jsonb" - }, - { - "ordinal": 5, - "name": "workflow_as_code_status: sqlx::types::Json>", - "type_info": "Jsonb" - }, - { - "ordinal": 6, - "name": "log_offset", - "type_info": "Int4" - }, - { - "ordinal": 7, - "name": "created_by!", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "progress", - "type_info": "Int4" - } - ], - "parameters": { - "Left": [ - "Int4", - "Text", - "Uuid", - "Bool", - "Bool", - "TextArray" - ] - }, - "nullable": [ - null, - null, - null, - null, - null, - null, - null, - false, - null - ] - }, - "hash": "278bc6b4f149f824b5db32dacfaa714ee3852dc2ddf2d661dfdd5a986a9bb62b" -} diff --git a/backend/.sqlx/query-e8b74683f994d198a051c2f211a2844241bf2a7d7702c569017ae53fca706c59.json b/backend/.sqlx/query-29673d489fbf45fc249da04c1a2fd60e2364ba87263f962ed7d4329c916620a1.json similarity index 89% rename from backend/.sqlx/query-e8b74683f994d198a051c2f211a2844241bf2a7d7702c569017ae53fca706c59.json rename to backend/.sqlx/query-29673d489fbf45fc249da04c1a2fd60e2364ba87263f962ed7d4329c916620a1.json index 8215259a01..251fce2637 100644 --- a/backend/.sqlx/query-e8b74683f994d198a051c2f211a2844241bf2a7d7702c569017ae53fca706c59.json +++ b/backend/.sqlx/query-29673d489fbf45fc249da04c1a2fd60e2364ba87263f962ed7d4329c916620a1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT label,\n concat(substring(token for 10)) AS token_prefix,\n expiration,\n created_at,\n last_used_at,\n scopes,\n email\n FROM token\n WHERE workspace_id = $1\n AND (\n scopes @> ARRAY['jobs:run:scripts:' || $2]::text[]\n OR scopes @> ARRAY['run:scripts/' || $2]::text[]\n )\n ", + "query": "\n SELECT label,\n concat(substring(token for 10)) AS token_prefix,\n expiration,\n created_at,\n last_used_at,\n scopes,\n email\n FROM token\n WHERE workspace_id = $1\n AND (\n scopes @> ARRAY['jobs:run:scripts:' || $2]::text[]\n OR scopes @> ARRAY['run:script/' || $2]::text[]\n )\n ", "describe": { "columns": [ { @@ -55,5 +55,5 @@ true ] }, - "hash": "e8b74683f994d198a051c2f211a2844241bf2a7d7702c569017ae53fca706c59" + "hash": "29673d489fbf45fc249da04c1a2fd60e2364ba87263f962ed7d4329c916620a1" } diff --git a/backend/.sqlx/query-31db242523c534b2abbb5f4985f56dd4493d19611ed988d694d9f4289dc29c48.json b/backend/.sqlx/query-31db242523c534b2abbb5f4985f56dd4493d19611ed988d694d9f4289dc29c48.json new file mode 100644 index 0000000000..6bb55d3c77 --- /dev/null +++ b/backend/.sqlx/query-31db242523c534b2abbb5f4985f56dd4493d19611ed988d694d9f4289dc29c48.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET auto_add_instance_groups = NULL, auto_add_instance_groups_roles = NULL WHERE workspace_id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "31db242523c534b2abbb5f4985f56dd4493d19611ed988d694d9f4289dc29c48" +} diff --git a/backend/.sqlx/query-9aa094ef0652f36d473866469e71cc41c7b1cf848d0b4beffa7eefc0fbf9d64f.json b/backend/.sqlx/query-33aaf2dd14397d0b50b986ed55bc458337b85b6a9a9ab3d93d8f33d0f57e4b0f.json similarity index 86% rename from backend/.sqlx/query-9aa094ef0652f36d473866469e71cc41c7b1cf848d0b4beffa7eefc0fbf9d64f.json rename to backend/.sqlx/query-33aaf2dd14397d0b50b986ed55bc458337b85b6a9a9ab3d93d8f33d0f57e4b0f.json index fa80b194ec..188dcc8d5b 100644 --- a/backend/.sqlx/query-9aa094ef0652f36d473866469e71cc41c7b1cf848d0b4beffa7eefc0fbf9d64f.json +++ b/backend/.sqlx/query-33aaf2dd14397d0b50b986ed55bc458337b85b6a9a9ab3d93d8f33d0f57e4b0f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM\n gcp_trigger\n WHERE\n delivery_type != 'push'::DELIVERY_MODE AND\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ", + "query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\",\n auto_acknowledge_msg\n FROM\n gcp_trigger\n WHERE\n delivery_type != 'push'::DELIVERY_MODE AND\n enabled IS TRUE\n AND (last_server_ping IS NULL OR\n last_server_ping < now() - interval '15 seconds'\n )\n ", "describe": { "columns": [ { @@ -127,6 +127,11 @@ "ordinal": 20, "name": "retry: _", "type_info": "Jsonb" + }, + { + "ordinal": 21, + "name": "auto_acknowledge_msg", + "type_info": "Bool" } ], "parameters": { @@ -153,8 +158,9 @@ false, true, true, + true, true ] }, - "hash": "9aa094ef0652f36d473866469e71cc41c7b1cf848d0b4beffa7eefc0fbf9d64f" + "hash": "33aaf2dd14397d0b50b986ed55bc458337b85b6a9a9ab3d93d8f33d0f57e4b0f" } diff --git a/backend/.sqlx/query-35b211d19e53da4b64b0bd097284de3236ab939e47a1fc2b15ffc9607b552f8d.json b/backend/.sqlx/query-35b211d19e53da4b64b0bd097284de3236ab939e47a1fc2b15ffc9607b552f8d.json new file mode 100644 index 0000000000..aa52f752ae --- /dev/null +++ b/backend/.sqlx/query-35b211d19e53da4b64b0bd097284de3236ab939e47a1fc2b15ffc9607b552f8d.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE \n websocket_trigger\n SET\n url = $1,\n script_path = $2,\n path = $3,\n is_flow = $4,\n filters = $5,\n initial_messages = $6,\n url_runnable_args = $7,\n edited_by = $8,\n email = $9,\n can_return_message = $10,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $13,\n error_handler_args = $14,\n retry = $15\n WHERE\n workspace_id = $11 AND path = $12\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "Bool", + "JsonbArray", + "JsonbArray", + "Jsonb", + "Varchar", + "Varchar", + "Bool", + "Text", + "Text", + "Varchar", + "Jsonb", + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "35b211d19e53da4b64b0bd097284de3236ab939e47a1fc2b15ffc9607b552f8d" +} diff --git a/backend/.sqlx/query-3ebc91867be0a4830ef4dbbe833aed348f78e3cb68fb1f3c855a491bdcda5017.json b/backend/.sqlx/query-3ebc91867be0a4830ef4dbbe833aed348f78e3cb68fb1f3c855a491bdcda5017.json new file mode 100644 index 0000000000..73d8f1858c --- /dev/null +++ b/backend/.sqlx/query-3ebc91867be0a4830ef4dbbe833aed348f78e3cb68fb1f3c855a491bdcda5017.json @@ -0,0 +1,28 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n workspace_id,\n auto_add_instance_groups_roles\n FROM workspace_settings\n WHERE\n auto_add_instance_groups IS NOT NULL\n AND $1 = ANY(auto_add_instance_groups)\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "auto_add_instance_groups_roles", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + true + ] + }, + "hash": "3ebc91867be0a4830ef4dbbe833aed348f78e3cb68fb1f3c855a491bdcda5017" +} diff --git a/backend/.sqlx/query-bf1fba73d3109aae48bda524cfafdf7f5b9b0e56396aed1b83a2c235222e8983.json b/backend/.sqlx/query-3ebf788b86d6aed59b8c61c08f1d9323e6c66107a1b7b37dadbbb7b91339e9d5.json similarity index 70% rename from backend/.sqlx/query-bf1fba73d3109aae48bda524cfafdf7f5b9b0e56396aed1b83a2c235222e8983.json rename to backend/.sqlx/query-3ebf788b86d6aed59b8c61c08f1d9323e6c66107a1b7b37dadbbb7b91339e9d5.json index b939715091..493a2ba2ec 100644 --- a/backend/.sqlx/query-bf1fba73d3109aae48bda524cfafdf7f5b9b0e56396aed1b83a2c235222e8983.json +++ b/backend/.sqlx/query-3ebf788b86d6aed59b8c61c08f1d9323e6c66107a1b7b37dadbbb7b91339e9d5.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT \n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n replication_slot_name,\n publication_name,\n postgres_resource_path,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\" \n FROM postgres_trigger\n WHERE workspace_id = $1", + "query": "\n SELECT \n workspace_id,\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n replication_slot_name,\n publication_name,\n postgres_resource_path,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM \n postgres_trigger\n WHERE \n workspace_id = $1\n ", "describe": { "columns": [ { @@ -120,5 +120,5 @@ true ] }, - "hash": "bf1fba73d3109aae48bda524cfafdf7f5b9b0e56396aed1b83a2c235222e8983" + "hash": "3ebf788b86d6aed59b8c61c08f1d9323e6c66107a1b7b37dadbbb7b91339e9d5" } diff --git a/backend/.sqlx/query-3f91433fc076f47dd3516b40c9337981c329ddc31132882f037eaf2709aa8805.json b/backend/.sqlx/query-3f91433fc076f47dd3516b40c9337981c329ddc31132882f037eaf2709aa8805.json deleted file mode 100644 index bbd1cbc510..0000000000 --- a/backend/.sqlx/query-3f91433fc076f47dd3516b40c9337981c329ddc31132882f037eaf2709aa8805.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE kafka_trigger SET kafka_resource_path = $1, group_id = $2, topics = $3, script_path = $4, path = $5, is_flow = $6, edited_by = $7, email = $8, edited_at = now(), server_id = NULL, error = NULL, error_handler_path = $11, error_handler_args = $12, retry = $13\n WHERE workspace_id = $9 AND path = $10", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "VarcharArray", - "Varchar", - "Varchar", - "Bool", - "Varchar", - "Varchar", - "Text", - "Text", - "Varchar", - "Jsonb", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "3f91433fc076f47dd3516b40c9337981c329ddc31132882f037eaf2709aa8805" -} diff --git a/backend/.sqlx/query-3fdfcab1a54c166b1d8d43215d61268a251160db4630f0342522091668f36af0.json b/backend/.sqlx/query-3fdfcab1a54c166b1d8d43215d61268a251160db4630f0342522091668f36af0.json deleted file mode 100644 index be7e8608de..0000000000 --- a/backend/.sqlx/query-3fdfcab1a54c166b1d8d43215d61268a251160db4630f0342522091668f36af0.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT usr.username \n FROM usr_to_group LEFT JOIN usr ON usr_to_group.usr = usr.username AND usr_to_group.workspace_id = $2\n WHERE group_ = $1 AND usr.workspace_id = $2 AND usr_to_group.workspace_id = $2", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "username", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - false - ] - }, - "hash": "3fdfcab1a54c166b1d8d43215d61268a251160db4630f0342522091668f36af0" -} diff --git a/backend/.sqlx/query-404233e74aaafd987879c6c87d1fd80928e6fade09e60a9a2d35121c81885cca.json b/backend/.sqlx/query-404233e74aaafd987879c6c87d1fd80928e6fade09e60a9a2d35121c81885cca.json new file mode 100644 index 0000000000..2ed01e7a44 --- /dev/null +++ b/backend/.sqlx/query-404233e74aaafd987879c6c87d1fd80928e6fade09e60a9a2d35121c81885cca.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET auto_add_instance_groups = $2, auto_add_instance_groups_roles = $3 WHERE workspace_id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "TextArray", + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "404233e74aaafd987879c6c87d1fd80928e6fade09e60a9a2d35121c81885cca" +} diff --git a/backend/.sqlx/query-4479beaefc36afba093e5113bb330a87979623d69b94f274863318ed351a1dac.json b/backend/.sqlx/query-4479beaefc36afba093e5113bb330a87979623d69b94f274863318ed351a1dac.json deleted file mode 100644 index 02e3467f5c..0000000000 --- a/backend/.sqlx/query-4479beaefc36afba093e5113bb330a87979623d69b94f274863318ed351a1dac.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, initial_messages = $6, url_runnable_args = $7, edited_by = $8, email = $9, can_return_message = $10, edited_at = now(), server_id = NULL, error = NULL, error_handler_path = $13, error_handler_args = $14, retry = $15\n WHERE workspace_id = $11 AND path = $12", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "Varchar", - "Bool", - "JsonbArray", - "JsonbArray", - "Jsonb", - "Varchar", - "Varchar", - "Bool", - "Text", - "Text", - "Varchar", - "Jsonb", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "4479beaefc36afba093e5113bb330a87979623d69b94f274863318ed351a1dac" -} diff --git a/backend/.sqlx/query-472f224dc9e17d2c50cb9db13c34cc1fb9adb6d0ea36cf223541adb7cac17bdd.json b/backend/.sqlx/query-472f224dc9e17d2c50cb9db13c34cc1fb9adb6d0ea36cf223541adb7cac17bdd.json new file mode 100644 index 0000000000..b60ecae184 --- /dev/null +++ b/backend/.sqlx/query-472f224dc9e17d2c50cb9db13c34cc1fb9adb6d0ea36cf223541adb7cac17bdd.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT workspace_id, username, email\n FROM usr\n WHERE email = $1\n AND added_via->>'source' = 'instance_group'\n AND added_via->>'group' = $2\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "username", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "email", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "472f224dc9e17d2c50cb9db13c34cc1fb9adb6d0ea36cf223541adb7cac17bdd" +} diff --git a/backend/.sqlx/query-48242fc46cb8bc9e4d41c788568634619565e4ef342051af23760c11edac5ee8.json b/backend/.sqlx/query-48242fc46cb8bc9e4d41c788568634619565e4ef342051af23760c11edac5ee8.json index b2d146291b..eafb17af4e 100644 --- a/backend/.sqlx/query-48242fc46cb8bc9e4d41c788568634619565e4ef342051af23760c11edac5ee8.json +++ b/backend/.sqlx/query-48242fc46cb8bc9e4d41c788568634619565e4ef342051af23760c11edac5ee8.json @@ -32,7 +32,8 @@ "oracledb", "nu", "java", - "duckdb" + "duckdb", + "ruby" ] } } diff --git a/backend/.sqlx/query-488dd591096b2b47787afdc3a1d73917ed13269f2ee20b86df79fca2c8efe672.json b/backend/.sqlx/query-488dd591096b2b47787afdc3a1d73917ed13269f2ee20b86df79fca2c8efe672.json index 561e202d1d..5f59450dc3 100644 --- a/backend/.sqlx/query-488dd591096b2b47787afdc3a1d73917ed13269f2ee20b86df79fca2c8efe672.json +++ b/backend/.sqlx/query-488dd591096b2b47787afdc3a1d73917ed13269f2ee20b86df79fca2c8efe672.json @@ -71,7 +71,8 @@ "oracledb", "nu", "java", - "duckdb" + "duckdb", + "ruby" ] } } diff --git a/backend/.sqlx/query-6489342f52a9e1e90466bad48e0a6079fcabd696b02d57e3dbe652d28b84f008.json b/backend/.sqlx/query-494d49e92dce7a1cd7074156704ccbbff020a2042409fc318e045852323cef5e.json similarity index 92% rename from backend/.sqlx/query-6489342f52a9e1e90466bad48e0a6079fcabd696b02d57e3dbe652d28b84f008.json rename to backend/.sqlx/query-494d49e92dce7a1cd7074156704ccbbff020a2042409fc318e045852323cef5e.json index fa87b125a2..25d6e0e6d3 100644 --- a/backend/.sqlx/query-6489342f52a9e1e90466bad48e0a6079fcabd696b02d57e3dbe652d28b84f008.json +++ b/backend/.sqlx/query-494d49e92dce7a1cd7074156704ccbbff020a2042409fc318e045852323cef5e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT \n workspace_id,\n path,\n kafka_resource_path,\n group_id,\n topics,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM kafka_trigger WHERE workspace_id = $1 AND path = $2", + "query": "SELECT \n workspace_id,\n path,\n kafka_resource_path,\n group_id,\n topics,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM \n kafka_trigger \n WHERE \n workspace_id = $1 AND \n path = $2\n ", "describe": { "columns": [ { @@ -121,5 +121,5 @@ true ] }, - "hash": "6489342f52a9e1e90466bad48e0a6079fcabd696b02d57e3dbe652d28b84f008" + "hash": "494d49e92dce7a1cd7074156704ccbbff020a2042409fc318e045852323cef5e" } diff --git a/backend/.sqlx/query-4a446b97cf813226d25ca40cefca2999cdcf938f817327e9168b3edb0fce7fb7.json b/backend/.sqlx/query-4a446b97cf813226d25ca40cefca2999cdcf938f817327e9168b3edb0fce7fb7.json new file mode 100644 index 0000000000..f984c967dd --- /dev/null +++ b/backend/.sqlx/query-4a446b97cf813226d25ca40cefca2999cdcf938f817327e9168b3edb0fce7fb7.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET\n auto_add_instance_groups = array_remove(auto_add_instance_groups, $1),\n auto_add_instance_groups_roles = auto_add_instance_groups_roles - $1\n WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "4a446b97cf813226d25ca40cefca2999cdcf938f817327e9168b3edb0fce7fb7" +} diff --git a/backend/.sqlx/query-4aaab98ebdaa90f1edf49ac96fba6c391c4d0054a618b861464ee37239f1f1e0.json b/backend/.sqlx/query-4aaab98ebdaa90f1edf49ac96fba6c391c4d0054a618b861464ee37239f1f1e0.json index 65fa6cc9eb..e16cd7db75 100644 --- a/backend/.sqlx/query-4aaab98ebdaa90f1edf49ac96fba6c391c4d0054a618b861464ee37239f1f1e0.json +++ b/backend/.sqlx/query-4aaab98ebdaa90f1edf49ac96fba6c391c4d0054a618b861464ee37239f1f1e0.json @@ -138,7 +138,8 @@ "oracledb", "nu", "java", - "duckdb" + "duckdb", + "ruby" ] } } @@ -223,7 +224,8 @@ "ui", "postgres", "sqs", - "gcp" + "gcp", + "mqtt" ] } } diff --git a/backend/.sqlx/query-4bc533074c720820cebff8d97a203df52520b7606378ecca267e88383a45b49b.json b/backend/.sqlx/query-4bc533074c720820cebff8d97a203df52520b7606378ecca267e88383a45b49b.json new file mode 100644 index 0000000000..8c9de521d2 --- /dev/null +++ b/backend/.sqlx/query-4bc533074c720820cebff8d97a203df52520b7606378ecca267e88383a45b49b.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO job_result_stream (workspace_id, job_id, stream)\n VALUES ($1, $2, $3)\n ON CONFLICT (job_id) DO UPDATE SET stream = job_result_stream.stream || $3\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Uuid", + "Text" + ] + }, + "nullable": [] + }, + "hash": "4bc533074c720820cebff8d97a203df52520b7606378ecca267e88383a45b49b" +} diff --git a/backend/.sqlx/query-4bfa42153df81184e02ffc7ab6c8c591605211c070e52b2919cf9d7dede0ea16.json b/backend/.sqlx/query-4bfa42153df81184e02ffc7ab6c8c591605211c070e52b2919cf9d7dede0ea16.json new file mode 100644 index 0000000000..94ea76acc2 --- /dev/null +++ b/backend/.sqlx/query-4bfa42153df81184e02ffc7ab6c8c591605211c070e52b2919cf9d7dede0ea16.json @@ -0,0 +1,26 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE kafka_trigger\n SET\n kafka_resource_path = $1,\n group_id = $2,\n topics = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n edited_by = $7,\n email = $8,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $11,\n error_handler_args = $12,\n retry = $13\n WHERE\n workspace_id = $9 AND path = $10\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "VarcharArray", + "Varchar", + "Varchar", + "Bool", + "Varchar", + "Varchar", + "Text", + "Text", + "Varchar", + "Jsonb", + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "4bfa42153df81184e02ffc7ab6c8c591605211c070e52b2919cf9d7dede0ea16" +} diff --git a/backend/.sqlx/query-8f511fd837a99fa37a95781e50f8a83eaf93651f82c36c9795637d3ad761d003.json b/backend/.sqlx/query-4c6fbed80ba44defe3d125c258bc3449b02925c8caf85d9203529471479fdde8.json similarity index 94% rename from backend/.sqlx/query-8f511fd837a99fa37a95781e50f8a83eaf93651f82c36c9795637d3ad761d003.json rename to backend/.sqlx/query-4c6fbed80ba44defe3d125c258bc3449b02925c8caf85d9203529471479fdde8.json index 8e37b499be..bb5c6096f8 100644 --- a/backend/.sqlx/query-8f511fd837a99fa37a95781e50f8a83eaf93651f82c36c9795637d3ad761d003.json +++ b/backend/.sqlx/query-4c6fbed80ba44defe3d125c258bc3449b02925c8caf85d9203529471479fdde8.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT \n workspace_id,\n path,\n kafka_resource_path,\n group_id,\n topics,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\" \n FROM kafka_trigger\n WHERE workspace_id = $1", + "query": "SELECT \n workspace_id,\n path,\n kafka_resource_path,\n group_id,\n topics,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM kafka_trigger\n WHERE workspace_id = $1", "describe": { "columns": [ { @@ -120,5 +120,5 @@ true ] }, - "hash": "8f511fd837a99fa37a95781e50f8a83eaf93651f82c36c9795637d3ad761d003" + "hash": "4c6fbed80ba44defe3d125c258bc3449b02925c8caf85d9203529471479fdde8" } diff --git a/backend/.sqlx/query-4f372d047c78532907adf2d2dc114352aa7b5b28dccc50a1231a7f6539397da7.json b/backend/.sqlx/query-4f372d047c78532907adf2d2dc114352aa7b5b28dccc50a1231a7f6539397da7.json new file mode 100644 index 0000000000..e8377c5ff4 --- /dev/null +++ b/backend/.sqlx/query-4f372d047c78532907adf2d2dc114352aa7b5b28dccc50a1231a7f6539397da7.json @@ -0,0 +1,95 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n c.id IS NOT NULL AS completed,\n CASE \n WHEN q.id IS NOT NULL THEN (CASE WHEN NOT $5 AND q.running THEN true ELSE null END)\n ELSE false\n END AS running,\n CASE WHEN $7::BOOLEAN THEN NULL ELSE SUBSTR(logs, GREATEST($1 - log_offset, 0)) END AS logs,\n SUBSTR(rs.stream, $8) AS new_result_stream,\n COALESCE(r.memory_peak, c.memory_peak) AS mem_peak,\n COALESCE(c.flow_status, f.flow_status) AS \"flow_status: sqlx::types::Json>\",\n COALESCE(c.workflow_as_code_status, f.workflow_as_code_status) AS \"workflow_as_code_status: sqlx::types::Json>\",\n CASE WHEN $7::BOOLEAN THEN NULL ELSE job_logs.log_offset + CHAR_LENGTH(job_logs.logs) + 1 END AS log_offset,\n CHAR_LENGTH(rs.stream) + 1 AS stream_offset,\n created_by AS \"created_by!\",\n CASE WHEN $4::BOOLEAN THEN (\n SELECT scalar_int FROM job_stats WHERE job_id = $3 AND metric_id = 'progress_perc'\n ) END AS progress,\n rs.stream AS \"result_stream: Option\"\n FROM v2_job j\n LEFT JOIN v2_job_queue q USING (id)\n LEFT JOIN v2_job_runtime r USING (id)\n LEFT JOIN v2_job_status f USING (id)\n LEFT JOIN v2_job_completed c USING (id)\n LEFT JOIN job_result_stream rs ON rs.job_id = $3\n LEFT JOIN job_logs ON job_logs.job_id = $3\n WHERE j.workspace_id = $2 AND j.id = $3\n AND ($6::text[] IS NULL OR j.tag = ANY($6))", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "completed", + "type_info": "Bool" + }, + { + "ordinal": 1, + "name": "running", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "logs", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "new_result_stream", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "mem_peak", + "type_info": "Int4" + }, + { + "ordinal": 5, + "name": "flow_status: sqlx::types::Json>", + "type_info": "Jsonb" + }, + { + "ordinal": 6, + "name": "workflow_as_code_status: sqlx::types::Json>", + "type_info": "Jsonb" + }, + { + "ordinal": 7, + "name": "log_offset", + "type_info": "Int4" + }, + { + "ordinal": 8, + "name": "stream_offset", + "type_info": "Int4" + }, + { + "ordinal": 9, + "name": "created_by!", + "type_info": "Varchar" + }, + { + "ordinal": 10, + "name": "progress", + "type_info": "Int4" + }, + { + "ordinal": 11, + "name": "result_stream: Option", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Int4", + "Text", + "Uuid", + "Bool", + "Bool", + "TextArray", + "Bool", + "Int4" + ] + }, + "nullable": [ + null, + null, + null, + null, + null, + null, + null, + null, + null, + false, + null, + false + ] + }, + "hash": "4f372d047c78532907adf2d2dc114352aa7b5b28dccc50a1231a7f6539397da7" +} diff --git a/backend/.sqlx/query-4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c.json b/backend/.sqlx/query-4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c.json new file mode 100644 index 0000000000..9709a354cf --- /dev/null +++ b/backend/.sqlx/query-4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "4fb3881cdbb4b9e93e28f460a9b3715bdc6a52b76c89f3a3913023b13c4e085c" +} diff --git a/backend/.sqlx/query-506f90984a8b672aeff50b6d9e4751ea1521c524b9b7f51a48ee26280825ac9a.json b/backend/.sqlx/query-506f90984a8b672aeff50b6d9e4751ea1521c524b9b7f51a48ee26280825ac9a.json new file mode 100644 index 0000000000..fca7ee186e --- /dev/null +++ b/backend/.sqlx/query-506f90984a8b672aeff50b6d9e4751ea1521c524b9b7f51a48ee26280825ac9a.json @@ -0,0 +1,36 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT \n COALESCE(jc.result, NULL) as \"result: sqlx::types::Json>\", \n SUBSTR(rs.stream, $3) AS \"result_stream: Option\", \n CHAR_LENGTH(rs.stream) + 1 AS stream_offset\n FROM (\n SELECT $2::uuid as job_id, $1::text as workspace_id\n ) base\n LEFT JOIN v2_job_completed jc ON jc.id = base.job_id AND jc.workspace_id = base.workspace_id\n LEFT JOIN job_result_stream rs ON rs.job_id = base.job_id\n WHERE base.job_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "result: sqlx::types::Json>", + "type_info": "Jsonb" + }, + { + "ordinal": 1, + "name": "result_stream: Option", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "stream_offset", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text", + "Uuid", + "Int4" + ] + }, + "nullable": [ + null, + null, + null + ] + }, + "hash": "506f90984a8b672aeff50b6d9e4751ea1521c524b9b7f51a48ee26280825ac9a" +} diff --git a/backend/.sqlx/query-a7b028e832396ee4d1ad6dfd44ba6134344f3eb37dbf0254154eba31b9cc2ed3.json b/backend/.sqlx/query-51cfe6efb154934f8cfbe77a7313891ef5c46a7febe4125d164a5ae5b55af2d6.json similarity index 50% rename from backend/.sqlx/query-a7b028e832396ee4d1ad6dfd44ba6134344f3eb37dbf0254154eba31b9cc2ed3.json rename to backend/.sqlx/query-51cfe6efb154934f8cfbe77a7313891ef5c46a7febe4125d164a5ae5b55af2d6.json index 2de5f54b1a..107e77d728 100644 --- a/backend/.sqlx/query-a7b028e832396ee4d1ad6dfd44ba6134344f3eb37dbf0254154eba31b9cc2ed3.json +++ b/backend/.sqlx/query-51cfe6efb154934f8cfbe77a7313891ef5c46a7febe4125d164a5ae5b55af2d6.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT usage.usage FROM usage \n WHERE is_workspace = false \n AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1", + "query": "\n SELECT usage.usage FROM usage\n WHERE is_workspace = false\n AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1", "describe": { "columns": [ { @@ -18,5 +18,5 @@ false ] }, - "hash": "a7b028e832396ee4d1ad6dfd44ba6134344f3eb37dbf0254154eba31b9cc2ed3" + "hash": "51cfe6efb154934f8cfbe77a7313891ef5c46a7febe4125d164a5ae5b55af2d6" } diff --git a/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json b/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json index f589df856d..0051151fc0 100644 --- a/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json +++ b/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json @@ -147,6 +147,16 @@ "ordinal": 28, "name": "ducklake", "type_info": "Jsonb" + }, + { + "ordinal": 29, + "name": "auto_add_instance_groups", + "type_info": "TextArray" + }, + { + "ordinal": 30, + "name": "auto_add_instance_groups_roles", + "type_info": "Jsonb" } ], "parameters": { @@ -183,6 +193,8 @@ true, true, false, + true, + true, true ] }, diff --git a/backend/.sqlx/query-570a4684bbdb390e4652301aa8d2d09b6e09b258f748d48cf2d188465621ee04.json b/backend/.sqlx/query-570a4684bbdb390e4652301aa8d2d09b6e09b258f748d48cf2d188465621ee04.json deleted file mode 100644 index f9d6c86004..0000000000 --- a/backend/.sqlx/query-570a4684bbdb390e4652301aa8d2d09b6e09b258f748d48cf2d188465621ee04.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE nats_trigger SET nats_resource_path = $1, subjects = $2, stream_name = $3, consumer_name = $4, use_jetstream = $5, script_path = $6, path = $7, is_flow = $8, edited_by = $9, email = $10, edited_at = now(), server_id = NULL, error = NULL, error_handler_path = $13, error_handler_args = $14, retry = $15\n WHERE workspace_id = $11 AND path = $12", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "VarcharArray", - "Varchar", - "Varchar", - "Bool", - "Varchar", - "Varchar", - "Bool", - "Varchar", - "Varchar", - "Text", - "Text", - "Varchar", - "Jsonb", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "570a4684bbdb390e4652301aa8d2d09b6e09b258f748d48cf2d188465621ee04" -} diff --git a/backend/.sqlx/query-5d1ec728380ea8baf64df54743e73008ee9a58e6f39a5bf31a2ed099727f5c04.json b/backend/.sqlx/query-5d1ec728380ea8baf64df54743e73008ee9a58e6f39a5bf31a2ed099727f5c04.json deleted file mode 100644 index 8a4fb840b1..0000000000 --- a/backend/.sqlx/query-5d1ec728380ea8baf64df54743e73008ee9a58e6f39a5bf31a2ed099727f5c04.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT EXISTS(SELECT 1 FROM worker_ping WHERE custom_tags @> $1 AND ping_at > now() - interval '1 minute')", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "exists", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "TextArray" - ] - }, - "nullable": [ - null - ] - }, - "hash": "5d1ec728380ea8baf64df54743e73008ee9a58e6f39a5bf31a2ed099727f5c04" -} diff --git a/backend/.sqlx/query-5d6adbe21b9f8dd984d1bfc750fb81763d8650c1316bb0b20816f1a5d61a678c.json b/backend/.sqlx/query-5d6adbe21b9f8dd984d1bfc750fb81763d8650c1316bb0b20816f1a5d61a678c.json index 78dea070ea..09775dcc3a 100644 --- a/backend/.sqlx/query-5d6adbe21b9f8dd984d1bfc750fb81763d8650c1316bb0b20816f1a5d61a678c.json +++ b/backend/.sqlx/query-5d6adbe21b9f8dd984d1bfc750fb81763d8650c1316bb0b20816f1a5d61a678c.json @@ -42,6 +42,11 @@ "ordinal": 7, "name": "role", "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "added_via", + "type_info": "Jsonb" } ], "parameters": { @@ -57,6 +62,7 @@ false, false, false, + true, true ] }, diff --git a/backend/.sqlx/query-60b3a59805d463a61eed68072d1ea032b00fc9bd7a6db22f530f67eb9730fa3b.json b/backend/.sqlx/query-60b3a59805d463a61eed68072d1ea032b00fc9bd7a6db22f530f67eb9730fa3b.json index 70ae06505c..3a635ab004 100644 --- a/backend/.sqlx/query-60b3a59805d463a61eed68072d1ea032b00fc9bd7a6db22f530f67eb9730fa3b.json +++ b/backend/.sqlx/query-60b3a59805d463a61eed68072d1ea032b00fc9bd7a6db22f530f67eb9730fa3b.json @@ -42,6 +42,11 @@ "ordinal": 7, "name": "role", "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "added_via", + "type_info": "Jsonb" } ], "parameters": { @@ -58,6 +63,7 @@ false, false, false, + true, true ] }, diff --git a/backend/.sqlx/query-61c29d684e8e683e839a6d7210b3b9b96854e5bfd752e45922c358c42ebea0c4.json b/backend/.sqlx/query-61c29d684e8e683e839a6d7210b3b9b96854e5bfd752e45922c358c42ebea0c4.json index 1a30ad011f..9560d17efa 100644 --- a/backend/.sqlx/query-61c29d684e8e683e839a6d7210b3b9b96854e5bfd752e45922c358c42ebea0c4.json +++ b/backend/.sqlx/query-61c29d684e8e683e839a6d7210b3b9b96854e5bfd752e45922c358c42ebea0c4.json @@ -62,7 +62,8 @@ "oracledb", "nu", "java", - "duckdb" + "duckdb", + "ruby" ] } } diff --git a/backend/.sqlx/query-69924462c788dbc8f31aacc7f8ae588d76bf1f25d631833ce4b194818a7d1437.json b/backend/.sqlx/query-69924462c788dbc8f31aacc7f8ae588d76bf1f25d631833ce4b194818a7d1437.json new file mode 100644 index 0000000000..f25cfd9eba --- /dev/null +++ b/backend/.sqlx/query-69924462c788dbc8f31aacc7f8ae588d76bf1f25d631833ce4b194818a7d1437.json @@ -0,0 +1,48 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT result as \"result: sqlx::types::Json>\", v2_job.tag,\n v2_job_queue.running as \"running: Option\", SUBSTR(rs.stream, $3) AS \"result_stream: Option\", CHAR_LENGTH(rs.stream) AS stream_offset\n FROM v2_job\n LEFT JOIN v2_job_queue USING (id)\n LEFT JOIN v2_job_completed USING (id)\n LEFT JOIN job_result_stream rs ON rs.job_id = $2\n WHERE v2_job.id = $2 AND v2_job.workspace_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "result: sqlx::types::Json>", + "type_info": "Jsonb" + }, + { + "ordinal": 1, + "name": "tag", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "running: Option", + "type_info": "Bool" + }, + { + "ordinal": 3, + "name": "result_stream: Option", + "type_info": "Text" + }, + { + "ordinal": 4, + "name": "stream_offset", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Text", + "Uuid", + "Int4" + ] + }, + "nullable": [ + true, + false, + false, + null, + null + ] + }, + "hash": "69924462c788dbc8f31aacc7f8ae588d76bf1f25d631833ce4b194818a7d1437" +} diff --git a/backend/.sqlx/query-334c9a6d770471ac53099eb5227778899a5c680b899bc6a6d1a3f89fdbf8c730.json b/backend/.sqlx/query-6a254de9005594dc75a59a545546417c8a5aa7635be1dc0b37dc29d0f9e7c163.json similarity index 89% rename from backend/.sqlx/query-334c9a6d770471ac53099eb5227778899a5c680b899bc6a6d1a3f89fdbf8c730.json rename to backend/.sqlx/query-6a254de9005594dc75a59a545546417c8a5aa7635be1dc0b37dc29d0f9e7c163.json index d971f4d444..3e7972dbd7 100644 --- a/backend/.sqlx/query-334c9a6d770471ac53099eb5227778899a5c680b899bc6a6d1a3f89fdbf8c730.json +++ b/backend/.sqlx/query-6a254de9005594dc75a59a545546417c8a5aa7635be1dc0b37dc29d0f9e7c163.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT label,\n concat(substring(token for 10)) AS token_prefix,\n expiration,\n created_at,\n last_used_at,\n scopes,\n email\n FROM token\n WHERE workspace_id = $1\n AND (\n scopes @> ARRAY['jobs:run:flows:' || $2]::text[]\n OR scopes @> ARRAY['run:flows/' || $2]::text[]\n )\n ", + "query": "\n SELECT label,\n concat(substring(token for 10)) AS token_prefix,\n expiration,\n created_at,\n last_used_at,\n scopes,\n email\n FROM token\n WHERE workspace_id = $1\n AND (\n scopes @> ARRAY['jobs:run:flows:' || $2]::text[]\n OR scopes @> ARRAY['run:flow/' || $2]::text[]\n )\n ", "describe": { "columns": [ { @@ -55,5 +55,5 @@ true ] }, - "hash": "334c9a6d770471ac53099eb5227778899a5c680b899bc6a6d1a3f89fdbf8c730" + "hash": "6a254de9005594dc75a59a545546417c8a5aa7635be1dc0b37dc29d0f9e7c163" } diff --git a/backend/.sqlx/query-6aabe704395c9be30c86d15a5d22f3509b4fcea56227b019588837132b64d58b.json b/backend/.sqlx/query-6aabe704395c9be30c86d15a5d22f3509b4fcea56227b019588837132b64d58b.json index 27e21028d4..6e1b36a97c 100644 --- a/backend/.sqlx/query-6aabe704395c9be30c86d15a5d22f3509b4fcea56227b019588837132b64d58b.json +++ b/backend/.sqlx/query-6aabe704395c9be30c86d15a5d22f3509b4fcea56227b019588837132b64d58b.json @@ -45,11 +45,16 @@ }, { "ordinal": 8, + "name": "added_via", + "type_info": "Jsonb" + }, + { + "ordinal": 9, "name": "super_admin", "type_info": "Bool" }, { - "ordinal": 9, + "ordinal": 10, "name": "name", "type_info": "Varchar" } @@ -69,6 +74,7 @@ false, false, true, + true, false, true ] diff --git a/backend/.sqlx/query-388d6fd335a3f8a405b2d465892cf21a68d4b50ace25ef88c4cdf5b347c3d5eb.json b/backend/.sqlx/query-6b2820973026b1628cb93fda943bc389a261a3bb975c86ac02dd1c552da92295.json similarity index 67% rename from backend/.sqlx/query-388d6fd335a3f8a405b2d465892cf21a68d4b50ace25ef88c4cdf5b347c3d5eb.json rename to backend/.sqlx/query-6b2820973026b1628cb93fda943bc389a261a3bb975c86ac02dd1c552da92295.json index 5965bc591a..350e0f9ff3 100644 --- a/backend/.sqlx/query-388d6fd335a3f8a405b2d465892cf21a68d4b50ace25ef88c4cdf5b347c3d5eb.json +++ b/backend/.sqlx/query-6b2820973026b1628cb93fda943bc389a261a3bb975c86ac02dd1c552da92295.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT EXISTS(SELECT 1 FROM group_ WHERE (group_.extra_perms ->> CONCAT('u/', $1::text))::boolean AND name = $2 AND workspace_id = $4) OR exists(\n SELECT 1 FROM group_ g, jsonb_each_text(g.extra_perms) f \n WHERE $2 = g.name AND $4 = g.workspace_id AND SPLIT_PART(key, '/', 1) = 'g' AND key = ANY($3::text[])\n AND value::boolean)", + "query": "SELECT EXISTS(SELECT 1 FROM group_ WHERE (group_.extra_perms ->> CONCAT('u/', $1::text))::boolean AND name = $2 AND workspace_id = $4) OR exists(\n SELECT 1 FROM group_ g, jsonb_each_text(g.extra_perms) f\n WHERE $2 = g.name AND $4 = g.workspace_id AND SPLIT_PART(key, '/', 1) = 'g' AND key = ANY($3::text[])\n AND value::boolean)", "describe": { "columns": [ { @@ -21,5 +21,5 @@ null ] }, - "hash": "388d6fd335a3f8a405b2d465892cf21a68d4b50ace25ef88c4cdf5b347c3d5eb" + "hash": "6b2820973026b1628cb93fda943bc389a261a3bb975c86ac02dd1c552da92295" } diff --git a/backend/.sqlx/query-6df2ca52e3e8515c398943da701600a33e64525809fdd67bce63e28d98eababc.json b/backend/.sqlx/query-6df2ca52e3e8515c398943da701600a33e64525809fdd67bce63e28d98eababc.json new file mode 100644 index 0000000000..af4ea862f6 --- /dev/null +++ b/backend/.sqlx/query-6df2ca52e3e8515c398943da701600a33e64525809fdd67bce63e28d98eababc.json @@ -0,0 +1,30 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT created_by AS \"created_by!\", coalesce(job_logs.logs, '') as logs\n FROM v2_job\n LEFT JOIN job_logs ON job_logs.job_id = v2_job.id\n WHERE v2_job.id = $1 AND v2_job.workspace_id = $2 AND ($3::text[] IS NULL OR v2_job.tag = ANY($3))\n ORDER BY job_logs.log_offset DESC\n LIMIT 100", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "created_by!", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "logs", + "type_info": "Text" + } + ], + "parameters": { + "Left": [ + "Uuid", + "Text", + "TextArray" + ] + }, + "nullable": [ + false, + null + ] + }, + "hash": "6df2ca52e3e8515c398943da701600a33e64525809fdd67bce63e28d98eababc" +} diff --git a/backend/.sqlx/query-6f36c05e6097066441acab227c0d5206d5a9a9828b04e4c107f1ecab4a9363d4.json b/backend/.sqlx/query-6f36c05e6097066441acab227c0d5206d5a9a9828b04e4c107f1ecab4a9363d4.json new file mode 100644 index 0000000000..852378e548 --- /dev/null +++ b/backend/.sqlx/query-6f36c05e6097066441acab227c0d5206d5a9a9828b04e4c107f1ecab4a9363d4.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT usr.username\n FROM usr_to_group LEFT JOIN usr ON usr_to_group.usr = usr.username AND usr_to_group.workspace_id = $2\n WHERE group_ = $1 AND usr.workspace_id = $2 AND usr_to_group.workspace_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "username", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "6f36c05e6097066441acab227c0d5206d5a9a9828b04e4c107f1ecab4a9363d4" +} diff --git a/backend/.sqlx/query-711c72a72298bb656882b35071e910dab721cda070f9c39bba3729bdc1467496.json b/backend/.sqlx/query-711c72a72298bb656882b35071e910dab721cda070f9c39bba3729bdc1467496.json new file mode 100644 index 0000000000..908368edd5 --- /dev/null +++ b/backend/.sqlx/query-711c72a72298bb656882b35071e910dab721cda070f9c39bba3729bdc1467496.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT workspace_id FROM workspace_settings WHERE auto_add_instance_groups IS NOT NULL AND $1 = ANY(auto_add_instance_groups)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "711c72a72298bb656882b35071e910dab721cda070f9c39bba3729bdc1467496" +} diff --git a/backend/.sqlx/query-5af0299841a0313d0cc270e98b04b5b5968184ae53dc076b1a9bc76af35923da.json b/backend/.sqlx/query-711e665344873454428d84e35f300edb7e0ebad5de40ba5645c1ce3d4a6b84be.json similarity index 71% rename from backend/.sqlx/query-5af0299841a0313d0cc270e98b04b5b5968184ae53dc076b1a9bc76af35923da.json rename to backend/.sqlx/query-711e665344873454428d84e35f300edb7e0ebad5de40ba5645c1ce3d4a6b84be.json index ebd6dafb25..3abff2fa36 100644 --- a/backend/.sqlx/query-5af0299841a0313d0cc270e98b04b5b5968184ae53dc076b1a9bc76af35923da.json +++ b/backend/.sqlx/query-711e665344873454428d84e35f300edb7e0ebad5de40ba5645c1ce3d4a6b84be.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT \n workspace_id,\n path,\n nats_resource_path,\n subjects,\n stream_name,\n consumer_name,\n use_jetstream,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM nats_trigger\n WHERE workspace_id = $1", + "query": "\n SELECT \n workspace_id,\n path,\n nats_resource_path,\n subjects,\n stream_name,\n consumer_name,\n use_jetstream,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM \n nats_trigger\n WHERE \n workspace_id = $1\n ", "describe": { "columns": [ { @@ -132,5 +132,5 @@ true ] }, - "hash": "5af0299841a0313d0cc270e98b04b5b5968184ae53dc076b1a9bc76af35923da" + "hash": "711e665344873454428d84e35f300edb7e0ebad5de40ba5645c1ce3d4a6b84be" } diff --git a/backend/.sqlx/query-71a040866adbd192080da165eb120abf7531b2542a2da225152e19144400f950.json b/backend/.sqlx/query-71a040866adbd192080da165eb120abf7531b2542a2da225152e19144400f950.json deleted file mode 100644 index d17cf3c524..0000000000 --- a/backend/.sqlx/query-71a040866adbd192080da165eb120abf7531b2542a2da225152e19144400f950.json +++ /dev/null @@ -1,184 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT workspace_id, slack_team_id, teams_team_id, teams_team_name, slack_name, slack_command_script, teams_command_script, slack_email, auto_invite_domain, auto_invite_operator, auto_add, customer_id, plan, webhook, deploy_to, ai_config, error_handler, error_handler_extra_args, error_handler_muted_on_cancel, large_file_storage, git_sync, deploy_ui, default_app, default_scripts, mute_critical_alerts, color, operator_settings, git_app_installations FROM workspace_settings WHERE workspace_id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "workspace_id", - "type_info": "Varchar" - }, - { - "ordinal": 1, - "name": "slack_team_id", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "teams_team_id", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "teams_team_name", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "slack_name", - "type_info": "Varchar" - }, - { - "ordinal": 5, - "name": "slack_command_script", - "type_info": "Varchar" - }, - { - "ordinal": 6, - "name": "teams_command_script", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "slack_email", - "type_info": "Varchar" - }, - { - "ordinal": 8, - "name": "auto_invite_domain", - "type_info": "Varchar" - }, - { - "ordinal": 9, - "name": "auto_invite_operator", - "type_info": "Bool" - }, - { - "ordinal": 10, - "name": "auto_add", - "type_info": "Bool" - }, - { - "ordinal": 11, - "name": "customer_id", - "type_info": "Varchar" - }, - { - "ordinal": 12, - "name": "plan", - "type_info": "Varchar" - }, - { - "ordinal": 13, - "name": "webhook", - "type_info": "Text" - }, - { - "ordinal": 14, - "name": "deploy_to", - "type_info": "Varchar" - }, - { - "ordinal": 15, - "name": "ai_config", - "type_info": "Jsonb" - }, - { - "ordinal": 16, - "name": "error_handler", - "type_info": "Varchar" - }, - { - "ordinal": 17, - "name": "error_handler_extra_args", - "type_info": "Json" - }, - { - "ordinal": 18, - "name": "error_handler_muted_on_cancel", - "type_info": "Bool" - }, - { - "ordinal": 19, - "name": "large_file_storage", - "type_info": "Jsonb" - }, - { - "ordinal": 20, - "name": "git_sync", - "type_info": "Jsonb" - }, - { - "ordinal": 21, - "name": "deploy_ui", - "type_info": "Jsonb" - }, - { - "ordinal": 22, - "name": "default_app", - "type_info": "Varchar" - }, - { - "ordinal": 23, - "name": "default_scripts", - "type_info": "Jsonb" - }, - { - "ordinal": 24, - "name": "mute_critical_alerts", - "type_info": "Bool" - }, - { - "ordinal": 25, - "name": "color", - "type_info": "Varchar" - }, - { - "ordinal": 26, - "name": "operator_settings", - "type_info": "Jsonb" - }, - { - "ordinal": 27, - "name": "git_app_installations", - "type_info": "Jsonb" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - true, - true, - true, - true, - true, - true, - false, - true, - true, - true, - true, - true, - true, - true, - true, - true, - true, - false, - true, - true, - true, - true, - true, - true, - true, - true, - false - ] - }, - "hash": "71a040866adbd192080da165eb120abf7531b2542a2da225152e19144400f950" -} diff --git a/backend/.sqlx/query-e653d36b607a16c0dfc0324690942ab25883b53a81ebb581fe019af2ec5eb567.json b/backend/.sqlx/query-72956f508f66312807738399b57aa01a048fa4f9281327cf5b78111178424b43.json similarity index 91% rename from backend/.sqlx/query-e653d36b607a16c0dfc0324690942ab25883b53a81ebb581fe019af2ec5eb567.json rename to backend/.sqlx/query-72956f508f66312807738399b57aa01a048fa4f9281327cf5b78111178424b43.json index 284cf3338f..2ffdf141b1 100644 --- a/backend/.sqlx/query-e653d36b607a16c0dfc0324690942ab25883b53a81ebb581fe019af2ec5eb567.json +++ b/backend/.sqlx/query-72956f508f66312807738399b57aa01a048fa4f9281327cf5b78111178424b43.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n id AS \"id!\", workspace_id AS \"workspace_id!\", parent_job, is_flow_step,\n flow_status AS \"flow_status: Box\", last_ping, same_worker\n FROM v2_as_queue\n WHERE running = true AND suspend = 0 AND suspend_until IS null AND scheduled_for <= now()\n AND (job_kind = 'flow' OR job_kind = 'flowpreview' OR job_kind = 'flownode')\n AND last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval\n AND canceled = false\n ", + "query": "\n SELECT\n id AS \"id!\", workspace_id AS \"workspace_id!\", parent_job, is_flow_step,\n flow_status AS \"flow_status: Box\", last_ping, same_worker\n FROM v2_as_queue\n WHERE running = true AND suspend = 0 AND suspend_until IS null AND scheduled_for <= now()\n AND (job_kind = 'flow' OR job_kind = 'flowpreview' OR job_kind = 'flownode')\n AND last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval\n AND canceled = false\n \n ", "describe": { "columns": [ { @@ -54,5 +54,5 @@ true ] }, - "hash": "e653d36b607a16c0dfc0324690942ab25883b53a81ebb581fe019af2ec5eb567" + "hash": "72956f508f66312807738399b57aa01a048fa4f9281327cf5b78111178424b43" } diff --git a/backend/.sqlx/query-74a2871aba7e35527dcefb2538b8cf41c35618f7c15ea047c5082f8feb7a8464.json b/backend/.sqlx/query-74a2871aba7e35527dcefb2538b8cf41c35618f7c15ea047c5082f8feb7a8464.json index 6a45170165..9aa5d8ccbd 100644 --- a/backend/.sqlx/query-74a2871aba7e35527dcefb2538b8cf41c35618f7c15ea047c5082f8feb7a8464.json +++ b/backend/.sqlx/query-74a2871aba7e35527dcefb2538b8cf41c35618f7c15ea047c5082f8feb7a8464.json @@ -14,7 +14,8 @@ "Enum": [ "s3object", "resource", - "variable" + "variable", + "ducklake" ] } } diff --git a/backend/.sqlx/query-804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf.json b/backend/.sqlx/query-804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf.json index 7933311a4c..6f08d98113 100644 --- a/backend/.sqlx/query-804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf.json +++ b/backend/.sqlx/query-804fc11e35f4afc0db194b6fe2594f91df7e588d4d2431bc85f4d8734920c8bf.json @@ -35,7 +35,8 @@ "oracledb", "nu", "java", - "duckdb" + "duckdb", + "ruby" ] } } diff --git a/backend/.sqlx/query-bff39cc57aba0729ddef1d53f3806c6736556f0a14b489d6708f9879393f9ea3.json b/backend/.sqlx/query-89940a53f29b173b6a8717f057a81a4f2edad152581950fdd80d758a0d242c17.json similarity index 79% rename from backend/.sqlx/query-bff39cc57aba0729ddef1d53f3806c6736556f0a14b489d6708f9879393f9ea3.json rename to backend/.sqlx/query-89940a53f29b173b6a8717f057a81a4f2edad152581950fdd80d758a0d242c17.json index 11e498f38d..25dd18003c 100644 --- a/backend/.sqlx/query-bff39cc57aba0729ddef1d53f3806c6736556f0a14b489d6708f9879393f9ea3.json +++ b/backend/.sqlx/query-89940a53f29b173b6a8717f057a81a4f2edad152581950fdd80d758a0d242c17.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT usr.email, usage.executions\n FROM usr\n , LATERAL (\n SELECT COALESCE(SUM(duration_ms + 1000)/1000 , 0)::BIGINT executions\n FROM v2_as_completed_job\n WHERE workspace_id = $1\n AND job_kind NOT IN ('flow', 'flowpreview', 'flownode')\n AND email = usr.email\n AND now() - '1 week'::interval < created_at \n ) usage\n WHERE workspace_id = $1\n ", + "query": "\n SELECT usr.email, usage.executions\n FROM usr\n , LATERAL (\n SELECT COALESCE(SUM(duration_ms + 1000)/1000 , 0)::BIGINT executions\n FROM v2_as_completed_job\n WHERE workspace_id = $1\n AND job_kind NOT IN ('flow', 'flowpreview', 'flownode')\n AND email = usr.email\n AND now() - '1 week'::interval < created_at\n ) usage\n WHERE workspace_id = $1\n ", "describe": { "columns": [ { @@ -24,5 +24,5 @@ null ] }, - "hash": "bff39cc57aba0729ddef1d53f3806c6736556f0a14b489d6708f9879393f9ea3" + "hash": "89940a53f29b173b6a8717f057a81a4f2edad152581950fdd80d758a0d242c17" } diff --git a/backend/.sqlx/query-09f43081a8b253d059d9d4302daa139cee6f055cdbafc213b2f992667f8ed578.json b/backend/.sqlx/query-89be9b595a892c82f2a9f3cfa4da213e360ebe8827d5b4680d3c227fb8ddec82.json similarity index 95% rename from backend/.sqlx/query-09f43081a8b253d059d9d4302daa139cee6f055cdbafc213b2f992667f8ed578.json rename to backend/.sqlx/query-89be9b595a892c82f2a9f3cfa4da213e360ebe8827d5b4680d3c227fb8ddec82.json index 9fb3f5ab6c..9b33a91ae2 100644 --- a/backend/.sqlx/query-09f43081a8b253d059d9d4302daa139cee6f055cdbafc213b2f992667f8ed578.json +++ b/backend/.sqlx/query-89be9b595a892c82f2a9f3cfa4da213e360ebe8827d5b4680d3c227fb8ddec82.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT \n workspace_id, \n workspaced_route,\n path, \n route_path, \n route_path_key, \n authentication_resource_path,\n script_path, \n is_flow, \n summary,\n description,\n edited_by, \n edited_at, \n email, \n extra_perms, \n is_async, \n authentication_method AS \"authentication_method: _\", \n http_method AS \"http_method: _\", \n static_asset_config AS \"static_asset_config: _\", \n is_static_website,\n wrap_body,\n raw_string,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM http_trigger\n WHERE workspace_id = $1\n ", + "query": "\n SELECT \n workspace_id, \n workspaced_route,\n path, \n route_path, \n route_path_key, \n authentication_resource_path,\n script_path, \n is_flow, \n summary,\n description,\n edited_by, \n edited_at, \n email, \n extra_perms, \n is_async, \n authentication_method AS \"authentication_method: _\", \n http_method AS \"http_method: _\", \n static_asset_config AS \"static_asset_config: _\", \n is_static_website,\n wrap_body,\n raw_string,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM \n http_trigger\n WHERE \n workspace_id = $1\n ", "describe": { "columns": [ { @@ -183,5 +183,5 @@ true ] }, - "hash": "09f43081a8b253d059d9d4302daa139cee6f055cdbafc213b2f992667f8ed578" + "hash": "89be9b595a892c82f2a9f3cfa4da213e360ebe8827d5b4680d3c227fb8ddec82" } diff --git a/backend/.sqlx/query-94feb54cc965f19bfaf09966fd431b925c565f08c100bea812d3ac0e28664ccb.json b/backend/.sqlx/query-94feb54cc965f19bfaf09966fd431b925c565f08c100bea812d3ac0e28664ccb.json new file mode 100644 index 0000000000..715fbce15e --- /dev/null +++ b/backend/.sqlx/query-94feb54cc965f19bfaf09966fd431b925c565f08c100bea812d3ac0e28664ccb.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE usr SET is_admin = true WHERE workspace_id = $1 AND email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "94feb54cc965f19bfaf09966fd431b925c565f08c100bea812d3ac0e28664ccb" +} diff --git a/backend/.sqlx/query-9530b234b2ac8f7360552be5e6bb25270e6b4185ce5c7f3c6edd9fbaabf77544.json b/backend/.sqlx/query-9530b234b2ac8f7360552be5e6bb25270e6b4185ce5c7f3c6edd9fbaabf77544.json new file mode 100644 index 0000000000..25a898e2f1 --- /dev/null +++ b/backend/.sqlx/query-9530b234b2ac8f7360552be5e6bb25270e6b4185ce5c7f3c6edd9fbaabf77544.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT DISTINCT email FROM usr WHERE added_via->>'source' = 'instance_group' AND added_via->>'group' = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "email", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "9530b234b2ac8f7360552be5e6bb25270e6b4185ce5c7f3c6edd9fbaabf77544" +} diff --git a/backend/.sqlx/query-97b6e5779661343587c297b275df9e75fd22924ad2dbe124bb361f3e7fd8bfe0.json b/backend/.sqlx/query-97b6e5779661343587c297b275df9e75fd22924ad2dbe124bb361f3e7fd8bfe0.json new file mode 100644 index 0000000000..69ed74c7ae --- /dev/null +++ b/backend/.sqlx/query-97b6e5779661343587c297b275df9e75fd22924ad2dbe124bb361f3e7fd8bfe0.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE\n workspace_settings\n SET\n error_handler = $1,\n error_handler_extra_args = $2,\n error_handler_muted_on_cancel = $3\n WHERE\n workspace_id = $4\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Json", + "Bool", + "Text" + ] + }, + "nullable": [] + }, + "hash": "97b6e5779661343587c297b275df9e75fd22924ad2dbe124bb361f3e7fd8bfe0" +} diff --git a/backend/.sqlx/query-a6608d47b96d851eb7b04d2e4b472889ff257db0f0e3e9252adda2e4ef2039d6.json b/backend/.sqlx/query-a6608d47b96d851eb7b04d2e4b472889ff257db0f0e3e9252adda2e4ef2039d6.json index 17dc7cfd2e..5f6611dae6 100644 --- a/backend/.sqlx/query-a6608d47b96d851eb7b04d2e4b472889ff257db0f0e3e9252adda2e4ef2039d6.json +++ b/backend/.sqlx/query-a6608d47b96d851eb7b04d2e4b472889ff257db0f0e3e9252adda2e4ef2039d6.json @@ -148,7 +148,8 @@ "oracledb", "nu", "java", - "duckdb" + "duckdb", + "ruby" ] } } diff --git a/backend/.sqlx/query-a8bcae108af1eda6efe3a4b8c6f8807bc464a81c0883e68f5a69b89b94b0b34b.json b/backend/.sqlx/query-a8bcae108af1eda6efe3a4b8c6f8807bc464a81c0883e68f5a69b89b94b0b34b.json index a3cb5a82b9..279d2f25fa 100644 --- a/backend/.sqlx/query-a8bcae108af1eda6efe3a4b8c6f8807bc464a81c0883e68f5a69b89b94b0b34b.json +++ b/backend/.sqlx/query-a8bcae108af1eda6efe3a4b8c6f8807bc464a81c0883e68f5a69b89b94b0b34b.json @@ -37,7 +37,8 @@ "oracledb", "nu", "java", - "duckdb" + "duckdb", + "ruby" ] } } diff --git a/backend/.sqlx/query-a9db7b2f435bb82acb8c5eeb7f800b28f3256491fdaa168591adc7b4b9f3327a.json b/backend/.sqlx/query-a9db7b2f435bb82acb8c5eeb7f800b28f3256491fdaa168591adc7b4b9f3327a.json index 16fea8720d..c2c6738167 100644 --- a/backend/.sqlx/query-a9db7b2f435bb82acb8c5eeb7f800b28f3256491fdaa168591adc7b4b9f3327a.json +++ b/backend/.sqlx/query-a9db7b2f435bb82acb8c5eeb7f800b28f3256491fdaa168591adc7b4b9f3327a.json @@ -42,7 +42,8 @@ "oracledb", "nu", "java", - "duckdb" + "duckdb", + "ruby" ] } } diff --git a/backend/.sqlx/query-ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338.json b/backend/.sqlx/query-ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338.json index c8cbb8e10d..072bc63b66 100644 --- a/backend/.sqlx/query-ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338.json +++ b/backend/.sqlx/query-ab04cda71f8e2be9acbecabe1ee5ef756b8e5c1955fbe111df9ee171dc262338.json @@ -66,7 +66,8 @@ "oracledb", "nu", "java", - "duckdb" + "duckdb", + "ruby" ] } } diff --git a/backend/.sqlx/query-6e60068b582af46afbb93010355cb5e7cc038042a6fa7089f3f73cd137a63956.json b/backend/.sqlx/query-ac1fd12e9ec92022be38aee0e91e9002e0e810d0e76dce5ced1000a7cb514adb.json similarity index 71% rename from backend/.sqlx/query-6e60068b582af46afbb93010355cb5e7cc038042a6fa7089f3f73cd137a63956.json rename to backend/.sqlx/query-ac1fd12e9ec92022be38aee0e91e9002e0e810d0e76dce5ced1000a7cb514adb.json index 39510e0f99..420d3b8573 100644 --- a/backend/.sqlx/query-6e60068b582af46afbb93010355cb5e7cc038042a6fa7089f3f73cd137a63956.json +++ b/backend/.sqlx/query-ac1fd12e9ec92022be38aee0e91e9002e0e810d0e76dce5ced1000a7cb514adb.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT workspace_id, slack_team_id, teams_team_id, teams_team_name, slack_name, slack_command_script, teams_command_script, slack_email, auto_invite_domain, auto_invite_operator, auto_add, customer_id, plan, webhook, deploy_to, ai_config, error_handler, error_handler_extra_args, error_handler_muted_on_cancel, large_file_storage, ducklake, git_sync, deploy_ui, default_app, default_scripts, mute_critical_alerts, color, operator_settings, git_app_installations FROM workspace_settings WHERE workspace_id = $1", + "query": "\n SELECT\n workspace_id,\n slack_team_id,\n teams_team_id,\n teams_team_name,\n slack_name,\n slack_command_script,\n teams_command_script,\n slack_email,\n auto_invite_domain,\n auto_invite_operator,\n auto_add,\n customer_id,\n plan,\n webhook,\n deploy_to,\n ai_config,\n error_handler,\n error_handler_extra_args,\n error_handler_muted_on_cancel,\n large_file_storage,\n ducklake,\n git_sync,\n deploy_ui,\n default_app,\n default_scripts,\n mute_critical_alerts,\n color,\n operator_settings,\n git_app_installations,\n auto_add_instance_groups,\n auto_add_instance_groups_roles\n FROM\n workspace_settings\n WHERE\n workspace_id = $1\n ", "describe": { "columns": [ { @@ -147,6 +147,16 @@ "ordinal": 28, "name": "git_app_installations", "type_info": "Jsonb" + }, + { + "ordinal": 29, + "name": "auto_add_instance_groups", + "type_info": "TextArray" + }, + { + "ordinal": 30, + "name": "auto_add_instance_groups_roles", + "type_info": "Jsonb" } ], "parameters": { @@ -183,8 +193,10 @@ true, true, true, - false + false, + true, + true ] }, - "hash": "6e60068b582af46afbb93010355cb5e7cc038042a6fa7089f3f73cd137a63956" + "hash": "ac1fd12e9ec92022be38aee0e91e9002e0e810d0e76dce5ced1000a7cb514adb" } diff --git a/backend/.sqlx/query-b7c3a66c3831eb5d145ff00807badae57bef81be051f150df754fd1444d7356d.json b/backend/.sqlx/query-acfe583fe17604ba72ba4800b62a72de0a9de0d58ef8c28dd709adf3be021597.json similarity index 82% rename from backend/.sqlx/query-b7c3a66c3831eb5d145ff00807badae57bef81be051f150df754fd1444d7356d.json rename to backend/.sqlx/query-acfe583fe17604ba72ba4800b62a72de0a9de0d58ef8c28dd709adf3be021597.json index e6ecb2b7d6..fbd4571adc 100644 --- a/backend/.sqlx/query-b7c3a66c3831eb5d145ff00807badae57bef81be051f150df754fd1444d7356d.json +++ b/backend/.sqlx/query-acfe583fe17604ba72ba4800b62a72de0a9de0d58ef8c28dd709adf3be021597.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "WITH inserted_job AS (\n INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job,\n created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger,\n script_lang, same_worker, pre_run_error, permissioned_as_email, visible_to_owner,\n flow_innermost_root_job, root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id,\n cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26,\n CASE WHEN $14::VARCHAR IS NOT NULL THEN 'schedule'::job_trigger_kind END,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id) \n values ($1, $32, $33, $34, $35, $36, $37, $2) \n ON CONFLICT (job_id) DO UPDATE SET email = $32, username = $33, is_admin = $34, is_operator = $35, folders = $36, groups = $37, workspace_id = $2\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 THEN now() END, $30, $31)", + "query": "WITH inserted_job AS (\n INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job,\n created_by, permissioned_as, runnable_id, runnable_path, args, kind, trigger,\n script_lang, same_worker, pre_run_error, permissioned_as_email, visible_to_owner,\n flow_innermost_root_job, root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id,\n cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18,\n $19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind,\n ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27)\n ),\n inserted_runtime AS (\n INSERT INTO v2_job_runtime (id, ping) VALUES ($1, null)\n ),\n inserted_job_perms AS (\n INSERT INTO job_perms (job_id, email, username, is_admin, is_operator, folders, groups, workspace_id) \n values ($1, $32, $33, $34, $35, $36, $37, $2) \n ON CONFLICT (job_id) DO UPDATE SET email = $32, username = $33, is_admin = $34, is_operator = $35, folders = $36, groups = $37, workspace_id = $2\n )\n INSERT INTO v2_job_queue\n (workspace_id, id, running, scheduled_for, started_at, tag, priority)\n VALUES ($2, $1, $28, COALESCE($29, now()), CASE WHEN $27 THEN now() END, $30, $31)", "describe": { "columns": [], "parameters": { @@ -71,7 +71,8 @@ "oracledb", "nu", "java", - "duckdb" + "duckdb", + "ruby" ] } } @@ -98,10 +99,32 @@ "Bool", "JsonbArray", "TextArray", - "Uuid" + "Uuid", + { + "Custom": { + "name": "job_trigger_kind", + "kind": { + "Enum": [ + "webhook", + "http", + "websocket", + "kafka", + "email", + "nats", + "schedule", + "app", + "ui", + "postgres", + "sqs", + "gcp", + "mqtt" + ] + } + } + } ] }, "nullable": [] }, - "hash": "b7c3a66c3831eb5d145ff00807badae57bef81be051f150df754fd1444d7356d" + "hash": "acfe583fe17604ba72ba4800b62a72de0a9de0d58ef8c28dd709adf3be021597" } diff --git a/backend/.sqlx/query-b45e17ad532a23b394226c9a5d7ab5a21e20202dbbf9c67831cc62eb067cd2ba.json b/backend/.sqlx/query-b45e17ad532a23b394226c9a5d7ab5a21e20202dbbf9c67831cc62eb067cd2ba.json new file mode 100644 index 0000000000..c3623a2b7e --- /dev/null +++ b/backend/.sqlx/query-b45e17ad532a23b394226c9a5d7ab5a21e20202dbbf9c67831cc62eb067cd2ba.json @@ -0,0 +1,40 @@ +{ + "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", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "tag", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "scheduled_for", + "type_info": "Timestamptz" + }, + { + "ordinal": 3, + "name": "workspace_id", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false, + false + ] + }, + "hash": "b45e17ad532a23b394226c9a5d7ab5a21e20202dbbf9c67831cc62eb067cd2ba" +} diff --git a/backend/.sqlx/query-b9b3c341fe452da916ee29637e14b5c1ad75462eba17083c6f81ff6ef35af77f.json b/backend/.sqlx/query-b9b3c341fe452da916ee29637e14b5c1ad75462eba17083c6f81ff6ef35af77f.json deleted file mode 100644 index 746d347214..0000000000 --- a/backend/.sqlx/query-b9b3c341fe452da916ee29637e14b5c1ad75462eba17083c6f81ff6ef35af77f.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT result as \"result: sqlx::types::Json>\", v2_job_queue.running as \"running: Option\" FROM v2_job_completed FULL OUTER JOIN v2_job_queue USING (id) WHERE (v2_job_queue.id = $1 AND v2_job_queue.workspace_id = $2) OR (v2_job_completed.id = $1 AND v2_job_completed.workspace_id = $2)", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "result: sqlx::types::Json>", - "type_info": "Jsonb" - }, - { - "ordinal": 1, - "name": "running: Option", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Text" - ] - }, - "nullable": [ - true, - false - ] - }, - "hash": "b9b3c341fe452da916ee29637e14b5c1ad75462eba17083c6f81ff6ef35af77f" -} diff --git a/backend/.sqlx/query-c269f14ae9ae4e96eff9483eb84cccbdfa316e5db051f1d52085a2d5447c81ae.json b/backend/.sqlx/query-c269f14ae9ae4e96eff9483eb84cccbdfa316e5db051f1d52085a2d5447c81ae.json new file mode 100644 index 0000000000..e566e02ac4 --- /dev/null +++ b/backend/.sqlx/query-c269f14ae9ae4e96eff9483eb84cccbdfa316e5db051f1d52085a2d5447c81ae.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO flow\n (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at)\n SELECT workspace_id, REGEXP_REPLACE(path,'u/' || $2 || '/(.*)','u/' || $1 || '/\\1'), summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at\n FROM flow\n WHERE path LIKE ('u/' || $2 || '/%') AND workspace_id = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "c269f14ae9ae4e96eff9483eb84cccbdfa316e5db051f1d52085a2d5447c81ae" +} diff --git a/backend/.sqlx/query-e82b99583d5d3004b4ad6606e7906b8f95b33c6137f136edabb0255bfa6a98f7.json b/backend/.sqlx/query-c61df65f2e4f8bc146b0f70e605f5243ec9aa66ea1a9da9662fc877f4becb04e.json similarity index 90% rename from backend/.sqlx/query-e82b99583d5d3004b4ad6606e7906b8f95b33c6137f136edabb0255bfa6a98f7.json rename to backend/.sqlx/query-c61df65f2e4f8bc146b0f70e605f5243ec9aa66ea1a9da9662fc877f4becb04e.json index 1af4d216a6..528ed0ce7d 100644 --- a/backend/.sqlx/query-e82b99583d5d3004b4ad6606e7906b8f95b33c6137f136edabb0255bfa6a98f7.json +++ b/backend/.sqlx/query-c61df65f2e4f8bc146b0f70e605f5243ec9aa66ea1a9da9662fc877f4becb04e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM \n gcp_trigger\n WHERE \n workspace_id = $1\n ", + "query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\",\n auto_acknowledge_msg\n FROM \n gcp_trigger\n WHERE \n workspace_id = $1\n ", "describe": { "columns": [ { @@ -127,6 +127,11 @@ "ordinal": 20, "name": "retry: _", "type_info": "Jsonb" + }, + { + "ordinal": 21, + "name": "auto_acknowledge_msg", + "type_info": "Bool" } ], "parameters": { @@ -155,8 +160,9 @@ false, true, true, + true, true ] }, - "hash": "e82b99583d5d3004b4ad6606e7906b8f95b33c6137f136edabb0255bfa6a98f7" + "hash": "c61df65f2e4f8bc146b0f70e605f5243ec9aa66ea1a9da9662fc877f4becb04e" } diff --git a/backend/.sqlx/query-c61f9bad73beb0bc317594f6ca68389c2d0adbef1fb6d73ad23e4153cff44f9e.json b/backend/.sqlx/query-c61f9bad73beb0bc317594f6ca68389c2d0adbef1fb6d73ad23e4153cff44f9e.json new file mode 100644 index 0000000000..22ba8df0fe --- /dev/null +++ b/backend/.sqlx/query-c61f9bad73beb0bc317594f6ca68389c2d0adbef1fb6d73ad23e4153cff44f9e.json @@ -0,0 +1,42 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT \n COALESCE(jc.result, jc.result) as \"result: sqlx::types::Json>\",\n jq.running as \"running: Option\",\n SUBSTR(rs.stream, $3) AS \"result_stream: Option\",\n CHAR_LENGTH(rs.stream) + 1 AS stream_offset\n FROM (\n SELECT $1::uuid as job_id, $2::text as workspace_id\n ) base\n LEFT JOIN v2_job_completed jc ON jc.id = base.job_id AND jc.workspace_id = base.workspace_id\n LEFT JOIN v2_job_queue jq ON jq.id = base.job_id AND jq.workspace_id = base.workspace_id\n LEFT JOIN job_result_stream rs ON rs.job_id = base.job_id\n WHERE base.job_id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "result: sqlx::types::Json>", + "type_info": "Jsonb" + }, + { + "ordinal": 1, + "name": "running: Option", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "result_stream: Option", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "stream_offset", + "type_info": "Int4" + } + ], + "parameters": { + "Left": [ + "Uuid", + "Text", + "Int4" + ] + }, + "nullable": [ + null, + false, + null, + null + ] + }, + "hash": "c61f9bad73beb0bc317594f6ca68389c2d0adbef1fb6d73ad23e4153cff44f9e" +} diff --git a/backend/.sqlx/query-974c7e623f3dfa440e134eaaa8d029334c0645147200219c39b2c00b30941172.json b/backend/.sqlx/query-ccbf71572dfc60b69a1666d4f1a883ba724d1e9c3303df32b001737b80a4b9f9.json similarity index 72% rename from backend/.sqlx/query-974c7e623f3dfa440e134eaaa8d029334c0645147200219c39b2c00b30941172.json rename to backend/.sqlx/query-ccbf71572dfc60b69a1666d4f1a883ba724d1e9c3303df32b001737b80a4b9f9.json index 0a735b5a2c..cb8ef42144 100644 --- a/backend/.sqlx/query-974c7e623f3dfa440e134eaaa8d029334c0645147200219c39b2c00b30941172.json +++ b/backend/.sqlx/query-ccbf71572dfc60b69a1666d4f1a883ba724d1e9c3303df32b001737b80a4b9f9.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "SELECT flow.workspace_id as workspace, flow.path, summary, description, flow_version.schema \n FROM flow \n LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.workspace_id = $1", + "query": "SELECT flow.workspace_id as workspace, flow.path, summary, description, flow_version.schema\n FROM flow\n LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)]\n WHERE flow.workspace_id = $1", "describe": { "columns": [ { @@ -42,5 +42,5 @@ true ] }, - "hash": "974c7e623f3dfa440e134eaaa8d029334c0645147200219c39b2c00b30941172" + "hash": "ccbf71572dfc60b69a1666d4f1a883ba724d1e9c3303df32b001737b80a4b9f9" } diff --git a/backend/.sqlx/query-ceb8c2607023883e1eebd4b9539e36ed202a6ecd12e3f90cb070341c38886de4.json b/backend/.sqlx/query-ceb8c2607023883e1eebd4b9539e36ed202a6ecd12e3f90cb070341c38886de4.json deleted file mode 100644 index 8800f8c6cc..0000000000 --- a/backend/.sqlx/query-ceb8c2607023883e1eebd4b9539e36ed202a6ecd12e3f90cb070341c38886de4.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT result as \"result: sqlx::types::Json>\" FROM v2_job_completed WHERE id = $2 AND workspace_id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "result: sqlx::types::Json>", - "type_info": "Jsonb" - } - ], - "parameters": { - "Left": [ - "Text", - "Uuid" - ] - }, - "nullable": [ - true - ] - }, - "hash": "ceb8c2607023883e1eebd4b9539e36ed202a6ecd12e3f90cb070341c38886de4" -} diff --git a/backend/.sqlx/query-d21e5be1ac26db926b7196316dae0c9fa82a865b95bb55de5370e4ee34889ba3.json b/backend/.sqlx/query-d21e5be1ac26db926b7196316dae0c9fa82a865b95bb55de5370e4ee34889ba3.json deleted file mode 100644 index 00f38b2f59..0000000000 --- a/backend/.sqlx/query-d21e5be1ac26db926b7196316dae0c9fa82a865b95bb55de5370e4ee34889ba3.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_settings SET error_handler = $1, error_handler_extra_args = $2, error_handler_muted_on_cancel = $3 WHERE workspace_id = $4", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Json", - "Bool", - "Text" - ] - }, - "nullable": [] - }, - "hash": "d21e5be1ac26db926b7196316dae0c9fa82a865b95bb55de5370e4ee34889ba3" -} diff --git a/backend/.sqlx/query-d9308c0154e029f7568b1d021368ca07f4867d192aa58b3888ae532d1c040828.json b/backend/.sqlx/query-d9308c0154e029f7568b1d021368ca07f4867d192aa58b3888ae532d1c040828.json deleted file mode 100644 index 879c18a45a..0000000000 --- a/backend/.sqlx/query-d9308c0154e029f7568b1d021368ca07f4867d192aa58b3888ae532d1c040828.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE workspace_settings SET error_handler = NULL, error_handler_extra_args = NULL WHERE workspace_id = $1", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [] - }, - "hash": "d9308c0154e029f7568b1d021368ca07f4867d192aa58b3888ae532d1c040828" -} diff --git a/backend/.sqlx/query-e5fb3531f8bc7ef1f7484524f8c3bc9c48f71a44827ba0d01ac5588dc31082a2.json b/backend/.sqlx/query-e5fb3531f8bc7ef1f7484524f8c3bc9c48f71a44827ba0d01ac5588dc31082a2.json index f70cbfde21..cdeb30f672 100644 --- a/backend/.sqlx/query-e5fb3531f8bc7ef1f7484524f8c3bc9c48f71a44827ba0d01ac5588dc31082a2.json +++ b/backend/.sqlx/query-e5fb3531f8bc7ef1f7484524f8c3bc9c48f71a44827ba0d01ac5588dc31082a2.json @@ -42,6 +42,11 @@ "ordinal": 7, "name": "role", "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "added_via", + "type_info": "Jsonb" } ], "parameters": { @@ -57,6 +62,7 @@ false, false, false, + true, true ] }, diff --git a/backend/.sqlx/query-7399ff3f22cbf2a522b6123e8ebf101ec9d41b7c8465cc501e61a1f6117981a2.json b/backend/.sqlx/query-e619fa013528a6275f98e14ae1727c55b0d4f4a5e4ee87c29251042e2916f0a0.json similarity index 64% rename from backend/.sqlx/query-7399ff3f22cbf2a522b6123e8ebf101ec9d41b7c8465cc501e61a1f6117981a2.json rename to backend/.sqlx/query-e619fa013528a6275f98e14ae1727c55b0d4f4a5e4ee87c29251042e2916f0a0.json index 13d86dfc34..2874c70c1c 100644 --- a/backend/.sqlx/query-7399ff3f22cbf2a522b6123e8ebf101ec9d41b7c8465cc501e61a1f6117981a2.json +++ b/backend/.sqlx/query-e619fa013528a6275f98e14ae1727c55b0d4f4a5e4ee87c29251042e2916f0a0.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO gcp_trigger (\n gcp_resource_path,\n subscription_id,\n topic_id,\n delivery_type,\n delivery_config,\n workspace_id, \n path, \n script_path, \n is_flow, \n email, \n enabled, \n edited_by,\n error_handler_path,\n error_handler_args,\n retry\n ) \n VALUES (\n $1, \n $2, \n $3, \n $4,\n $5,\n $6, \n $7, \n $8, \n $9,\n $10,\n $11,\n $12,\n $13,\n $14,\n $15\n )", + "query": "\n INSERT INTO gcp_trigger (\n gcp_resource_path,\n subscription_id,\n topic_id,\n delivery_type,\n delivery_config,\n workspace_id, \n path, \n script_path, \n is_flow, \n email, \n enabled, \n edited_by,\n error_handler_path,\n error_handler_args,\n retry,\n auto_acknowledge_msg\n ) \n VALUES (\n $1, \n $2, \n $3, \n $4,\n $5,\n $6, \n $7, \n $8, \n $9,\n $10,\n $11,\n $12,\n $13,\n $14,\n $15,\n $16\n )", "describe": { "columns": [], "parameters": { @@ -29,10 +29,11 @@ "Varchar", "Varchar", "Jsonb", - "Jsonb" + "Jsonb", + "Bool" ] }, "nullable": [] }, - "hash": "7399ff3f22cbf2a522b6123e8ebf101ec9d41b7c8465cc501e61a1f6117981a2" + "hash": "e619fa013528a6275f98e14ae1727c55b0d4f4a5e4ee87c29251042e2916f0a0" } diff --git a/backend/.sqlx/query-ee182378d9a760c3593b483703ec1682488ca9e6e402ec41abaa6ae3ca263854.json b/backend/.sqlx/query-ee182378d9a760c3593b483703ec1682488ca9e6e402ec41abaa6ae3ca263854.json new file mode 100644 index 0000000000..ec170d17c8 --- /dev/null +++ b/backend/.sqlx/query-ee182378d9a760c3593b483703ec1682488ca9e6e402ec41abaa6ae3ca263854.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT email_to_igroup.email\n FROM email_to_igroup\n INNER JOIN instance_group ON instance_group.name = email_to_igroup.igroup\n WHERE instance_group.name = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "email", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "ee182378d9a760c3593b483703ec1682488ca9e6e402ec41abaa6ae3ca263854" +} diff --git a/backend/.sqlx/query-f22964772dc2d67aee437bbbd08b64792c00da1d713d7ca8f9904ccce7bfdae7.json b/backend/.sqlx/query-f22964772dc2d67aee437bbbd08b64792c00da1d713d7ca8f9904ccce7bfdae7.json new file mode 100644 index 0000000000..fb865ca894 --- /dev/null +++ b/backend/.sqlx/query-f22964772dc2d67aee437bbbd08b64792c00da1d713d7ca8f9904ccce7bfdae7.json @@ -0,0 +1,248 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT\n cj.id AS \"id!\",\n cj.workspace_id AS \"workspace_id!\",\n cj.parent_job,\n cj.created_by AS \"created_by!\",\n cj.duration_ms AS \"duration_ms!\",\n cj.success AS \"success!\",\n cj.script_hash AS \"script_hash!: Option\",\n cj.script_path,\n cj.args AS \"args: sqlx::types::Json>>\",\n cj.result AS \"result: sqlx::types::Json>\",\n cj.deleted AS \"deleted!\",\n cj.canceled AS \"canceled!\",\n cj.canceled_by,\n cj.canceled_reason,\n cj.job_kind AS \"job_kind!: JobKind\",\n cj.schedule_path,\n cj.permissioned_as AS \"permissioned_as!\",\n cj.is_flow_step AS \"is_flow_step!\",\n cj.language AS \"language: ScriptLang\",\n cj.is_skipped AS \"is_skipped!\",\n cj.email AS \"email!\",\n cj.visible_to_owner AS \"visible_to_owner!\",\n cj.mem_peak,\n cj.tag AS \"tag!\",\n cj.created_at AS \"created_at!\",\n cj.started_at,\n job_logs.logs,\n job_logs.log_offset AS \"log_offset?\",\n job_logs.log_file_index\n\n FROM v2_as_completed_job AS cj\n LEFT JOIN job_logs ON cj.id = job_logs.job_id\n WHERE cj.created_at < $1\n ORDER BY cj.created_at ASC LIMIT $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id!", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "workspace_id!", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "parent_job", + "type_info": "Uuid" + }, + { + "ordinal": 3, + "name": "created_by!", + "type_info": "Varchar" + }, + { + "ordinal": 4, + "name": "duration_ms!", + "type_info": "Int8" + }, + { + "ordinal": 5, + "name": "success!", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "script_hash!: Option", + "type_info": "Int8" + }, + { + "ordinal": 7, + "name": "script_path", + "type_info": "Varchar" + }, + { + "ordinal": 8, + "name": "args: sqlx::types::Json>>", + "type_info": "Jsonb" + }, + { + "ordinal": 9, + "name": "result: sqlx::types::Json>", + "type_info": "Jsonb" + }, + { + "ordinal": 10, + "name": "deleted!", + "type_info": "Bool" + }, + { + "ordinal": 11, + "name": "canceled!", + "type_info": "Bool" + }, + { + "ordinal": 12, + "name": "canceled_by", + "type_info": "Varchar" + }, + { + "ordinal": 13, + "name": "canceled_reason", + "type_info": "Text" + }, + { + "ordinal": 14, + "name": "job_kind!: JobKind", + "type_info": { + "Custom": { + "name": "job_kind", + "kind": { + "Enum": [ + "script", + "preview", + "flow", + "dependencies", + "flowpreview", + "script_hub", + "identity", + "flowdependencies", + "http", + "graphql", + "postgresql", + "noop", + "appdependencies", + "deploymentcallback", + "singlescriptflow", + "flowscript", + "flownode", + "appscript" + ] + } + } + } + }, + { + "ordinal": 15, + "name": "schedule_path", + "type_info": "Varchar" + }, + { + "ordinal": 16, + "name": "permissioned_as!", + "type_info": "Varchar" + }, + { + "ordinal": 17, + "name": "is_flow_step!", + "type_info": "Bool" + }, + { + "ordinal": 18, + "name": "language: ScriptLang", + "type_info": { + "Custom": { + "name": "script_lang", + "kind": { + "Enum": [ + "python3", + "deno", + "go", + "bash", + "postgresql", + "nativets", + "bun", + "mysql", + "bigquery", + "snowflake", + "graphql", + "powershell", + "mssql", + "php", + "bunnative", + "rust", + "ansible", + "csharp", + "oracledb", + "nu", + "java", + "duckdb", + "ruby" + ] + } + } + } + }, + { + "ordinal": 19, + "name": "is_skipped!", + "type_info": "Bool" + }, + { + "ordinal": 20, + "name": "email!", + "type_info": "Varchar" + }, + { + "ordinal": 21, + "name": "visible_to_owner!", + "type_info": "Bool" + }, + { + "ordinal": 22, + "name": "mem_peak", + "type_info": "Int4" + }, + { + "ordinal": 23, + "name": "tag!", + "type_info": "Varchar" + }, + { + "ordinal": 24, + "name": "created_at!", + "type_info": "Timestamptz" + }, + { + "ordinal": 25, + "name": "started_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 26, + "name": "logs", + "type_info": "Text" + }, + { + "ordinal": 27, + "name": "log_offset?", + "type_info": "Int4" + }, + { + "ordinal": 28, + "name": "log_file_index", + "type_info": "TextArray" + } + ], + "parameters": { + "Left": [ + "Timestamptz", + "Int8" + ] + }, + "nullable": [ + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + true, + false, + true + ] + }, + "hash": "f22964772dc2d67aee437bbbd08b64792c00da1d713d7ca8f9904ccce7bfdae7" +} diff --git a/backend/.sqlx/query-b13d1dec373e70ffd58001cd17e9853e76ee8527e46afbfbc303528e512ebed9.json b/backend/.sqlx/query-f434cac3a8eded700b09a8c99dcc5f7c93fbdb1e7d090b11612aa284342e86a1.json similarity index 89% rename from backend/.sqlx/query-b13d1dec373e70ffd58001cd17e9853e76ee8527e46afbfbc303528e512ebed9.json rename to backend/.sqlx/query-f434cac3a8eded700b09a8c99dcc5f7c93fbdb1e7d090b11612aa284342e86a1.json index 154341c628..21d2ddb41e 100644 --- a/backend/.sqlx/query-b13d1dec373e70ffd58001cd17e9853e76ee8527e46afbfbc303528e512ebed9.json +++ b/backend/.sqlx/query-f434cac3a8eded700b09a8c99dcc5f7c93fbdb1e7d090b11612aa284342e86a1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM \n gcp_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ", + "query": "\n SELECT\n gcp_resource_path,\n subscription_id,\n topic_id,\n workspace_id,\n delivery_type AS \"delivery_type: _\",\n delivery_config AS \"delivery_config: _\",\n subscription_mode AS \"subscription_mode: _\",\n path,\n script_path,\n is_flow,\n edited_by,\n email,\n edited_at,\n server_id,\n last_server_ping,\n extra_perms,\n error,\n enabled,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\",\n auto_acknowledge_msg\n FROM \n gcp_trigger\n WHERE \n workspace_id = $1 AND \n path = $2\n ", "describe": { "columns": [ { @@ -127,6 +127,11 @@ "ordinal": 20, "name": "retry: _", "type_info": "Jsonb" + }, + { + "ordinal": 21, + "name": "auto_acknowledge_msg", + "type_info": "Bool" } ], "parameters": { @@ -156,8 +161,9 @@ false, true, true, + true, true ] }, - "hash": "b13d1dec373e70ffd58001cd17e9853e76ee8527e46afbfbc303528e512ebed9" + "hash": "f434cac3a8eded700b09a8c99dcc5f7c93fbdb1e7d090b11612aa284342e86a1" } diff --git a/backend/.sqlx/query-f582cac90b4b7d732956b74eebc51323ef8acd3f627e7517451fcc72998d22bc.json b/backend/.sqlx/query-f582cac90b4b7d732956b74eebc51323ef8acd3f627e7517451fcc72998d22bc.json new file mode 100644 index 0000000000..5bfc4710c8 --- /dev/null +++ b/backend/.sqlx/query-f582cac90b4b7d732956b74eebc51323ef8acd3f627e7517451fcc72998d22bc.json @@ -0,0 +1,16 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE usr SET added_via = $1 WHERE workspace_id = $2 AND email = $3", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "f582cac90b4b7d732956b74eebc51323ef8acd3f627e7517451fcc72998d22bc" +} diff --git a/backend/.sqlx/query-fab257c4e20aa51b8f785b1882aa0b16fde33b246cbf0749ffa0e4ed63504451.json b/backend/.sqlx/query-fab257c4e20aa51b8f785b1882aa0b16fde33b246cbf0749ffa0e4ed63504451.json deleted file mode 100644 index 14762cc32c..0000000000 --- a/backend/.sqlx/query-fab257c4e20aa51b8f785b1882aa0b16fde33b246cbf0749ffa0e4ed63504451.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT result as \"result: sqlx::types::Json>\", v2_job.tag,\n v2_job_queue.running as \"running: Option\"\n FROM v2_job\n LEFT JOIN v2_job_queue USING (id)\n LEFT JOIN v2_job_completed USING (id)\n WHERE v2_job.id = $2 AND v2_job.workspace_id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "result: sqlx::types::Json>", - "type_info": "Jsonb" - }, - { - "ordinal": 1, - "name": "tag", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "running: Option", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "Text", - "Uuid" - ] - }, - "nullable": [ - true, - false, - false - ] - }, - "hash": "fab257c4e20aa51b8f785b1882aa0b16fde33b246cbf0749ffa0e4ed63504451" -} diff --git a/backend/.sqlx/query-ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238.json b/backend/.sqlx/query-ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238.json index 4e911403de..b100feb47d 100644 --- a/backend/.sqlx/query-ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238.json +++ b/backend/.sqlx/query-ff0403790674cdb07022af71c2377afbd8b3a660b3be27514b517c077c63c238.json @@ -66,7 +66,8 @@ "oracledb", "nu", "java", - "duckdb" + "duckdb", + "ruby" ] } } diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 293b14d6f0..6eecebb1e6 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -175,9 +175,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" dependencies = [ "anstyle", "anstyle-parse", @@ -205,35 +205,35 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] @@ -334,7 +334,7 @@ dependencies = [ "chrono", "chrono-tz", "half", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "num", ] @@ -556,7 +556,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -635,7 +635,7 @@ dependencies = [ "bytes", "http 1.3.1", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.23", "serde", "serde-aux", "serde_json", @@ -658,7 +658,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -680,18 +680,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1007,7 +1007,7 @@ dependencies = [ "aws-smithy-runtime-api", "aws-smithy-types", "h2 0.3.27", - "h2 0.4.11", + "h2 0.4.12", "http 0.2.12", "http 1.3.1", "http-body 0.4.6", @@ -1347,7 +1347,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.104", + "syn 2.0.106", "which 4.4.2", ] @@ -1368,7 +1368,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1534,7 +1534,7 @@ dependencies = [ "serde_json", "serde_repr", "serde_urlencoded", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-util", "tower-service", @@ -1555,9 +1555,9 @@ dependencies = [ [[package]] name = "bon" -version = "3.6.5" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d9ef19ae5263a138da9a86871eca537478ab0332a7770bac7e3f08b801f89f" +checksum = "67a0c21249ad725ebcadcb1b1885f8e3d56e8e6b8924f560268aab000982d637" dependencies = [ "bon-macros", "rustversion", @@ -1565,17 +1565,17 @@ dependencies = [ [[package]] name = "bon-macros" -version = "3.6.5" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577ae008f2ca11ca7641bd44601002ee5ab49ef0af64846ce1ab6057218a5cc1" +checksum = "a660ebdea4d4d3ec7788cfc9c035b66efb66028b9b97bf6cde7023ccc8e77e28" dependencies = [ - "darling 0.21.1", + "darling 0.21.2", "ident_case", "prettyplease", "proc-macro2", "quote", "rustversion", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1598,7 +1598,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1608,7 +1608,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17d4f95e880cfd28c4ca5a006cf7f6af52b4bcb7b5866f573b2faa126fb7affb" dependencies = [ "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1635,9 +1635,9 @@ dependencies = [ [[package]] name = "brotli" -version = "8.0.1" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1727,22 +1727,22 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.23.1" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "441473f2b4b0459a68628c744bc61d23e730fb00128b841d30fa4bb3972257e4" +checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1885,7 +1885,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b4a6cae9efc04cc6cbb8faf338d2c497c165c83e74509cf4dbedea948bbf6e5" dependencies = [ "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -1905,9 +1905,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.31" +version = "1.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" dependencies = [ "jobserver", "libc", @@ -2014,9 +2014,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.42" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" +checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" dependencies = [ "clap_builder", "clap_derive", @@ -2024,9 +2024,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.42" +version = "4.5.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" +checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" dependencies = [ "anstream", "anstyle", @@ -2036,14 +2036,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.41" +version = "4.5.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2098,7 +2098,7 @@ dependencies = [ "nom 7.1.3", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2480,7 +2480,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2526,12 +2526,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b136475da5ef7b6ac596c0e956e37bad51b85b987ff3d5e230e964936736b2" +checksum = "08440b3dd222c3d0433e63e097463969485f112baff337dfdaca043a0d760570" dependencies = [ - "darling_core 0.21.1", - "darling_macro 0.21.1", + "darling_core 0.21.2", + "darling_macro 0.21.2", ] [[package]] @@ -2573,21 +2573,21 @@ dependencies = [ "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "darling_core" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b44ad32f92b75fb438b04b68547e521a548be8acc339a6dacc4a7121488f53e6" +checksum = "d25b7912bc28a04ab1b7715a68ea03aaa15662b43a1a4b2c480531fd19f8bf7e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -2620,18 +2620,18 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "darling_macro" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5be8a7a562d315a5b92a630c30cec6bcf663e6673f00fbb69cca66a6f521b9" +checksum = "ce154b9bea7fb0c8e8326e62d00354000c36e79770ff21b8c84e3aa267d9d531" dependencies = [ - "darling_core 0.21.1", + "darling_core 0.21.2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3123,7 +3123,7 @@ checksum = "df6f88d7ee27daf8b108ba910f9015176b36fbc72902b1ca5c2a5f1d1717e1a1" dependencies = [ "datafusion-expr", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3324,7 +3324,7 @@ dependencies = [ "swc_visit", "swc_visit_macros", "text_lines", - "thiserror 2.0.12", + "thiserror 2.0.14", "unicode-width 0.1.14", "url", ] @@ -3338,7 +3338,7 @@ dependencies = [ "async-trait", "deno_core", "deno_error", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "uuid", ] @@ -3355,7 +3355,7 @@ dependencies = [ "rusqlite", "serde", "sha2 0.10.9", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", ] @@ -3399,7 +3399,7 @@ dependencies = [ "deno_webgpu", "image", "serde", - "thiserror 2.0.12", + "thiserror 2.0.14", ] [[package]] @@ -3425,7 +3425,7 @@ dependencies = [ "serde", "serde_json", "sys_traits", - "thiserror 2.0.12", + "thiserror 2.0.14", "url", ] @@ -3470,7 +3470,7 @@ dependencies = [ "smallvec", "sourcemap 8.0.1", "static_assertions", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "url", "v8", @@ -3495,7 +3495,7 @@ dependencies = [ "deno_core", "deno_error", "saffron", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", ] @@ -3533,7 +3533,7 @@ dependencies = [ "sha2 0.10.9", "signature", "spki", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "uuid", "x25519-dalek", @@ -3561,7 +3561,7 @@ checksum = "babccedee31ce7e57c3e6dff2cb3ab8d68c49d0df8222fe0d11d628e65192790" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -3581,7 +3581,7 @@ dependencies = [ "deno_tls", "dyn-clone", "error_reporter", - "h2 0.4.11", + "h2 0.4.12", "hickory-resolver", "http 1.3.1", "http-body-util", @@ -3593,7 +3593,7 @@ dependencies = [ "rustls-webpki 0.102.8", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-rustls 0.26.2", "tokio-socks", @@ -3621,7 +3621,7 @@ dependencies = [ "serde", "serde-value", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "winapi", ] @@ -3647,7 +3647,7 @@ dependencies = [ "rand 0.8.5", "rayon", "serde", - "thiserror 2.0.12", + "thiserror 2.0.14", "winapi", "windows-sys 0.59.0", ] @@ -3686,7 +3686,7 @@ dependencies = [ "scopeguard", "serde", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-util", ] @@ -3746,7 +3746,7 @@ dependencies = [ "rand 0.8.5", "rusqlite", "serde", - "thiserror 2.0.12", + "thiserror 2.0.14", "url", ] @@ -3759,7 +3759,7 @@ dependencies = [ "deno_semver", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.14", ] [[package]] @@ -3786,7 +3786,7 @@ dependencies = [ "libloading 0.7.4", "log", "napi_sym", - "thiserror 2.0.12", + "thiserror 2.0.14", "windows-sys 0.59.0", ] @@ -3820,7 +3820,7 @@ dependencies = [ "rustls-tokio-stream", "serde", "socket2 0.5.10", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", ] @@ -3862,7 +3862,7 @@ dependencies = [ "elliptic-curve", "errno", "faster-hex", - "h2 0.4.11", + "h2 0.4.12", "hkdf", "http 1.3.1", "http-body-util", @@ -3907,7 +3907,7 @@ dependencies = [ "spki", "stable_deref_trait", "sys_traits", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-eld", "url", @@ -3935,7 +3935,7 @@ dependencies = [ "monch", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.14", "url", ] @@ -3952,8 +3952,8 @@ dependencies = [ "stringcase", "strum 0.25.0", "strum_macros 0.25.3", - "syn 2.0.104", - "thiserror 2.0.12", + "syn 2.0.106", + "thiserror 2.0.14", ] [[package]] @@ -3974,7 +3974,7 @@ dependencies = [ "serde", "signal-hook", "signal-hook-registry", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "winapi", ] @@ -3993,7 +3993,7 @@ dependencies = [ "serde", "serde_json", "sys_traits", - "thiserror 2.0.12", + "thiserror 2.0.14", "url", ] @@ -4006,7 +4006,7 @@ dependencies = [ "deno_error", "percent-encoding", "sys_traits", - "thiserror 2.0.12", + "thiserror 2.0.14", "url", ] @@ -4027,7 +4027,7 @@ dependencies = [ "once_cell", "percent-encoding", "serde", - "thiserror 2.0.12", + "thiserror 2.0.14", "which 6.0.3", "winapi", ] @@ -4054,7 +4054,7 @@ dependencies = [ "serde", "simd-json", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "which 6.0.3", "winapi", @@ -4088,7 +4088,7 @@ dependencies = [ "once_cell", "parking_lot 0.12.4", "sys_traits", - "thiserror 2.0.12", + "thiserror 2.0.14", "url", ] @@ -4153,7 +4153,7 @@ dependencies = [ "serde", "sys_traits", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-metrics", "twox-hash 1.6.3", @@ -4176,7 +4176,7 @@ dependencies = [ "monch", "once_cell", "serde", - "thiserror 2.0.12", + "thiserror 2.0.14", "url", ] @@ -4203,7 +4203,7 @@ dependencies = [ "opentelemetry_sdk", "pin-project", "serde", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", ] @@ -4231,7 +4231,7 @@ dependencies = [ "rustls-tokio-stream", "rustls-webpki 0.102.8", "serde", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "webpki-roots 0.26.11", ] @@ -4255,7 +4255,7 @@ checksum = "d79e743ad841f7826d46c6944580f5ba665fe9ab4c31a68c4eed8b5a78225da3" dependencies = [ "deno_core", "deno_error", - "thiserror 2.0.12", + "thiserror 2.0.14", "urlpattern", ] @@ -4275,7 +4275,7 @@ dependencies = [ "flate2", "futures", "serde", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "uuid", ] @@ -4290,7 +4290,7 @@ dependencies = [ "deno_error", "raw-window-handle", "serde", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "wgpu-core", "wgpu-types", @@ -4318,7 +4318,7 @@ dependencies = [ "deno_permissions", "deno_tls", "fastwebsockets", - "h2 0.4.11", + "h2 0.4.12", "http 1.3.1", "http-body-util", "hyper 1.6.0", @@ -4326,7 +4326,7 @@ dependencies = [ "once_cell", "rustls-tokio-stream", "serde", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", ] @@ -4340,7 +4340,7 @@ dependencies = [ "deno_error", "deno_web", "rusqlite", - "thiserror 2.0.12", + "thiserror 2.0.14", ] [[package]] @@ -4388,7 +4388,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-util", "url", @@ -4413,7 +4413,7 @@ dependencies = [ "rand 0.8.5", "rusqlite", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-stream", "uuid", @@ -4454,7 +4454,7 @@ checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4469,13 +4469,13 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4519,7 +4519,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4645,7 +4645,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4680,7 +4680,7 @@ checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4955,7 +4955,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -4975,7 +4975,7 @@ checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -5276,7 +5276,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -5314,7 +5314,7 @@ checksum = "32016f1242eb82af5474752d00fd8ebcd9004bd69b462b1c91de833972d08ed4" dependencies = [ "proc-macro2", "swc_macros_common", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -5449,7 +5449,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -5848,7 +5848,7 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -5864,9 +5864,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "globset" @@ -5926,7 +5926,7 @@ dependencies = [ "google-cloud-token", "home", "jsonwebtoken 9.3.1", - "reqwest 0.12.22", + "reqwest 0.12.23", "serde", "serde_json", "thiserror 1.0.69", @@ -5969,7 +5969,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d901aeb453fd80e51d64df4ee005014f6cf39f2d736dd64f7239c132d9d39a6a" dependencies = [ - "reqwest 0.12.22", + "reqwest 0.12.23", "thiserror 1.0.69", "tokio", ] @@ -6041,7 +6041,7 @@ checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ "bitflags 2.9.1", "gpu-descriptor-types", - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -6094,9 +6094,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -6156,9 +6156,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", @@ -6173,7 +6173,7 @@ checksum = "f208758247e68e239acaa059e72e4ce1f30f2a4b6523f19c1b923d25b7e9cceb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -6191,7 +6191,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -6253,10 +6253,10 @@ dependencies = [ "native-tls", "num_cpus", "rand 0.9.0", - "reqwest 0.12.22", + "reqwest 0.12.23", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "ureq", "windows-sys 0.60.2", @@ -6281,7 +6281,7 @@ dependencies = [ "once_cell", "rand 0.9.0", "serde", - "thiserror 2.0.12", + "thiserror 2.0.14", "tinyvec", "tokio", "tracing", @@ -6305,7 +6305,7 @@ dependencies = [ "resolv-conf", "serde", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tracing", ] @@ -6475,7 +6475,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.11", + "h2 0.4.12", "http 1.3.1", "http-body 1.0.1", "httparse", @@ -6813,7 +6813,7 @@ dependencies = [ "percent-encoding", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.14", "url", ] @@ -6835,7 +6835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "serde", ] @@ -6966,7 +6966,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7216,7 +7216,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7306,9 +7306,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "libduckdb-sys" @@ -7520,7 +7520,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -7529,7 +7529,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f8cc7106155f10bdf99a6f379688f543ad6596a415375b36a59a054ceda1198" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -7859,7 +7859,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -7979,7 +7979,7 @@ checksum = "c402a4092d5e204f32c9e155431046831fa712637043c58cb73bc6bc6c9663b5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -8024,9 +8024,9 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "termcolor", - "thiserror 2.0.12", + "thiserror 2.0.14", ] [[package]] @@ -8051,7 +8051,7 @@ dependencies = [ "serde", "serde_json", "socket2 0.5.10", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-native-tls", "tokio-util", @@ -8083,7 +8083,7 @@ dependencies = [ "serde_json", "sha1", "sha2 0.10.9", - "thiserror 2.0.12", + "thiserror 2.0.14", "uuid", ] @@ -8127,7 +8127,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -8253,7 +8253,7 @@ dependencies = [ "serde", "serde_json", "sys_traits", - "thiserror 2.0.12", + "thiserror 2.0.14", "url", ] @@ -8343,7 +8343,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -8420,7 +8420,7 @@ dependencies = [ "num-format", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.14", "typetag", "windows-sys 0.48.0", ] @@ -8612,7 +8612,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -8632,7 +8632,7 @@ dependencies = [ "getrandom 0.2.16", "http 1.3.1", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.23", "serde", "serde_json", "serde_path_to_error", @@ -8681,13 +8681,13 @@ dependencies = [ "percent-encoding", "quick-xml 0.37.5", "rand 0.9.0", - "reqwest 0.12.22", + "reqwest 0.12.23", "ring 0.17.14", "rustls-pemfile 2.2.0", "serde", "serde_json", "serde_urlencoded", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tracing", "url", @@ -8831,7 +8831,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -8842,9 +8842,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-src" -version = "300.5.1+3.5.1" +version = "300.5.2+3.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "735230c832b28c000e3bc117119e6466a663ec73506bc0a9907ea4187508e42a" +checksum = "d270b79e2926f5150189d475bc7e9d2c69f9c4697b185fa917d5a32b792d21b4" dependencies = [ "cc", ] @@ -9165,13 +9165,13 @@ dependencies = [ "arrow-schema", "arrow-select", "base64 0.22.1", - "brotli 8.0.1", + "brotli 8.0.2", "bytes", "chrono", "flate2", "futures", "half", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "lz4_flex", "num", "num-bigint", @@ -9339,7 +9339,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9389,7 +9389,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9601,7 +9601,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" dependencies = [ "proc-macro2", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9665,7 +9665,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9676,7 +9676,7 @@ checksum = "07c277e4e643ef00c1233393c673f655e3672cf7eb3ba08a00bdd0ea59139b5f" dependencies = [ "proc-macro-rules-macros", "proc-macro2", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9688,14 +9688,14 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "d61789d7719defeb74ea5fe81f2fdfdbd28a803847077cecce2ff14e1472f6f1" dependencies = [ "unicode-ident", ] @@ -9770,7 +9770,7 @@ dependencies = [ "lazy_static", "memchr", "parking_lot 0.12.4", - "thiserror 2.0.12", + "thiserror 2.0.14", ] [[package]] @@ -9799,7 +9799,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.104", + "syn 2.0.106", "tempfile", ] @@ -9813,7 +9813,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -9936,7 +9936,7 @@ checksum = "9ad6644cb07b7f3488b9f3d2fde3b4c0a7fa367cafefb39dff93a659f76eb786" dependencies = [ "ahash 0.8.12", "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "parking_lot 0.12.4", ] @@ -9954,7 +9954,7 @@ dependencies = [ "rustc-hash 2.1.1", "rustls 0.23.29", "socket2 0.5.10", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tracing", "web-time", @@ -9975,7 +9975,7 @@ dependencies = [ "rustls 0.23.29", "rustls-pki-types", "slab", - "thiserror 2.0.12", + "thiserror 2.0.14", "tinyvec", "tracing", "web-time", @@ -10138,9 +10138,9 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -10159,9 +10159,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -10222,7 +10222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" dependencies = [ "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -10271,7 +10271,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.12", + "thiserror 2.0.14", ] [[package]] @@ -10291,7 +10291,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -10403,9 +10403,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" dependencies = [ "async-compression", "base64 0.22.1", @@ -10413,7 +10413,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.4.11", + "h2 0.4.12", "http 1.3.1", "http-body 1.0.1", "http-body-util", @@ -10460,7 +10460,7 @@ dependencies = [ "anyhow", "async-trait", "http 1.3.1", - "reqwest 0.12.22", + "reqwest 0.12.23", "serde", "thiserror 1.0.69", "tower-service", @@ -10479,7 +10479,7 @@ dependencies = [ "http 1.3.1", "hyper 1.6.0", "parking_lot 0.11.2", - "reqwest 0.12.22", + "reqwest 0.12.23", "reqwest-middleware", "retry-policies", "thiserror 1.0.69", @@ -10601,7 +10601,7 @@ dependencies = [ "serde", "serde_json", "sse-stream", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-stream", "tokio-util", @@ -10620,7 +10620,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -10710,7 +10710,7 @@ dependencies = [ "quote", "rust-embed-utils", "shellexpand", - "syn 2.0.104", + "syn 2.0.106", "walkdir", ] @@ -10898,7 +10898,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.2.0", + "security-framework 3.3.0", ] [[package]] @@ -11033,9 +11033,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rustyline" @@ -11197,7 +11197,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -11270,9 +11270,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" dependencies = [ "bitflags 2.9.1", "core-foundation 0.10.1", @@ -11377,7 +11377,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -11388,7 +11388,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -11440,7 +11440,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -11474,7 +11474,7 @@ dependencies = [ "num-bigint", "serde", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.14", "v8", ] @@ -11507,7 +11507,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -11689,7 +11689,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.12", + "thiserror 2.0.14", "time", ] @@ -11722,9 +11722,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slotmap" @@ -11925,7 +11925,7 @@ checksum = "da5fc6819faabb412da764b99d3b713bb55083c11e7e0c00144d386cd6a1939c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -11959,7 +11959,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "hashlink 0.10.0", "indexmap 2.10.0", "log", @@ -11971,7 +11971,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.14", "tokio", "tokio-stream", "tracing", @@ -11990,7 +11990,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12013,7 +12013,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.104", + "syn 2.0.106", "tokio", "url", ] @@ -12057,7 +12057,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.12", + "thiserror 2.0.14", "tracing", "uuid", "whoami", @@ -12098,7 +12098,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.12", + "thiserror 2.0.14", "tracing", "uuid", "whoami", @@ -12124,7 +12124,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.12", + "thiserror 2.0.14", "tracing", "url", "uuid", @@ -12177,7 +12177,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12246,7 +12246,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12258,7 +12258,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12377,7 +12377,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12426,7 +12426,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12511,7 +12511,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12618,7 +12618,7 @@ checksum = "63db0adcff29d220c3d151c5b25c0eabe7e32dd936212b84cdaa1392e3130497" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12629,7 +12629,7 @@ checksum = "f486687bfb7b5c560868f69ed2d458b880cebc9babebcb67e49f31b55c5bf847" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12652,7 +12652,7 @@ dependencies = [ "proc-macro2", "quote", "swc_macros_common", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12668,9 +12668,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.104" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -12712,7 +12712,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -12883,7 +12883,7 @@ dependencies = [ "tantivy-stacker", "tantivy-tokenizer-api", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.14", "time", "uuid", "winapi", @@ -13017,12 +13017,12 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ "rustix 1.0.8", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -13055,11 +13055,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.14", ] [[package]] @@ -13070,18 +13070,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -13327,7 +13327,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -13507,7 +13507,7 @@ dependencies = [ "futures-io", "futures-sink", "futures-util", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "pin-project-lite", "slab", "tokio", @@ -13591,7 +13591,7 @@ dependencies = [ "base64 0.22.1", "bytes", "flate2", - "h2 0.4.11", + "h2 0.4.12", "http 1.3.1", "http-body 1.0.1", "http-body-util", @@ -13735,7 +13735,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -13776,7 +13776,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3beec919fbdf99d719de8eda6adae3281f8a5b71ae40431f44dc7423053d34" dependencies = [ "loki-api", - "reqwest 0.12.22", + "reqwest 0.12.23", "serde", "serde_json", "snap", @@ -13877,6 +13877,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4013970217383f67b18aef68f6fb2e8d409bc5755227092d32efb0422ba24b8" +[[package]] +name = "tree-sitter-ruby" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be0484ea4ef6bb9c575b4fdabde7e31340a8d2dbc7d52b321ac83da703249f95" +dependencies = [ + "cc", + "tree-sitter-language", +] + [[package]] name = "triomphe" version = "0.1.14" @@ -13978,7 +13988,7 @@ checksum = "35f5380909ffc31b4de4f4bdf96b877175a016aa2ca98cee39fcfd8c4d53d952" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -14299,9 +14309,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" dependencies = [ "getrandom 0.3.3", "js-sys", @@ -14448,7 +14458,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -14483,7 +14493,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -14518,7 +14528,7 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -14556,7 +14566,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eeee3bdea6257cc36d756fa745a70f9d393571e47d69e0ed97581676a5369ca" dependencies = [ "deno_error", - "thiserror 2.0.12", + "thiserror 2.0.14", ] [[package]] @@ -14724,11 +14734,11 @@ dependencies = [ [[package]] name = "whoami" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ - "redox_syscall 0.5.17", + "libredox", "wasite", "web-sys", ] @@ -14772,7 +14782,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windmill" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "aws-sdk-config", @@ -14794,7 +14804,7 @@ dependencies = [ "prometheus", "quote", "rand 0.9.0", - "reqwest 0.12.22", + "reqwest 0.12.23", "rustls 0.23.29", "serde", "serde_json", @@ -14826,7 +14836,7 @@ dependencies = [ [[package]] name = "windmill-api" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "argon2", @@ -14891,7 +14901,7 @@ dependencies = [ "rand 0.9.0", "rdkafka", "regex", - "reqwest 0.12.22", + "reqwest 0.12.23", "rmcp", "rsa", "rumqttc", @@ -14908,7 +14918,7 @@ dependencies = [ "sql-builder", "sqlx", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.14", "time", "tinyvector", "tokenizers", @@ -14944,7 +14954,7 @@ dependencies = [ [[package]] name = "windmill-api-client" -version = "1.518.2" +version = "1.527.1" dependencies = [ "base64 0.22.1", "chrono", @@ -14959,7 +14969,7 @@ dependencies = [ [[package]] name = "windmill-audit" -version = "1.518.2" +version = "1.527.1" dependencies = [ "chrono", "serde", @@ -14972,7 +14982,7 @@ dependencies = [ [[package]] name = "windmill-autoscaling" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "serde", @@ -14986,7 +14996,7 @@ dependencies = [ [[package]] name = "windmill-common" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "async-recursion", @@ -15030,7 +15040,7 @@ dependencies = [ "quick_cache", "rand 0.9.0", "regex", - "reqwest 0.12.22", + "reqwest 0.12.23", "reqwest-middleware", "reqwest-retry", "semver 1.0.26", @@ -15045,7 +15055,7 @@ dependencies = [ "systemstat", "tar", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.14", "tikv-jemalloc-ctl", "tokio", "tokio-stream", @@ -15067,7 +15077,7 @@ dependencies = [ [[package]] name = "windmill-git-sync" -version = "1.518.2" +version = "1.527.1" dependencies = [ "regex", "serde", @@ -15082,7 +15092,7 @@ dependencies = [ [[package]] name = "windmill-indexer" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "bytes", @@ -15106,19 +15116,19 @@ dependencies = [ [[package]] name = "windmill-macros" -version = "1.518.2" +version = "1.527.1" dependencies = [ "itertools 0.14.0", "lazy_static", "proc-macro2", "quote", "regex", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] name = "windmill-parser" -version = "1.518.2" +version = "1.527.1" dependencies = [ "convert_case 0.6.0", "serde", @@ -15127,7 +15137,7 @@ dependencies = [ [[package]] name = "windmill-parser-bash" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "lazy_static", @@ -15139,7 +15149,7 @@ dependencies = [ [[package]] name = "windmill-parser-csharp" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "serde_json", @@ -15151,7 +15161,7 @@ dependencies = [ [[package]] name = "windmill-parser-go" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "gosyn", @@ -15163,7 +15173,7 @@ dependencies = [ [[package]] name = "windmill-parser-graphql" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "lazy_static", @@ -15175,7 +15185,7 @@ dependencies = [ [[package]] name = "windmill-parser-java" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "serde_json", @@ -15187,7 +15197,7 @@ dependencies = [ [[package]] name = "windmill-parser-nu" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "nu-parser", @@ -15198,7 +15208,7 @@ dependencies = [ [[package]] name = "windmill-parser-php" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15209,7 +15219,7 @@ dependencies = [ [[package]] name = "windmill-parser-py" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "itertools 0.14.0", @@ -15221,7 +15231,7 @@ dependencies = [ [[package]] name = "windmill-parser-py-imports" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "async-recursion", @@ -15242,9 +15252,23 @@ dependencies = [ "windmill-parser", ] +[[package]] +name = "windmill-parser-ruby" +version = "1.527.1" +dependencies = [ + "anyhow", + "lazy_static", + "regex", + "serde_json", + "tree-sitter", + "tree-sitter-ruby", + "wasm-bindgen", + "windmill-parser", +] + [[package]] name = "windmill-parser-rust" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "convert_case 0.6.0", @@ -15254,14 +15278,14 @@ dependencies = [ "quote", "regex", "serde_json", - "syn 2.0.104", + "syn 2.0.106", "toml", "windmill-parser", ] [[package]] name = "windmill-parser-sql" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "lazy_static", @@ -15275,7 +15299,7 @@ dependencies = [ [[package]] name = "windmill-parser-ts" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "lazy_static", @@ -15293,7 +15317,7 @@ dependencies = [ [[package]] name = "windmill-parser-wasm" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "getrandom 0.2.16", @@ -15309,6 +15333,7 @@ dependencies = [ "windmill-parser-nu", "windmill-parser-php", "windmill-parser-py", + "windmill-parser-ruby", "windmill-parser-rust", "windmill-parser-sql", "windmill-parser-ts", @@ -15317,7 +15342,7 @@ dependencies = [ [[package]] name = "windmill-parser-yaml" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "serde_json", @@ -15327,7 +15352,7 @@ dependencies = [ [[package]] name = "windmill-queue" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "async-recursion", @@ -15344,7 +15369,7 @@ dependencies = [ "lazy_static", "prometheus", "regex", - "reqwest 0.12.22", + "reqwest 0.12.23", "serde", "serde_json", "serde_urlencoded", @@ -15360,7 +15385,7 @@ dependencies = [ [[package]] name = "windmill-sql-datatype-parser-wasm" -version = "1.518.2" +version = "1.527.1" dependencies = [ "wasm-bindgen", "wasm-bindgen-test", @@ -15370,7 +15395,7 @@ dependencies = [ [[package]] name = "windmill-worker" -version = "1.518.2" +version = "1.527.1" dependencies = [ "anyhow", "async-recursion", @@ -15423,7 +15448,7 @@ dependencies = [ "prometheus", "rand 0.9.0", "regex", - "reqwest 0.12.22", + "reqwest 0.12.23", "reqwest-middleware", "rust_decimal", "serde", @@ -15455,6 +15480,7 @@ dependencies = [ "windmill-parser-php", "windmill-parser-py", "windmill-parser-py-imports", + "windmill-parser-ruby", "windmill-parser-rust", "windmill-parser-sql", "windmill-parser-ts", @@ -15561,7 +15587,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -15572,7 +15598,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -15583,7 +15609,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -15594,7 +15620,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -15605,7 +15631,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -15616,7 +15642,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -16084,7 +16110,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "synstructure 0.13.2", ] @@ -16096,7 +16122,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "synstructure 0.13.2", ] @@ -16117,7 +16143,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -16137,7 +16163,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", "synstructure 0.13.2", ] @@ -16158,7 +16184,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] @@ -16174,9 +16200,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbb9122ea75b11bf96e7492afb723e8a7fbe12c67417aa95e7e3d18144d37cd" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke 0.8.0", "zerofrom", @@ -16191,7 +16217,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.104", + "syn 2.0.106", ] [[package]] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index c95344fbf6..435345eb59 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "windmill" -version = "1.518.2" +version = "1.527.1" authors.workspace = true edition.workspace = true @@ -24,6 +24,7 @@ members = [ "./parsers/windmill-parser-csharp", "./parsers/windmill-parser-nu", "./parsers/windmill-parser-java", + "./parsers/windmill-parser-ruby", "./parsers/windmill-parser-bash", "./parsers/windmill-parser-py", "./parsers/windmill-parser-py-imports", @@ -32,7 +33,7 @@ members = [ ] [workspace.package] -version = "1.518.2" +version = "1.527.1" authors = ["Ruben Fiszel "] edition = "2021" @@ -48,7 +49,7 @@ incremental = true lto = "thin" [features] -default = [] +default = ["ruby"] private = ["windmill-api/private", "windmill-autoscaling/private", "windmill-common/private", "windmill-git-sync/private", "windmill-indexer/private", "windmill-queue/private", "windmill-worker/private"] agent_worker_server = ["windmill-api/agent_worker_server"] enterprise = ["windmill-worker/enterprise", "windmill-queue/enterprise", "windmill-api/enterprise", "dep:windmill-autoscaling", "windmill-autoscaling/enterprise", "windmill-git-sync/enterprise", "windmill-common/prometheus", "windmill-common/enterprise"] @@ -77,7 +78,7 @@ mcp = ["windmill-api/mcp"] mqtt_trigger = ["windmill-api/mqtt_trigger"] sqs_trigger = ["windmill-api/sqs_trigger", "windmill-common/aws_auth", "windmill-api/openidconnect"] gcp_trigger = ["windmill-api/gcp_trigger"] -smtp = ["windmill-api/smtp", "windmill-common/smtp"] +smtp = ["windmill-api/smtp", "windmill-common/smtp", "windmill-queue/smtp"] license = ["windmill-api/license"] oauth2 = ["windmill-api/oauth2"] zip = ["windmill-api/zip"] @@ -95,7 +96,8 @@ php = ["windmill-worker/php"] csharp = ["windmill-worker/csharp"] nu = ["windmill-worker/nu"] java = ["windmill-worker/java"] -all_languages = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql", "bigquery", "csharp", "nu", "php", "java"] +ruby = ["windmill-worker/ruby"] +all_languages = ["python", "deno_core", "rust", "mysql", "oracledb", "duckdb", "mssql", "bigquery", "csharp", "nu", "php", "java", "ruby"] # For windows we have another set of languages enabled # NOTE: DuckDB is ignored because of compilation problems all_languages_windows = ["python", "deno_core", "rust", "mysql", "oracledb", "mssql", "bigquery", "csharp", "nu", "php", "java"] @@ -181,6 +183,7 @@ windmill-parser-rust = { path = "./parsers/windmill-parser-rust" } windmill-parser-yaml = { path = "./parsers/windmill-parser-yaml" } windmill-parser-csharp = { path = "./parsers/windmill-parser-csharp" } windmill-parser-java = { path = "./parsers/windmill-parser-java" } +windmill-parser-ruby = { path = "./parsers/windmill-parser-ruby" } windmill-parser-nu = { path = "./parsers/windmill-parser-nu" } windmill-parser-bash = { path = "./parsers/windmill-parser-bash" } windmill-parser-sql = { path = "./parsers/windmill-parser-sql" } @@ -409,6 +412,7 @@ tokio-tungstenite = { version = "0.24.0", features = ["native-tls"] } tree-sitter = { version = "0.23.0", features = [] } tree-sitter-c-sharp = "0.23.0" tree-sitter-java = "0.23.0" +tree-sitter-ruby = "0.23.0" oracle = { version = "0.6.3", features = ["chrono"] } rumqttc = { version = "0.24.0", features = ["use-native-tls"]} strum = { version = "0.27", features = ["derive"] } diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index f61a583810..41a59cdb81 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -bb3ec8ecf883cd13be33f81cba98dec745500f62 \ No newline at end of file +d04959121c6869d764e11e005676003fd2919629 \ No newline at end of file diff --git a/backend/generate_mcp_endpoints_tools/README.md b/backend/generate_mcp_endpoints_tools/README.md new file mode 100644 index 0000000000..b19245fcdd --- /dev/null +++ b/backend/generate_mcp_endpoints_tools/README.md @@ -0,0 +1,35 @@ +## MCP Tools Generator + +The `generate_mcp_tools.py` script parses the OpenAPI specification and generates Rust code for MCP (Model Context Protocol) tools. + +### Setup + +```bash +cd backend/generate_mcp_endpoints_tools +pip install -r requirements.txt +``` + +### Usage + +```bash +python3 generate_mcp_tools.py +``` + +The script will: +1. Parse `backend/windmill-api/openapi.yaml` +2. Find all endpoints marked with `x-mcp-tool: true` +3. Generate `backend/windmill-api/src/mcp_tools.rs` with a const array of tools + +### Adding MCP Tools + +To mark an endpoint as an MCP tool, add `x-mcp-tool: true` to the operation in the OpenAPI spec. You can also add `x-mcp-instructions` to complete the description of the tool with instructions on how to correctly use the endpoint: + +```yaml +/w/{workspace}/scripts/list: + get: + x-mcp-tool: true + x-mcp-instructions: you should call that with this or that arg + summary: list scripts in workspace + operationId: listScripts + # ... rest of endpoint definition +``` \ No newline at end of file diff --git a/backend/generate_mcp_endpoints_tools/generate_mcp_tools.py b/backend/generate_mcp_endpoints_tools/generate_mcp_tools.py new file mode 100644 index 0000000000..e82683eb52 --- /dev/null +++ b/backend/generate_mcp_endpoints_tools/generate_mcp_tools.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 +""" +Script to parse the OpenAPI YAML file and generate Rust code with MCP tools. +Searches for endpoints tagged with 'x-mcp-tool: true' and creates a const array. +""" + +import json +import sys +from pathlib import Path +from typing import Dict, List, Any, Optional + +IMPORTS = """ +use std::borrow::Cow; +use serde::{Deserialize, Serialize}; +""" + +ENDPOINT_STRUCT = """ +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct EndpointTool { + pub name: Cow<'static, str>, + pub description: Cow<'static, str>, + pub instructions: Cow<'static, str>, + pub path: Cow<'static, str>, + pub method: Cow<'static, str>, + pub path_params_schema: Option, + pub query_params_schema: Option, + pub body_schema: Option, +} +""" + +def load_openapi_spec(file_path: str) -> Dict[str, Any]: + """Load and parse the OpenAPI YAML specification.""" + try: + import yaml + with open(file_path, 'r', encoding='utf-8') as f: + return yaml.safe_load(f) + except ImportError: + print("PyYAML not found. Please install it with: pip install PyYAML", file=sys.stderr) + sys.exit(1) + except Exception as e: + print(f"Error loading OpenAPI spec: {e}", file=sys.stderr) + sys.exit(1) + +def extract_separate_schemas(parameters: List[Dict[str, Any]], request_body: Optional[Dict[str, Any]], spec: Dict[str, Any]) -> tuple: + """Extract separate schemas for path parameters, query parameters, and request body.""" + path_params_schema = { + "type": "object", + "properties": {}, + "required": [] + } + + query_params_schema = { + "type": "object", + "properties": {}, + "required": [] + } + + body_schema = None + + # Process parameters + for param in parameters: + # Resolve $ref if present + if '$ref' in param: + param = resolve_schema_refs(param, spec) + + param_name = param.get('name', '') + param_schema = param.get('schema', {'type': 'string'}) + param_required = param.get('required', False) + param_description = param.get('description', '') + param_in = param.get('in', 'query') + + # Resolve any refs in the parameter schema + param_schema = resolve_schema_refs(param_schema, spec) + + # Add description if available + if param_description: + param_schema = dict(param_schema) + param_schema['description'] = param_description + + # Route to appropriate schema based on parameter location + if param_in == 'path': + # Skip 'workspace' path parameter as it's automatically provided by the MCP context + if param_name != 'workspace': + path_params_schema['properties'][param_name] = param_schema + if param_required: + path_params_schema['required'].append(param_name) + elif param_in == 'query': + query_params_schema['properties'][param_name] = param_schema + if param_required: + query_params_schema['required'].append(param_name) + + # Process request body if present + if request_body: + body_schema = extract_request_body_schema(request_body, spec) + + # Return None for empty schemas + path_params_schema = path_params_schema if path_params_schema['properties'] else None + query_params_schema = query_params_schema if query_params_schema['properties'] else None + + return (path_params_schema, query_params_schema, body_schema) + +def resolve_ref(ref_path: str, spec: Dict[str, Any]) -> Optional[Dict[str, Any]]: + """Resolve a $ref path to the actual schema definition.""" + if not ref_path.startswith('#/'): + return None + + # Remove the '#/' prefix and split by '/' + path_parts = ref_path[2:].split('/') + + # Navigate through the spec following the path + current = spec + for part in path_parts: + if isinstance(current, dict) and part in current: + current = current[part] + else: + return None + + return current if isinstance(current, dict) else None + +def resolve_schema_refs(schema: Dict[str, Any], spec: Dict[str, Any]) -> Dict[str, Any]: + """Recursively resolve all $ref references in a schema.""" + if not isinstance(schema, dict): + return schema + + # If this is a $ref, resolve it + if '$ref' in schema: + ref_path = schema['$ref'] + resolved = resolve_ref(ref_path, spec) + if resolved: + # Recursively resolve any refs in the resolved schema + return resolve_schema_refs(resolved, spec) + else: + print(f"Warning: Could not resolve $ref: {ref_path}") + return schema + + # Recursively process all values in the schema + resolved_schema = {} + for key, value in schema.items(): + if isinstance(value, dict): + resolved_schema[key] = resolve_schema_refs(value, spec) + elif isinstance(value, list): + resolved_schema[key] = [ + resolve_schema_refs(item, spec) if isinstance(item, dict) else item + for item in value + ] + else: + resolved_schema[key] = value + + return resolved_schema + +def extract_request_body_schema(request_body: Dict[str, Any], spec: Dict[str, Any]) -> Optional[Dict[str, Any]]: + """Extract request body schema from OpenAPI requestBody definition and resolve refs.""" + if not request_body: + return None + + content = request_body.get('content', {}) + json_content = content.get('application/json', {}) + schema = json_content.get('schema', {}) + + if schema: + # Resolve any $ref references in the schema + return resolve_schema_refs(schema, spec) + + return None + +def http_method_to_rust(method: str) -> str: + """Convert HTTP method string to Rust http::Method enum.""" + method_map = { + 'get': 'http::Method::GET', + 'post': 'http::Method::POST', + 'put': 'http::Method::PUT', + 'delete': 'http::Method::DELETE', + 'patch': 'http::Method::PATCH', + 'head': 'http::Method::HEAD', + 'options': 'http::Method::OPTIONS' + } + return method_map.get(method.lower(), f'http::Method::{method.upper()}') + +def schema_to_rust_value(schema: Optional[Dict[str, Any]]) -> str: + """Convert a schema dict to a Rust serde_json::json! expression.""" + if schema is None: + return "None" + return f"Some(serde_json::json!({json.dumps(schema, indent=8)}))" + +def find_mcp_tools(spec: Dict[str, Any]) -> List[Dict[str, Any]]: + """Find all endpoints marked with x-mcp-tool: true.""" + tools = [] + paths = spec.get('paths', {}) + + for path, path_item in paths.items(): + for method, operation in path_item.items(): + if isinstance(operation, dict) and operation.get('x-mcp-tool') is True: + # Extract tool information + tool = { + 'name': operation.get('operationId', f"{method}_{path.replace('/', '_').replace('{', '').replace('}', '')}"), + 'description': operation.get('summary', operation.get('description', f'{method.upper()} {path}')), + 'instructions': operation.get('x-mcp-instructions', ''), + 'path': path, + 'method': method.upper(), + 'parameters': operation.get('parameters', []), + 'requestBody': operation.get('requestBody'), + } + tools.append(tool) + + return tools + +def generate_rust_code(tools: List[Dict[str, Any]], spec: Dict[str, Any]) -> str: + """Generate the complete Rust code with MCP tools.""" + if not tools: + return """// No MCP tools found in the OpenAPI specification + +{IMPORTS} +{ENDPOINT_STRUCT} +pub fn all_tools() -> Vec { + vec![] +} +""" + + tool_definitions = [] + + for tool in tools: + tool_name = tool['name'] + description = tool['description'] + instructions = tool['instructions'] + path = tool['path'] + method = tool['method'].upper() + + # Generate separate schemas + path_params_schema, query_params_schema, body_schema = extract_separate_schemas( + tool['parameters'], tool['requestBody'], spec + ) + + path_params_rust = schema_to_rust_value(path_params_schema) + query_params_rust = schema_to_rust_value(query_params_schema) + body_schema_rust = schema_to_rust_value(body_schema) + + # Generate tool definition + tool_def = f""" EndpointTool {{ + name: Cow::Borrowed("{tool_name}"), + description: Cow::Borrowed("{description}"), + instructions: Cow::Borrowed("{instructions}"), + path: Cow::Borrowed("{path}"), + method: Cow::Borrowed("{method}"), + path_params_schema: {path_params_rust}, + query_params_schema: {query_params_rust}, + body_schema: {body_schema_rust}, + }}""" + tool_definitions.append(tool_def) + + # Combine everything + tool_definitions_str = ",\n".join(tool_definitions) + + rust_code = f"""// Auto-generated MCP tools from OpenAPI specification +// This file is generated by generate_mcp_tools.py - DO NOT EDIT MANUALLY + +{IMPORTS} +{ENDPOINT_STRUCT} +pub fn all_tools() -> Vec {{ + vec![ +{tool_definitions_str} + ] +}} +""" + + return rust_code + +def main(): + """Main function to parse OpenAPI and generate Rust code.""" + script_dir = Path(__file__).parent + backend_dir = script_dir.parent + openapi_file = backend_dir / "windmill-api" / "openapi.yaml" + output_file = backend_dir / "windmill-api" / "src" / "mcp" / "tools" / "auto_generated_endpoints.rs" + + if not openapi_file.exists(): + print(f"OpenAPI file not found: {openapi_file}", file=sys.stderr) + sys.exit(1) + + print(f"Loading OpenAPI specification from: {openapi_file}") + spec = load_openapi_spec(str(openapi_file)) + + print("Searching for endpoints with x-mcp-tool: true...") + tools = find_mcp_tools(spec) + + if tools: + print(f"Found {len(tools)} MCP tool(s):") + for tool in tools: + print(f" - {tool['name']}: {tool['method']} {tool['path']}") + else: + print("No MCP tools found (no endpoints with x-mcp-tool: true)") + + print(f"Generating Rust code...") + rust_code = generate_rust_code(tools, spec) + + print(f"Writing to: {output_file}") + output_file.parent.mkdir(parents=True, exist_ok=True) + with open(output_file, 'w', encoding='utf-8') as f: + f.write(rust_code) + + print("Done!") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/backend/generate_mcp_endpoints_tools/requirements.txt b/backend/generate_mcp_endpoints_tools/requirements.txt new file mode 100644 index 0000000000..043876c0b8 --- /dev/null +++ b/backend/generate_mcp_endpoints_tools/requirements.txt @@ -0,0 +1 @@ +PyYAML>=6.0 \ No newline at end of file diff --git a/backend/migrations/20250721160151_add_ruby.down.sql b/backend/migrations/20250721160151_add_ruby.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250721160151_add_ruby.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250721160151_add_ruby.up.sql b/backend/migrations/20250721160151_add_ruby.up.sql new file mode 100644 index 0000000000..3cf454718c --- /dev/null +++ b/backend/migrations/20250721160151_add_ruby.up.sql @@ -0,0 +1,3 @@ +-- Add up migration script here +ALTER TYPE SCRIPT_LANG ADD VALUE IF NOT EXISTS 'ruby'; +UPDATE config set config = jsonb_set(config, '{worker_tags}', config->'worker_tags' || '["ruby"]'::jsonb) where name = 'worker__default' and config @> '{"worker_tags": ["deno", "python3", "go", "bash", "powershell", "dependency", "flow", "hub", "other", "bun", "php", "rust", "ansible", "csharp", "nu", "java", "duckdb"]}'::jsonb AND NOT config->'worker_tags' @> '"ruby"'::jsonb; diff --git a/backend/migrations/20250723154518_add_mqtt_trigger_to_job_kind_trigger_enum_type.down.sql b/backend/migrations/20250723154518_add_mqtt_trigger_to_job_kind_trigger_enum_type.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20250723154518_add_mqtt_trigger_to_job_kind_trigger_enum_type.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20250723154518_add_mqtt_trigger_to_job_kind_trigger_enum_type.up.sql b/backend/migrations/20250723154518_add_mqtt_trigger_to_job_kind_trigger_enum_type.up.sql new file mode 100644 index 0000000000..d662e626bf --- /dev/null +++ b/backend/migrations/20250723154518_add_mqtt_trigger_to_job_kind_trigger_enum_type.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER TYPE job_trigger_kind ADD VALUE IF NOT EXISTS 'mqtt'; \ No newline at end of file diff --git a/backend/migrations/20250804155709_add_stream_result.down.sql b/backend/migrations/20250804155709_add_stream_result.down.sql new file mode 100644 index 0000000000..f8720d6bd6 --- /dev/null +++ b/backend/migrations/20250804155709_add_stream_result.down.sql @@ -0,0 +1,2 @@ +-- Add down migration script here +DROP TABLE job_result_stream; \ No newline at end of file diff --git a/backend/migrations/20250804155709_add_stream_result.up.sql b/backend/migrations/20250804155709_add_stream_result.up.sql new file mode 100644 index 0000000000..70e7ebff25 --- /dev/null +++ b/backend/migrations/20250804155709_add_stream_result.up.sql @@ -0,0 +1,11 @@ +-- Add up migration script here +CREATE TABLE job_result_stream ( + job_id UUID NOT NULL PRIMARY KEY, + workspace_id TEXT NOT NULL, + stream TEXT NOT NULL +); + +ALTER TABLE job_result_stream ADD CONSTRAINT fk_job_result_stream_job_id FOREIGN KEY (job_id) REFERENCES v2_job_queue(id) ON DELETE CASCADE; + +GRANT ALL ON TABLE job_result_stream TO windmill_admin; +GRANT ALL ON TABLE job_result_stream TO windmill_user; \ No newline at end of file diff --git a/backend/migrations/20250804221654_add_auto_acknowledge_msg_to_gcp.down.sql b/backend/migrations/20250804221654_add_auto_acknowledge_msg_to_gcp.down.sql new file mode 100644 index 0000000000..930dd826c6 --- /dev/null +++ b/backend/migrations/20250804221654_add_auto_acknowledge_msg_to_gcp.down.sql @@ -0,0 +1,2 @@ +-- Add down migration script here +ALTER table gcp_trigger DROP COLUMN auto_acknowledge_msg; diff --git a/backend/migrations/20250804221654_add_auto_acknowledge_msg_to_gcp.up.sql b/backend/migrations/20250804221654_add_auto_acknowledge_msg_to_gcp.up.sql new file mode 100644 index 0000000000..ceb08eec46 --- /dev/null +++ b/backend/migrations/20250804221654_add_auto_acknowledge_msg_to_gcp.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER table gcp_trigger ADD COLUMN auto_acknowledge_msg BOOLEAN DEFAULT true; \ No newline at end of file diff --git a/backend/migrations/20250808214101_instance_group_auto_add.down.sql b/backend/migrations/20250808214101_instance_group_auto_add.down.sql new file mode 100644 index 0000000000..4da259a6b1 --- /dev/null +++ b/backend/migrations/20250808214101_instance_group_auto_add.down.sql @@ -0,0 +1,4 @@ +-- Remove auto-add columns for instance groups +ALTER TABLE workspace_settings +DROP COLUMN auto_add_instance_groups, +DROP COLUMN auto_add_instance_groups_roles; diff --git a/backend/migrations/20250808214101_instance_group_auto_add.up.sql b/backend/migrations/20250808214101_instance_group_auto_add.up.sql new file mode 100644 index 0000000000..a97f00ca99 --- /dev/null +++ b/backend/migrations/20250808214101_instance_group_auto_add.up.sql @@ -0,0 +1,4 @@ +-- Add auto-add columns for instance groups +ALTER TABLE workspace_settings +ADD COLUMN auto_add_instance_groups text[] DEFAULT '{}', +ADD COLUMN auto_add_instance_groups_roles jsonb DEFAULT '{}'; diff --git a/backend/migrations/20250811171024_add_usr_added_via.down.sql b/backend/migrations/20250811171024_add_usr_added_via.down.sql new file mode 100644 index 0000000000..49cf62e51f --- /dev/null +++ b/backend/migrations/20250811171024_add_usr_added_via.down.sql @@ -0,0 +1,3 @@ +-- Remove added_via tracking +DROP INDEX idx_usr_added_via; +ALTER TABLE usr DROP COLUMN added_via; diff --git a/backend/migrations/20250811171024_add_usr_added_via.up.sql b/backend/migrations/20250811171024_add_usr_added_via.up.sql new file mode 100644 index 0000000000..6eb4741181 --- /dev/null +++ b/backend/migrations/20250811171024_add_usr_added_via.up.sql @@ -0,0 +1,8 @@ +-- Add added_via column to track how users were added to workspaces +-- NULL = manual addition +-- {"source": "domain", "domain": "company.com"} = domain auto-add +-- {"source": "instance_group", "group": "developers", "role": "developer"} = instance group auto-add +ALTER TABLE usr ADD COLUMN added_via jsonb DEFAULT NULL; + +-- Add index for efficient queries on added_via +CREATE INDEX idx_usr_added_via ON usr USING gin (added_via); diff --git a/backend/parsers/windmill-parser-py/src/lib.rs b/backend/parsers/windmill-parser-py/src/lib.rs index 52983872c6..89ebcc659e 100644 --- a/backend/parsers/windmill-parser-py/src/lib.rs +++ b/backend/parsers/windmill-parser-py/src/lib.rs @@ -128,7 +128,7 @@ pub fn parse_python_signature( && default.is_some() && default != Some(json!(FUNCTION_CALL)) { - typ = json_to_typ(default.as_ref().unwrap()); + typ = json_to_typ(default.as_ref().unwrap(), false); } // if the type is still a list of unknowns after checking the default, we set it to a list of strings to not break past behavior diff --git a/backend/parsers/windmill-parser-ruby/Cargo.toml b/backend/parsers/windmill-parser-ruby/Cargo.toml new file mode 100644 index 0000000000..779b0116a0 --- /dev/null +++ b/backend/parsers/windmill-parser-ruby/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "windmill-parser-ruby" +version.workspace = true +edition.workspace = true +authors.workspace = true + +[lib] +name = "windmill_parser_ruby" +path = "./src/lib.rs" + +[dependencies] +windmill-parser.workspace = true +tree-sitter.workspace = true +tree-sitter-ruby.workspace = true +anyhow.workspace = true +wasm-bindgen.workspace = true +serde_json.workspace = true +regex.workspace = true +lazy_static.workspace = true diff --git a/backend/parsers/windmill-parser-ruby/src/lib.rs b/backend/parsers/windmill-parser-ruby/src/lib.rs new file mode 100644 index 0000000000..c736cef86a --- /dev/null +++ b/backend/parsers/windmill-parser-ruby/src/lib.rs @@ -0,0 +1,410 @@ +#![cfg_attr(target_arch = "wasm32", feature(c_variadic))] + +#[cfg(target_arch = "wasm32")] +pub mod wasm_libc; + +use anyhow::anyhow; +use anyhow::bail; +use regex::Regex; +use serde_json::Value; +use tree_sitter::Node; +use tree_sitter::Range; +use windmill_parser::json_to_typ; +use windmill_parser::Arg; +use windmill_parser::MainArgSignature; + +pub fn parse_ruby_sig_meta(code: &str) -> anyhow::Result { + let mut parser = tree_sitter::Parser::new(); + let language = tree_sitter_ruby::LANGUAGE; + parser + .set_language(&language.into()) + .map_err(|e| anyhow!("Error setting Ruby as language: {e}"))?; + + // Parse code + let tree = parser + .parse(code, None) + .ok_or(anyhow!("Failed to parse code"))?; + let root_node = tree.root_node(); + + root_node.clone().to_string(); + // Traverse the AST to find the Main method signature + let args = find_main_signature(root_node, code)?; + let no_main_func = Some(args.is_none()); + + let main_sig = MainArgSignature { + star_args: false, + star_kwargs: false, + args: args.unwrap_or_default(), + has_preprocessor: None, + no_main_func, + }; + + Ok(main_sig) +} + +pub fn parse_ruby_signature(code: &str) -> anyhow::Result { + Ok(parse_ruby_sig_meta(code)?) +} + +pub fn parse_ruby_requirements(code: &str) -> anyhow::Result { + let mut parser = tree_sitter::Parser::new(); + let language = tree_sitter_ruby::LANGUAGE; + parser + .set_language(&language.into()) + .map_err(|e| anyhow!("Error setting Ruby as language: {e}"))?; + + // Parse code + let tree = parser + .parse(code, None) + .ok_or(anyhow!("Failed to parse code"))?; + let root_node = tree.root_node(); + + root_node.clone().to_string(); + let mut cursor = root_node.walk(); + 'top_level: for x in root_node.children(&mut cursor) { + if x.kind() == "call" { + for (i, n) in x.children(&mut x.walk()).enumerate() { + if i == 0 + && n.kind() == "identifier" + && !n + .utf8_text(code.as_bytes()) + .map(|ident| ident == "gemfile") + .unwrap_or_default() + { + continue 'top_level; + } else if n.kind() == "do_block" { + let req = n.utf8_text(code.as_bytes())?.to_owned(); + + lazy_static::lazy_static! { + static ref WINDMILL_RE: Regex = Regex::new(r"(?m)^\s*require\s*'windmill/inline'").unwrap(); + } + + if WINDMILL_RE.find(&code).is_none() { + return Err(anyhow!( + "`require 'windmill/inline'` is not detected - please add `require 'windmill/inline'` in order to use inline gemfile. + Your Gemfile syntax will be compatible with bundler/inline." + ) + .into()); + } + + return req + // gemfile do_block comes with 'do' and 'end' + // we want to omit these by taking slice + .get(2..(req.len() - 3)) + .map(str::to_owned) + .ok_or(anyhow!("Invalid gemfile do block")); + } + } + } + } + Ok(String::new()) +} + +// Function to find the Main method's signature +fn find_main_signature<'a>(root_node: Node<'a>, code: &str) -> anyhow::Result>> { + let mut cursor = root_node.walk(); + 'top_level: for x in root_node.children(&mut cursor) { + if x.kind() == "method" { + let mut args = vec![]; + for (i, m) in x.children(&mut x.walk()).skip(1).enumerate() { + if i == 0 + && m.kind() == "identifier" + && !m + .utf8_text(code.as_bytes()) + .map(|ident| ident == "main") + .unwrap_or_default() + { + continue 'top_level; + } else if m.kind() == "method_parameters" { + for a in m.children(&mut m.walk()) { + if a.kind() == "identifier" { + a.utf8_text(code.as_bytes()).inspect(|name| { + args.push(Arg { name: (*name).to_owned(), ..Default::default() }) + })?; + } + if a.kind() == "optional_parameter" { + let mut walk = a.walk(); + let mut it = a.children(&mut walk).into_iter(); + match (it.next().and_then(|n| n.utf8_text(code.as_bytes()).ok()), { + // Skip `=` + it.next(); + it.next().map(|n| n.range()) + }) { + (Some(ident), Some(Range { start_byte, end_byte, .. })) => { + let unparsed = + &code[start_byte..end_byte].replace("nil", "null"); + let default: Value = serde_json::from_str(unparsed).map_err( + |e| + anyhow!("Cannot convert default value to json\n\tvalue: {unparsed}\n\terror: {e}{}", + if e.to_string().contains("key must be a string") { + "\n\nNOTE: If you are trying to declare default hash, use following syntax:\n { \"\": }" + } else { + "" + } + ))?; + args.push(Arg { + name: ident.to_owned(), + typ: json_to_typ(&default, true), + default: Some(default), + has_default: true, + ..Default::default() + }); + } + _ => { + let Range { start_byte, end_byte, .. } = a.range(); + bail!( + "Cannot parse optional parameter: {}", + &code.get(start_byte..end_byte).unwrap_or("CANNOT DISPLAY") + ) + } + } + } + let kind = a.kind(); + if matches!( + kind, + "keyword_parameter" | "splat_parameter" | "hash_splat_parameter" + ) { + let Range { start_byte, end_byte, .. } = a.range(); + bail!( + " - {}\n{}s are not supported", + &code.get(start_byte..end_byte).unwrap_or("CANNOT DISPLAY"), + kind.replace("_", " ") + ); + } + } + } + } + return Ok(Some(args)); + } + } + Ok(None) +} + +#[cfg(test)] +mod test { + + use serde_json::json; + use windmill_parser::{ObjectProperty, ObjectType, Typ}; + + use super::parse_ruby_sig_meta as parse; + use super::*; + #[test] + fn test_parse_ruby_no_main() { + let code = r#" +def not_main end +def private_fn end +"#; + let sig = parse(code).unwrap(); + + assert_eq!( + sig, + MainArgSignature { no_main_func: Some(true), ..Default::default() } + ); + } + #[test] + fn test_parse_ruby_no_args() { + let code = r#" +def main +end +"#; + let sig = parse(code).unwrap(); + + assert_eq!( + sig, + MainArgSignature { no_main_func: Some(false), ..Default::default() } + ); + } + #[test] + fn test_parse_ruby_positional_args() { + let sig = { + let code = r#"def main(a, b, c) end"#; + parse(code).unwrap() + }; + let sig2 = { + let code = r#" + def main a, b, c + end"#; + parse(code).unwrap() + }; + + assert_eq!( + sig, + MainArgSignature { + args: vec![ + Arg { name: "a".into(), ..Default::default() }, + Arg { name: "b".into(), ..Default::default() }, + Arg { name: "c".into(), ..Default::default() } + ], + no_main_func: Some(false), + ..Default::default() + } + ); + assert_eq!(sig2, sig); + } + #[test] + fn test_parse_ruby_lists() { + let sig = { + let code = r#"def main(a = [ 1, 2, 3 ], b = [ 1, false, nil, "test"]) end"#; + parse(code).unwrap() + }; + assert_eq!( + sig, + MainArgSignature { + args: vec![ + Arg { + name: "a".into(), + has_default: true, + typ: Typ::List(Box::new(Typ::Int)), + default: Some(json!([1, 2, 3])), + ..Default::default() + }, + Arg { + name: "b".into(), + has_default: true, + typ: Typ::List(Box::new(Typ::Unknown)), + default: Some(json!([1, false, null, "test"])), + ..Default::default() + }, + ], + no_main_func: Some(false), + ..Default::default() + } + ); + } + #[test] + fn test_parse_ruby_hashes() { + let sig = { + let code = r#"def main(a = { "1": 4, "2": [ 1, 2, 3] }) end"#; + parse(code).unwrap() + }; + assert_eq!( + sig, + MainArgSignature { + args: vec![Arg { + name: "a".into(), + has_default: true, + typ: Typ::Object(ObjectType::new( + None, + Some(vec![ + ObjectProperty { key: "1".into(), typ: Box::new(Typ::Int) }, + ObjectProperty { + key: "2".into(), + typ: Box::new(Typ::List(Box::new(Typ::Int))) + } + ],) + )), + default: Some(json!({"1": 4, "2": [ 1, 2, 3 ]})), + ..Default::default() + },], + no_main_func: Some(false), + ..Default::default() + } + ); + } + #[test] + fn test_parse_ruby_default_args() { + let sig = { + let code = + r#"def main(a = 10, b = "hey", c = false, d = [ 1, 2, 3 ], e = { "a": 43 }) end"#; + parse(code).unwrap() + }; + + assert_eq!( + sig, + MainArgSignature { + args: vec![ + Arg { + name: "a".into(), + has_default: true, + typ: Typ::Int, + default: Some(json!(10)), + ..Default::default() + }, + Arg { + name: "b".into(), + has_default: true, + typ: Typ::Str(None), + default: Some(json!("hey")), + ..Default::default() + }, + Arg { + name: "c".into(), + has_default: true, + typ: Typ::Bool, + default: Some(json!(false)), + ..Default::default() + }, + Arg { + name: "d".into(), + has_default: true, + typ: Typ::List(Box::new(Typ::Int)), + default: Some(json!([1, 2, 3])), + ..Default::default() + }, + Arg { + name: "e".into(), + has_default: true, + typ: Typ::Object(ObjectType::new( + None, + Some(vec![ObjectProperty { + key: "a".into(), + typ: Box::new(Typ::Int) + }]) + )), + default: Some(json!({"a": 43})), + ..Default::default() + }, + ], + no_main_func: Some(false), + ..Default::default() + } + ); + } + + #[test] + fn test_parse_ruby_unsupported() { + assert_eq!( + &parse("def main( a: ) end").unwrap_err().to_string(), + " - a:\nkeyword parameters are not supported" + ); + + assert_eq!( + &parse("def main( *a ) end").unwrap_err().to_string(), + " - *a\nsplat parameters are not supported" + ); + + assert_eq!( + &parse("def main( **a ) end").unwrap_err().to_string(), + " - **a\nhash splat parameters are not supported" + ); + + assert!(&parse("def main( a = Time.now ) end").is_err()); + assert!(&parse("def main( a = :symbol ) end").is_err()); + assert!(&parse("def main( a = { b: 2 } ) end").is_err()); + assert!(&parse("def main( a = { b => 2 } ) end").is_err()); + assert!(&parse(r#"def main( a = { "b" => 2 } ) end"#).is_err()); + } + + // #[test] + // fn test_parse_ruby_requirements() { + // assert_eq!( + // parse_ruby_requirements( + // " + // require 'dep1' + // require 'dep2' + // require 'dep2/submod' + // require 'u/username/script' + // require 'f/folder/script' + // " + // ) + // .unwrap(), + // vec![ + // "dep1".to_owned(), + // "dep2".into(), + // "dep2".into(), + // "u/username/script".into(), + // "f/folder/script".into(), + // ] + // ); + // } +} diff --git a/backend/parsers/windmill-parser-ruby/src/wasm_libc.rs b/backend/parsers/windmill-parser-ruby/src/wasm_libc.rs new file mode 100644 index 0000000000..ddb6705023 --- /dev/null +++ b/backend/parsers/windmill-parser-ruby/src/wasm_libc.rs @@ -0,0 +1,297 @@ +use std::collections::BTreeMap; +use std::sync::{Mutex, OnceLock}; +use std::{ + alloc::{self, Layout}, + ffi::{c_char, c_int, c_void}, + mem::align_of, + ptr, +}; +use wasm_bindgen::prelude::*; + +/* -------------------------------- stdlib.h -------------------------------- */ + +#[no_mangle] +pub unsafe extern "C" fn abort() { + panic!("Aborted from C"); +} + +macro_rules! console_log { + ($($t:tt)*) => (unsafe { log(&format_args!($($t)*).to_string()) }) +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = console)] + fn log(a: &str); +} + +#[no_mangle] +pub unsafe extern "C" fn malloc(size: usize) -> *mut c_void { + if size == 0 { + return ptr::null_mut(); + } + + let (layout, offset_to_data) = layout_for_size_prepended(size); + let buf = alloc::alloc(layout); + store_layout(buf, layout, offset_to_data) +} + +#[no_mangle] +pub unsafe extern "C" fn calloc(count: usize, size: usize) -> *mut c_void { + if count == 0 || size == 0 { + return ptr::null_mut(); + } + + let (layout, offset_to_data) = layout_for_size_prepended(size * count); + let buf = alloc::alloc_zeroed(layout); + store_layout(buf, layout, offset_to_data) +} + +#[no_mangle] +pub unsafe extern "C" fn realloc(buf: *mut c_void, new_size: usize) -> *mut c_void { + if buf.is_null() { + malloc(new_size) + } else if new_size == 0 { + free(buf); + ptr::null_mut() + } else { + let (old_buf, old_layout) = retrieve_layout(buf); + let (new_layout, offset_to_data) = layout_for_size_prepended(new_size); + let new_buf = alloc::realloc(old_buf, old_layout, new_layout.size()); + store_layout(new_buf, new_layout, offset_to_data) + } +} + +#[no_mangle] +pub unsafe extern "C" fn free(buf: *mut c_void) { + if buf.is_null() { + return; + } + let (buf, layout) = retrieve_layout(buf); + alloc::dealloc(buf, layout); +} + +// In all these allocations, we store the layout before the data for later retrieval. +// This is because we need to know the layout when deallocating the memory. +// Here are some helper methods for that: + +/// Given a pointer to the data, retrieve the layout and the pointer to the layout. +unsafe fn retrieve_layout(buf: *mut c_void) -> (*mut u8, Layout) { + let (_, layout_offset) = Layout::new::() + .extend(Layout::from_size_align(0, align_of::<*const u8>() * 2).unwrap()) + .unwrap(); + + let buf = (buf as *mut u8).offset(-(layout_offset as isize)); + let layout = *(buf as *mut Layout); + + (buf, layout) +} + +/// Calculate a layout for a given size with space for storing a layout at the start. +/// Returns the layout and the offset to the data. +fn layout_for_size_prepended(size: usize) -> (Layout, usize) { + Layout::new::() + .extend(Layout::from_size_align(size, align_of::<*const u8>() * 2).unwrap()) + .unwrap() +} + +/// Store a layout in the pointer, returning a pointer to where the data should be stored. +unsafe fn store_layout(buf: *mut u8, layout: Layout, offset_to_data: usize) -> *mut c_void { + *(buf as *mut Layout) = layout; + (buf as *mut u8).offset(offset_to_data as isize) as *mut c_void +} + +/* -------------------------------- string.h -------------------------------- */ + +#[no_mangle] +pub unsafe extern "C" fn strncmp(ptr1: *const c_void, ptr2: *const c_void, n: usize) -> c_int { + let s1 = std::slice::from_raw_parts(ptr1 as *const u8, n); + let s2 = std::slice::from_raw_parts(ptr2 as *const u8, n); + + for (a, b) in s1.iter().zip(s2.iter()) { + if *a != *b || *a == 0 { + return (*a as i32) - (*b as i32); + } + } + + 0 +} + +// Implementation by AI: +pub type size_t = usize; +use std::slice; +#[no_mangle] +pub unsafe extern "C" fn memchr( + haystack: *const c_void, + needle: c_int, + len: usize, +) -> *mut c_void { + if haystack.is_null() || len == 0 { + return ptr::null_mut(); // Return null if the input pointer is null or length is zero + } + + let needle_byte = needle as u8; // Convert needle to a byte + + // Create a pointer to the start of the haystack + let mut current = haystack as *const u8; + + // Iterate through the memory block + for _ in 0..len { + if *current == needle_byte { + return current as *mut c_void; // Return the pointer to the found byte + } + current = current.add(1); // Move to the next byte + } + + ptr::null_mut() // Return null if the byte was not found +} + +#[no_mangle] +pub unsafe extern "C" fn strchr(mut s: *const c_char, c: c_int) -> *mut c_char { + if s.is_null() { + return std::ptr::null_mut(); // Return null if the input string is null + } + + let target = c as u8 as char; // Convert c to a char + let mut current = s; + + // Iterate through the string until we find the character or reach the end + while *current != 0 { + if *current as u8 as char == target { + return current as *mut c_char; // Return the pointer to the found character + } + current = current.add(1); // Move to the next character + } + + std::ptr::null_mut() // Return null if the character was not found +} +// End of AI implemetation +/* -------------------------------- wctype.h -------------------------------- */ + +#[no_mangle] +pub unsafe extern "C" fn iswspace(c: c_int) -> bool { + char::from_u32(c as u32).map_or(false, |c| c.is_whitespace()) +} + +#[no_mangle] +pub unsafe extern "C" fn iswalnum(c: c_int) -> bool { + char::from_u32(c as u32).map_or(false, |c| c.is_alphanumeric()) +} + +// Implementation by AI: +pub type wint_t = u32; + +#[no_mangle] +pub extern "C" fn iswdigit(wc: wint_t) -> c_int { + // Check if the character is a digit ('0' to '9') + if wc >= '0' as wint_t && wc <= '9' as wint_t { + return 1; // Return true (1) + } + 0 // Return false (0) +} + +#[no_mangle] +pub extern "C" fn iswupper(wc: wint_t) -> c_int { + // Check if the character is an uppercase letter ('A' to 'Z') + if wc >= 'A' as wint_t && wc <= 'Z' as wint_t { + return 1; // Return true (1) + } + 0 // Return false (0) +} + +#[no_mangle] +pub extern "C" fn iswalpha(wc: wint_t) -> c_int { + // Check if the character is an alphabetic character ('A' to 'Z' or 'a' to 'z') + if (wc >= 'A' as wint_t && wc <= 'Z' as wint_t) || (wc >= 'a' as wint_t && wc <= 'z' as wint_t) { + return 1; // Return true (1) + } + 0 // Return false (0) +} + +#[no_mangle] +pub extern "C" fn iswlower(wc: wint_t) -> c_int { + // Check if the character is a lowercase letter ('a' to 'z') + if wc >= 'a' as wint_t && wc <= 'z' as wint_t { + return 1; // Return true (1) + } + 0 // Return false (0) +} +// End of AI implemetation + + +/* --------------------------------- time.h --------------------------------- */ + +#[no_mangle] +pub unsafe extern "C" fn clock() -> u64 { + panic!("clock is not supported"); +} + +/* --------------------------------- ctype.h -------------------------------- */ + +#[no_mangle] +pub unsafe extern "C" fn isprint(c: c_int) -> bool { + c >= 32 && c <= 126 +} + +/* --------------------------------- stdio.h -------------------------------- */ + +#[no_mangle] +pub unsafe extern "C" fn fprintf(_file: *mut c_void, _format: *const c_void, _args: ...) -> c_int { + panic!("fprintf is not supported"); +} + +#[no_mangle] +pub unsafe extern "C" fn fputs(_s: *const c_void, _file: *mut c_void) -> c_int { + panic!("fputs is not supported"); +} + +#[no_mangle] +pub unsafe extern "C" fn fputc(_c: c_int, _file: *mut c_void) -> c_int { + panic!("fputc is not supported"); +} + +#[no_mangle] +pub unsafe extern "C" fn fdopen(_fd: c_int, _mode: *const c_void) -> *mut c_void { + panic!("fdopen is not supported"); +} + +#[no_mangle] +pub unsafe extern "C" fn fclose(_file: *mut c_void) -> c_int { + panic!("fclose is not supported"); +} + +#[no_mangle] +pub unsafe extern "C" fn fwrite( + _ptr: *const c_void, + _size: usize, + _nmemb: usize, + _stream: *mut c_void, +) -> usize { + panic!("fwrite is not supported"); +} + +#[no_mangle] +pub unsafe extern "C" fn vsnprintf( + _buf: *mut c_char, + _size: usize, + _format: *const c_char, + _args: ... +) -> c_int { + panic!("vsnprintf is not supported"); +} + +#[no_mangle] +pub extern "C" fn clock_gettime(ptr: usize, new_size: usize) { + panic!("clock_gettime is not supported"); +} + +// int snprintf( char* restrict buffer, size_t bufsz, const char* restrict format, ... ); +#[no_mangle] +pub extern "C" fn snprintf() { + panic!("snprintf is not supported"); +} + +#[no_mangle] +pub extern "C" fn __assert_fail(_: *const i32, _: *const i32, _: *const i32, _: *const i32) { + panic!("oh no"); +} diff --git a/backend/parsers/windmill-parser-ts/src/lib.rs b/backend/parsers/windmill-parser-ts/src/lib.rs index 055dce6a81..7d998dbac7 100644 --- a/backend/parsers/windmill-parser-ts/src/lib.rs +++ b/backend/parsers/windmill-parser-ts/src/lib.rs @@ -388,7 +388,7 @@ fn parse_param( }; if typ == Typ::Unknown && dflt.is_some() { - typ = json_to_typ(dflt.as_ref().unwrap()); + typ = json_to_typ(dflt.as_ref().unwrap(), false); } Ok(Arg { otyp: None, name, typ, default: dflt, has_default: true, oidx: None }) } diff --git a/backend/parsers/windmill-parser-wasm/Cargo.toml b/backend/parsers/windmill-parser-wasm/Cargo.toml index 5b1c884143..0d60735a3a 100644 --- a/backend/parsers/windmill-parser-wasm/Cargo.toml +++ b/backend/parsers/windmill-parser-wasm/Cargo.toml @@ -29,6 +29,7 @@ ansible-parser = [ "dep:windmill-parser-yaml"] csharp-parser = [ "dep:windmill-parser-csharp"] nu-parser = [ "dep:windmill-parser-nu"] java-parser = [ "dep:windmill-parser-java"] +ruby-parser = [ "dep:windmill-parser-ruby"] [dependencies] anyhow.workspace = true @@ -45,6 +46,7 @@ windmill-parser-yaml = { workspace = true, optional = true } windmill-parser-csharp = { workspace = true, optional = true } windmill-parser-nu = { workspace = true, optional = true } windmill-parser-java = { workspace = true, optional = true } +windmill-parser-ruby = { workspace = true, optional = true } wasm-bindgen.workspace = true serde_json.workspace = true getrandom = { workspace = true, features = ["js"] } diff --git a/backend/parsers/windmill-parser-wasm/build.nu b/backend/parsers/windmill-parser-wasm/build.nu index 60fdead893..1ab87f9873 100755 --- a/backend/parsers/windmill-parser-wasm/build.nu +++ b/backend/parsers/windmill-parser-wasm/build.nu @@ -50,11 +50,11 @@ const targets = [ desc: "Java", features: "java-parser", env: "tree-sitter", - # }, { - # ident: "ruby", - # desc: "Ruby", - # features: "ruby-parser", - # env: "tree-sitter", + }, { + ident: "ruby", + desc: "Ruby", + features: "ruby-parser", + env: "tree-sitter", }, # ^^^ Add new entry here ^^^ ]; @@ -130,3 +130,4 @@ def main [ } } } + diff --git a/backend/parsers/windmill-parser-wasm/src/lib.rs b/backend/parsers/windmill-parser-wasm/src/lib.rs index e02e280abb..238b207dd2 100644 --- a/backend/parsers/windmill-parser-wasm/src/lib.rs +++ b/backend/parsers/windmill-parser-wasm/src/lib.rs @@ -168,6 +168,11 @@ pub fn parse_java(code: &str) -> String { wrap_sig(windmill_parser_java::parse_java_signature(code)) } +#[cfg(feature = "ruby-parser")] +#[wasm_bindgen] +pub fn parse_ruby(code: &str) -> String { + wrap_sig(windmill_parser_ruby::parse_ruby_signature(code)) +} #[cfg(feature = "sql-parser")] #[wasm_bindgen] pub fn parse_assets_sql(code: &str) -> String { diff --git a/backend/parsers/windmill-parser-wasm/wasm-sysroot/string.h b/backend/parsers/windmill-parser-wasm/wasm-sysroot/string.h index c0687e9c88..30fce92495 100644 --- a/backend/parsers/windmill-parser-wasm/wasm-sysroot/string.h +++ b/backend/parsers/windmill-parser-wasm/wasm-sysroot/string.h @@ -5,3 +5,7 @@ void *memmove(void *dest, const void *src, unsigned long n); void *memset(void *s, int c, unsigned long n); int memcmp(const void *ptr1, const void *ptr2, unsigned long n); int strncmp(const char *s1, const char *s2, unsigned long n); + +// Manually added (needed for Ruby) +void *memchr (void *s, int c, size_t n); +char *strchr (char *s, int c); diff --git a/backend/parsers/windmill-parser-wasm/wasm-sysroot/wctype.h b/backend/parsers/windmill-parser-wasm/wasm-sysroot/wctype.h index 517a954be5..ab0d3fd5ce 100644 --- a/backend/parsers/windmill-parser-wasm/wasm-sysroot/wctype.h +++ b/backend/parsers/windmill-parser-wasm/wasm-sysroot/wctype.h @@ -5,3 +5,9 @@ typedef __WINT_TYPE__ wint_t; int iswspace(wchar_t ch); int iswalnum(wint_t _wc); + +// Manually added (needed for Ruby) +int iswdigit(wint_t _wc); +int iswupper(wint_t _wc); +int iswalpha(wint_t _wc); +int iswlower(wint_t _wc); diff --git a/backend/parsers/windmill-parser/src/lib.rs b/backend/parsers/windmill-parser/src/lib.rs index 493222c3c3..565be52cd4 100644 --- a/backend/parsers/windmill-parser/src/lib.rs +++ b/backend/parsers/windmill-parser/src/lib.rs @@ -6,6 +6,8 @@ * LICENSE-AGPL for a copy of the license. */ +use std::mem::discriminant; + use convert_case::{Boundary, Case, Casing}; use serde::Serialize; use serde_json::Value; @@ -53,7 +55,7 @@ pub struct OneOfVariant { pub properties: Vec, } -#[derive(Serialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Clone, Debug, PartialEq, Default)] #[serde(rename_all(serialize = "lowercase"))] pub enum Typ { Str(Option>), @@ -69,10 +71,11 @@ pub enum Typ { DynSelect(String), Object(ObjectType), OneOf(Vec), + #[default] Unknown, } -#[derive(Serialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Clone, Debug, PartialEq, Default)] pub struct Arg { pub name: String, pub otyp: Option, @@ -82,7 +85,7 @@ pub struct Arg { pub oidx: Option, } -pub fn json_to_typ(js: &Value) -> Typ { +pub fn json_to_typ(js: &Value, precise_arrays: bool) -> Typ { match js { Value::String(_) => Typ::Str(None), Value::Number(n) if n.is_i64() => Typ::Int, @@ -92,11 +95,28 @@ pub fn json_to_typ(js: &Value) -> Typ { None, Some( o.iter() - .map(|(k, v)| ObjectProperty { key: k.to_string(), typ: Box::new(json_to_typ(v)) }) + .map(|(k, v)| ObjectProperty { + key: k.to_string(), + typ: Box::new(json_to_typ(v, precise_arrays)), + }) .collect(), ), )), - Value::Array(a) => Typ::List(Box::new(a.first().map(json_to_typ).unwrap_or(Typ::Unknown))), + Value::Array(a) => Typ::List(Box::new({ + // Check if all variant types are the same + if !precise_arrays + || a.windows(2).all(|pair| { + let (l, r) = (&pair[0], &pair[1]); + discriminant(l) == discriminant(r) + }) + { + a.first() + .map(|js| json_to_typ(js, precise_arrays)) + .unwrap_or(Typ::Unknown) + } else { + Typ::Unknown + } + })), _ => Typ::Unknown, } } diff --git a/backend/res.json b/backend/res.json deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/backend/src/main.rs b/backend/src/main.rs index c0c3123b97..9b80e58eef 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -10,7 +10,7 @@ use monitor::{ load_base_url, load_otel, reload_critical_alerts_on_db_oversize, reload_delete_logs_periodically_setting, reload_indexer_config, reload_instance_python_version_setting, reload_maven_repos_setting, - reload_no_default_maven_setting, reload_nuget_config_setting, + reload_no_default_maven_setting, reload_nuget_config_setting, reload_ruby_repos_setting, reload_timeout_wait_result_setting, send_current_log_file_to_object_store, send_logs_to_object_store, WORKERS_NAMES, }; @@ -36,18 +36,18 @@ use windmill_common::{ agent_workers::build_agent_http_client, get_database_url, global_settings::{ - BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING, - CRITICAL_ALERT_MUTE_UI_SETTING, CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, - DEFAULT_TAGS_PER_WORKSPACE_SETTING, DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING, - ENV_SETTINGS, EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, - EXTRA_PIP_INDEX_URL_SETTING, HUB_BASE_URL_SETTING, INDEXER_SETTING, - INSTANCE_PYTHON_VERSION_SETTING, JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, - KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING, - MONITOR_LOGS_ON_OBJECT_STORE_SETTING, NO_DEFAULT_MAVEN_SETTING, - NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_SETTING, - PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, + APP_WORKSPACED_ROUTE_SETTING, BASE_URL_SETTING, BUNFIG_INSTALL_SCOPES_SETTING, + CRITICAL_ALERTS_ON_DB_OVERSIZE_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING, + CRITICAL_ERROR_CHANNELS_SETTING, CUSTOM_TAGS_SETTING, DEFAULT_TAGS_PER_WORKSPACE_SETTING, + DEFAULT_TAGS_WORKSPACES_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS, + EXPOSE_DEBUG_METRICS_SETTING, EXPOSE_METRICS_SETTING, EXTRA_PIP_INDEX_URL_SETTING, + HUB_BASE_URL_SETTING, INDEXER_SETTING, INSTANCE_PYTHON_VERSION_SETTING, + JOB_DEFAULT_TIMEOUT_SECS_SETTING, JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, + LICENSE_KEY_SETTING, MAVEN_REPOS_SETTING, MONITOR_LOGS_ON_OBJECT_STORE_SETTING, + NO_DEFAULT_MAVEN_SETTING, NPM_CONFIG_REGISTRY_SETTING, NUGET_CONFIG_SETTING, OAUTH_SETTING, + OTEL_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING, REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING, - SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TEAMS_SETTING, + RUBY_REPOS_SETTING, SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TEAMS_SETTING, TIMEOUT_WAIT_RESULT_SETTING, }, scripts::ScriptLang, @@ -83,17 +83,18 @@ use windmill_worker::{ get_hub_script_content_and_requirements, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, CSHARP_CACHE_DIR, DENO_CACHE_DIR, DENO_CACHE_DIR_DEPS, DENO_CACHE_DIR_NPM, GO_BIN_CACHE_DIR, GO_CACHE_DIR, JAVA_CACHE_DIR, NU_CACHE_DIR, POWERSHELL_CACHE_DIR, PY310_CACHE_DIR, PY311_CACHE_DIR, - PY312_CACHE_DIR, PY313_CACHE_DIR, RUST_CACHE_DIR, TAR_JAVA_CACHE_DIR, UV_CACHE_DIR, + PY312_CACHE_DIR, PY313_CACHE_DIR, RUBY_CACHE_DIR, RUST_CACHE_DIR, TAR_JAVA_CACHE_DIR, + UV_CACHE_DIR, }; use crate::monitor::{ initial_load, load_keep_job_dir, load_metrics_debug_enabled, load_require_preexisting_user, load_tag_per_workspace_enabled, load_tag_per_workspace_workspaces, monitor_db, - reload_base_url_setting, reload_bunfig_install_scopes_setting, - reload_critical_alert_mute_ui_setting, reload_critical_error_channels_setting, - reload_extra_pip_index_url_setting, reload_hub_base_url_setting, - reload_job_default_timeout_setting, reload_jwt_secret_setting, reload_license_key, - reload_npm_config_registry_setting, reload_pip_index_url_setting, + reload_app_workspaced_route_setting, reload_base_url_setting, + reload_bunfig_install_scopes_setting, reload_critical_alert_mute_ui_setting, + reload_critical_error_channels_setting, reload_extra_pip_index_url_setting, + reload_hub_base_url_setting, reload_job_default_timeout_setting, reload_jwt_secret_setting, + reload_license_key, reload_npm_config_registry_setting, reload_pip_index_url_setting, reload_retention_period_setting, reload_scim_token_setting, reload_smtp_config, reload_worker_config, MonitorIteration, }; @@ -1014,6 +1015,9 @@ Windmill Community Edition {GIT_VERSION} NO_DEFAULT_MAVEN_SETTING => { reload_no_default_maven_setting(&conn).await }, + RUBY_REPOS_SETTING => { + reload_ruby_repos_setting(&conn).await + }, KEEP_JOB_DIR_SETTING => { load_keep_job_dir(&conn).await; }, @@ -1034,6 +1038,11 @@ Windmill Community Edition {GIT_VERSION} if let Err(e) = load_metrics_debug_enabled(&conn).await { tracing::error!(error = %e, "Could not reload debug metrics setting"); } + }, + APP_WORKSPACED_ROUTE_SETTING => { + if let Err(e) = reload_app_workspaced_route_setting(&db).await { + tracing::error!(error = %e, "Could not reload app workspaced route setting"); + } }, OTEL_SETTING => { tracing::info!("OTEL setting changed, restarting"); @@ -1363,6 +1372,7 @@ pub async fn run_workers( HUB_CACHE_DIR, POWERSHELL_CACHE_DIR, JAVA_CACHE_DIR, + RUBY_CACHE_DIR, TAR_JAVA_CACHE_DIR, // for related places search: ADD_NEW_LANG ] { DirBuilder::new() diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 4492afe692..31f376d4b6 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -11,13 +11,14 @@ use std::{ time::Duration, }; -use chrono::{NaiveDateTime, Utc}; +use chrono::{DateTime, NaiveDateTime, Utc}; use futures::{stream::FuturesUnordered, StreamExt}; use serde::{de::DeserializeOwned, Deserialize}; use sqlx::{Pool, Postgres}; use tokio::{ join, sync::{mpsc, RwLock}, + time::timeout, }; use uuid::Uuid; @@ -33,13 +34,13 @@ use windmill_common::ee_oss::low_disk_alerts; #[cfg(feature = "enterprise")] use windmill_common::ee_oss::{jobs_waiting_alerts, worker_groups_alerts}; -use windmill_common::client::AuthedClient; #[cfg(feature = "oauth2")] use windmill_common::global_settings::OAUTH_SETTING; #[cfg(feature = "parquet")] use windmill_common::s3_helpers::reload_object_store_setting; use windmill_common::{ agent_workers::DECODED_AGENT_TOKEN, + apps::APP_WORKSPACED_ROUTE, auth::create_token_for_owner, ee_oss::CriticalErrorChannel, error, @@ -77,6 +78,7 @@ use windmill_common::{ METRICS_DEBUG_ENABLED, METRICS_ENABLED, MONITOR_LOGS_ON_OBJECT_STORE, OTEL_LOGS_ENABLED, OTEL_METRICS_ENABLED, OTEL_TRACING_ENABLED, SERVICE_LOG_RETENTION_SECS, }; +use windmill_common::{client::AuthedClient, global_settings::APP_WORKSPACED_ROUTE_SETTING}; use windmill_queue::{cancel_job, MiniPulledJob, SameWorkerPayload}; use windmill_worker::{ handle_job_error, JobCompletedSender, SameWorkerSender, BUNFIG_INSTALL_SCOPES, @@ -140,6 +142,9 @@ lazy_static::lazy_static! { static ref QUEUE_COUNT_TAGS: Arc>> = Arc::new(RwLock::new(Vec::new())); static ref DISABLE_CONCURRENCY_LIMIT: bool = std::env::var("DISABLE_CONCURRENCY_LIMIT").is_ok_and(|s| s == "true"); + static ref STALE_JOB_TRESHOLD_MINUTES: Option = std::env::var("STALE_JOB_TRESHOLD_MINUTES") + .ok() + .and_then(|x| x.parse::().ok()); } pub async fn initial_load( @@ -236,6 +241,10 @@ pub async fn initial_load( if let Err(e) = reload_custom_tags_setting(db).await { tracing::error!("Error reloading custom tags: {:?}", e) } + + if let Err(e) = reload_app_workspaced_route_setting(db).await { + tracing::error!("Error reloading app workspaced route: {:?}", e) + } } #[cfg(feature = "parquet")] @@ -282,6 +291,7 @@ pub async fn initial_load( reload_nuget_config_setting(&conn).await; reload_maven_repos_setting(&conn).await; reload_no_default_maven_setting(&conn).await; + reload_ruby_repos_setting(&conn).await; } } @@ -676,20 +686,25 @@ async fn send_log_file_to_object_store( let (ok_lines, err_lines) = read_log_counters(ts_str); if let Some(db) = conn.as_sql() { - if let Err(e) = sqlx::query!("INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) + match timeout(Duration::from_secs(10), sqlx::query!("INSERT INTO log_file (hostname, mode, worker_group, log_ts, file_path, ok_lines, err_lines, json_fmt) VALUES ($1, $2::text::LOG_MODE, $3, $4, $5, $6, $7, $8) ON CONFLICT (hostname, log_ts) DO UPDATE SET ok_lines = log_file.ok_lines + $6, err_lines = log_file.err_lines + $7", hostname, mode.to_string(), worker_group.clone(), ts, highest_file, ok_lines as i64, err_lines as i64, *JSON_FMT) - .execute(db) - .await { - tracing::error!("Error inserting log file: {:?}", e); - } else { - if let Err(e) = LAST_LOG_FILE_SENT.lock().map(|mut last_log_file_sent| { - last_log_file_sent.replace(ts); - }) { - tracing::error!("Error updating last log file sent: {:?}", e); + .execute(db)).await { + Ok(Ok(_)) => { + if let Err(e) = LAST_LOG_FILE_SENT.lock().map(|mut last_log_file_sent| { + last_log_file_sent.replace(ts); + }) { + tracing::error!("Error updating last log file sent: {:?}", e); + } + tracing::info!("Log file sent: {}", highest_file); + } + Ok(Err(e)) => { + tracing::error!("Error inserting log file: {:?}", e); + } + Err(e) => { + tracing::error!("Error inserting log file, timeout elapsed: {:?}", e); } - tracing::info!("Log file sent: {}", highest_file); } } else { // tracing::warn!("Not sending log file to object store in agent mode"); @@ -1009,6 +1024,7 @@ pub async fn reload_timeout_wait_result_setting(conn: &Connection) { ) .await; } + pub async fn reload_saml_metadata_setting(conn: &Connection) { reload_option_setting_with_tracing( conn, @@ -1078,6 +1094,7 @@ pub async fn reload_nuget_config_setting(conn: &Connection) { ) .await; } + pub async fn reload_maven_repos_setting(conn: &Connection) { reload_option_setting_with_tracing( conn, @@ -1087,6 +1104,7 @@ pub async fn reload_maven_repos_setting(conn: &Connection) { ) .await; } + pub async fn reload_no_default_maven_setting(conn: &Connection) { let value = load_value_from_global_settings_with_conn( conn, @@ -1103,6 +1121,16 @@ pub async fn reload_no_default_maven_setting(conn: &Connection) { }; } +pub async fn reload_ruby_repos_setting(conn: &Connection) { + reload_url_list_setting_with_tracing( + conn, + windmill_common::global_settings::RUBY_REPOS_SETTING, + "RUBY_REPOS", + windmill_worker::RUBY_REPOS.clone(), + ) + .await; +} + pub async fn reload_retention_period_setting(conn: &Connection) { if let Err(e) = reload_setting( conn, @@ -1272,6 +1300,89 @@ pub async fn reload_option_setting( Ok(()) } +pub async fn reload_url_list_setting_with_tracing( + conn: &Connection, + setting_name: &str, + std_env_var: &str, + lock: Arc>>>, +) { + if let Err(e) = reload_url_list_setting(conn, setting_name, std_env_var, lock.clone()).await { + tracing::error!("Error reloading setting {}: {:?}", setting_name, e) + } +} + +pub async fn reload_url_list_setting( + conn: &Connection, + setting_name: &str, + std_env_var: &str, + lock: Arc>>>, +) -> error::Result<()> { + // Check for force environment variable + if let Ok(force_value) = std::env::var(format!("FORCE_{}", std_env_var)) { + let mut urls = Vec::new(); + for url_str in force_value.trim().split_whitespace() { + match url::Url::parse(url_str) { + Ok(url) => urls.push(url), + Err(e) => { + return Err(error::Error::BadRequest(format!("Invalid URL in FORCE_{}: '{}': {}", std_env_var, url_str, e))); + } + } + } + let mut l = lock.write().await; + *l = if urls.is_empty() { None } else { Some(urls) }; + return Ok(()); + } + + let q = load_value_from_global_settings_with_conn(conn, setting_name, true).await?; + + // Check regular environment variable + let mut value = if let Ok(env_value) = std::env::var(std_env_var) { + let mut urls = Vec::new(); + for url_str in env_value.trim().split_whitespace() { + match url::Url::parse(url_str) { + Ok(url) => urls.push(url), + Err(_) => { + // Log error but continue, similar to force variable handling + tracing::error!("Invalid URL in {}: '{}'", std_env_var, url_str); + } + } + } + if urls.is_empty() { None } else { Some(urls) } + } else { + None + }; + + // Check database setting + if let Some(q) = q { + if let Ok(repos_str) = serde_json::from_value::(q.clone()) { + let mut urls = Vec::new(); + for url_str in repos_str.trim().split_whitespace() { + match url::Url::parse(url_str) { + Ok(url) => urls.push(url), + Err(e) => { + tracing::error!("Invalid URL in {}: '{}': {}", setting_name, url_str, e); + // Continue with other URLs, just skip invalid ones + } + } + } + tracing::info!("Loaded setting {} from db config: {} URLs", setting_name, urls.len()); + value = if urls.is_empty() { None } else { Some(urls) }; + } else { + tracing::error!("Could not parse {} found: {:#?}", setting_name, &q); + } + } + + { + if value.is_none() { + tracing::info!("Loaded {} setting to None", setting_name); + } + let mut l = lock.write().await; + *l = value; + } + + Ok(()) +} + pub async fn reload_setting( conn: &Connection, setting_name: &str, @@ -1371,6 +1482,14 @@ pub async fn monitor_db( } }; + let stale_jobs_f = async { + if server_mode && !initial_load { + if let Some(db) = conn.as_sql() { + stale_job_cancellation(&db).await; + } + } + }; + // run every 5 minutes let cleanup_concurrency_counters_f = async { if server_mode && iteration.is_some() && iteration.as_ref().unwrap().should_run(10) { @@ -1469,6 +1588,7 @@ pub async fn monitor_db( join!( expired_items_f, zombie_jobs_f, + stale_jobs_f, expose_queue_metrics_f, verify_license_key_f, worker_groups_alerts_f, @@ -1711,6 +1831,67 @@ pub async fn reload_base_url_setting(conn: &Connection) -> error::Result<()> { Ok(()) } +async fn stale_job_cancellation(db: &Pool) { + if let Some(threshold) = *STALE_JOB_TRESHOLD_MINUTES { + let stale_jobs = sqlx::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", + threshold.to_string() + ) + .fetch_all(db) + .await + .ok() + .unwrap_or_else(|| vec![]); + + if !stale_jobs.is_empty() { + tracing::info!( + "Cancelling {} stale jobs (> {} minutes old)", + stale_jobs.len(), + threshold + ); + } + for job in stale_jobs { + if let Err(e) = + cancel_stale_job(db, job.id, job.tag, job.workspace_id, job.scheduled_for).await + { + tracing::error!("Error cancelling stale job {}: {}", job.id, e); + } + } + } +} + +async fn cancel_stale_job( + db: &Pool, + id: Uuid, + tag: String, + workspace_id: String, + scheduled_for: DateTime, +) -> error::Result<()> { + let mut tx = db.begin().await?; + tracing::error!( + "Stale job detected: {} in workspace {} with tag {} (scheduled for: {}) . Cancelling it.", + id, + workspace_id, + tag, + scheduled_for + ); + (tx, _) = cancel_job( + "monitor", + Some(format!( + "Stale job cancellation (scheduled for: {})", + scheduled_for + )), + id, + &workspace_id, + tx, + db, + true, + false, + ) + .await?; + tx.commit().await?; + Ok(()) +} + const RESTART_LIMIT: i32 = 3; async fn handle_zombie_jobs(db: &Pool, base_internal_url: &str, worker_name: &str) { @@ -2129,6 +2310,7 @@ async fn handle_zombie_flows(db: &DB) -> error::Result<()> { AND (job_kind = 'flow' OR job_kind = 'flowpreview' OR job_kind = 'flownode') AND last_ping IS NOT NULL AND last_ping < NOW() - ($1 || ' seconds')::interval AND canceled = false + "#, FLOW_ZOMBIE_TRANSITION_TIMEOUT.as_str() ) @@ -2343,6 +2525,31 @@ pub async fn reload_critical_error_channels_setting(conn: &DB) -> error::Result< Ok(()) } +pub async fn reload_app_workspaced_route_setting(conn: &DB) -> error::Result<()> { + let app_workspaced_route = + load_value_from_global_settings(conn, APP_WORKSPACED_ROUTE_SETTING).await?; + + println!("Updating..."); + + let ws_route = match app_workspaced_route { + Some(serde_json::Value::Bool(ws_route)) => ws_route, + None => false, + _ => { + tracing::error!( + "Expected {} to be a boolean got: {:?}. Defaulting to false", + APP_WORKSPACED_ROUTE_SETTING, + app_workspaced_route + ); + false + } + }; + + let mut l = APP_WORKSPACED_ROUTE.write().await; + + *l = ws_route; + Ok(()) +} + pub async fn reload_critical_alerts_on_db_oversize(conn: &DB) -> error::Result<()> { #[derive(Deserialize)] struct DBOversize { diff --git a/backend/tests/worker.rs b/backend/tests/worker.rs index 5fa2670c5d..a8631f3484 100644 --- a/backend/tests/worker.rs +++ b/backend/tests/worker.rs @@ -27,7 +27,7 @@ use serde::Serialize; use windmill_common::flows::InputTransform; use windmill_common::worker::WORKER_CONFIG; -#[cfg(feature = "python")] +#[cfg(any(feature = "python", feature = "deno_core"))] use windmill_common::flow_status::{FlowStatus, FlowStatusModule, RestartedFrom}; use windmill_common::{ @@ -3468,6 +3468,7 @@ async fn test_script_schedule_handlers(db: Pool) { tag: None, paused_until: None, cron_version: None, + description: None, }; let _ = client.create_schedule("test-workspace", &schedule).await; @@ -3538,6 +3539,7 @@ async fn test_script_schedule_handlers(db: Pool) { tag: None, paused_until: None, cron_version: None, + description: None, }, ) .await @@ -3621,6 +3623,7 @@ async fn test_flow_schedule_handlers(db: Pool) { tag: None, paused_until: None, cron_version: None, + description: None, }; let _ = client.create_schedule("test-workspace", &schedule).await; @@ -3692,6 +3695,7 @@ async fn test_flow_schedule_handlers(db: Pool) { tag: None, paused_until: None, cron_version: None, + description: None, }, ) .await @@ -3777,6 +3781,7 @@ async fn run_deployed_relative_imports( codebase: None, has_preprocessor: None, on_behalf_of_email: None, + assets: vec![], }, ) .await @@ -4028,6 +4033,7 @@ async fn assert_lockfile( codebase: None, has_preprocessor: None, on_behalf_of_email: None, + assets: vec![], }, ) .await diff --git a/backend/windmill-api-client/codegen.rs b/backend/windmill-api-client/codegen.rs index 202145159e..4de622c668 100644 --- a/backend/windmill-api-client/codegen.rs +++ b/backend/windmill-api-client/codegen.rs @@ -8213,6 +8213,9 @@ the execution of this script will be permissioned_as and by extension its DT_TOK Csharp, #[serde(rename = "nu")] Nu, + #[serde(rename = "ruby")] + Ruby, + // for related places search: ADD_NEW_LANG } impl From<&ScriptLang> for ScriptLang { fn from(value: &ScriptLang) -> Self { @@ -8241,6 +8244,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK Self::Ansible => "ansible".to_string(), Self::Csharp => "csharp".to_string(), Self::Nu => "nu".to_string(), + Self::Ruby => "ruby".to_string(), + // for related places search: ADD_NEW_LANG } } } @@ -8267,6 +8272,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK "ansible" => Ok(Self::Ansible), "csharp" => Ok(Self::Csharp), "nu" => Ok(Self::Nu), + "ruby" => Ok(Self::Ruby), + // for related places search: ADD_NEW_LANG _ => Err("invalid value"), } } diff --git a/backend/windmill-api-client/src/codegen.rs b/backend/windmill-api-client/src/codegen.rs index 9bc26262d5..2ef8fe8cac 100644 --- a/backend/windmill-api-client/src/codegen.rs +++ b/backend/windmill-api-client/src/codegen.rs @@ -7,6 +7,20 @@ pub mod types { use serde::{Deserialize, Serialize}; #[allow(unused_imports)] use std::convert::TryFrom; + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AiConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub code_completion_model: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub default_model: Option, + #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")] + pub providers: std::collections::HashMap, + } + impl From<&AiConfig> for AiConfig { + fn from(value: &AiConfig) -> Self { + value.clone() + } + } #[derive( Clone, Copy, @@ -22,6 +36,8 @@ pub mod types { pub enum AiProvider { #[serde(rename = "openai")] Openai, + #[serde(rename = "azure_openai")] + AzureOpenai, #[serde(rename = "anthropic")] Anthropic, #[serde(rename = "mistral")] @@ -34,6 +50,8 @@ pub mod types { Groq, #[serde(rename = "openrouter")] Openrouter, + #[serde(rename = "togetherai")] + Togetherai, #[serde(rename = "customai")] Customai, } @@ -46,12 +64,14 @@ pub mod types { fn to_string(&self) -> String { match *self { Self::Openai => "openai".to_string(), + Self::AzureOpenai => "azure_openai".to_string(), Self::Anthropic => "anthropic".to_string(), Self::Mistral => "mistral".to_string(), Self::Deepseek => "deepseek".to_string(), Self::Googleai => "googleai".to_string(), Self::Groq => "groq".to_string(), Self::Openrouter => "openrouter".to_string(), + Self::Togetherai => "togetherai".to_string(), Self::Customai => "customai".to_string(), } } @@ -61,12 +81,14 @@ pub mod types { fn from_str(value: &str) -> Result { match value { "openai" => Ok(Self::Openai), + "azure_openai" => Ok(Self::AzureOpenai), "anthropic" => Ok(Self::Anthropic), "mistral" => Ok(Self::Mistral), "deepseek" => Ok(Self::Deepseek), "googleai" => Ok(Self::Googleai), "groq" => Ok(Self::Groq), "openrouter" => Ok(Self::Openrouter), + "togetherai" => Ok(Self::Togetherai), "customai" => Ok(Self::Customai), _ => Err("invalid value"), } @@ -91,12 +113,35 @@ pub mod types { } } #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct AiResource { - pub path: String, + pub struct AiProviderConfig { + pub models: Vec, + pub resource_path: String, + } + impl From<&AiProviderConfig> for AiProviderConfig { + fn from(value: &AiProviderConfig) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct AiProviderModel { + pub model: String, pub provider: AiProvider, } - impl From<&AiResource> for AiResource { - fn from(value: &AiResource) -> Self { + impl From<&AiProviderModel> for AiProviderModel { + fn from(value: &AiProviderModel) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Alert { + pub alert_cooldown_seconds: i64, + pub alert_time_threshold_seconds: i64, + pub jobs_num_threshold: i64, + pub name: String, + pub tags_to_monitor: Vec, + } + impl From<&Alert> for Alert { + fn from(value: &Alert) -> Self { value.clone() } } @@ -210,6 +255,201 @@ pub mod types { } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Asset { + pub kind: AssetKind, + pub path: String, + } + impl From<&Asset> for Asset { + fn from(value: &Asset) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum AssetKind { + #[serde(rename = "s3object")] + S3object, + #[serde(rename = "resource")] + Resource, + #[serde(rename = "ducklake")] + Ducklake, + } + impl From<&AssetKind> for AssetKind { + fn from(value: &AssetKind) -> Self { + value.clone() + } + } + impl ToString for AssetKind { + fn to_string(&self) -> String { + match *self { + Self::S3object => "s3object".to_string(), + Self::Resource => "resource".to_string(), + Self::Ducklake => "ducklake".to_string(), + } + } + } + impl std::str::FromStr for AssetKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "s3object" => Ok(Self::S3object), + "resource" => Ok(Self::Resource), + "ducklake" => Ok(Self::Ducklake), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for AssetKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for AssetKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for AssetKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum AssetUsageAccessType { + #[serde(rename = "r")] + R, + #[serde(rename = "w")] + W, + #[serde(rename = "rw")] + Rw, + } + impl From<&AssetUsageAccessType> for AssetUsageAccessType { + fn from(value: &AssetUsageAccessType) -> Self { + value.clone() + } + } + impl ToString for AssetUsageAccessType { + fn to_string(&self) -> String { + match *self { + Self::R => "r".to_string(), + Self::W => "w".to_string(), + Self::Rw => "rw".to_string(), + } + } + } + impl std::str::FromStr for AssetUsageAccessType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "r" => Ok(Self::R), + "w" => Ok(Self::W), + "rw" => Ok(Self::Rw), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for AssetUsageAccessType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for AssetUsageAccessType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for AssetUsageAccessType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum AssetUsageKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "flow")] + Flow, + } + impl From<&AssetUsageKind> for AssetUsageKind { + fn from(value: &AssetUsageKind) -> Self { + value.clone() + } + } + impl ToString for AssetUsageKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Flow => "flow".to_string(), + } + } + } + impl std::str::FromStr for AssetUsageKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "flow" => Ok(Self::Flow), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for AssetUsageKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for AssetUsageKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for AssetUsageKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct AuditLog { pub action_kind: AuditLogActionKind, pub id: i64, @@ -218,6 +458,8 @@ pub mod types { pub parameters: std::collections::HashMap, #[serde(default, skip_serializing_if = "Option::is_none")] pub resource: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub span: Option, pub timestamp: chrono::DateTime, pub username: String, } @@ -690,6 +932,81 @@ pub mod types { value.parse() } } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum AuthenticationMethod { + #[serde(rename = "none")] + None, + #[serde(rename = "windmill")] + Windmill, + #[serde(rename = "api_key")] + ApiKey, + #[serde(rename = "basic_http")] + BasicHttp, + #[serde(rename = "custom_script")] + CustomScript, + #[serde(rename = "signature")] + Signature, + } + impl From<&AuthenticationMethod> for AuthenticationMethod { + fn from(value: &AuthenticationMethod) -> Self { + value.clone() + } + } + impl ToString for AuthenticationMethod { + fn to_string(&self) -> String { + match *self { + Self::None => "none".to_string(), + Self::Windmill => "windmill".to_string(), + Self::ApiKey => "api_key".to_string(), + Self::BasicHttp => "basic_http".to_string(), + Self::CustomScript => "custom_script".to_string(), + Self::Signature => "signature".to_string(), + } + } + } + impl std::str::FromStr for AuthenticationMethod { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "none" => Ok(Self::None), + "windmill" => Ok(Self::Windmill), + "api_key" => Ok(Self::ApiKey), + "basic_http" => Ok(Self::BasicHttp), + "custom_script" => Ok(Self::CustomScript), + "signature" => Ok(Self::Signature), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for AuthenticationMethod { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for AuthenticationMethod { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for AuthenticationMethod { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct AutoscalingEvent { #[serde(default, skip_serializing_if = "Option::is_none")] @@ -710,6 +1027,65 @@ pub mod types { value.clone() } } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum AwsAuthResourceType { + #[serde(rename = "oidc")] + Oidc, + #[serde(rename = "credentials")] + Credentials, + } + impl From<&AwsAuthResourceType> for AwsAuthResourceType { + fn from(value: &AwsAuthResourceType) -> Self { + value.clone() + } + } + impl ToString for AwsAuthResourceType { + fn to_string(&self) -> String { + match *self { + Self::Oidc => "oidc".to_string(), + Self::Credentials => "credentials".to_string(), + } + } + } + impl std::str::FromStr for AwsAuthResourceType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "oidc" => Ok(Self::Oidc), + "credentials" => Ok(Self::Credentials), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for AwsAuthResourceType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for AwsAuthResourceType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for AwsAuthResourceType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct BranchAll { pub branches: Vec, @@ -874,9 +1250,8 @@ pub mod types { pub struct Capture { pub created_at: chrono::DateTime, pub id: i64, - pub payload: serde_json::Value, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub trigger_extra: Option, + pub main_args: serde_json::Value, + pub preprocessor_args: serde_json::Value, pub trigger_kind: CaptureTriggerKind, } impl From<&Capture> for Capture { @@ -930,6 +1305,8 @@ pub mod types { Sqs, #[serde(rename = "mqtt")] Mqtt, + #[serde(rename = "gcp")] + Gcp, } impl From<&CaptureTriggerKind> for CaptureTriggerKind { fn from(value: &CaptureTriggerKind) -> Self { @@ -948,6 +1325,7 @@ pub mod types { Self::Postgres => "postgres".to_string(), Self::Sqs => "sqs".to_string(), Self::Mqtt => "mqtt".to_string(), + Self::Gcp => "gcp".to_string(), } } } @@ -964,6 +1342,7 @@ pub mod types { "postgres" => Ok(Self::Postgres), "sqs" => Ok(Self::Sqs), "mqtt" => Ok(Self::Mqtt), + "gcp" => Ok(Self::Gcp), _ => Err("invalid value"), } } @@ -1064,6 +1443,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Option::is_none")] pub worker: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub workflow_as_code_status: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub workspace_id: Option, } impl From<&CompletedJob> for CompletedJob { @@ -1200,6 +1581,39 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct Configs(pub Option); + impl std::ops::Deref for Configs { + type Target = Option; + fn deref(&self) -> &Option { + &self.0 + } + } + impl From for Option { + fn from(value: Configs) -> Self { + value.0 + } + } + impl From<&Configs> for Configs { + fn from(value: &Configs) -> Self { + value.clone() + } + } + impl From> for Configs { + fn from(value: Option) -> Self { + Self(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ConfigsInner { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub alerts: Vec, + } + impl From<&ConfigsInner> for ConfigsInner { + fn from(value: &ConfigsInner) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ContextualVariable { pub description: String, pub is_custom: bool, @@ -1237,9 +1651,12 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct CreateResource { + ///The description of the resource #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, + ///The path to the resource pub path: String, + ///The resource_type associated with the resource pub resource_type: String, pub value: serde_json::Value, } @@ -1250,15 +1667,22 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct CreateVariable { + ///The account identifier #[serde(default, skip_serializing_if = "Option::is_none")] pub account: Option, + ///The description of the variable pub description: String, + ///The expiration date of the variable #[serde(default, skip_serializing_if = "Option::is_none")] pub expires_at: Option>, + ///Whether the variable is an OAuth variable #[serde(default, skip_serializing_if = "Option::is_none")] pub is_oauth: Option, + ///Whether the variable is a secret pub is_secret: bool, + ///The path to the variable pub path: String, + ///The value of the variable pub value: String, } impl From<&CreateVariable> for CreateVariable { @@ -1307,27 +1731,11 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct EditHttpTrigger { - pub http_method: EditHttpTriggerHttpMethod, - pub is_async: bool, - pub is_flow: bool, - pub is_static_website: bool, - pub path: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub raw_string: Option, - pub requires_auth: bool, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub route_path: Option, - pub script_path: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub static_asset_config: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub workspaced_route: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub wrap_body: Option, + pub struct DeleteGcpSubscription { + pub subscription_id: String, } - impl From<&EditHttpTrigger> for EditHttpTrigger { - fn from(value: &EditHttpTrigger) -> Self { + impl From<&DeleteGcpSubscription> for DeleteGcpSubscription { + fn from(value: &DeleteGcpSubscription) -> Self { value.clone() } } @@ -1343,66 +1751,201 @@ the execution of this script will be permissioned_as and by extension its DT_TOK PartialOrd, Serialize )] - pub enum EditHttpTriggerHttpMethod { - #[serde(rename = "get")] - Get, - #[serde(rename = "post")] - Post, - #[serde(rename = "put")] - Put, - #[serde(rename = "delete")] - Delete, - #[serde(rename = "patch")] - Patch, + pub enum DeliveryType { + #[serde(rename = "push")] + Push, + #[serde(rename = "pull")] + Pull, } - impl From<&EditHttpTriggerHttpMethod> for EditHttpTriggerHttpMethod { - fn from(value: &EditHttpTriggerHttpMethod) -> Self { + impl From<&DeliveryType> for DeliveryType { + fn from(value: &DeliveryType) -> Self { value.clone() } } - impl ToString for EditHttpTriggerHttpMethod { + impl ToString for DeliveryType { fn to_string(&self) -> String { match *self { - Self::Get => "get".to_string(), - Self::Post => "post".to_string(), - Self::Put => "put".to_string(), - Self::Delete => "delete".to_string(), - Self::Patch => "patch".to_string(), + Self::Push => "push".to_string(), + Self::Pull => "pull".to_string(), } } } - impl std::str::FromStr for EditHttpTriggerHttpMethod { + impl std::str::FromStr for DeliveryType { type Err = &'static str; fn from_str(value: &str) -> Result { match value { - "get" => Ok(Self::Get), - "post" => Ok(Self::Post), - "put" => Ok(Self::Put), - "delete" => Ok(Self::Delete), - "patch" => Ok(Self::Patch), + "push" => Ok(Self::Push), + "pull" => Ok(Self::Pull), _ => Err("invalid value"), } } } - impl std::convert::TryFrom<&str> for EditHttpTriggerHttpMethod { + impl std::convert::TryFrom<&str> for DeliveryType { type Error = &'static str; fn try_from(value: &str) -> Result { value.parse() } } - impl std::convert::TryFrom<&String> for EditHttpTriggerHttpMethod { + impl std::convert::TryFrom<&String> for DeliveryType { type Error = &'static str; fn try_from(value: &String) -> Result { value.parse() } } - impl std::convert::TryFrom for EditHttpTriggerHttpMethod { + impl std::convert::TryFrom for DeliveryType { type Error = &'static str; fn try_from(value: String) -> Result { value.parse() } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct DucklakeSettings { + pub ducklakes: std::collections::HashMap, + } + impl From<&DucklakeSettings> for DucklakeSettings { + fn from(value: &DucklakeSettings) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct DucklakeSettingsDucklakesValue { + pub catalog: DucklakeSettingsDucklakesValueCatalog, + pub storage: DucklakeSettingsDucklakesValueStorage, + } + impl From<&DucklakeSettingsDucklakesValue> for DucklakeSettingsDucklakesValue { + fn from(value: &DucklakeSettingsDucklakesValue) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct DucklakeSettingsDucklakesValueCatalog { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub resource_path: Option, + pub resource_type: DucklakeSettingsDucklakesValueCatalogResourceType, + } + impl From<&DucklakeSettingsDucklakesValueCatalog> + for DucklakeSettingsDucklakesValueCatalog { + fn from(value: &DucklakeSettingsDucklakesValueCatalog) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum DucklakeSettingsDucklakesValueCatalogResourceType { + #[serde(rename = "postgresql")] + Postgresql, + #[serde(rename = "mysql")] + Mysql, + #[serde(rename = "instance")] + Instance, + } + impl From<&DucklakeSettingsDucklakesValueCatalogResourceType> + for DucklakeSettingsDucklakesValueCatalogResourceType { + fn from(value: &DucklakeSettingsDucklakesValueCatalogResourceType) -> Self { + value.clone() + } + } + impl ToString for DucklakeSettingsDucklakesValueCatalogResourceType { + fn to_string(&self) -> String { + match *self { + Self::Postgresql => "postgresql".to_string(), + Self::Mysql => "mysql".to_string(), + Self::Instance => "instance".to_string(), + } + } + } + impl std::str::FromStr for DucklakeSettingsDucklakesValueCatalogResourceType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "postgresql" => Ok(Self::Postgresql), + "mysql" => Ok(Self::Mysql), + "instance" => Ok(Self::Instance), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> + for DucklakeSettingsDucklakesValueCatalogResourceType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> + for DucklakeSettingsDucklakesValueCatalogResourceType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom + for DucklakeSettingsDucklakesValueCatalogResourceType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct DucklakeSettingsDucklakesValueStorage { + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub storage: Option, + } + impl From<&DucklakeSettingsDucklakesValueStorage> + for DucklakeSettingsDucklakesValueStorage { + fn from(value: &DucklakeSettingsDucklakesValueStorage) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EditHttpTrigger { + pub authentication_method: AuthenticationMethod, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub authentication_resource_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, + pub http_method: HttpMethod, + pub is_async: bool, + pub is_flow: bool, + pub is_static_website: bool, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_string: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub route_path: Option, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub static_asset_config: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspaced_route: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub wrap_body: Option, + } + impl From<&EditHttpTrigger> for EditHttpTrigger { + fn from(value: &EditHttpTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct EditHttpTriggerStaticAssetConfig { #[serde(default, skip_serializing_if = "Option::is_none")] pub filename: Option, @@ -1417,10 +1960,16 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct EditKafkaTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, pub group_id: String, pub is_flow: bool, pub kafka_resource_path: String, pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, pub script_path: String, pub topics: Vec, } @@ -1436,9 +1985,15 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Option::is_none")] pub client_version: Option, pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, pub is_flow: bool, pub mqtt_resource_path: String, pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, pub script_path: String, pub subscribe_topics: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -1455,9 +2010,15 @@ the execution of this script will be permissioned_as and by extension its DT_TOK pub struct EditNatsTrigger { #[serde(default, skip_serializing_if = "Option::is_none")] pub consumer_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, pub is_flow: bool, pub nats_resource_path: String, pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, pub script_path: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub stream_name: Option, @@ -1472,6 +2033,10 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[derive(Clone, Debug, Deserialize, Serialize)] pub struct EditPostgresTrigger { pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, pub is_flow: bool, pub path: String, pub postgres_resource_path: String, @@ -1479,6 +2044,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK pub publication: Option, pub publication_name: String, pub replication_slot_name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, pub script_path: String, } impl From<&EditPostgresTrigger> for EditPostgresTrigger { @@ -1488,10 +2055,15 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct EditResource { + ///The new description of the resource #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, + ///The path to the resource #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option, + ///The new resource_type to be associated with the resource + #[serde(default, skip_serializing_if = "Option::is_none")] + pub resource_type: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub value: Option, } @@ -1515,38 +2087,53 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[derive(Clone, Debug, Deserialize, Serialize)] pub struct EditSchedule { pub args: ScriptArgs, + ///The version of the cron schedule to use (last is v2) #[serde(default, skip_serializing_if = "Option::is_none")] pub cron_version: Option, + ///The description of the schedule + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + ///Whether the schedule should not run if a flow is already running #[serde(default, skip_serializing_if = "Option::is_none")] pub no_flow_overlap: Option, + ///The path to the script or flow to trigger on failure #[serde(default, skip_serializing_if = "Option::is_none")] pub on_failure: Option, + ///Whether the schedule should only run on the exact time #[serde(default, skip_serializing_if = "Option::is_none")] pub on_failure_exact: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub on_failure_extra_args: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub on_failure_times: Option, + ///The path to the script or flow to trigger on recovery #[serde(default, skip_serializing_if = "Option::is_none")] pub on_recovery: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub on_recovery_extra_args: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub on_recovery_times: Option, + ///The path to the script or flow to trigger on success #[serde(default, skip_serializing_if = "Option::is_none")] pub on_success: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub on_success_extra_args: Option, + ///The date and time the schedule will be paused until #[serde(default, skip_serializing_if = "Option::is_none")] pub paused_until: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub retry: Option, + ///The cron schedule to trigger the script or flow. Should include seconds. pub schedule: String, + ///The summary of the schedule #[serde(default, skip_serializing_if = "Option::is_none")] pub summary: Option, + ///The tag of the schedule #[serde(default, skip_serializing_if = "Option::is_none")] pub tag: Option, + ///The timezone to use for the cron schedule pub timezone: String, + ///Whether the WebSocket error handler is muted #[serde(default, skip_serializing_if = "Option::is_none")] pub ws_error_handler_muted: Option, } @@ -1557,13 +2144,20 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct EditSqsTrigger { + pub aws_auth_resource_type: AwsAuthResourceType, pub aws_resource_path: String, pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, pub is_flow: bool, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub message_attributes: Vec, pub path: String, pub queue_url: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, pub script_path: String, } impl From<&EditSqsTrigger> for EditSqsTrigger { @@ -1573,12 +2167,16 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct EditVariable { + ///The new description of the variable #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, + ///Whether the variable is a secret #[serde(default, skip_serializing_if = "Option::is_none")] pub is_secret: Option, + ///The path to the variable #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option, + ///The new value of the variable #[serde(default, skip_serializing_if = "Option::is_none")] pub value: Option, } @@ -1590,11 +2188,17 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[derive(Clone, Debug, Deserialize, Serialize)] pub struct EditWebsocketTrigger { pub can_return_message: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, pub filters: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub initial_messages: Vec, pub is_flow: bool, pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, pub script_path: String, pub url: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -1630,6 +2234,104 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct EndpointTool { + ///JSON schema for request body + #[serde(default, skip_serializing_if = "Option::is_none")] + pub body_schema: Option>, + ///Short description of the tool + pub description: String, + ///Detailed instructions for using the tool + pub instructions: String, + ///HTTP method (GET, POST, etc.) + pub method: String, + ///The tool name/operation ID + pub name: String, + ///API endpoint path + pub path: String, + ///JSON schema for path parameters + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path_params_schema: Option< + std::collections::HashMap, + >, + ///JSON schema for query parameters + #[serde(default, skip_serializing_if = "Option::is_none")] + pub query_params_schema: Option< + std::collections::HashMap, + >, + } + impl From<&EndpointTool> for EndpointTool { + fn from(value: &EndpointTool) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum ErrorHandler { + #[serde(rename = "custom")] + Custom, + #[serde(rename = "slack")] + Slack, + #[serde(rename = "teams")] + Teams, + #[serde(rename = "email")] + Email, + } + impl From<&ErrorHandler> for ErrorHandler { + fn from(value: &ErrorHandler) -> Self { + value.clone() + } + } + impl ToString for ErrorHandler { + fn to_string(&self) -> String { + match *self { + Self::Custom => "custom".to_string(), + Self::Slack => "slack".to_string(), + Self::Teams => "teams".to_string(), + Self::Email => "email".to_string(), + } + } + } + impl std::str::FromStr for ErrorHandler { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "custom" => Ok(Self::Custom), + "slack" => Ok(Self::Slack), + "teams" => Ok(Self::Teams), + "email" => Ok(Self::Email), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for ErrorHandler { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for ErrorHandler { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for ErrorHandler { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ExportedInstanceGroup { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub emails: Vec, @@ -1771,9 +2473,9 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Option::is_none")] pub sleep: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub stop_after_all_iters_if: Option, + pub stop_after_all_iters_if: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub stop_after_if: Option, + pub stop_after_if: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub summary: Option, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -1809,30 +2511,6 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct FlowModuleStopAfterAllItersIf { - pub expr: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub skip_if_stopped: Option, - } - impl From<&FlowModuleStopAfterAllItersIf> for FlowModuleStopAfterAllItersIf { - fn from(value: &FlowModuleStopAfterAllItersIf) -> Self { - value.clone() - } - } - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct FlowModuleStopAfterIf { - pub expr: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub skip_if_stopped: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub error_message: Option - } - impl From<&FlowModuleStopAfterIf> for FlowModuleStopAfterIf { - fn from(value: &FlowModuleStopAfterIf) -> Self { - value.clone() - } - } - #[derive(Clone, Debug, Deserialize, Serialize)] pub struct FlowModuleSuspend { #[serde(default, skip_serializing_if = "Option::is_none")] pub continue_on_disapprove_timeout: Option, @@ -2319,14 +2997,105 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct GitRepositorySettings { + pub struct GcpTrigger { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub delivery_config: Option, + pub delivery_type: DeliveryType, + pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, + pub gcp_resource_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub last_server_ping: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub server_id: Option, + pub subscription_id: String, + pub subscription_mode: SubscriptionMode, + pub topic_id: String, + } + impl From<&GcpTrigger> for GcpTrigger { + fn from(value: &GcpTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GcpTriggerData { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub auto_acknowledge_msg: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub base_endpoint: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub delivery_config: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub delivery_type: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub enabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, + pub gcp_resource_path: String, + pub is_flow: bool, + pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + pub script_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub subscription_id: Option, + pub subscription_mode: SubscriptionMode, + pub topic_id: String, + } + impl From<&GcpTriggerData> for GcpTriggerData { + fn from(value: &GcpTriggerData) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GenerateOpenapiSpec { #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub exclude_types_override: Vec, + pub http_route_filters: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub info: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub openapi_spec_format: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub webhook_filters: Vec, + } + impl From<&GenerateOpenapiSpec> for GenerateOpenapiSpec { + fn from(value: &GenerateOpenapiSpec) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GetAllTopicSubscription { + pub topic_id: String, + } + impl From<&GetAllTopicSubscription> for GetAllTopicSubscription { + fn from(value: &GetAllTopicSubscription) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GitRepositorySettings { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub collapsed: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub exclude_types_override: Vec, pub git_repo_resource_path: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub group_by_folder: Option, pub script_path: String, #[serde(default, skip_serializing_if = "Option::is_none")] + pub settings: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub use_individual_branch: Option, } impl From<&GitRepositorySettings> for GitRepositorySettings { @@ -2334,6 +3103,22 @@ the execution of this script will be permissioned_as and by extension its DT_TOK value.clone() } } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GitRepositorySettingsSettings { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub exclude_path: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub extra_include_path: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub include_path: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub include_type: Vec, + } + impl From<&GitRepositorySettingsSettings> for GitRepositorySettingsSettings { + fn from(value: &GitRepositorySettingsSettings) -> Self { + value.clone() + } + } #[derive( Clone, Copy, @@ -2346,7 +3131,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK PartialOrd, Serialize )] - pub enum GitRepositorySettingsExcludeTypesOverrideItem { + pub enum GitSyncObjectType { #[serde(rename = "script")] Script, #[serde(rename = "flow")] @@ -2369,14 +3154,19 @@ the execution of this script will be permissioned_as and by extension its DT_TOK User, #[serde(rename = "group")] Group, + #[serde(rename = "trigger")] + Trigger, + #[serde(rename = "settings")] + Settings, + #[serde(rename = "key")] + Key, } - impl From<&GitRepositorySettingsExcludeTypesOverrideItem> - for GitRepositorySettingsExcludeTypesOverrideItem { - fn from(value: &GitRepositorySettingsExcludeTypesOverrideItem) -> Self { + impl From<&GitSyncObjectType> for GitSyncObjectType { + fn from(value: &GitSyncObjectType) -> Self { value.clone() } } - impl ToString for GitRepositorySettingsExcludeTypesOverrideItem { + impl ToString for GitSyncObjectType { fn to_string(&self) -> String { match *self { Self::Script => "script".to_string(), @@ -2390,10 +3180,13 @@ the execution of this script will be permissioned_as and by extension its DT_TOK Self::Schedule => "schedule".to_string(), Self::User => "user".to_string(), Self::Group => "group".to_string(), + Self::Trigger => "trigger".to_string(), + Self::Settings => "settings".to_string(), + Self::Key => "key".to_string(), } } } - impl std::str::FromStr for GitRepositorySettingsExcludeTypesOverrideItem { + impl std::str::FromStr for GitSyncObjectType { type Err = &'static str; fn from_str(value: &str) -> Result { match value { @@ -2408,31 +3201,79 @@ the execution of this script will be permissioned_as and by extension its DT_TOK "schedule" => Ok(Self::Schedule), "user" => Ok(Self::User), "group" => Ok(Self::Group), + "trigger" => Ok(Self::Trigger), + "settings" => Ok(Self::Settings), + "key" => Ok(Self::Key), _ => Err("invalid value"), } } } - impl std::convert::TryFrom<&str> for GitRepositorySettingsExcludeTypesOverrideItem { + impl std::convert::TryFrom<&str> for GitSyncObjectType { type Error = &'static str; fn try_from(value: &str) -> Result { value.parse() } } - impl std::convert::TryFrom<&String> - for GitRepositorySettingsExcludeTypesOverrideItem { + impl std::convert::TryFrom<&String> for GitSyncObjectType { type Error = &'static str; fn try_from(value: &String) -> Result { value.parse() } } - impl std::convert::TryFrom - for GitRepositorySettingsExcludeTypesOverrideItem { + impl std::convert::TryFrom for GitSyncObjectType { type Error = &'static str; fn try_from(value: String) -> Result { value.parse() } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GithubInstallations(pub Vec); + impl std::ops::Deref for GithubInstallations { + type Target = Vec; + fn deref(&self) -> &Vec { + &self.0 + } + } + impl From for Vec { + fn from(value: GithubInstallations) -> Self { + value.0 + } + } + impl From<&GithubInstallations> for GithubInstallations { + fn from(value: &GithubInstallations) -> Self { + value.clone() + } + } + impl From> for GithubInstallations { + fn from(value: Vec) -> Self { + Self(value) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GithubInstallationsItem { + pub account_id: String, + pub installation_id: f64, + pub repositories: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + } + impl From<&GithubInstallationsItem> for GithubInstallationsItem { + fn from(value: &GithubInstallationsItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct GithubInstallationsItemRepositoriesItem { + pub name: String, + pub url: String, + } + impl From<&GithubInstallationsItemRepositoriesItem> + for GithubInstallationsItemRepositoriesItem { + fn from(value: &GithubInstallationsItemRepositoriesItem) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct GlobalSetting { pub name: String, pub value: std::collections::HashMap, @@ -2538,24 +3379,6 @@ the execution of this script will be permissioned_as and by extension its DT_TOK value.clone() } } - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct HttpTrigger { - pub http_method: HttpTriggerHttpMethod, - pub is_async: bool, - pub is_static_website: bool, - pub raw_string: bool, - pub requires_auth: bool, - pub route_path: String, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub static_asset_config: Option, - pub workspaced_route: bool, - pub wrap_body: bool, - } - impl From<&HttpTrigger> for HttpTrigger { - fn from(value: &HttpTrigger) -> Self { - value.clone() - } - } #[derive( Clone, Copy, @@ -2568,7 +3391,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK PartialOrd, Serialize )] - pub enum HttpTriggerHttpMethod { + pub enum HttpMethod { #[serde(rename = "get")] Get, #[serde(rename = "post")] @@ -2580,12 +3403,12 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(rename = "patch")] Patch, } - impl From<&HttpTriggerHttpMethod> for HttpTriggerHttpMethod { - fn from(value: &HttpTriggerHttpMethod) -> Self { + impl From<&HttpMethod> for HttpMethod { + fn from(value: &HttpMethod) -> Self { value.clone() } } - impl ToString for HttpTriggerHttpMethod { + impl ToString for HttpMethod { fn to_string(&self) -> String { match *self { Self::Get => "get".to_string(), @@ -2596,7 +3419,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } } - impl std::str::FromStr for HttpTriggerHttpMethod { + impl std::str::FromStr for HttpMethod { type Err = &'static str; fn from_str(value: &str) -> Result { match value { @@ -2609,25 +3432,55 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } } - impl std::convert::TryFrom<&str> for HttpTriggerHttpMethod { + impl std::convert::TryFrom<&str> for HttpMethod { type Error = &'static str; fn try_from(value: &str) -> Result { value.parse() } } - impl std::convert::TryFrom<&String> for HttpTriggerHttpMethod { + impl std::convert::TryFrom<&String> for HttpMethod { type Error = &'static str; fn try_from(value: &String) -> Result { value.parse() } } - impl std::convert::TryFrom for HttpTriggerHttpMethod { + impl std::convert::TryFrom for HttpMethod { type Error = &'static str; fn try_from(value: String) -> Result { value.parse() } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct HttpTrigger { + pub authentication_method: AuthenticationMethod, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub authentication_resource_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, + pub http_method: HttpMethod, + pub is_async: bool, + pub is_static_website: bool, + pub raw_string: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + pub route_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub static_asset_config: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + pub workspaced_route: bool, + pub wrap_body: bool, + } + impl From<&HttpTrigger> for HttpTrigger { + fn from(value: &HttpTrigger) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct HttpTriggerStaticAssetConfig { #[serde(default, skip_serializing_if = "Option::is_none")] pub filename: Option, @@ -2640,27 +3493,71 @@ the execution of this script will be permissioned_as and by extension its DT_TOK value.clone() } } - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct HubScriptKind(pub serde_json::Value); - impl std::ops::Deref for HubScriptKind { - type Target = serde_json::Value; - fn deref(&self) -> &serde_json::Value { - &self.0 - } - } - impl From for serde_json::Value { - fn from(value: HubScriptKind) -> Self { - value.0 - } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum HubScriptKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "failure")] + Failure, + #[serde(rename = "trigger")] + Trigger, + #[serde(rename = "approval")] + Approval, } impl From<&HubScriptKind> for HubScriptKind { fn from(value: &HubScriptKind) -> Self { value.clone() } } - impl From for HubScriptKind { - fn from(value: serde_json::Value) -> Self { - Self(value) + impl ToString for HubScriptKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Failure => "failure".to_string(), + Self::Trigger => "trigger".to_string(), + Self::Approval => "approval".to_string(), + } + } + } + impl std::str::FromStr for HubScriptKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "failure" => Ok(Self::Failure), + "trigger" => Ok(Self::Trigger), + "approval" => Ok(Self::Approval), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for HubScriptKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for HubScriptKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for HubScriptKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() } } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -2780,6 +3677,21 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct InstanceGroupWithWorkspaces { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub emails: Vec, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub workspaces: Vec, + } + impl From<&InstanceGroupWithWorkspaces> for InstanceGroupWithWorkspaces { + fn from(value: &InstanceGroupWithWorkspaces) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct JavascriptTransform { pub expr: String, #[serde(rename = "type")] @@ -3013,11 +3925,17 @@ the execution of this script will be permissioned_as and by extension its DT_TOK pub enabled: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, pub group_id: String, pub kafka_resource_path: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub last_server_ping: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub server_id: Option, pub topics: Vec, } @@ -3085,6 +4003,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Option::is_none")] pub azure_blob_resource_path: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub gcs_resource_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub public_resource: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub s3_resource_path: Option, @@ -3106,6 +4026,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Option::is_none")] pub azure_blob_resource_path: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub gcs_resource_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub public_resource: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub s3_resource_path: Option, @@ -3135,6 +4057,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK AzureBlobStorage, AzureWorkloadIdentity, S3AwsOidc, + GoogleCloudStorage, } impl From<&LargeFileStorageSecondaryStorageValueType> for LargeFileStorageSecondaryStorageValueType { @@ -3149,6 +4072,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK Self::AzureBlobStorage => "AzureBlobStorage".to_string(), Self::AzureWorkloadIdentity => "AzureWorkloadIdentity".to_string(), Self::S3AwsOidc => "S3AwsOidc".to_string(), + Self::GoogleCloudStorage => "GoogleCloudStorage".to_string(), } } } @@ -3160,6 +4084,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK "AzureBlobStorage" => Ok(Self::AzureBlobStorage), "AzureWorkloadIdentity" => Ok(Self::AzureWorkloadIdentity), "S3AwsOidc" => Ok(Self::S3AwsOidc), + "GoogleCloudStorage" => Ok(Self::GoogleCloudStorage), _ => Err("invalid value"), } } @@ -3199,6 +4124,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK AzureBlobStorage, AzureWorkloadIdentity, S3AwsOidc, + GoogleCloudStorage, } impl From<&LargeFileStorageType> for LargeFileStorageType { fn from(value: &LargeFileStorageType) -> Self { @@ -3212,6 +4138,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK Self::AzureBlobStorage => "AzureBlobStorage".to_string(), Self::AzureWorkloadIdentity => "AzureWorkloadIdentity".to_string(), Self::S3AwsOidc => "S3AwsOidc".to_string(), + Self::GoogleCloudStorage => "GoogleCloudStorage".to_string(), } } } @@ -3223,6 +4150,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK "AzureBlobStorage" => Ok(Self::AzureBlobStorage), "AzureWorkloadIdentity" => Ok(Self::AzureWorkloadIdentity), "S3AwsOidc" => Ok(Self::S3AwsOidc), + "GoogleCloudStorage" => Ok(Self::GoogleCloudStorage), _ => Err("invalid value"), } } @@ -3253,6 +4181,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK pub id: i64, pub path: String, #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_app: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub starred: Option, pub summary: String, pub version: i64, @@ -3479,7 +4409,12 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(rename = "str")] Str(Option>), #[serde(rename = "object")] - Object(Vec), + Object { + #[serde(default, skip_serializing_if = "Option::is_none")] + name: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + props: Vec, + }, #[serde(rename = "list")] List(MainArgSignatureArgsItemTypList), } @@ -3498,12 +4433,6 @@ the execution of this script will be permissioned_as and by extension its DT_TOK Self::Str(value) } } - impl From> - for MainArgSignatureArgsItemTyp { - fn from(value: Vec) -> Self { - Self::Object(value) - } - } impl From for MainArgSignatureArgsItemTyp { fn from(value: MainArgSignatureArgsItemTypList) -> Self { Self::List(value) @@ -3543,18 +4472,18 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct MainArgSignatureArgsItemTypObjectItem { + pub struct MainArgSignatureArgsItemTypObjectPropsItem { pub key: String, - pub typ: MainArgSignatureArgsItemTypObjectItemTyp, + pub typ: MainArgSignatureArgsItemTypObjectPropsItemTyp, } - impl From<&MainArgSignatureArgsItemTypObjectItem> - for MainArgSignatureArgsItemTypObjectItem { - fn from(value: &MainArgSignatureArgsItemTypObjectItem) -> Self { + impl From<&MainArgSignatureArgsItemTypObjectPropsItem> + for MainArgSignatureArgsItemTypObjectPropsItem { + fn from(value: &MainArgSignatureArgsItemTypObjectPropsItem) -> Self { value.clone() } } #[derive(Clone, Debug, Deserialize, Serialize)] - pub enum MainArgSignatureArgsItemTypObjectItemTyp { + pub enum MainArgSignatureArgsItemTypObjectPropsItemTyp { #[serde(rename = "float")] Float, #[serde(rename = "int")] @@ -3576,13 +4505,13 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(rename = "str")] Str(serde_json::Value), } - impl From<&MainArgSignatureArgsItemTypObjectItemTyp> - for MainArgSignatureArgsItemTypObjectItemTyp { - fn from(value: &MainArgSignatureArgsItemTypObjectItemTyp) -> Self { + impl From<&MainArgSignatureArgsItemTypObjectPropsItemTyp> + for MainArgSignatureArgsItemTypObjectPropsItemTyp { + fn from(value: &MainArgSignatureArgsItemTypObjectPropsItemTyp) -> Self { value.clone() } } - impl From for MainArgSignatureArgsItemTypObjectItemTyp { + impl From for MainArgSignatureArgsItemTypObjectPropsItemTyp { fn from(value: serde_json::Value) -> Self { Self::Str(value) } @@ -3807,9 +4736,15 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Option::is_none")] pub error: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub last_server_ping: Option>, pub mqtt_resource_path: String, #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub server_id: Option, pub subscribe_topics: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -3839,7 +4774,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Option::is_none")] pub session_expiry_interval: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub topic_alias: Option, + pub topic_alias_maximum: Option, } impl From<&MqttV5Config> for MqttV5Config { fn from(value: &MqttV5Config) -> Self { @@ -3854,9 +4789,15 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Option::is_none")] pub error: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub last_server_ping: Option>, pub nats_resource_path: String, #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub server_id: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub stream_name: Option, @@ -3870,19 +4811,31 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NewHttpTrigger { - pub http_method: NewHttpTriggerHttpMethod, + pub authentication_method: AuthenticationMethod, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub authentication_resource_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, + pub http_method: HttpMethod, pub is_async: bool, pub is_flow: bool, pub is_static_website: bool, pub path: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub raw_string: Option, - pub requires_auth: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, pub route_path: String, pub script_path: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub static_asset_config: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub summary: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub workspaced_route: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub wrap_body: Option, @@ -3892,77 +4845,6 @@ the execution of this script will be permissioned_as and by extension its DT_TOK value.clone() } } - #[derive( - Clone, - Copy, - Debug, - Deserialize, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - Serialize - )] - pub enum NewHttpTriggerHttpMethod { - #[serde(rename = "get")] - Get, - #[serde(rename = "post")] - Post, - #[serde(rename = "put")] - Put, - #[serde(rename = "delete")] - Delete, - #[serde(rename = "patch")] - Patch, - } - impl From<&NewHttpTriggerHttpMethod> for NewHttpTriggerHttpMethod { - fn from(value: &NewHttpTriggerHttpMethod) -> Self { - value.clone() - } - } - impl ToString for NewHttpTriggerHttpMethod { - fn to_string(&self) -> String { - match *self { - Self::Get => "get".to_string(), - Self::Post => "post".to_string(), - Self::Put => "put".to_string(), - Self::Delete => "delete".to_string(), - Self::Patch => "patch".to_string(), - } - } - } - impl std::str::FromStr for NewHttpTriggerHttpMethod { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "get" => Ok(Self::Get), - "post" => Ok(Self::Post), - "put" => Ok(Self::Put), - "delete" => Ok(Self::Delete), - "patch" => Ok(Self::Patch), - _ => Err("invalid value"), - } - } - } - impl std::convert::TryFrom<&str> for NewHttpTriggerHttpMethod { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - impl std::convert::TryFrom<&String> for NewHttpTriggerHttpMethod { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - impl std::convert::TryFrom for NewHttpTriggerHttpMethod { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() - } - } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NewHttpTriggerStaticAssetConfig { #[serde(default, skip_serializing_if = "Option::is_none")] @@ -3980,10 +4862,16 @@ the execution of this script will be permissioned_as and by extension its DT_TOK pub struct NewKafkaTrigger { #[serde(default, skip_serializing_if = "Option::is_none")] pub enabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, pub group_id: String, pub is_flow: bool, pub kafka_resource_path: String, pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, pub script_path: String, pub topics: Vec, } @@ -4000,9 +4888,15 @@ the execution of this script will be permissioned_as and by extension its DT_TOK pub client_version: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub enabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, pub is_flow: bool, pub mqtt_resource_path: String, pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, pub script_path: String, pub subscribe_topics: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -4021,9 +4915,15 @@ the execution of this script will be permissioned_as and by extension its DT_TOK pub consumer_name: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub enabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, pub is_flow: bool, pub nats_resource_path: String, pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, pub script_path: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub stream_name: Option, @@ -4038,6 +4938,10 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NewPostgresTrigger { pub enabled: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, pub is_flow: bool, pub path: String, pub postgres_resource_path: String, @@ -4047,6 +4951,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK pub publication_name: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub replication_slot_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, pub script_path: String, } impl From<&NewPostgresTrigger> for NewPostgresTrigger { @@ -4057,43 +4963,62 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NewSchedule { pub args: ScriptArgs, + ///The version of the cron schedule to use (last is v2) #[serde(default, skip_serializing_if = "Option::is_none")] pub cron_version: Option, + ///The description of the schedule + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + ///Whether the schedule is enabled #[serde(default, skip_serializing_if = "Option::is_none")] pub enabled: Option, + ///Whether the schedule is for a flow pub is_flow: bool, + ///Whether the schedule should not run if a flow is already running #[serde(default, skip_serializing_if = "Option::is_none")] pub no_flow_overlap: Option, + ///The path to the script or flow to trigger on failure #[serde(default, skip_serializing_if = "Option::is_none")] pub on_failure: Option, + ///Whether the schedule should only run on the exact time #[serde(default, skip_serializing_if = "Option::is_none")] pub on_failure_exact: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub on_failure_extra_args: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub on_failure_times: Option, + ///The path to the script or flow to trigger on recovery #[serde(default, skip_serializing_if = "Option::is_none")] pub on_recovery: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub on_recovery_extra_args: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub on_recovery_times: Option, + ///The path to the script or flow to trigger on success #[serde(default, skip_serializing_if = "Option::is_none")] pub on_success: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub on_success_extra_args: Option, + ///The path where the schedule will be created pub path: String, + ///The date and time the schedule will be paused until #[serde(default, skip_serializing_if = "Option::is_none")] pub paused_until: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub retry: Option, + ///The cron schedule to trigger the script or flow. Should include seconds. pub schedule: String, + ///The path to the script or flow to trigger pub script_path: String, + ///The summary of the schedule #[serde(default, skip_serializing_if = "Option::is_none")] pub summary: Option, + ///The tag of the schedule #[serde(default, skip_serializing_if = "Option::is_none")] pub tag: Option, + ///The timezone to use for the cron schedule pub timezone: String, + ///Whether the WebSocket error handler is muted #[serde(default, skip_serializing_if = "Option::is_none")] pub ws_error_handler_muted: Option, } @@ -4104,6 +5029,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NewScript { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub assets: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] pub cache_ttl: Option, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -4163,6 +5090,146 @@ the execution of this script will be permissioned_as and by extension its DT_TOK value.clone() } } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct NewScriptAssetsItem { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub access_type: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub alt_access_type: Option, + pub kind: AssetKind, + pub path: String, + } + impl From<&NewScriptAssetsItem> for NewScriptAssetsItem { + fn from(value: &NewScriptAssetsItem) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum NewScriptAssetsItemAccessType { + #[serde(rename = "r")] + R, + #[serde(rename = "w")] + W, + #[serde(rename = "rw")] + Rw, + } + impl From<&NewScriptAssetsItemAccessType> for NewScriptAssetsItemAccessType { + fn from(value: &NewScriptAssetsItemAccessType) -> Self { + value.clone() + } + } + impl ToString for NewScriptAssetsItemAccessType { + fn to_string(&self) -> String { + match *self { + Self::R => "r".to_string(), + Self::W => "w".to_string(), + Self::Rw => "rw".to_string(), + } + } + } + impl std::str::FromStr for NewScriptAssetsItemAccessType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "r" => Ok(Self::R), + "w" => Ok(Self::W), + "rw" => Ok(Self::Rw), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for NewScriptAssetsItemAccessType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for NewScriptAssetsItemAccessType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for NewScriptAssetsItemAccessType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum NewScriptAssetsItemAltAccessType { + #[serde(rename = "r")] + R, + #[serde(rename = "w")] + W, + #[serde(rename = "rw")] + Rw, + } + impl From<&NewScriptAssetsItemAltAccessType> for NewScriptAssetsItemAltAccessType { + fn from(value: &NewScriptAssetsItemAltAccessType) -> Self { + value.clone() + } + } + impl ToString for NewScriptAssetsItemAltAccessType { + fn to_string(&self) -> String { + match *self { + Self::R => "r".to_string(), + Self::W => "w".to_string(), + Self::Rw => "rw".to_string(), + } + } + } + impl std::str::FromStr for NewScriptAssetsItemAltAccessType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "r" => Ok(Self::R), + "w" => Ok(Self::W), + "rw" => Ok(Self::Rw), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for NewScriptAssetsItemAltAccessType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for NewScriptAssetsItemAltAccessType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for NewScriptAssetsItemAltAccessType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } #[derive( Clone, Copy, @@ -4253,14 +5320,21 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct NewSqsTrigger { + pub aws_auth_resource_type: AwsAuthResourceType, pub aws_resource_path: String, #[serde(default, skip_serializing_if = "Option::is_none")] pub enabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, pub is_flow: bool, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub message_attributes: Vec, pub path: String, pub queue_url: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, pub script_path: String, } impl From<&NewSqsTrigger> for NewSqsTrigger { @@ -4304,11 +5378,17 @@ the execution of this script will be permissioned_as and by extension its DT_TOK pub can_return_message: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub enabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, pub filters: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub initial_messages: Vec, pub is_flow: bool, pub path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, pub script_path: String, pub url: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -4383,6 +5463,121 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OpenapiHttpRouteFilters { + pub folder_regex: String, + pub path_regex: String, + pub route_path_regex: String, + } + impl From<&OpenapiHttpRouteFilters> for OpenapiHttpRouteFilters { + fn from(value: &OpenapiHttpRouteFilters) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum OpenapiSpecFormat { + #[serde(rename = "yaml")] + Yaml, + #[serde(rename = "json")] + Json, + } + impl From<&OpenapiSpecFormat> for OpenapiSpecFormat { + fn from(value: &OpenapiSpecFormat) -> Self { + value.clone() + } + } + impl ToString for OpenapiSpecFormat { + fn to_string(&self) -> String { + match *self { + Self::Yaml => "yaml".to_string(), + Self::Json => "json".to_string(), + } + } + } + impl std::str::FromStr for OpenapiSpecFormat { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "yaml" => Ok(Self::Yaml), + "json" => Ok(Self::Json), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for OpenapiSpecFormat { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for OpenapiSpecFormat { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for OpenapiSpecFormat { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OpenapiV3Info { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub contact: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub license: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub terms_of_service: Option, + pub title: String, + pub version: String, + } + impl From<&OpenapiV3Info> for OpenapiV3Info { + fn from(value: &OpenapiV3Info) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OpenapiV3InfoContact { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub email: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url: Option, + } + impl From<&OpenapiV3InfoContact> for OpenapiV3InfoContact { + fn from(value: &OpenapiV3InfoContact) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct OpenapiV3InfoLicense { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub identifier: Option, + pub name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url: Option, + } + impl From<&OpenapiV3InfoLicense> for OpenapiV3InfoLicense { + fn from(value: &OpenapiV3InfoLicense) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct OperatorSettings(pub Option); impl std::ops::Deref for OperatorSettings { type Target = Option; @@ -4407,6 +5602,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct OperatorSettingsInner { + ///Whether operators can view assets + pub assets: bool, ///Whether operators can view audit logs pub audit_logs: bool, ///Whether operators can view folders page @@ -4689,11 +5886,17 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Option::is_none")] pub error: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub last_server_ping: Option>, pub postgres_resource_path: String, pub publication_name: String, pub replication_slot_name: String, #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub server_id: Option, } impl From<&PostgresTrigger> for PostgresTrigger { @@ -4717,6 +5920,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Option::is_none")] pub path: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub script_hash: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub tag: Option, } impl From<&Preview> for Preview { @@ -4799,6 +6004,17 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct PushConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub audience: Option, + pub authenticate: bool, + } + impl From<&PushConfig> for PushConfig { + fn from(value: &PushConfig) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct QueuedJob { #[serde(default, skip_serializing_if = "Option::is_none")] pub aggregate_wait_time_ms: Option, @@ -4861,6 +6077,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Option::is_none")] pub worker: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub workflow_as_code_status: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub workspace_id: Option, } impl From<&QueuedJob> for QueuedJob { @@ -4977,6 +6195,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct RawScript { + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub assets: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] pub concurrency_time_window_s: Option, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -5003,6 +6223,209 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct RawScriptAssetsItem { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub access_type: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub alt_access_type: Option, + pub kind: RawScriptAssetsItemKind, + pub path: String, + } + impl From<&RawScriptAssetsItem> for RawScriptAssetsItem { + fn from(value: &RawScriptAssetsItem) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum RawScriptAssetsItemAccessType { + #[serde(rename = "r")] + R, + #[serde(rename = "w")] + W, + #[serde(rename = "rw")] + Rw, + } + impl From<&RawScriptAssetsItemAccessType> for RawScriptAssetsItemAccessType { + fn from(value: &RawScriptAssetsItemAccessType) -> Self { + value.clone() + } + } + impl ToString for RawScriptAssetsItemAccessType { + fn to_string(&self) -> String { + match *self { + Self::R => "r".to_string(), + Self::W => "w".to_string(), + Self::Rw => "rw".to_string(), + } + } + } + impl std::str::FromStr for RawScriptAssetsItemAccessType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "r" => Ok(Self::R), + "w" => Ok(Self::W), + "rw" => Ok(Self::Rw), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for RawScriptAssetsItemAccessType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for RawScriptAssetsItemAccessType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for RawScriptAssetsItemAccessType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum RawScriptAssetsItemAltAccessType { + #[serde(rename = "r")] + R, + #[serde(rename = "w")] + W, + #[serde(rename = "rw")] + Rw, + } + impl From<&RawScriptAssetsItemAltAccessType> for RawScriptAssetsItemAltAccessType { + fn from(value: &RawScriptAssetsItemAltAccessType) -> Self { + value.clone() + } + } + impl ToString for RawScriptAssetsItemAltAccessType { + fn to_string(&self) -> String { + match *self { + Self::R => "r".to_string(), + Self::W => "w".to_string(), + Self::Rw => "rw".to_string(), + } + } + } + impl std::str::FromStr for RawScriptAssetsItemAltAccessType { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "r" => Ok(Self::R), + "w" => Ok(Self::W), + "rw" => Ok(Self::Rw), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for RawScriptAssetsItemAltAccessType { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for RawScriptAssetsItemAltAccessType { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for RawScriptAssetsItemAltAccessType { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum RawScriptAssetsItemKind { + #[serde(rename = "s3object")] + S3object, + #[serde(rename = "resource")] + Resource, + #[serde(rename = "ducklake")] + Ducklake, + } + impl From<&RawScriptAssetsItemKind> for RawScriptAssetsItemKind { + fn from(value: &RawScriptAssetsItemKind) -> Self { + value.clone() + } + } + impl ToString for RawScriptAssetsItemKind { + fn to_string(&self) -> String { + match *self { + Self::S3object => "s3object".to_string(), + Self::Resource => "resource".to_string(), + Self::Ducklake => "ducklake".to_string(), + } + } + } + impl std::str::FromStr for RawScriptAssetsItemKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "s3object" => Ok(Self::S3object), + "resource" => Ok(Self::Resource), + "ducklake" => Ok(Self::Ducklake), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for RawScriptAssetsItemKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for RawScriptAssetsItemKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for RawScriptAssetsItemKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct RawScriptForDependencies { pub language: ScriptLang, pub path: String, @@ -5299,6 +6722,65 @@ the execution of this script will be permissioned_as and by extension its DT_TOK PartialOrd, Serialize )] + pub enum RunnableKind { + #[serde(rename = "script")] + Script, + #[serde(rename = "flow")] + Flow, + } + impl From<&RunnableKind> for RunnableKind { + fn from(value: &RunnableKind) -> Self { + value.clone() + } + } + impl ToString for RunnableKind { + fn to_string(&self) -> String { + match *self { + Self::Script => "script".to_string(), + Self::Flow => "flow".to_string(), + } + } + } + impl std::str::FromStr for RunnableKind { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "script" => Ok(Self::Script), + "flow" => Ok(Self::Flow), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for RunnableKind { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for RunnableKind { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for RunnableKind { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] pub enum RunnableType { ScriptHash, ScriptPath, @@ -5348,6 +6830,21 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct S3Object { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub filename: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub presigned: Option, + pub s3: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub storage: Option, + } + impl From<&S3Object> for S3Object { + fn from(value: &S3Object) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct S3Resource { #[serde(rename = "accessKey", default, skip_serializing_if = "Option::is_none")] pub access_key: Option, @@ -5384,6 +6881,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK pub args: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub cron_version: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, pub edited_at: chrono::DateTime, pub edited_by: String, pub email: String, @@ -5422,8 +6921,6 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Option::is_none")] pub summary: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub description: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] pub tag: Option, pub timezone: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -5458,6 +6955,31 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ScopeDefinition { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + pub label: String, + pub requires_resource_path: bool, + pub value: String, + } + impl From<&ScopeDefinition> for ScopeDefinition { + fn from(value: &ScopeDefinition) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct ScopeDomain { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + pub name: String, + pub scopes: Vec, + } + impl From<&ScopeDomain> for ScopeDomain { + fn from(value: &ScopeDomain) -> Self { + value.clone() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Script { pub archived: bool, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -5687,6 +7209,12 @@ the execution of this script will be permissioned_as and by extension its DT_TOK Csharp, #[serde(rename = "nu")] Nu, + #[serde(rename = "java")] + Java, + #[serde(rename = "ruby")] + Ruby, + #[serde(rename = "duckdb")] + Duckdb, } impl From<&ScriptLang> for ScriptLang { fn from(value: &ScriptLang) -> Self { @@ -5715,6 +7243,9 @@ the execution of this script will be permissioned_as and by extension its DT_TOK Self::Ansible => "ansible".to_string(), Self::Csharp => "csharp".to_string(), Self::Nu => "nu".to_string(), + Self::Java => "java".to_string(), + Self::Ruby => "ruby".to_string(), + Self::Duckdb => "duckdb".to_string(), } } } @@ -5741,6 +7272,9 @@ the execution of this script will be permissioned_as and by extension its DT_TOK "ansible" => Ok(Self::Ansible), "csharp" => Ok(Self::Csharp), "nu" => Ok(Self::Nu), + "java" => Ok(Self::Java), + "ruby" => Ok(Self::Ruby), + "duckdb" => Ok(Self::Duckdb), _ => Err("invalid value"), } } @@ -5809,16 +7343,23 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct SqsTrigger { + pub aws_auth_resource_type: AwsAuthResourceType, pub aws_resource_path: String, pub enabled: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub error: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub last_server_ping: Option>, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub message_attributes: Vec, pub queue_url: String, #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub server_id: Option, } impl From<&SqsTrigger> for SqsTrigger { @@ -5830,8 +7371,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK pub struct StaticTransform { #[serde(rename = "type")] pub type_: StaticTransformType, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub value: Option, + pub value: serde_json::Value, } impl From<&StaticTransform> for StaticTransform { fn from(value: &StaticTransform) -> Self { @@ -5851,8 +7391,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK Serialize )] pub enum StaticTransformType { - #[serde(rename = "javascript")] - Javascript, + #[serde(rename = "static")] + Static, } impl From<&StaticTransformType> for StaticTransformType { fn from(value: &StaticTransformType) -> Self { @@ -5862,7 +7402,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK impl ToString for StaticTransformType { fn to_string(&self) -> String { match *self { - Self::Javascript => "javascript".to_string(), + Self::Static => "static".to_string(), } } } @@ -5870,7 +7410,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK type Err = &'static str; fn from_str(value: &str) -> Result { match value { - "javascript" => Ok(Self::Javascript), + "static" => Ok(Self::Static), _ => Err("invalid value"), } } @@ -5894,6 +7434,79 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct StopAfterIf { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_message: Option, + pub expr: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skip_if_stopped: Option, + } + impl From<&StopAfterIf> for StopAfterIf { + fn from(value: &StopAfterIf) -> Self { + value.clone() + } + } + ///The mode of subscription. 'existing' means using an existing GCP subscription, while 'create_update' involves creating or updating a new subscription. + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum SubscriptionMode { + #[serde(rename = "existing")] + Existing, + #[serde(rename = "create_update")] + CreateUpdate, + } + impl From<&SubscriptionMode> for SubscriptionMode { + fn from(value: &SubscriptionMode) -> Self { + value.clone() + } + } + impl ToString for SubscriptionMode { + fn to_string(&self) -> String { + match *self { + Self::Existing => "existing".to_string(), + Self::CreateUpdate => "create_update".to_string(), + } + } + } + impl std::str::FromStr for SubscriptionMode { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "existing" => Ok(Self::Existing), + "create_update" => Ok(Self::CreateUpdate), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for SubscriptionMode { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for SubscriptionMode { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for SubscriptionMode { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct TableToTrack(pub Vec); impl std::ops::Deref for TableToTrack { type Target = Vec; @@ -5944,6 +7557,254 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct TeamsChannel { + ///Microsoft Teams channel ID + pub channel_id: TeamsChannelChannelId, + ///Microsoft Teams channel name + pub channel_name: TeamsChannelChannelName, + ///Microsoft Teams team ID + pub team_id: TeamsChannelTeamId, + ///Microsoft Teams team name + pub team_name: TeamsChannelTeamName, + } + impl From<&TeamsChannel> for TeamsChannel { + fn from(value: &TeamsChannel) -> Self { + value.clone() + } + } + ///Microsoft Teams channel ID + #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub struct TeamsChannelChannelId(String); + impl std::ops::Deref for TeamsChannelChannelId { + type Target = String; + fn deref(&self) -> &String { + &self.0 + } + } + impl From for String { + fn from(value: TeamsChannelChannelId) -> Self { + value.0 + } + } + impl From<&TeamsChannelChannelId> for TeamsChannelChannelId { + fn from(value: &TeamsChannelChannelId) -> Self { + value.clone() + } + } + impl std::str::FromStr for TeamsChannelChannelId { + type Err = &'static str; + fn from_str(value: &str) -> Result { + if value.len() < 1usize { + return Err("shorter than 1 characters"); + } + Ok(Self(value.to_string())) + } + } + impl std::convert::TryFrom<&str> for TeamsChannelChannelId { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for TeamsChannelChannelId { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for TeamsChannelChannelId { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + impl<'de> serde::Deserialize<'de> for TeamsChannelChannelId { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + String::deserialize(deserializer)? + .parse() + .map_err(|e: &'static str| { + ::custom(e.to_string()) + }) + } + } + ///Microsoft Teams channel name + #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub struct TeamsChannelChannelName(String); + impl std::ops::Deref for TeamsChannelChannelName { + type Target = String; + fn deref(&self) -> &String { + &self.0 + } + } + impl From for String { + fn from(value: TeamsChannelChannelName) -> Self { + value.0 + } + } + impl From<&TeamsChannelChannelName> for TeamsChannelChannelName { + fn from(value: &TeamsChannelChannelName) -> Self { + value.clone() + } + } + impl std::str::FromStr for TeamsChannelChannelName { + type Err = &'static str; + fn from_str(value: &str) -> Result { + if value.len() < 1usize { + return Err("shorter than 1 characters"); + } + Ok(Self(value.to_string())) + } + } + impl std::convert::TryFrom<&str> for TeamsChannelChannelName { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for TeamsChannelChannelName { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for TeamsChannelChannelName { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + impl<'de> serde::Deserialize<'de> for TeamsChannelChannelName { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + String::deserialize(deserializer)? + .parse() + .map_err(|e: &'static str| { + ::custom(e.to_string()) + }) + } + } + ///Microsoft Teams team ID + #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub struct TeamsChannelTeamId(String); + impl std::ops::Deref for TeamsChannelTeamId { + type Target = String; + fn deref(&self) -> &String { + &self.0 + } + } + impl From for String { + fn from(value: TeamsChannelTeamId) -> Self { + value.0 + } + } + impl From<&TeamsChannelTeamId> for TeamsChannelTeamId { + fn from(value: &TeamsChannelTeamId) -> Self { + value.clone() + } + } + impl std::str::FromStr for TeamsChannelTeamId { + type Err = &'static str; + fn from_str(value: &str) -> Result { + if value.len() < 1usize { + return Err("shorter than 1 characters"); + } + Ok(Self(value.to_string())) + } + } + impl std::convert::TryFrom<&str> for TeamsChannelTeamId { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for TeamsChannelTeamId { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for TeamsChannelTeamId { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + impl<'de> serde::Deserialize<'de> for TeamsChannelTeamId { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + String::deserialize(deserializer)? + .parse() + .map_err(|e: &'static str| { + ::custom(e.to_string()) + }) + } + } + ///Microsoft Teams team name + #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] + pub struct TeamsChannelTeamName(String); + impl std::ops::Deref for TeamsChannelTeamName { + type Target = String; + fn deref(&self) -> &String { + &self.0 + } + } + impl From for String { + fn from(value: TeamsChannelTeamName) -> Self { + value.0 + } + } + impl From<&TeamsChannelTeamName> for TeamsChannelTeamName { + fn from(value: &TeamsChannelTeamName) -> Self { + value.clone() + } + } + impl std::str::FromStr for TeamsChannelTeamName { + type Err = &'static str; + fn from_str(value: &str) -> Result { + if value.len() < 1usize { + return Err("shorter than 1 characters"); + } + Ok(Self(value.to_string())) + } + } + impl std::convert::TryFrom<&str> for TeamsChannelTeamName { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for TeamsChannelTeamName { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for TeamsChannelTeamName { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + impl<'de> serde::Deserialize<'de> for TeamsChannelTeamName { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + String::deserialize(deserializer)? + .parse() + .map_err(|e: &'static str| { + ::custom(e.to_string()) + }) + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct TemplateScript { pub language: Language, pub postgres_resource_path: String, @@ -5971,6 +7832,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Option::is_none")] pub expires_in: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub grant_type: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub refresh_token: Option, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub scope: Vec, @@ -6001,6 +7864,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Option::is_none")] pub email_count: Option, #[serde(default, skip_serializing_if = "Option::is_none")] + pub gcp_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub http_routes_count: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub kafka_count: Option, @@ -6078,6 +7943,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct User { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub added_via: Option, pub created_at: chrono::DateTime, pub disabled: bool, pub email: String, @@ -6098,6 +7965,86 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct UserSource { + ///The domain used for auto-invite (when source is 'domain') + #[serde(default, skip_serializing_if = "Option::is_none")] + pub domain: Option, + ///The instance group name (when source is 'instance_group') + #[serde(default, skip_serializing_if = "Option::is_none")] + pub group: Option, + ///How the user was added to the workspace + pub source: UserSourceSource, + } + impl From<&UserSource> for UserSource { + fn from(value: &UserSource) -> Self { + value.clone() + } + } + ///How the user was added to the workspace + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum UserSourceSource { + #[serde(rename = "domain")] + Domain, + #[serde(rename = "instance_group")] + InstanceGroup, + #[serde(rename = "manual")] + Manual, + } + impl From<&UserSourceSource> for UserSourceSource { + fn from(value: &UserSourceSource) -> Self { + value.clone() + } + } + impl ToString for UserSourceSource { + fn to_string(&self) -> String { + match *self { + Self::Domain => "domain".to_string(), + Self::InstanceGroup => "instance_group".to_string(), + Self::Manual => "manual".to_string(), + } + } + } + impl std::str::FromStr for UserSourceSource { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "domain" => Ok(Self::Domain), + "instance_group" => Ok(Self::InstanceGroup), + "manual" => Ok(Self::Manual), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for UserSourceSource { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for UserSourceSource { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for UserSourceSource { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct UserUsage { #[serde(default, skip_serializing_if = "Option::is_none")] pub email: Option, @@ -6134,17 +8081,98 @@ the execution of this script will be permissioned_as and by extension its DT_TOK } } #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WebhookFilters { + pub path: String, + pub runnable_kind: RunnableKind, + pub user_or_folder_regex: WebhookFiltersUserOrFolderRegex, + pub user_or_folder_regex_value: String, + } + impl From<&WebhookFilters> for WebhookFilters { + fn from(value: &WebhookFilters) -> Self { + value.clone() + } + } + #[derive( + Clone, + Copy, + Debug, + Deserialize, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize + )] + pub enum WebhookFiltersUserOrFolderRegex { + #[serde(rename = "*")] + X, + #[serde(rename = "u")] + U, + #[serde(rename = "f")] + F, + } + impl From<&WebhookFiltersUserOrFolderRegex> for WebhookFiltersUserOrFolderRegex { + fn from(value: &WebhookFiltersUserOrFolderRegex) -> Self { + value.clone() + } + } + impl ToString for WebhookFiltersUserOrFolderRegex { + fn to_string(&self) -> String { + match *self { + Self::X => "*".to_string(), + Self::U => "u".to_string(), + Self::F => "f".to_string(), + } + } + } + impl std::str::FromStr for WebhookFiltersUserOrFolderRegex { + type Err = &'static str; + fn from_str(value: &str) -> Result { + match value { + "*" => Ok(Self::X), + "u" => Ok(Self::U), + "f" => Ok(Self::F), + _ => Err("invalid value"), + } + } + } + impl std::convert::TryFrom<&str> for WebhookFiltersUserOrFolderRegex { + type Error = &'static str; + fn try_from(value: &str) -> Result { + value.parse() + } + } + impl std::convert::TryFrom<&String> for WebhookFiltersUserOrFolderRegex { + type Error = &'static str; + fn try_from(value: &String) -> Result { + value.parse() + } + } + impl std::convert::TryFrom for WebhookFiltersUserOrFolderRegex { + type Error = &'static str; + fn try_from(value: String) -> Result { + value.parse() + } + } + #[derive(Clone, Debug, Deserialize, Serialize)] pub struct WebsocketTrigger { pub can_return_message: bool, pub enabled: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_args: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error_handler_path: Option, pub filters: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub initial_messages: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] pub last_server_ping: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] + pub retry: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub server_id: Option, pub url: String, #[serde(default, skip_serializing_if = "Option::is_none")] @@ -6206,8 +8234,8 @@ the execution of this script will be permissioned_as and by extension its DT_TOK Serialize )] pub enum WhileloopFlowType { - #[serde(rename = "forloopflow")] - Forloopflow, + #[serde(rename = "whileloopflow")] + Whileloopflow, } impl From<&WhileloopFlowType> for WhileloopFlowType { fn from(value: &WhileloopFlowType) -> Self { @@ -6217,7 +8245,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK impl ToString for WhileloopFlowType { fn to_string(&self) -> String { match *self { - Self::Forloopflow => "forloopflow".to_string(), + Self::Whileloopflow => "whileloopflow".to_string(), } } } @@ -6225,7 +8253,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK type Err = &'static str; fn from_str(value: &str) -> Result { match value { - "forloopflow" => Ok(Self::Forloopflow), + "whileloopflow" => Ok(Self::Whileloopflow), _ => Err("invalid value"), } } @@ -6476,99 +8504,15 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[serde(default, skip_serializing_if = "Vec::is_empty")] pub include_path: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub include_type: Vec, + pub include_type: Vec, } impl From<&WorkspaceDeployUiSettings> for WorkspaceDeployUiSettings { fn from(value: &WorkspaceDeployUiSettings) -> Self { value.clone() } } - #[derive( - Clone, - Copy, - Debug, - Deserialize, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - Serialize - )] - pub enum WorkspaceDeployUiSettingsIncludeTypeItem { - #[serde(rename = "script")] - Script, - #[serde(rename = "flow")] - Flow, - #[serde(rename = "app")] - App, - #[serde(rename = "resource")] - Resource, - #[serde(rename = "variable")] - Variable, - #[serde(rename = "secret")] - Secret, - #[serde(rename = "trigger")] - Trigger, - } - impl From<&WorkspaceDeployUiSettingsIncludeTypeItem> - for WorkspaceDeployUiSettingsIncludeTypeItem { - fn from(value: &WorkspaceDeployUiSettingsIncludeTypeItem) -> Self { - value.clone() - } - } - impl ToString for WorkspaceDeployUiSettingsIncludeTypeItem { - fn to_string(&self) -> String { - match *self { - Self::Script => "script".to_string(), - Self::Flow => "flow".to_string(), - Self::App => "app".to_string(), - Self::Resource => "resource".to_string(), - Self::Variable => "variable".to_string(), - Self::Secret => "secret".to_string(), - Self::Trigger => "trigger".to_string(), - } - } - } - impl std::str::FromStr for WorkspaceDeployUiSettingsIncludeTypeItem { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "script" => Ok(Self::Script), - "flow" => Ok(Self::Flow), - "app" => Ok(Self::App), - "resource" => Ok(Self::Resource), - "variable" => Ok(Self::Variable), - "secret" => Ok(Self::Secret), - "trigger" => Ok(Self::Trigger), - _ => Err("invalid value"), - } - } - } - impl std::convert::TryFrom<&str> for WorkspaceDeployUiSettingsIncludeTypeItem { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - impl std::convert::TryFrom<&String> for WorkspaceDeployUiSettingsIncludeTypeItem { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - impl std::convert::TryFrom for WorkspaceDeployUiSettingsIncludeTypeItem { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() - } - } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct WorkspaceGitSyncSettings { - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub include_path: Vec, - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub include_type: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub repositories: Vec, } @@ -6577,100 +8521,28 @@ the execution of this script will be permissioned_as and by extension its DT_TOK value.clone() } } - #[derive( - Clone, - Copy, - Debug, - Deserialize, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - Serialize - )] - pub enum WorkspaceGitSyncSettingsIncludeTypeItem { - #[serde(rename = "script")] - Script, - #[serde(rename = "flow")] - Flow, - #[serde(rename = "app")] - App, - #[serde(rename = "folder")] - Folder, - #[serde(rename = "resource")] - Resource, - #[serde(rename = "variable")] - Variable, - #[serde(rename = "secret")] - Secret, - #[serde(rename = "resourcetype")] - Resourcetype, - #[serde(rename = "schedule")] - Schedule, - #[serde(rename = "user")] - User, - #[serde(rename = "group")] - Group, + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkspaceGithubInstallation { + pub account_id: String, + pub installation_id: f64, } - impl From<&WorkspaceGitSyncSettingsIncludeTypeItem> - for WorkspaceGitSyncSettingsIncludeTypeItem { - fn from(value: &WorkspaceGitSyncSettingsIncludeTypeItem) -> Self { + impl From<&WorkspaceGithubInstallation> for WorkspaceGithubInstallation { + fn from(value: &WorkspaceGithubInstallation) -> Self { value.clone() } } - impl ToString for WorkspaceGitSyncSettingsIncludeTypeItem { - fn to_string(&self) -> String { - match *self { - Self::Script => "script".to_string(), - Self::Flow => "flow".to_string(), - Self::App => "app".to_string(), - Self::Folder => "folder".to_string(), - Self::Resource => "resource".to_string(), - Self::Variable => "variable".to_string(), - Self::Secret => "secret".to_string(), - Self::Resourcetype => "resourcetype".to_string(), - Self::Schedule => "schedule".to_string(), - Self::User => "user".to_string(), - Self::Group => "group".to_string(), - } - } + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct WorkspaceInfo { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub role: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_name: Option, } - impl std::str::FromStr for WorkspaceGitSyncSettingsIncludeTypeItem { - type Err = &'static str; - fn from_str(value: &str) -> Result { - match value { - "script" => Ok(Self::Script), - "flow" => Ok(Self::Flow), - "app" => Ok(Self::App), - "folder" => Ok(Self::Folder), - "resource" => Ok(Self::Resource), - "variable" => Ok(Self::Variable), - "secret" => Ok(Self::Secret), - "resourcetype" => Ok(Self::Resourcetype), - "schedule" => Ok(Self::Schedule), - "user" => Ok(Self::User), - "group" => Ok(Self::Group), - _ => Err("invalid value"), - } - } - } - impl std::convert::TryFrom<&str> for WorkspaceGitSyncSettingsIncludeTypeItem { - type Error = &'static str; - fn try_from(value: &str) -> Result { - value.parse() - } - } - impl std::convert::TryFrom<&String> for WorkspaceGitSyncSettingsIncludeTypeItem { - type Error = &'static str; - fn try_from(value: &String) -> Result { - value.parse() - } - } - impl std::convert::TryFrom for WorkspaceGitSyncSettingsIncludeTypeItem { - type Error = &'static str; - fn try_from(value: String) -> Result { - value.parse() + impl From<&WorkspaceInfo> for WorkspaceInfo { + fn from(value: &WorkspaceInfo) -> Self { + value.clone() } } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -6689,7 +8561,7 @@ the execution of this script will be permissioned_as and by extension its DT_TOK #[derive(Clone, Debug)] /**Client for Windmill API -Version: 1.478.1*/ +Version: 1.526.1*/ pub struct Client { pub(crate) baseurl: String, pub(crate) client: reqwest::Client, @@ -6735,7 +8607,7 @@ impl Client { /// This string is pulled directly from the source OpenAPI /// document and may be in any format the API selects. pub fn api_version(&self) -> &'static str { - "1.478.1" + "1.526.1" } } impl Client { diff --git a/backend/windmill-api/openapi-deref.json b/backend/windmill-api/openapi-deref.json index 1186be6069..fc711cbbf1 100644 --- a/backend/windmill-api/openapi-deref.json +++ b/backend/windmill-api/openapi-deref.json @@ -1,7 +1,7 @@ { "openapi": "3.0.3", "info": { - "version": "1.492.1", + "version": "1.520.1", "title": "Windmill API", "contact": { "name": "Windmill Team", @@ -573,6 +573,10 @@ }, "company": { "type": "string" + }, + "skip_email": { + "type": "boolean", + "description": "Skip sending email notifications to the user" } }, "required": [ @@ -889,7 +893,7 @@ "summary": "get connected repositories", "operationId": "getGlobalConnectedRepositories", "tags": [ - "git_sync" + "Git Sync" ], "responses": { "200": { @@ -1118,6 +1122,73 @@ } } }, + "/settings/databases_exist": { + "post": { + "summary": "checks that all given databases exist or else return the ones that don't", + "operationId": "databasesExist", + "tags": [ + "setting" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "responses": { + "200": { + "description": "databases that do not exist", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "/settings/create_ducklake_database/{name}": { + "post": { + "summary": "Runs CREATE DATABASE on the Windmill Postgres and grants access to the ducklake_user", + "operationId": "createDucklakeDatabase", + "tags": [ + "setting" + ], + "parameters": [ + { + "in": "path", + "name": "name", + "required": true, + "schema": { + "type": "string" + }, + "description": "The name of the database to create" + } + ], + "responses": { + "200": { + "description": "status", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, "/settings/global/{key}": { "get": { "summary": "get global settings", @@ -2933,6 +3004,9 @@ "large_file_storage": { "$ref": "#/components/schemas/LargeFileStorage" }, + "ducklake": { + "$ref": "#/components/schemas/DucklakeSettings" + }, "git_sync": { "$ref": "#/components/schemas/WorkspaceGitSyncSettings" }, @@ -3738,6 +3812,78 @@ } } }, + "/w/{workspace}/workspaces/list_ducklakes": { + "get": { + "summary": "list ducklakes", + "operationId": "listDucklakes", + "tags": [ + "workspace" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "responses": { + "200": { + "description": "status", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "/w/{workspace}/workspaces/edit_ducklake_config": { + "post": { + "summary": "edit ducklake settings", + "operationId": "editDucklakeConfig", + "tags": [ + "workspace" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "Ducklake settings", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "settings" + ], + "properties": { + "settings": { + "$ref": "#/components/schemas/DucklakeSettings" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "status", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, "/w/{workspace}/workspaces/edit_git_sync_config": { "post": { "summary": "edit workspace git sync settings", @@ -3778,6 +3924,98 @@ } } }, + "/w/{workspace}/workspaces/edit_git_sync_repository": { + "post": { + "summary": "add or update individual git sync repository", + "operationId": "editGitSyncRepository", + "tags": [ + "workspace" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "Git sync repository settings to add or update", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "git_repo_resource_path": { + "type": "string", + "description": "The resource path of the git repository to update" + }, + "repository": { + "$ref": "#/components/schemas/GitRepositorySettings" + } + }, + "required": [ + "git_repo_resource_path", + "repository" + ] + } + } + } + }, + "responses": { + "200": { + "description": "status", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, + "/w/{workspace}/workspaces/delete_git_sync_repository": { + "delete": { + "summary": "delete individual git sync repository", + "operationId": "deleteGitSyncRepository", + "tags": [ + "workspace" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "Git sync repository to delete", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "git_repo_resource_path": { + "type": "string", + "description": "The resource path of the git repository to delete" + } + }, + "required": [ + "git_repo_resource_path" + ] + } + } + } + }, + "responses": { + "200": { + "description": "status", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, "/w/{workspace}/workspaces/edit_deploy_ui_config": { "post": { "summary": "edit workspace deploy ui settings", @@ -4314,6 +4552,30 @@ } } }, + "/tokens/list/scopes": { + "get": { + "summary": "list of available scopes", + "operationId": "ListAvailableScopes", + "tags": [ + "token" + ], + "responses": { + "200": { + "description": "list of available scopes", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ScopeDomain" + } + } + } + } + } + } + } + }, "/users/tokens/create": { "post": { "summary": "create token", @@ -4486,6 +4748,7 @@ "post": { "summary": "create variable", "operationId": "createVariable", + "x-mcp-tool": true, "tags": [ "variable" ], @@ -4495,6 +4758,7 @@ }, { "name": "already_encrypted", + "description": "whether the variable is already encrypted (default false)", "in": "query", "schema": { "type": "boolean" @@ -4567,6 +4831,7 @@ "delete": { "summary": "delete variable", "operationId": "deleteVariable", + "x-mcp-tool": true, "tags": [ "variable" ], @@ -4596,6 +4861,7 @@ "post": { "summary": "update variable", "operationId": "updateVariable", + "x-mcp-tool": true, "tags": [ "variable" ], @@ -4608,6 +4874,7 @@ }, { "name": "already_encrypted", + "description": "whether the variable is already encrypted (default false)", "in": "query", "schema": { "type": "boolean" @@ -4643,6 +4910,7 @@ "get": { "summary": "get variable", "operationId": "getVariable", + "x-mcp-tool": true, "tags": [ "variable" ], @@ -4746,6 +5014,7 @@ "get": { "summary": "list variables", "operationId": "listVariable", + "x-mcp-tool": true, "tags": [ "variable" ], @@ -4755,6 +5024,7 @@ }, { "name": "path_start", + "description": "filter variables by path prefix", "in": "query", "schema": { "type": "string" @@ -4813,6 +5083,35 @@ } } }, + "/w/{workspace}/workspaces/get_secondary_storage_names": { + "get": { + "summary": "get secondary storage names", + "operationId": "getSecondaryStorageNames", + "tags": [ + "setting" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "responses": { + "200": { + "description": "status", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, "/w/{workspace}/workspaces/critical_alerts": { "get": { "summary": "Get all critical alerts for this workspace", @@ -5217,6 +5516,22 @@ }, "client": { "type": "string" + }, + "grant_type": { + "type": "string", + "default": "authorization_code" + }, + "cc_client_id": { + "type": "string", + "description": "OAuth client ID for resource-level credentials (client_credentials flow only)" + }, + "cc_client_secret": { + "type": "string", + "description": "OAuth client secret for resource-level credentials (client_credentials flow only)" + }, + "cc_token_url": { + "type": "string", + "description": "OAuth token URL override for resource-level authentication (client_credentials flow only)" } }, "required": [ @@ -5241,6 +5556,73 @@ } } }, + "/oauth/connect_client_credentials/{client}": { + "post": { + "summary": "connect OAuth using client credentials", + "operationId": "connectClientCredentials", + "tags": [ + "oauth" + ], + "parameters": [ + { + "name": "client", + "in": "path", + "description": "OAuth client name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "client credentials flow parameters", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "scopes": { + "type": "array", + "items": { + "type": "string" + } + }, + "cc_client_id": { + "type": "string", + "description": "OAuth client ID for resource-level authentication" + }, + "cc_client_secret": { + "type": "string", + "description": "OAuth client secret for resource-level authentication" + }, + "cc_token_url": { + "type": "string", + "description": "OAuth token URL override for resource-level authentication" + } + }, + "required": [ + "cc_client_id", + "cc_client_secret" + ] + } + } + } + }, + "responses": { + "200": { + "description": "OAuth token response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TokenResponse" + } + } + } + } + } + } + }, "/w/{workspace}/oauth/refresh_token/{id}": { "post": { "summary": "refresh token", @@ -5474,6 +5856,12 @@ "items": { "type": "string" } + }, + "grant_types": { + "type": "array", + "items": { + "type": "string" + } } } } @@ -5559,6 +5947,7 @@ "post": { "summary": "create resource", "operationId": "createResource", + "x-mcp-tool": true, "tags": [ "resource" ], @@ -5568,6 +5957,7 @@ }, { "name": "update_if_exists", + "description": "update the resource if it already exists (default false)", "in": "query", "schema": { "type": "boolean" @@ -5603,6 +5993,7 @@ "delete": { "summary": "delete resource", "operationId": "deleteResource", + "x-mcp-tool": true, "tags": [ "resource" ], @@ -5632,6 +6023,7 @@ "post": { "summary": "update resource", "operationId": "updateResource", + "x-mcp-tool": true, "tags": [ "resource" ], @@ -5715,6 +6107,7 @@ "get": { "summary": "get resource", "operationId": "getResource", + "x-mcp-tool": true, "tags": [ "resource" ], @@ -5836,6 +6229,7 @@ "get": { "summary": "list resources", "operationId": "listResource", + "x-mcp-tool": true, "tags": [ "resource" ], @@ -5867,6 +6261,7 @@ }, { "name": "path_start", + "description": "filter resources by path prefix", "in": "query", "schema": { "type": "string" @@ -6165,6 +6560,7 @@ "get": { "summary": "list resource_types", "operationId": "listResourceType", + "x-mcp-tool": true, "tags": [ "resource" ], @@ -6859,6 +7255,7 @@ "get": { "summary": "list all scripts", "operationId": "listScripts", + "x-mcp-tool": true, "tags": [ "script" ], @@ -6973,6 +7370,14 @@ "schema": { "type": "boolean" } + }, + { + "name": "languages", + "in": "query", + "description": "Filter to only include scripts written in the given languages.\nAccepts multiple values as a comma-separated list.\n", + "schema": { + "type": "string" + } } ], "responses": { @@ -7415,6 +7820,7 @@ "get": { "summary": "get script by path", "operationId": "getScriptByPath", + "x-mcp-tool": true, "tags": [ "script" ], @@ -7788,6 +8194,13 @@ "schema": { "type": "boolean" } + }, + { + "name": "authed", + "in": "query", + "schema": { + "type": "boolean" + } } ], "responses": { @@ -8404,6 +8817,7 @@ "get": { "summary": "list all flows", "operationId": "listFlows", + "x-mcp-tool": true, "tags": [ "flow" ], @@ -8697,6 +9111,7 @@ "get": { "summary": "get flow by path", "operationId": "getFlowByPath", + "x-mcp-tool": true, "tags": [ "flow" ], @@ -11072,6 +11487,7 @@ "get": { "summary": "list all queued jobs", "operationId": "listQueue", + "x-mcp-tool": true, "tags": [ "job" ], @@ -11736,6 +12152,7 @@ "get": { "summary": "list all jobs", "operationId": "listJobs", + "x-mcp-tool": true, "tags": [ "job" ], @@ -11981,6 +12398,13 @@ "schema": { "type": "boolean" } + }, + { + "name": "no_code", + "in": "query", + "schema": { + "type": "boolean" + } } ], "responses": { @@ -12110,12 +12534,26 @@ "type": "integer" } }, + { + "name": "stream_offset", + "in": "query", + "schema": { + "type": "integer" + } + }, { "name": "get_progress", "in": "query", "schema": { "type": "boolean" } + }, + { + "name": "no_logs", + "in": "query", + "schema": { + "type": "boolean" + } } ], "responses": { @@ -12144,8 +12582,17 @@ "progress": { "type": "integer" }, + "stream_offset": { + "type": "integer" + }, + "new_result_stream": { + "type": "string" + }, "flow_status": { - "$ref": "#/components/schemas/WorkflowStatusRecord" + "$ref": "#/components/schemas/FlowStatus" + }, + "workflow_as_code_status": { + "$ref": "#/components/schemas/WorkflowStatus" } } } @@ -12155,6 +12602,77 @@ } } }, + "/w/{workspace}/jobs_u/getupdate_sse/{id}": { + "get": { + "summary": "get job updates via server-sent events", + "operationId": "getJobUpdatesSSE", + "tags": [ + "job" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/JobId" + }, + { + "name": "running", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "log_offset", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "stream_offset", + "in": "query", + "schema": { + "type": "integer" + } + }, + { + "name": "get_progress", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "only_result", + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "no_logs", + "in": "query", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "server-sent events stream of job updates", + "content": { + "text/event-stream": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/w/{workspace}/jobs_u/get_log_file/{path}": { "get": { "summary": "get log file from object store", @@ -13247,6 +13765,8 @@ "post": { "summary": "create schedule", "operationId": "createSchedule", + "x-mcp-tool": true, + "x-mcp-instructions": "Creates a new schedule.\nThe schedule should include seconds.\nYou should get the schema of the script or flow before creating the schedule to correctly specify the arguments needed.\n", "tags": [ "schedule" ], @@ -13284,6 +13804,8 @@ "post": { "summary": "update schedule", "operationId": "updateSchedule", + "x-mcp-tool": true, + "x-mcp-instructions": "Updates a schedule.\nThe schedule should include seconds.\nYou should get the schema of the script or flow before updating the schedule to correctly specify the arguments needed.\n", "tags": [ "schedule" ], @@ -13372,6 +13894,7 @@ "delete": { "summary": "delete schedule", "operationId": "deleteSchedule", + "x-mcp-tool": true, "tags": [ "schedule" ], @@ -13401,6 +13924,7 @@ "get": { "summary": "get schedule", "operationId": "getSchedule", + "x-mcp-tool": true, "tags": [ "schedule" ], @@ -13459,6 +13983,7 @@ "get": { "summary": "list schedules", "operationId": "listSchedules", + "x-mcp-tool": true, "tags": [ "schedule" ], @@ -13485,6 +14010,7 @@ }, { "name": "is_flow", + "description": "filter schedules by whether they target a flow", "in": "query", "schema": { "type": "boolean" @@ -13492,6 +14018,7 @@ }, { "name": "path_start", + "description": "filter schedules by path prefix", "in": "query", "schema": { "type": "string" @@ -13612,6 +14139,119 @@ } } }, + "/w/{workspace}/openapi/generate": { + "post": { + "summary": "generate openapi spec from http routes/webhook", + "operationId": "generateOpenapiSpec", + "tags": [ + "openapi" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "openapi spec info and url", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateOpenapiSpec" + } + } + } + }, + "responses": { + "200": { + "description": "openapi spec", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/w/{workspace}/openapi/download": { + "post": { + "summary": "Download the OpenAPI v3.1 spec as a file", + "operationId": "DownloadOpenapiSpec", + "tags": [ + "openapi" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "openapi spec info and url", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateOpenapiSpec" + } + } + } + }, + "responses": { + "200": { + "description": "Downloaded OpenAPI spec", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/w/{workspace}/http_triggers/create_many": { + "post": { + "summary": "create many HTTP triggers", + "operationId": "createHttpTriggers", + "tags": [ + "http_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "new http trigger", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NewHttpTrigger" + } + } + } + } + }, + "responses": { + "201": { + "description": "http trigger created", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/w/{workspace}/http_triggers/create": { "post": { "summary": "create http trigger", @@ -13858,14 +14498,7 @@ "type": "string" }, "http_method": { - "type": "string", - "enum": [ - "get", - "post", - "put", - "delete", - "patch" - ] + "$ref": "#/components/schemas/HttpMethod" }, "trigger_path": { "type": "string" @@ -15908,6 +16541,35 @@ } } }, + "/w/{workspace}/postgres_triggers/postgres/version/{path}": { + "get": { + "summary": "get postgres version", + "operationId": "getPostgresVersion", + "tags": [ + "postgres_trigger" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + }, + { + "$ref": "#/components/parameters/Path" + } + ], + "responses": { + "200": { + "description": "postgres version", + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/w/{workspace}/postgres_triggers/is_valid_postgres_configuration/{path}": { "get": { "summary": "check if postgres configuration is set to logical", @@ -17672,6 +18334,7 @@ "get": { "summary": "list workers", "operationId": "listWorkers", + "x-mcp-tool": true, "tags": [ "worker" ], @@ -18067,6 +18730,140 @@ } } }, + "/agent_workers/blacklist_token": { + "post": { + "summary": "blacklist agent token (requires super admin)", + "operationId": "blacklistAgentToken", + "tags": [ + "agent_workers" + ], + "requestBody": { + "description": "token to blacklist", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "The agent token to blacklist" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "Optional expiration date for the blacklist entry" + } + }, + "required": [ + "token" + ] + } + } + } + }, + "responses": { + "200": { + "description": "token blacklisted successfully" + } + } + } + }, + "/agent_workers/remove_blacklist_token": { + "post": { + "summary": "remove agent token from blacklist (requires super admin)", + "operationId": "removeBlacklistAgentToken", + "tags": [ + "agent_workers" + ], + "requestBody": { + "description": "token to remove from blacklist", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "The agent token to remove from blacklist" + } + }, + "required": [ + "token" + ] + } + } + } + }, + "responses": { + "200": { + "description": "token removed from blacklist successfully" + } + } + } + }, + "/agent_workers/list_blacklisted_tokens": { + "get": { + "summary": "list blacklisted agent tokens (requires super admin)", + "operationId": "listBlacklistedAgentTokens", + "tags": [ + "agent_workers" + ], + "parameters": [ + { + "name": "include_expired", + "in": "query", + "description": "Whether to include expired blacklisted tokens", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "list of blacklisted tokens", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "The blacklisted token (without prefix)" + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "When the blacklist entry expires" + }, + "blacklisted_at": { + "type": "string", + "format": "date-time", + "description": "When the token was blacklisted" + }, + "blacklisted_by": { + "type": "string", + "description": "Email of the user who blacklisted the token" + } + }, + "required": [ + "token", + "expires_at", + "blacklisted_at", + "blacklisted_by" + ] + } + } + } + } + } + } + } + }, "/w/{workspace}/acls/get/{kind}/{path}": { "get": { "summary": "get granular acls", @@ -19499,9 +20296,7 @@ "200": { "description": "Parquet Preview", "content": { - "application/json": { - "schema": {} - } + "application/json": {} } } } @@ -19637,9 +20432,7 @@ "200": { "description": "Csv Preview", "content": { - "application/json": { - "schema": {} - } + "application/json": {} } } } @@ -20680,6 +21473,187 @@ } } } + }, + "/w/{workspace}/assets/list": { + "get": { + "summary": "List all assets in the workspace", + "operationId": "listAssets", + "tags": [ + "asset" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "responses": { + "200": { + "description": "all assets in the workspace", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "kind", + "usages" + ], + "properties": { + "path": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/AssetKind" + }, + "usages": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "kind" + ], + "properties": { + "path": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/AssetUsageKind" + }, + "access_type": { + "$ref": "#/components/schemas/AssetUsageAccessType" + } + } + } + }, + "metadata": { + "type": "object", + "properties": { + "resource_type": { + "type": "string" + } + } + } + } + } + } + } + } + } + } + } + }, + "/w/{workspace}/assets/list_by_usages": { + "post": { + "summary": "List all assets used by given usages paths", + "operationId": "listAssetsByUsage", + "tags": [ + "asset" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "requestBody": { + "description": "list assets by usages", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "usages" + ], + "properties": { + "usages": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "kind" + ], + "properties": { + "path": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/AssetUsageKind" + } + } + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "all assets used by the given usage paths, in the same order", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "kind" + ], + "properties": { + "path": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/AssetKind" + }, + "access_type": { + "$ref": "#/components/schemas/AssetUsageAccessType" + } + } + } + } + } + } + } + } + } + } + }, + "/mcp/w/{workspace}/list_tools": { + "get": { + "summary": "list available MCP tools", + "operationId": "listMcpTools", + "tags": [ + "mcp" + ], + "parameters": [ + { + "$ref": "#/components/parameters/WorkspaceId" + } + ], + "responses": { + "200": { + "description": "list of MCP tools available for the workspace", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EndpointTool" + } + } + } + } + } + } + } } }, "components": { @@ -20723,6 +21697,7 @@ "name": "publication", "in": "path", "required": true, + "description": "The name of the publication", "schema": { "type": "string" } @@ -21204,13 +22179,876 @@ } }, "schemas": { - "InputTransform": { - "allOf": [ - { - "$ref": "#/components/schemas/schemas-InputTransform" + "OpenFlow": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": { + "$ref": "#/components/schemas/schemas-FlowValue" + }, + "schema": { + "type": "object" } + }, + "required": [ + "summary", + "value" ] }, + "FlowValue": { + "type": "object", + "properties": { + "modules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/schemas-FlowModule" + } + }, + "failure_module": { + "$ref": "#/components/schemas/schemas-FlowModule" + }, + "preprocessor_module": { + "$ref": "#/components/schemas/schemas-FlowModule" + }, + "same_worker": { + "type": "boolean" + }, + "concurrent_limit": { + "type": "number" + }, + "concurrency_key": { + "type": "string" + }, + "concurrency_time_window_s": { + "type": "number" + }, + "skip_expr": { + "type": "string" + }, + "cache_ttl": { + "type": "number" + }, + "priority": { + "type": "number" + }, + "early_return": { + "type": "string" + } + }, + "required": [ + "modules" + ] + }, + "Retry": { + "type": "object", + "properties": { + "constant": { + "type": "object", + "properties": { + "attempts": { + "type": "integer" + }, + "seconds": { + "type": "integer" + } + } + }, + "exponential": { + "type": "object", + "properties": { + "attempts": { + "type": "integer" + }, + "multiplier": { + "type": "integer" + }, + "seconds": { + "type": "integer" + }, + "random_factor": { + "type": "integer", + "minimum": 0, + "maximum": 100 + } + } + } + } + }, + "StopAfterIf": { + "type": "object", + "properties": { + "skip_if_stopped": { + "type": "boolean" + }, + "expr": { + "type": "string" + }, + "error_message": { + "type": "string" + } + }, + "required": [ + "expr" + ] + }, + "FlowModule": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "value": { + "$ref": "#/components/schemas/schemas-FlowModuleValue" + }, + "stop_after_if": { + "$ref": "#/components/schemas/schemas-StopAfterIf" + }, + "stop_after_all_iters_if": { + "$ref": "#/components/schemas/schemas-StopAfterIf" + }, + "skip_if": { + "type": "object", + "properties": { + "expr": { + "type": "string" + } + }, + "required": [ + "expr" + ] + }, + "sleep": { + "$ref": "#/components/schemas/schemas-InputTransform" + }, + "cache_ttl": { + "type": "number" + }, + "timeout": { + "type": "number" + }, + "delete_after_use": { + "type": "boolean" + }, + "summary": { + "type": "string" + }, + "mock": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "return_value": {} + } + }, + "suspend": { + "type": "object", + "properties": { + "required_events": { + "type": "integer" + }, + "timeout": { + "type": "integer" + }, + "resume_form": { + "type": "object", + "properties": { + "schema": { + "type": "object" + } + } + }, + "user_auth_required": { + "type": "boolean" + }, + "user_groups_required": { + "$ref": "#/components/schemas/schemas-InputTransform" + }, + "self_approval_disabled": { + "type": "boolean" + }, + "hide_cancel": { + "type": "boolean" + }, + "continue_on_disapprove_timeout": { + "type": "boolean" + } + } + }, + "priority": { + "type": "number" + }, + "continue_on_error": { + "type": "boolean" + }, + "retry": { + "$ref": "#/components/schemas/schemas-Retry" + } + }, + "required": [ + "value", + "id" + ] + }, + "InputTransform": { + "oneOf": [ + { + "$ref": "#/components/schemas/schemas-StaticTransform" + }, + { + "$ref": "#/components/schemas/schemas-JavascriptTransform" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "static": "#/components/schemas/schemas-StaticTransform", + "javascript": "#/components/schemas/schemas-JavascriptTransform" + } + } + }, + "StaticTransform": { + "type": "object", + "properties": { + "value": {}, + "type": { + "type": "string", + "enum": [ + "static" + ] + } + }, + "required": [ + "value", + "type" + ] + }, + "JavascriptTransform": { + "type": "object", + "properties": { + "expr": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "javascript" + ] + } + }, + "required": [ + "expr", + "type" + ] + }, + "FlowModuleValue": { + "oneOf": [ + { + "$ref": "#/components/schemas/schemas-RawScript" + }, + { + "$ref": "#/components/schemas/schemas-PathScript" + }, + { + "$ref": "#/components/schemas/schemas-PathFlow" + }, + { + "$ref": "#/components/schemas/schemas-ForloopFlow" + }, + { + "$ref": "#/components/schemas/schemas-WhileloopFlow" + }, + { + "$ref": "#/components/schemas/schemas-BranchOne" + }, + { + "$ref": "#/components/schemas/schemas-BranchAll" + }, + { + "$ref": "#/components/schemas/schemas-Identity" + } + ], + "discriminator": { + "propertyName": "type", + "mapping": { + "rawscript": "#/components/schemas/schemas-RawScript", + "script": "#/components/schemas/schemas-PathScript", + "flow": "#/components/schemas/schemas-PathFlow", + "forloopflow": "#/components/schemas/schemas-ForloopFlow", + "whileloopflow": "#/components/schemas/schemas-WhileloopFlow", + "branchone": "#/components/schemas/schemas-BranchOne", + "branchall": "#/components/schemas/schemas-BranchAll", + "identity": "#/components/schemas/schemas-Identity" + } + } + }, + "RawScript": { + "type": "object", + "properties": { + "input_transforms": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/schemas-InputTransform" + } + }, + "content": { + "type": "string" + }, + "language": { + "type": "string", + "enum": [ + "deno", + "bun", + "python3", + "go", + "bash", + "powershell", + "postgresql", + "mysql", + "bigquery", + "snowflake", + "mssql", + "oracledb", + "graphql", + "nativets", + "php" + ] + }, + "path": { + "type": "string" + }, + "lock": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "rawscript" + ] + }, + "tag": { + "type": "string" + }, + "concurrent_limit": { + "type": "number" + }, + "concurrency_time_window_s": { + "type": "number" + }, + "custom_concurrency_key": { + "type": "string" + }, + "is_trigger": { + "type": "boolean" + }, + "assets": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "kind" + ], + "properties": { + "path": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "s3object", + "resource", + "ducklake" + ] + }, + "access_type": { + "type": "string", + "enum": [ + "r", + "w", + "rw" + ] + }, + "alt_access_type": { + "type": "string", + "enum": [ + "r", + "w", + "rw" + ] + } + } + } + } + }, + "required": [ + "type", + "content", + "language", + "input_transforms" + ] + }, + "PathScript": { + "type": "object", + "properties": { + "input_transforms": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/schemas-InputTransform" + } + }, + "path": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "script" + ] + }, + "tag_override": { + "type": "string" + }, + "is_trigger": { + "type": "boolean" + } + }, + "required": [ + "type", + "path", + "input_transforms" + ] + }, + "PathFlow": { + "type": "object", + "properties": { + "input_transforms": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/schemas-InputTransform" + } + }, + "path": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "flow" + ] + } + }, + "required": [ + "type", + "path", + "input_transforms" + ] + }, + "ForloopFlow": { + "type": "object", + "properties": { + "modules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/schemas-FlowModule" + } + }, + "iterator": { + "$ref": "#/components/schemas/schemas-InputTransform" + }, + "skip_failures": { + "type": "boolean" + }, + "type": { + "type": "string", + "enum": [ + "forloopflow" + ] + }, + "parallel": { + "type": "boolean" + }, + "parallelism": { + "type": "integer" + } + }, + "required": [ + "modules", + "iterator", + "skip_failures", + "type" + ] + }, + "WhileloopFlow": { + "type": "object", + "properties": { + "modules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/schemas-FlowModule" + } + }, + "skip_failures": { + "type": "boolean" + }, + "type": { + "type": "string", + "enum": [ + "whileloopflow" + ] + }, + "parallel": { + "type": "boolean" + }, + "parallelism": { + "type": "integer" + } + }, + "required": [ + "modules", + "skip_failures", + "type" + ] + }, + "BranchOne": { + "type": "object", + "properties": { + "branches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "expr": { + "type": "string" + }, + "modules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/schemas-FlowModule" + } + } + }, + "required": [ + "modules", + "expr" + ] + } + }, + "default": { + "type": "array", + "items": { + "$ref": "#/components/schemas/schemas-FlowModule" + }, + "required": [ + "modules" + ] + }, + "type": { + "type": "string", + "enum": [ + "branchone" + ] + } + }, + "required": [ + "branches", + "default", + "type" + ] + }, + "BranchAll": { + "type": "object", + "properties": { + "branches": { + "type": "array", + "items": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "skip_failure": { + "type": "boolean" + }, + "modules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/schemas-FlowModule" + } + } + }, + "required": [ + "modules" + ] + } + }, + "type": { + "type": "string", + "enum": [ + "branchall" + ] + }, + "parallel": { + "type": "boolean" + } + }, + "required": [ + "branches", + "type" + ] + }, + "Identity": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "identity" + ] + }, + "flow": { + "type": "boolean" + } + }, + "required": [ + "type" + ] + }, + "FlowStatus": { + "type": "object", + "properties": { + "step": { + "type": "integer" + }, + "modules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/schemas-FlowStatusModule" + } + }, + "user_states": { + "additionalProperties": true + }, + "preprocessor_module": { + "allOf": [ + { + "$ref": "#/components/schemas/schemas-FlowStatusModule" + } + ] + }, + "failure_module": { + "allOf": [ + { + "$ref": "#/components/schemas/schemas-FlowStatusModule" + }, + { + "type": "object", + "properties": { + "parent_module": { + "type": "string" + } + } + } + ] + }, + "retry": { + "type": "object", + "properties": { + "fail_count": { + "type": "integer" + }, + "failed_jobs": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + } + }, + "required": [ + "step", + "modules", + "failure_module" + ] + }, + "FlowStatusModule": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "WaitingForPriorSteps", + "WaitingForEvents", + "WaitingForExecutor", + "InProgress", + "Success", + "Failure" + ] + }, + "id": { + "type": "string" + }, + "job": { + "type": "string", + "format": "uuid" + }, + "count": { + "type": "integer" + }, + "progress": { + "type": "integer" + }, + "iterator": { + "type": "object", + "properties": { + "index": { + "type": "integer" + }, + "itered": { + "type": "array", + "items": {} + }, + "args": {} + } + }, + "flow_jobs": { + "type": "array", + "items": { + "type": "string" + } + }, + "flow_jobs_success": { + "type": "array", + "items": { + "type": "boolean" + } + }, + "branch_chosen": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "branch", + "default" + ] + }, + "branch": { + "type": "integer" + } + }, + "required": [ + "type" + ] + }, + "branchall": { + "type": "object", + "properties": { + "branch": { + "type": "integer" + }, + "len": { + "type": "integer" + } + }, + "required": [ + "branch", + "len" + ] + }, + "approvers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "resume_id": { + "type": "integer" + }, + "approver": { + "type": "string" + } + }, + "required": [ + "resume_id", + "approver" + ] + } + }, + "failed_retries": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "skipped": { + "type": "boolean" + } + }, + "required": [ + "type" + ] + }, + "EndpointTool": { + "type": "object", + "required": [ + "name", + "description", + "instructions", + "path", + "method" + ], + "properties": { + "name": { + "type": "string", + "description": "The tool name/operation ID" + }, + "description": { + "type": "string", + "description": "Short description of the tool" + }, + "instructions": { + "type": "string", + "description": "Detailed instructions for using the tool" + }, + "path": { + "type": "string", + "description": "API endpoint path" + }, + "method": { + "type": "string", + "description": "HTTP method (GET, POST, etc.)" + }, + "path_params_schema": { + "type": "object", + "description": "JSON schema for path parameters", + "nullable": true + }, + "query_params_schema": { + "type": "object", + "description": "JSON schema for query parameters", + "nullable": true + }, + "body_schema": { + "type": "object", + "description": "JSON schema for request body", + "nullable": true + } + } + }, "AIProvider": { "type": "string", "enum": [ @@ -21226,6 +23064,25 @@ "customai" ] }, + "GitSyncObjectType": { + "type": "string", + "enum": [ + "script", + "flow", + "app", + "folder", + "resource", + "variable", + "secret", + "resourcetype", + "schedule", + "user", + "group", + "trigger", + "settings", + "key" + ] + }, "AIProviderModel": { "type": "object", "properties": { @@ -21574,6 +23431,40 @@ }, "on_behalf_of_email": { "type": "string" + }, + "assets": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "kind" + ], + "properties": { + "path": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/AssetKind" + }, + "access_type": { + "type": "string", + "enum": [ + "r", + "w", + "rw" + ] + }, + "alt_access_type": { + "type": "string", + "enum": [ + "r", + "w", + "rw" + ] + } + } + } } }, "required": [ @@ -21621,6 +23512,7 @@ }, "ScriptArgs": { "type": "object", + "description": "The arguments to pass to the script or flow", "additionalProperties": {} }, "Input": { @@ -21787,6 +23679,9 @@ "flow_status": { "$ref": "#/components/schemas/FlowStatus" }, + "workflow_as_code_status": { + "$ref": "#/components/schemas/WorkflowStatus" + }, "raw_flow": { "$ref": "#/components/schemas/FlowValue" }, @@ -21927,6 +23822,9 @@ "flow_status": { "$ref": "#/components/schemas/FlowStatus" }, + "workflow_as_code_status": { + "$ref": "#/components/schemas/WorkflowStatus" + }, "raw_flow": { "$ref": "#/components/schemas/FlowValue" }, @@ -22307,25 +24205,32 @@ "type": "object", "properties": { "path": { - "type": "string" + "type": "string", + "description": "The path to the variable" }, "value": { - "type": "string" + "type": "string", + "description": "The value of the variable" }, "is_secret": { - "type": "boolean" + "type": "boolean", + "description": "Whether the variable is a secret" }, "description": { - "type": "string" + "type": "string", + "description": "The description of the variable" }, "account": { - "type": "integer" + "type": "integer", + "description": "The account identifier" }, "is_oauth": { - "type": "boolean" + "type": "boolean", + "description": "Whether the variable is an OAuth variable" }, "expires_at": { "type": "string", + "description": "The expiration date of the variable", "format": "date-time" } }, @@ -22340,16 +24245,20 @@ "type": "object", "properties": { "path": { - "type": "string" + "type": "string", + "description": "The path to the variable" }, "value": { - "type": "string" + "type": "string", + "description": "The new value of the variable" }, "is_secret": { - "type": "boolean" + "type": "boolean", + "description": "Whether the variable is a secret" }, "description": { - "type": "string" + "type": "string", + "description": "The new description of the variable" } } }, @@ -22470,6 +24379,9 @@ }, "parameters": { "type": "object" + }, + "span": { + "type": "string" } }, "required": [ @@ -22554,45 +24466,53 @@ "type": "object", "properties": { "object": { - "type": "array", - "items": { - "type": "object", - "properties": { - "key": { - "type": "string" - }, - "typ": { - "oneOf": [ - { - "type": "string", - "enum": [ - "float", - "int", - "bool", - "email", - "unknown", - "bytes", - "dict", - "datetime", - "sql" - ] + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "props": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" }, - { - "type": "object", - "properties": { - "str": {} - }, - "required": [ - "str" + "typ": { + "oneOf": [ + { + "type": "string", + "enum": [ + "float", + "int", + "bool", + "email", + "unknown", + "bytes", + "dict", + "datetime", + "sql" + ] + }, + { + "type": "object", + "properties": { + "str": {} + }, + "required": [ + "str" + ] + } ] } + }, + "required": [ + "key", + "typ" ] } - }, - "required": [ - "key", - "typ" - ] + } } } }, @@ -22690,7 +24610,8 @@ "ansible", "csharp", "nu", - "java" + "java", + "duckdb" ] }, "Preview": { @@ -22773,14 +24694,17 @@ "type": "object", "properties": { "path": { - "type": "string" + "type": "string", + "description": "The path to the resource" }, "value": {}, "description": { - "type": "string" + "type": "string", + "description": "The description of the resource" }, "resource_type": { - "type": "string" + "type": "string", + "description": "The resource_type associated with the resource" } }, "required": [ @@ -22793,12 +24717,18 @@ "type": "object", "properties": { "path": { - "type": "string" + "type": "string", + "description": "The path to the resource" }, "description": { - "type": "string" + "type": "string", + "description": "The new description of the resource" }, - "value": {} + "value": {}, + "resource_type": { + "type": "string", + "description": "The new resource_type to be associated with the resource" + } } }, "Resource": { @@ -23076,81 +25006,114 @@ } ] }, + "ErrorHandler": { + "type": "string", + "enum": [ + "custom", + "slack", + "teams", + "email" + ] + }, "NewSchedule": { "type": "object", "properties": { "path": { - "type": "string" + "type": "string", + "description": "The path where the schedule will be created" }, "schedule": { - "type": "string" + "type": "string", + "description": "The cron schedule to trigger the script or flow. Should include seconds." }, "timezone": { - "type": "string" + "type": "string", + "description": "The timezone to use for the cron schedule" }, "script_path": { - "type": "string" + "type": "string", + "description": "The path to the script or flow to trigger" }, "is_flow": { - "type": "boolean" + "type": "boolean", + "description": "Whether the schedule is for a flow" }, "args": { - "$ref": "#/components/schemas/ScriptArgs" + "$ref": "#/components/schemas/ScriptArgs", + "description": "The arguments to pass to the script or flow" }, "enabled": { - "type": "boolean" + "type": "boolean", + "description": "Whether the schedule is enabled" }, "on_failure": { - "type": "string" + "type": "string", + "description": "The path to the script or flow to trigger on failure" }, "on_failure_times": { - "type": "number" + "type": "number", + "description": "The number of times to retry on failure" }, "on_failure_exact": { - "type": "boolean" + "type": "boolean", + "description": "Whether the schedule should only run on the exact time" }, "on_failure_extra_args": { - "$ref": "#/components/schemas/ScriptArgs" + "$ref": "#/components/schemas/ScriptArgs", + "description": "The arguments to pass to the script or flow on failure" }, "on_recovery": { - "type": "string" + "type": "string", + "description": "The path to the script or flow to trigger on recovery" }, "on_recovery_times": { - "type": "number" + "type": "number", + "description": "The number of times to retry on recovery" }, "on_recovery_extra_args": { - "$ref": "#/components/schemas/ScriptArgs" + "$ref": "#/components/schemas/ScriptArgs", + "description": "The arguments to pass to the script or flow on recovery" }, "on_success": { - "type": "string" + "type": "string", + "description": "The path to the script or flow to trigger on success" }, "on_success_extra_args": { - "$ref": "#/components/schemas/ScriptArgs" + "$ref": "#/components/schemas/ScriptArgs", + "description": "The arguments to pass to the script or flow on success" }, "ws_error_handler_muted": { - "type": "boolean" + "type": "boolean", + "description": "Whether the WebSocket error handler is muted" }, "retry": { - "$ref": "#/components/schemas/Retry" + "$ref": "#/components/schemas/Retry", + "description": "The retry configuration for the schedule" }, "no_flow_overlap": { - "type": "boolean" + "type": "boolean", + "description": "Whether the schedule should not run if a flow is already running" }, "summary": { - "type": "string" + "type": "string", + "description": "The summary of the schedule" }, "description": { - "type": "string" + "type": "string", + "description": "The description of the schedule" }, "tag": { - "type": "string" + "type": "string", + "description": "The tag of the schedule" }, "paused_until": { "type": "string", + "description": "The date and time the schedule will be paused until", "format": "date-time" }, "cron_version": { - "type": "string" + "type": "string", + "description": "The version of the cron schedule to use (last is v2)" } }, "required": [ @@ -23166,72 +25129,90 @@ "type": "object", "properties": { "schedule": { - "type": "string" + "type": "string", + "description": "The cron schedule to trigger the script or flow. Should include seconds." }, "timezone": { - "type": "string" + "type": "string", + "description": "The timezone to use for the cron schedule" }, "args": { - "$ref": "#/components/schemas/ScriptArgs" + "$ref": "#/components/schemas/ScriptArgs", + "description": "The arguments to pass to the script or flow" }, "on_failure": { - "type": "string" + "type": "string", + "description": "The path to the script or flow to trigger on failure" }, "on_failure_times": { - "type": "number" + "type": "number", + "description": "The number of times to retry on failure" }, "on_failure_exact": { - "type": "boolean" + "type": "boolean", + "description": "Whether the schedule should only run on the exact time" }, "on_failure_extra_args": { - "$ref": "#/components/schemas/ScriptArgs" + "$ref": "#/components/schemas/ScriptArgs", + "description": "The arguments to pass to the script or flow on failure" }, "on_recovery": { - "type": "string" + "type": "string", + "description": "The path to the script or flow to trigger on recovery" }, "on_recovery_times": { - "type": "number" + "type": "number", + "description": "The number of times to retry on recovery" }, "on_recovery_extra_args": { - "$ref": "#/components/schemas/ScriptArgs" + "$ref": "#/components/schemas/ScriptArgs", + "description": "The arguments to pass to the script or flow on recovery" }, "on_success": { - "type": "string" + "type": "string", + "description": "The path to the script or flow to trigger on success" }, "on_success_extra_args": { - "$ref": "#/components/schemas/ScriptArgs" + "$ref": "#/components/schemas/ScriptArgs", + "description": "The arguments to pass to the script or flow on success" }, "ws_error_handler_muted": { - "type": "boolean" + "type": "boolean", + "description": "Whether the WebSocket error handler is muted" }, "retry": { - "$ref": "#/components/schemas/Retry" + "$ref": "#/components/schemas/Retry", + "description": "The retry configuration for the schedule" }, "no_flow_overlap": { - "type": "boolean" + "type": "boolean", + "description": "Whether the schedule should not run if a flow is already running" }, "summary": { - "type": "string" + "type": "string", + "description": "The summary of the schedule" }, "description": { - "type": "string" + "type": "string", + "description": "The description of the schedule" }, "tag": { - "type": "string" + "type": "string", + "description": "The tag of the schedule" }, "paused_until": { "type": "string", + "description": "The date and time the schedule will be paused until", "format": "date-time" }, "cron_version": { - "type": "string" + "type": "string", + "description": "The version of the cron schedule to use (last is v2)" } }, "required": [ "schedule", "timezone", - "script_path", - "is_flow", "args" ] }, @@ -23289,6 +25270,155 @@ "signature" ] }, + "RunnableKind": { + "type": "string", + "enum": [ + "script", + "flow" + ] + }, + "OpenapiSpecFormat": { + "type": "string", + "enum": [ + "yaml", + "json" + ] + }, + "OpenapiHttpRouteFilters": { + "type": "object", + "properties": { + "folder_regex": { + "type": "string" + }, + "path_regex": { + "type": "string" + }, + "route_path_regex": { + "type": "string" + } + }, + "required": [ + "folder_regex", + "path_regex", + "route_path_regex" + ] + }, + "WebhookFilters": { + "type": "object", + "properties": { + "user_or_folder_regex": { + "type": "string", + "enum": [ + "*", + "u", + "f" + ] + }, + "user_or_folder_regex_value": { + "type": "string" + }, + "path": { + "type": "string" + }, + "runnable_kind": { + "$ref": "#/components/schemas/RunnableKind" + } + }, + "required": [ + "user_or_folder_regex", + "user_or_folder_regex_value", + "path", + "runnable_kind" + ] + }, + "OpenapiV3Info": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "version": { + "type": "string" + }, + "description": { + "type": "string" + }, + "terms_of_service": { + "type": "string" + }, + "contact": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "email": { + "type": "string" + } + } + }, + "license": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "identifier": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "required": [ + "title", + "version" + ] + }, + "GenerateOpenapiSpec": { + "type": "object", + "properties": { + "info": { + "$ref": "#/components/schemas/OpenapiV3Info" + }, + "url": { + "type": "string" + }, + "openapi_spec_format": { + "$ref": "#/components/schemas/OpenapiSpecFormat" + }, + "http_route_filters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OpenapiHttpRouteFilters" + } + }, + "webhook_filters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/WebhookFilters" + } + } + } + }, + "HttpMethod": { + "type": "string", + "enum": [ + "get", + "post", + "put", + "delete", + "patch" + ] + }, "HttpTrigger": { "allOf": [ { @@ -23318,18 +25448,17 @@ ] }, "http_method": { - "type": "string", - "enum": [ - "get", - "post", - "put", - "delete", - "patch" - ] + "$ref": "#/components/schemas/HttpMethod" }, "authentication_resource_path": { "type": "string" }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, "is_async": { "type": "boolean" }, @@ -23347,6 +25476,15 @@ }, "raw_string": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -23375,6 +25513,12 @@ "workspaced_route": { "type": "boolean" }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, "static_asset_config": { "type": "object", "properties": { @@ -23396,14 +25540,7 @@ "type": "boolean" }, "http_method": { - "type": "string", - "enum": [ - "get", - "post", - "put", - "delete", - "patch" - ] + "$ref": "#/components/schemas/HttpMethod" }, "authentication_resource_path": { "type": "string" @@ -23422,6 +25559,15 @@ }, "raw_string": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -23447,6 +25593,12 @@ "route_path": { "type": "string" }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, "workspaced_route": { "type": "boolean" }, @@ -23474,14 +25626,7 @@ "type": "boolean" }, "http_method": { - "type": "string", - "enum": [ - "get", - "post", - "put", - "delete", - "patch" - ] + "$ref": "#/components/schemas/HttpMethod" }, "is_async": { "type": "boolean" @@ -23497,6 +25642,15 @@ }, "raw_string": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -23607,6 +25761,15 @@ }, "can_return_message": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -23661,6 +25824,15 @@ }, "can_return_message": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -23714,6 +25886,15 @@ }, "can_return_message": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -23789,7 +25970,7 @@ "clean_start": { "type": "boolean" }, - "topic_alias": { + "topic_alias_maximum": { "type": "number" }, "session_expiry_interval": { @@ -23860,6 +26041,15 @@ }, "enabled": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -23903,6 +26093,15 @@ }, "enabled": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -23948,6 +26147,15 @@ }, "enabled": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -24019,6 +26227,15 @@ }, "enabled": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -24073,6 +26290,18 @@ }, "enabled": { "type": "boolean" + }, + "auto_acknowledge_msg": { + "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -24148,6 +26377,15 @@ }, "enabled": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -24186,6 +26424,15 @@ }, "enabled": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -24226,6 +26473,15 @@ }, "enabled": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -24372,6 +26628,15 @@ "last_server_ping": { "type": "string", "format": "date-time" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -24407,6 +26672,15 @@ }, "publication": { "$ref": "#/components/schemas/PublicationData" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -24443,6 +26717,15 @@ }, "publication": { "$ref": "#/components/schemas/PublicationData" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -24487,6 +26770,15 @@ }, "enabled": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -24522,6 +26814,15 @@ }, "enabled": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -24556,6 +26857,15 @@ }, "is_flow": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -24605,6 +26915,15 @@ }, "enabled": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -24646,6 +26965,15 @@ }, "enabled": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -24686,6 +27014,15 @@ }, "is_flow": { "type": "boolean" + }, + "error_handler_path": { + "type": "string" + }, + "error_handler_args": { + "$ref": "#/components/schemas/ScriptArgs" + }, + "retry": { + "$ref": "#/components/schemas/Retry" } }, "required": [ @@ -25272,6 +27609,51 @@ "execution_mode" ] }, + "ScopeDefinition": { + "type": "object", + "properties": { + "value": { + "type": "string" + }, + "label": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "requires_resource_path": { + "type": "boolean" + } + }, + "required": [ + "value", + "label", + "requires_resource_path" + ] + }, + "ScopeDomain": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string", + "nullable": true + }, + "scopes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ScopeDefinition" + } + } + }, + "required": [ + "name", + "scopes" + ] + }, "ListableRawApp": { "type": "object", "properties": { @@ -25470,6 +27852,9 @@ "items": { "type": "string" } + }, + "grant_type": { + "type": "string" } }, "required": [ @@ -25505,7 +27890,8 @@ "S3Storage", "AzureBlobStorage", "AzureWorkloadIdentity", - "S3AwsOidc" + "S3AwsOidc", + "GoogleCloudStorage" ] }, "s3_resource_path": { @@ -25514,6 +27900,9 @@ "azure_blob_resource_path": { "type": "string" }, + "gcs_resource_path": { + "type": "string" + }, "public_resource": { "type": "boolean" }, @@ -25528,7 +27917,8 @@ "S3Storage", "AzureBlobStorage", "AzureWorkloadIdentity", - "S3AwsOidc" + "S3AwsOidc", + "GoogleCloudStorage" ] }, "s3_resource_path": { @@ -25537,6 +27927,9 @@ "azure_blob_resource_path": { "type": "string" }, + "gcs_resource_path": { + "type": "string" + }, "public_resource": { "type": "boolean" } @@ -25545,6 +27938,59 @@ } } }, + "DucklakeSettings": { + "type": "object", + "required": [ + "ducklakes" + ], + "properties": { + "ducklakes": { + "type": "object", + "additionalProperties": { + "type": "object", + "required": [ + "catalog", + "storage" + ], + "properties": { + "catalog": { + "type": "object", + "properties": { + "resource_type": { + "type": "string", + "enum": [ + "postgresql", + "mysql", + "instance" + ] + }, + "resource_path": { + "type": "string" + } + }, + "required": [ + "resource_type" + ] + }, + "storage": { + "type": "object", + "properties": { + "storage": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "path" + ] + } + } + } + } + } + }, "WindmillLargeFile": { "type": "object", "properties": { @@ -25637,32 +28083,6 @@ "WorkspaceGitSyncSettings": { "type": "object", "properties": { - "include_path": { - "type": "array", - "items": { - "type": "string" - } - }, - "include_type": { - "type": "array", - "items": { - "type": "string", - "enum": [ - "script", - "flow", - "app", - "folder", - "resource", - "variable", - "secret", - "resourcetype", - "schedule", - "user", - "group", - "trigger" - ] - } - }, "repositories": { "type": "array", "items": { @@ -25683,16 +28103,7 @@ "include_type": { "type": "array", "items": { - "type": "string", - "enum": [ - "script", - "flow", - "app", - "resource", - "variable", - "secret", - "trigger" - ] + "$ref": "#/components/schemas/GitSyncObjectType" } } } @@ -25734,24 +28145,42 @@ "group_by_folder": { "type": "boolean" }, + "collapsed": { + "type": "boolean" + }, + "settings": { + "type": "object", + "properties": { + "include_path": { + "type": "array", + "items": { + "type": "string" + } + }, + "include_type": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GitSyncObjectType" + } + }, + "exclude_path": { + "type": "array", + "items": { + "type": "string" + } + }, + "extra_include_path": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "exclude_types_override": { "type": "array", "items": { - "type": "string", - "enum": [ - "script", - "flow", - "app", - "folder", - "resource", - "variable", - "secret", - "resourcetype", - "schedule", - "user", - "group", - "trigger" - ] + "$ref": "#/components/schemas/GitSyncObjectType" } } }, @@ -26128,6 +28557,7 @@ "runs", "schedules", "resources", + "assets", "variables", "triggers", "audit_logs", @@ -26152,6 +28582,10 @@ "type": "boolean", "description": "Whether operators can view variables" }, + "assets": { + "type": "boolean", + "description": "Whether operators can view assets" + }, "audit_logs": { "type": "boolean", "description": "Whether operators can view audit logs" @@ -26338,23 +28772,61 @@ } } }, - "StaticTransform": { + "AssetUsageKind": { + "type": "string", + "enum": [ + "script", + "flow" + ] + }, + "AssetUsageAccessType": { + "type": "string", + "enum": [ + "r", + "w", + "rw" + ] + }, + "AssetKind": { + "type": "string", + "enum": [ + "s3object", + "resource", + "ducklake" + ] + }, + "Asset": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "kind": { + "$ref": "#/components/schemas/AssetKind" + } + }, + "required": [ + "path", + "kind" + ] + }, + "schemas-StaticTransform": { "type": "object", "properties": { "value": {}, "type": { "type": "string", "enum": [ - "javascript" + "static" ] } }, "required": [ - "expr", + "value", "type" ] }, - "JavascriptTransform": { + "schemas-JavascriptTransform": { "type": "object", "properties": { "expr": { @@ -26375,21 +28847,21 @@ "schemas-InputTransform": { "oneOf": [ { - "$ref": "#/components/schemas/StaticTransform" + "$ref": "#/components/schemas/schemas-StaticTransform" }, { - "$ref": "#/components/schemas/JavascriptTransform" + "$ref": "#/components/schemas/schemas-JavascriptTransform" } ], "discriminator": { "propertyName": "type", "mapping": { - "static": "#/components/schemas/StaticTransform", - "javascript": "#/components/schemas/JavascriptTransform" + "static": "#/components/schemas/schemas-StaticTransform", + "javascript": "#/components/schemas/schemas-JavascriptTransform" } } }, - "RawScript": { + "schemas-RawScript": { "type": "object", "properties": { "input_transforms": { @@ -26447,6 +28919,45 @@ }, "is_trigger": { "type": "boolean" + }, + "assets": { + "type": "array", + "items": { + "type": "object", + "required": [ + "path", + "kind" + ], + "properties": { + "path": { + "type": "string" + }, + "kind": { + "type": "string", + "enum": [ + "s3object", + "resource", + "ducklake" + ] + }, + "access_type": { + "type": "string", + "enum": [ + "r", + "w", + "rw" + ] + }, + "alt_access_type": { + "type": "string", + "enum": [ + "r", + "w", + "rw" + ] + } + } + } } }, "required": [ @@ -26456,7 +28967,7 @@ "input_transforms" ] }, - "PathScript": { + "schemas-PathScript": { "type": "object", "properties": { "input_transforms": { @@ -26490,7 +29001,7 @@ "input_transforms" ] }, - "PathFlow": { + "schemas-PathFlow": { "type": "object", "properties": { "input_transforms": { @@ -26515,20 +29026,20 @@ "input_transforms" ] }, - "FlowModule": { + "schemas-FlowModule": { "type": "object", "properties": { "id": { "type": "string" }, "value": { - "$ref": "#/components/schemas/FlowModuleValue" + "$ref": "#/components/schemas/schemas-FlowModuleValue" }, "stop_after_if": { - "$ref": "#/components/schemas/StopAfterIf" + "$ref": "#/components/schemas/schemas-StopAfterIf" }, "stop_after_all_iters_if": { - "$ref": "#/components/schemas/StopAfterIf" + "$ref": "#/components/schemas/schemas-StopAfterIf" }, "skip_if": { "type": "object", @@ -26606,7 +29117,7 @@ "type": "boolean" }, "retry": { - "$ref": "#/components/schemas/Retry" + "$ref": "#/components/schemas/schemas-Retry" } }, "required": [ @@ -26614,13 +29125,13 @@ "id" ] }, - "ForloopFlow": { + "schemas-ForloopFlow": { "type": "object", "properties": { "modules": { "type": "array", "items": { - "$ref": "#/components/schemas/FlowModule" + "$ref": "#/components/schemas/schemas-FlowModule" } }, "iterator": { @@ -26649,13 +29160,13 @@ "type" ] }, - "WhileloopFlow": { + "schemas-WhileloopFlow": { "type": "object", "properties": { "modules": { "type": "array", "items": { - "$ref": "#/components/schemas/FlowModule" + "$ref": "#/components/schemas/schemas-FlowModule" } }, "skip_failures": { @@ -26664,7 +29175,7 @@ "type": { "type": "string", "enum": [ - "forloopflow" + "whileloopflow" ] }, "parallel": { @@ -26680,7 +29191,7 @@ "type" ] }, - "BranchOne": { + "schemas-BranchOne": { "type": "object", "properties": { "branches": { @@ -26697,7 +29208,7 @@ "modules": { "type": "array", "items": { - "$ref": "#/components/schemas/FlowModule" + "$ref": "#/components/schemas/schemas-FlowModule" } } }, @@ -26710,7 +29221,7 @@ "default": { "type": "array", "items": { - "$ref": "#/components/schemas/FlowModule" + "$ref": "#/components/schemas/schemas-FlowModule" }, "required": [ "modules" @@ -26729,7 +29240,7 @@ "type" ] }, - "BranchAll": { + "schemas-BranchAll": { "type": "object", "properties": { "branches": { @@ -26746,13 +29257,12 @@ "modules": { "type": "array", "items": { - "$ref": "#/components/schemas/FlowModule" + "$ref": "#/components/schemas/schemas-FlowModule" } } }, "required": [ - "modules", - "expr" + "modules" ] } }, @@ -26771,7 +29281,7 @@ "type" ] }, - "Identity": { + "schemas-Identity": { "type": "object", "properties": { "type": { @@ -26788,48 +29298,48 @@ "type" ] }, - "FlowModuleValue": { + "schemas-FlowModuleValue": { "oneOf": [ { - "$ref": "#/components/schemas/RawScript" + "$ref": "#/components/schemas/schemas-RawScript" }, { - "$ref": "#/components/schemas/PathScript" + "$ref": "#/components/schemas/schemas-PathScript" }, { - "$ref": "#/components/schemas/PathFlow" + "$ref": "#/components/schemas/schemas-PathFlow" }, { - "$ref": "#/components/schemas/ForloopFlow" + "$ref": "#/components/schemas/schemas-ForloopFlow" }, { - "$ref": "#/components/schemas/WhileloopFlow" + "$ref": "#/components/schemas/schemas-WhileloopFlow" }, { - "$ref": "#/components/schemas/BranchOne" + "$ref": "#/components/schemas/schemas-BranchOne" }, { - "$ref": "#/components/schemas/BranchAll" + "$ref": "#/components/schemas/schemas-BranchAll" }, { - "$ref": "#/components/schemas/Identity" + "$ref": "#/components/schemas/schemas-Identity" } ], "discriminator": { "propertyName": "type", "mapping": { - "rawscript": "#/components/schemas/RawScript", - "script": "#/components/schemas/PathScript", - "flow": "#/components/schemas/PathFlow", - "forloopflow": "#/components/schemas/ForloopFlow", - "whileloopflow": "#/components/schemas/WhileloopFlow", - "branchone": "#/components/schemas/BranchOne", - "branchall": "#/components/schemas/BranchAll", - "identity": "#/components/schemas/Identity" + "rawscript": "#/components/schemas/schemas-RawScript", + "script": "#/components/schemas/schemas-PathScript", + "flow": "#/components/schemas/schemas-PathFlow", + "forloopflow": "#/components/schemas/schemas-ForloopFlow", + "whileloopflow": "#/components/schemas/schemas-WhileloopFlow", + "branchone": "#/components/schemas/schemas-BranchOne", + "branchall": "#/components/schemas/schemas-BranchAll", + "identity": "#/components/schemas/schemas-Identity" } } }, - "StopAfterIf": { + "schemas-StopAfterIf": { "type": "object", "properties": { "skip_if_stopped": { @@ -26846,7 +29356,7 @@ "expr" ] }, - "Retry": { + "schemas-Retry": { "type": "object", "properties": { "constant": { @@ -26881,20 +29391,20 @@ } } }, - "FlowValue": { + "schemas-FlowValue": { "type": "object", "properties": { "modules": { "type": "array", "items": { - "$ref": "#/components/schemas/FlowModule" + "$ref": "#/components/schemas/schemas-FlowModule" } }, "failure_module": { - "$ref": "#/components/schemas/FlowModule" + "$ref": "#/components/schemas/schemas-FlowModule" }, "preprocessor_module": { - "$ref": "#/components/schemas/FlowModule" + "$ref": "#/components/schemas/schemas-FlowModule" }, "same_worker": { "type": "boolean" @@ -26925,28 +29435,7 @@ "modules" ] }, - "OpenFlow": { - "type": "object", - "properties": { - "summary": { - "type": "string" - }, - "description": { - "type": "string" - }, - "value": { - "$ref": "#/components/schemas/FlowValue" - }, - "schema": { - "type": "object" - } - }, - "required": [ - "summary", - "value" - ] - }, - "FlowStatusModule": { + "schemas-FlowStatusModule": { "type": "object", "properties": { "type": { @@ -27063,65 +29552,6 @@ "required": [ "type" ] - }, - "FlowStatus": { - "type": "object", - "properties": { - "step": { - "type": "integer" - }, - "modules": { - "type": "array", - "items": { - "$ref": "#/components/schemas/FlowStatusModule" - } - }, - "user_states": { - "additionalProperties": true - }, - "preprocessor_module": { - "allOf": [ - { - "$ref": "#/components/schemas/FlowStatusModule" - } - ] - }, - "failure_module": { - "allOf": [ - { - "$ref": "#/components/schemas/FlowStatusModule" - }, - { - "type": "object", - "properties": { - "parent_module": { - "type": "string" - } - } - } - ] - }, - "retry": { - "type": "object", - "properties": { - "fail_count": { - "type": "integer" - }, - "failed_jobs": { - "type": "array", - "items": { - "type": "string", - "format": "uuid" - } - } - } - } - }, - "required": [ - "step", - "modules", - "failure_module" - ] } } } diff --git a/backend/windmill-api/openapi-deref.yaml b/backend/windmill-api/openapi-deref.yaml index 4c13a9c7f8..70cc786faf 100644 --- a/backend/windmill-api/openapi-deref.yaml +++ b/backend/windmill-api/openapi-deref.yaml @@ -1,6 +1,6 @@ openapi: 3.0.3 info: - version: 1.492.1 + version: 1.520.1 title: Windmill API contact: name: Windmill Team @@ -87,7 +87,7 @@ paths: - name: id in: path required: true - schema: &ref_34 + schema: &ref_41 type: integer responses: '200': @@ -203,6 +203,8 @@ paths: type: string parameters: type: object + span: + type: string required: &ref_2 - id - timestamp @@ -233,24 +235,24 @@ paths: - name: before description: filter on started before (inclusive) timestamp in: query - schema: &ref_183 + schema: &ref_200 type: string format: date-time - name: after description: filter on created after (exclusive) timestamp in: query - schema: &ref_184 + schema: &ref_201 type: string format: date-time - name: username description: filter on exact username of user in: query - schema: &ref_189 + schema: &ref_209 type: string - name: operation description: filter on exact or prefix name of operation in: query - schema: &ref_190 + schema: &ref_210 type: string - name: operations in: query @@ -265,12 +267,12 @@ paths: - name: resource description: filter on exact or prefix name of resource in: query - schema: &ref_191 + schema: &ref_211 type: string - name: action_kind description: filter on type of operation in: query - schema: &ref_192 + schema: &ref_212 type: string enum: - Create @@ -307,12 +309,12 @@ paths: application/json: schema: type: object - properties: &ref_210 + properties: &ref_251 email: type: string password: type: string - required: &ref_211 + required: &ref_252 - email - password responses: @@ -437,7 +439,7 @@ paths: application/json: schema: type: object - properties: &ref_212 + properties: &ref_253 is_admin: type: boolean operator: @@ -465,7 +467,7 @@ paths: - name: path in: path required: true - schema: &ref_26 + schema: &ref_31 type: string responses: '200': @@ -585,6 +587,9 @@ paths: type: string company: type: string + skip_email: + type: boolean + description: Skip sending email notifications to the user required: - email - password @@ -806,7 +811,7 @@ paths: summary: get connected repositories operationId: getGlobalConnectedRepositories tags: - - git_sync + - Git Sync responses: '200': description: connected repositories @@ -814,7 +819,7 @@ paths: application/json: schema: type: array - items: &ref_331 + items: &ref_376 type: object properties: workspace_id: @@ -895,7 +900,7 @@ paths: application/json: schema: type: object - properties: &ref_280 + properties: &ref_325 email: type: string workspaces: @@ -918,6 +923,7 @@ paths: - runs - schedules - resources + - assets - variables - triggers - audit_logs @@ -937,6 +943,9 @@ paths: variables: type: boolean description: Whether operators can view variables + assets: + type: boolean + description: Whether operators can view assets audit_logs: type: boolean description: Whether operators can view audit logs @@ -957,7 +966,7 @@ paths: - name - username - color - required: &ref_281 + required: &ref_326 - email - workspaces /workspaces/list_as_superadmin: @@ -999,7 +1008,7 @@ paths: application/json: schema: type: object - properties: &ref_282 + properties: &ref_327 id: type: string name: @@ -1008,7 +1017,7 @@ paths: type: string color: type: string - required: &ref_283 + required: &ref_328 - id - name responses: @@ -1070,6 +1079,50 @@ paths: text/plain: schema: type: boolean + /settings/databases_exist: + post: + summary: checks that all given databases exist or else return the ones that don't + operationId: databasesExist + tags: + - setting + requestBody: + required: true + content: + application/json: + schema: + type: array + items: + type: string + responses: + '200': + description: databases that do not exist + content: + application/json: + schema: + type: array + items: + type: string + /settings/create_ducklake_database/{name}: + post: + summary: >- + Runs CREATE DATABASE on the Windmill Postgres and grants access to the + ducklake_user + operationId: createDucklakeDatabase + tags: + - setting + parameters: + - in: path + name: name + required: true + schema: + type: string + description: The name of the database to create + responses: + '200': + description: status + content: + application/json: + schema: {} /settings/global/{key}: get: summary: get global settings @@ -1244,7 +1297,7 @@ paths: type: array items: type: object - properties: &ref_29 + properties: &ref_34 id: type: integer description: Unique identifier for the alert @@ -1468,12 +1521,12 @@ paths: type: array items: type: object - properties: &ref_318 + properties: &ref_363 name: type: string value: type: object - required: &ref_319 + required: &ref_364 - name - value /users/email: @@ -2356,19 +2409,19 @@ paths: type: string ai_config: type: object - properties: &ref_20 + properties: &ref_21 providers: type: object additionalProperties: type: object - properties: &ref_197 + properties: &ref_238 resource_path: type: string models: type: array items: type: string - required: &ref_198 + required: &ref_239 - resource_path - models default_model: @@ -2378,7 +2431,7 @@ paths: type: string provider: type: string - enum: &ref_196 + enum: &ref_237 - openai - azure_openai - anthropic @@ -2400,12 +2453,13 @@ paths: type: string error_handler_extra_args: type: object - additionalProperties: &ref_21 {} + description: The arguments to pass to the script or flow + additionalProperties: &ref_22 {} error_handler_muted_on_cancel: type: boolean large_file_storage: type: object - properties: &ref_22 + properties: &ref_23 type: type: string enum: @@ -2413,10 +2467,13 @@ paths: - AzureBlobStorage - AzureWorkloadIdentity - S3AwsOidc + - GoogleCloudStorage s3_resource_path: type: string azure_blob_resource_path: type: string + gcs_resource_path: + type: string public_resource: type: boolean secondary_storage: @@ -2431,41 +2488,58 @@ paths: - AzureBlobStorage - AzureWorkloadIdentity - S3AwsOidc + - GoogleCloudStorage s3_resource_path: type: string azure_blob_resource_path: type: string + gcs_resource_path: + type: string public_resource: type: boolean + ducklake: + type: object + required: &ref_24 + - ducklakes + properties: &ref_25 + ducklakes: + type: object + additionalProperties: + type: object + required: + - catalog + - storage + properties: + catalog: + type: object + properties: + resource_type: + type: string + enum: + - postgresql + - mysql + - instance + resource_path: + type: string + required: + - resource_type + storage: + type: object + properties: + storage: + type: string + path: + type: string + required: + - path git_sync: type: object - properties: &ref_23 - include_path: - type: array - items: - type: string - include_type: - type: array - items: - type: string - enum: - - script - - flow - - app - - folder - - resource - - variable - - secret - - resourcetype - - schedule - - user - - group - - trigger + properties: &ref_26 repositories: type: array items: type: object - properties: &ref_302 + properties: &ref_27 script_path: type: string git_repo_resource_path: @@ -2474,29 +2548,53 @@ paths: type: boolean group_by_folder: type: boolean + collapsed: + type: boolean + settings: + type: object + properties: + include_path: + type: array + items: + type: string + include_type: + type: array + items: + type: string + enum: &ref_20 + - script + - flow + - app + - folder + - resource + - variable + - secret + - resourcetype + - schedule + - user + - group + - trigger + - settings + - key + exclude_path: + type: array + items: + type: string + extra_include_path: + type: array + items: + type: string exclude_types_override: type: array items: type: string - enum: - - script - - flow - - app - - folder - - resource - - variable - - secret - - resourcetype - - schedule - - user - - group - - trigger - required: &ref_303 + enum: *ref_20 + required: &ref_28 - script_path - git_repo_resource_path deploy_ui: type: object - properties: &ref_24 + properties: &ref_29 include_path: type: array items: @@ -2505,19 +2603,12 @@ paths: type: array items: type: string - enum: - - script - - flow - - app - - resource - - variable - - secret - - trigger + enum: *ref_20 default_app: type: string default_scripts: type: object - properties: &ref_25 + properties: &ref_30 order: type: array items: @@ -2975,7 +3066,7 @@ paths: application/json: schema: type: object - properties: *ref_20 + properties: *ref_21 responses: '200': description: status @@ -3001,7 +3092,7 @@ paths: application/json: schema: type: object - properties: *ref_20 + properties: *ref_21 /w/{workspace}/workspaces/edit_error_handler: post: summary: edit error handler @@ -3025,7 +3116,8 @@ paths: type: string error_handler_extra_args: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 error_handler_muted_on_cancel: type: boolean responses: @@ -3056,7 +3148,58 @@ paths: properties: large_file_storage: type: object - properties: *ref_22 + properties: *ref_23 + responses: + '200': + description: status + content: + application/json: + schema: {} + /w/{workspace}/workspaces/list_ducklakes: + get: + summary: list ducklakes + operationId: listDucklakes + tags: + - workspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + responses: + '200': + description: status + content: + application/json: + schema: + type: array + items: + type: string + /w/{workspace}/workspaces/edit_ducklake_config: + post: + summary: edit ducklake settings + operationId: editDucklakeConfig + tags: + - workspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: Ducklake settings + required: true + content: + application/json: + schema: + type: object + required: + - settings + properties: + settings: + type: object + required: *ref_24 + properties: *ref_25 responses: '200': description: status @@ -3084,7 +3227,72 @@ paths: properties: git_sync_settings: type: object - properties: *ref_23 + properties: *ref_26 + responses: + '200': + description: status + content: + application/json: + schema: {} + /w/{workspace}/workspaces/edit_git_sync_repository: + post: + summary: add or update individual git sync repository + operationId: editGitSyncRepository + tags: + - workspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: Git sync repository settings to add or update + required: true + content: + application/json: + schema: + type: object + properties: + git_repo_resource_path: + type: string + description: The resource path of the git repository to update + repository: + type: object + properties: *ref_27 + required: *ref_28 + required: + - git_repo_resource_path + - repository + responses: + '200': + description: status + content: + application/json: + schema: {} + /w/{workspace}/workspaces/delete_git_sync_repository: + delete: + summary: delete individual git sync repository + operationId: deleteGitSyncRepository + tags: + - workspace + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: Git sync repository to delete + required: true + content: + application/json: + schema: + type: object + properties: + git_repo_resource_path: + type: string + description: The resource path of the git repository to delete + required: + - git_repo_resource_path responses: '200': description: status @@ -3112,7 +3320,7 @@ paths: properties: deploy_ui_settings: type: object - properties: *ref_24 + properties: *ref_29 responses: '200': description: status @@ -3164,7 +3372,7 @@ paths: application/json: schema: type: object - properties: *ref_25 + properties: *ref_30 responses: '200': description: status @@ -3189,7 +3397,7 @@ paths: application/json: schema: type: object - properties: *ref_25 + properties: *ref_30 /w/{workspace}/workspaces/set_environment_variable: post: summary: set environment variable @@ -3315,7 +3523,7 @@ paths: application/json: schema: type: object - properties: *ref_22 + properties: *ref_23 /w/{workspace}/workspaces/usage: get: summary: get usage @@ -3420,7 +3628,7 @@ paths: type: array items: type: object - properties: &ref_209 + properties: &ref_250 email: type: string executions: @@ -3468,6 +3676,48 @@ paths: text/plain: schema: type: string + /tokens/list/scopes: + get: + summary: list of available scopes + operationId: ListAvailableScopes + tags: + - token + responses: + '200': + description: list of available scopes + content: + application/json: + schema: + type: array + items: + type: object + properties: &ref_339 + name: + type: string + description: + type: string + nullable: true + scopes: + type: array + items: + type: object + properties: &ref_337 + value: + type: string + label: + type: string + description: + type: string + nullable: true + requires_resource_path: + type: boolean + required: &ref_338 + - value + - label + - requires_resource_path + required: &ref_340 + - name + - scopes /users/tokens/create: post: summary: create token @@ -3481,7 +3731,7 @@ paths: application/json: schema: type: object - properties: &ref_213 + properties: &ref_254 label: type: string expiration: @@ -3513,7 +3763,7 @@ paths: application/json: schema: type: object - properties: &ref_214 + properties: &ref_255 label: type: string expiration: @@ -3523,7 +3773,7 @@ paths: type: string workspace_id: type: string - required: &ref_215 + required: &ref_256 - impersonate_email responses: '201': @@ -3579,7 +3829,7 @@ paths: type: array items: type: object - properties: &ref_47 + properties: &ref_54 label: type: string expiration: @@ -3599,7 +3849,7 @@ paths: type: string email: type: string - required: &ref_48 + required: &ref_55 - token_prefix - created_at - last_used_at @@ -3630,6 +3880,7 @@ paths: post: summary: create variable operationId: createVariable + x-mcp-tool: true tags: - variable parameters: @@ -3638,6 +3889,7 @@ paths: required: true schema: *ref_0 - name: already_encrypted + description: whether the variable is already encrypted (default false) in: query schema: type: boolean @@ -3648,23 +3900,30 @@ paths: application/json: schema: type: object - properties: &ref_218 + properties: &ref_259 path: type: string + description: The path to the variable value: type: string + description: The value of the variable is_secret: type: boolean + description: Whether the variable is a secret description: type: string + description: The description of the variable account: type: integer + description: The account identifier is_oauth: type: boolean + description: Whether the variable is an OAuth variable expires_at: type: string + description: The expiration date of the variable format: date-time - required: &ref_219 + required: &ref_260 - path - value - is_secret @@ -3705,6 +3964,7 @@ paths: delete: summary: delete variable operationId: deleteVariable + x-mcp-tool: true tags: - variable parameters: @@ -3715,7 +3975,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: variable deleted @@ -3727,6 +3987,7 @@ paths: post: summary: update variable operationId: updateVariable + x-mcp-tool: true tags: - variable parameters: @@ -3737,8 +3998,9 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: already_encrypted + description: whether the variable is already encrypted (default false) in: query schema: type: boolean @@ -3749,15 +4011,19 @@ paths: application/json: schema: type: object - properties: &ref_220 + properties: &ref_261 path: type: string + description: The path to the variable value: type: string + description: The new value of the variable is_secret: type: boolean + description: Whether the variable is a secret description: type: string + description: The new description of the variable responses: '200': description: variable updated @@ -3769,6 +4035,7 @@ paths: get: summary: get variable operationId: getVariable + x-mcp-tool: true tags: - variable parameters: @@ -3779,7 +4046,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: decrypt_secret description: | ask to decrypt secret if this variable is secret @@ -3801,7 +4068,7 @@ paths: application/json: schema: type: object - properties: &ref_27 + properties: &ref_32 workspace_id: type: string path: @@ -3831,7 +4098,7 @@ paths: expires_at: type: string format: date-time - required: &ref_28 + required: &ref_33 - workspace_id - path - is_secret @@ -3850,7 +4117,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: variable @@ -3872,7 +4139,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: variable @@ -3884,6 +4151,7 @@ paths: get: summary: list variables operationId: listVariable + x-mcp-tool: true tags: - variable parameters: @@ -3892,6 +4160,7 @@ paths: required: true schema: *ref_0 - name: path_start + description: filter variables by path prefix in: query schema: type: string @@ -3912,8 +4181,8 @@ paths: type: array items: type: object - properties: *ref_27 - required: *ref_28 + properties: *ref_32 + required: *ref_33 /w/{workspace}/variables/list_contextual: get: summary: list contextual variables @@ -3934,7 +4203,7 @@ paths: type: array items: type: object - properties: &ref_216 + properties: &ref_257 name: type: string value: @@ -3943,11 +4212,31 @@ paths: type: string is_custom: type: boolean - required: &ref_217 + required: &ref_258 - name - value - description - is_custom + /w/{workspace}/workspaces/get_secondary_storage_names: + get: + summary: get secondary storage names + operationId: getSecondaryStorageNames + tags: + - setting + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + responses: + '200': + description: status + content: + application/json: + schema: + type: array + items: + type: string /w/{workspace}/workspaces/critical_alerts: get: summary: Get all critical alerts for this workspace @@ -3992,7 +4281,7 @@ paths: type: array items: type: object - properties: *ref_29 + properties: *ref_34 total_rows: type: integer description: Total number of rows matching the query. @@ -4089,7 +4378,7 @@ paths: - name: client_name in: path required: true - schema: &ref_30 + schema: &ref_35 type: string requestBody: description: Partially filled script @@ -4190,7 +4479,7 @@ paths: - name: client_name in: path required: true - schema: *ref_30 + schema: *ref_35 requestBody: description: code endpoint required: true @@ -4213,7 +4502,7 @@ paths: application/json: schema: type: object - properties: &ref_295 + properties: &ref_36 access_token: type: string expires_in: @@ -4224,7 +4513,9 @@ paths: type: array items: type: string - required: &ref_296 + grant_type: + type: string + required: &ref_37 - access_token /w/{workspace}/oauth/create_account: post: @@ -4251,6 +4542,24 @@ paths: type: integer client: type: string + grant_type: + type: string + default: authorization_code + cc_client_id: + type: string + description: >- + OAuth client ID for resource-level credentials + (client_credentials flow only) + cc_client_secret: + type: string + description: >- + OAuth client secret for resource-level credentials + (client_credentials flow only) + cc_token_url: + type: string + description: >- + OAuth token URL override for resource-level authentication + (client_credentials flow only) required: - expires_in - client @@ -4261,6 +4570,52 @@ paths: text/plain: schema: type: string + /oauth/connect_client_credentials/{client}: + post: + summary: connect OAuth using client credentials + operationId: connectClientCredentials + tags: + - oauth + parameters: + - name: client + in: path + description: OAuth client name + required: true + schema: + type: string + requestBody: + description: client credentials flow parameters + required: true + content: + application/json: + schema: + type: object + properties: + scopes: + type: array + items: + type: string + cc_client_id: + type: string + description: OAuth client ID for resource-level authentication + cc_client_secret: + type: string + description: OAuth client secret for resource-level authentication + cc_token_url: + type: string + description: OAuth token URL override for resource-level authentication + required: + - cc_client_id + - cc_client_secret + responses: + '200': + description: OAuth token response + content: + application/json: + schema: + type: object + properties: *ref_36 + required: *ref_37 /w/{workspace}/oauth/refresh_token/{id}: post: summary: refresh token @@ -4275,7 +4630,7 @@ paths: - name: id in: path required: true - schema: &ref_31 + schema: &ref_38 type: integer requestBody: description: variable path @@ -4310,7 +4665,7 @@ paths: - name: id in: path required: true - schema: *ref_31 + schema: *ref_38 responses: '200': description: disconnected client @@ -4425,6 +4780,10 @@ paths: type: array items: type: string + grant_types: + type: array + items: + type: string /teams/sync: post: operationId: syncTeams @@ -4440,11 +4799,11 @@ paths: type: array items: type: object - required: &ref_327 + required: &ref_372 - team_id - team_name - channels - properties: &ref_328 + properties: &ref_373 team_id: type: string description: The unique identifier of the Microsoft Teams team @@ -4458,12 +4817,12 @@ paths: description: List of channels within the team items: type: object - required: &ref_329 + required: &ref_374 - channel_id - channel_name - tenant_id - service_url - properties: &ref_330 + properties: &ref_375 channel_id: type: string description: The unique identifier of the channel @@ -4518,6 +4877,7 @@ paths: post: summary: create resource operationId: createResource + x-mcp-tool: true tags: - resource parameters: @@ -4526,6 +4886,7 @@ paths: required: true schema: *ref_0 - name: update_if_exists + description: update the resource if it already exists (default false) in: query schema: type: boolean @@ -4536,15 +4897,18 @@ paths: application/json: schema: type: object - properties: &ref_227 + properties: &ref_266 path: type: string + description: The path to the resource value: {} description: type: string + description: The description of the resource resource_type: type: string - required: &ref_228 + description: The resource_type associated with the resource + required: &ref_267 - path - value - resource_type @@ -4559,6 +4923,7 @@ paths: delete: summary: delete resource operationId: deleteResource + x-mcp-tool: true tags: - resource parameters: @@ -4569,7 +4934,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: resource deleted @@ -4581,6 +4946,7 @@ paths: post: summary: update resource operationId: updateResource + x-mcp-tool: true tags: - resource parameters: @@ -4591,7 +4957,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated resource required: true @@ -4599,12 +4965,17 @@ paths: application/json: schema: type: object - properties: &ref_229 + properties: &ref_268 path: type: string + description: The path to the resource description: type: string + description: The new description of the resource value: {} + resource_type: + type: string + description: The new resource_type to be associated with the resource responses: '200': description: resource updated @@ -4626,7 +4997,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated resource required: true @@ -4647,6 +5018,7 @@ paths: get: summary: get resource operationId: getResource + x-mcp-tool: true tags: - resource parameters: @@ -4657,7 +5029,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: resource @@ -4665,7 +5037,7 @@ paths: application/json: schema: type: object - properties: &ref_230 + properties: &ref_269 workspace_id: type: string path: @@ -4686,7 +5058,7 @@ paths: edited_at: type: string format: date-time - required: &ref_231 + required: &ref_270 - path - resource_type - is_oauth @@ -4704,7 +5076,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: job_id description: job id in: query @@ -4731,7 +5103,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: resource value @@ -4752,7 +5124,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: does resource exists @@ -4764,6 +5136,7 @@ paths: get: summary: list resources operationId: listResource + x-mcp-tool: true tags: - resource parameters: @@ -4790,6 +5163,7 @@ paths: schema: type: string - name: path_start + description: filter resources by path prefix in: query schema: type: string @@ -4802,7 +5176,7 @@ paths: type: array items: type: object - properties: &ref_232 + properties: &ref_271 workspace_id: type: string path: @@ -4833,7 +5207,7 @@ paths: edited_at: type: string format: date-time - required: &ref_233 + required: &ref_272 - path - resource_type - is_oauth @@ -4880,7 +5254,7 @@ paths: - name: name in: path required: true - schema: &ref_163 + schema: &ref_180 type: string responses: '200': @@ -4917,7 +5291,7 @@ paths: application/json: schema: type: object - properties: &ref_32 + properties: &ref_39 workspace_id: type: string name: @@ -4932,7 +5306,7 @@ paths: format: date-time format_extension: type: string - required: &ref_33 + required: &ref_40 - name responses: '201': @@ -4972,7 +5346,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: resource_type deleted @@ -4994,7 +5368,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated resource_type required: true @@ -5002,7 +5376,7 @@ paths: application/json: schema: type: object - properties: &ref_234 + properties: &ref_273 schema: {} description: type: string @@ -5027,7 +5401,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: resource_type deleted @@ -5035,8 +5409,8 @@ paths: application/json: schema: type: object - properties: *ref_32 - required: *ref_33 + properties: *ref_39 + required: *ref_40 /w/{workspace}/resources/type/exists/{path}: get: summary: does resource_type exists @@ -5051,7 +5425,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: does resource_type exist @@ -5063,6 +5437,7 @@ paths: get: summary: list resource_types operationId: listResourceType + x-mcp-tool: true tags: - resource parameters: @@ -5079,8 +5454,8 @@ paths: type: array items: type: object - properties: *ref_32 - required: *ref_33 + properties: *ref_39 + required: *ref_40 /w/{workspace}/resources/type/listnames: get: summary: list resource_types names @@ -5219,7 +5594,7 @@ paths: - name: id in: path required: true - schema: *ref_34 + schema: *ref_41 responses: '200': description: flow @@ -5230,55 +5605,57 @@ paths: properties: flow: type: object - properties: &ref_61 + properties: &ref_68 summary: type: string description: type: string value: type: object - properties: &ref_78 + properties: &ref_377 modules: type: array items: type: object - properties: &ref_37 + properties: &ref_44 id: type: string value: - oneOf: &ref_352 + oneOf: &ref_216 - type: object - properties: &ref_336 + properties: &ref_219 input_transforms: type: object additionalProperties: - oneOf: &ref_35 + oneOf: &ref_42 - type: object - properties: &ref_332 + properties: &ref_84 value: {} type: type: string enum: - - javascript - required: &ref_333 - - expr + - static + required: &ref_85 + - value - type - type: object - properties: &ref_334 + properties: &ref_86 expr: type: string type: type: string enum: - javascript - required: &ref_335 + required: &ref_87 - expr - type - discriminator: &ref_36 + discriminator: &ref_43 propertyName: type mapping: - static: '#/components/schemas/StaticTransform' - javascript: '#/components/schemas/JavascriptTransform' + static: >- + #/components/schemas/schemas-StaticTransform + javascript: >- + #/components/schemas/schemas-JavascriptTransform content: type: string language: @@ -5317,18 +5694,46 @@ paths: type: string is_trigger: type: boolean - required: &ref_337 + assets: + type: array + items: + type: object + required: + - path + - kind + properties: + path: + type: string + kind: + type: string + enum: + - s3object + - resource + - ducklake + access_type: + type: string + enum: + - r + - w + - rw + alt_access_type: + type: string + enum: + - r + - w + - rw + required: &ref_220 - type - content - language - input_transforms - type: object - properties: &ref_338 + properties: &ref_221 input_transforms: type: object additionalProperties: - oneOf: *ref_35 - discriminator: *ref_36 + oneOf: *ref_42 + discriminator: *ref_43 path: type: string hash: @@ -5341,40 +5746,40 @@ paths: type: string is_trigger: type: boolean - required: &ref_339 + required: &ref_222 - type - path - input_transforms - type: object - properties: &ref_340 + properties: &ref_223 input_transforms: type: object additionalProperties: - oneOf: *ref_35 - discriminator: *ref_36 + oneOf: *ref_42 + discriminator: *ref_43 path: type: string type: type: string enum: - flow - required: &ref_341 + required: &ref_224 - type - path - input_transforms - type: object - properties: &ref_342 + properties: &ref_225 modules: type: array items: type: object - properties: *ref_37 - required: &ref_38 + properties: *ref_44 + required: &ref_45 - value - id iterator: - oneOf: *ref_35 - discriminator: *ref_36 + oneOf: *ref_42 + discriminator: *ref_43 skip_failures: type: boolean type: @@ -5385,35 +5790,35 @@ paths: type: boolean parallelism: type: integer - required: &ref_343 + required: &ref_226 - modules - iterator - skip_failures - type - type: object - properties: &ref_344 + properties: &ref_227 modules: type: array items: type: object - properties: *ref_37 - required: *ref_38 + properties: *ref_44 + required: *ref_45 skip_failures: type: boolean type: type: string enum: - - forloopflow + - whileloopflow parallel: type: boolean parallelism: type: integer - required: &ref_345 + required: &ref_228 - modules - skip_failures - type - type: object - properties: &ref_346 + properties: &ref_229 branches: type: array items: @@ -5427,8 +5832,8 @@ paths: type: array items: type: object - properties: *ref_37 - required: *ref_38 + properties: *ref_44 + required: *ref_45 required: - modules - expr @@ -5436,20 +5841,20 @@ paths: type: array items: type: object - properties: *ref_37 - required: *ref_38 + properties: *ref_44 + required: *ref_45 required: - modules type: type: string enum: - branchone - required: &ref_347 + required: &ref_230 - branches - default - type - type: object - properties: &ref_348 + properties: &ref_231 branches: type: array items: @@ -5463,56 +5868,56 @@ paths: type: array items: type: object - properties: *ref_37 - required: *ref_38 + properties: *ref_44 + required: *ref_45 required: - modules - - expr type: type: string enum: - branchall parallel: type: boolean - required: &ref_349 + required: &ref_232 - branches - type - type: object - properties: &ref_350 + properties: &ref_233 type: type: string enum: - identity flow: type: boolean - required: &ref_351 + required: &ref_234 - type - discriminator: &ref_353 + discriminator: &ref_217 propertyName: type mapping: - rawscript: '#/components/schemas/RawScript' - script: '#/components/schemas/PathScript' - flow: '#/components/schemas/PathFlow' - forloopflow: '#/components/schemas/ForloopFlow' - whileloopflow: '#/components/schemas/WhileloopFlow' - branchone: '#/components/schemas/BranchOne' - branchall: '#/components/schemas/BranchAll' - identity: '#/components/schemas/Identity' + rawscript: '#/components/schemas/schemas-RawScript' + script: '#/components/schemas/schemas-PathScript' + flow: '#/components/schemas/schemas-PathFlow' + forloopflow: '#/components/schemas/schemas-ForloopFlow' + whileloopflow: >- + #/components/schemas/schemas-WhileloopFlow + branchone: '#/components/schemas/schemas-BranchOne' + branchall: '#/components/schemas/schemas-BranchAll' + identity: '#/components/schemas/schemas-Identity' stop_after_if: type: object - properties: &ref_39 + properties: &ref_46 skip_if_stopped: type: boolean expr: type: string error_message: type: string - required: &ref_40 + required: &ref_47 - expr stop_after_all_iters_if: type: object - properties: *ref_39 - required: *ref_40 + properties: *ref_46 + required: *ref_47 skip_if: type: object properties: @@ -5521,8 +5926,8 @@ paths: required: - expr sleep: - oneOf: *ref_35 - discriminator: *ref_36 + oneOf: *ref_42 + discriminator: *ref_43 cache_ttl: type: number timeout: @@ -5552,8 +5957,8 @@ paths: user_auth_required: type: boolean user_groups_required: - oneOf: *ref_35 - discriminator: *ref_36 + oneOf: *ref_42 + discriminator: *ref_43 self_approval_disabled: type: boolean hide_cancel: @@ -5566,7 +5971,7 @@ paths: type: boolean retry: type: object - properties: &ref_114 + properties: &ref_218 constant: type: object properties: @@ -5587,15 +5992,15 @@ paths: type: integer minimum: 0 maximum: 100 - required: *ref_38 + required: *ref_45 failure_module: type: object - properties: *ref_37 - required: *ref_38 + properties: *ref_44 + required: *ref_45 preprocessor_module: type: object - properties: *ref_37 - required: *ref_38 + properties: *ref_44 + required: *ref_45 same_worker: type: boolean concurrent_limit: @@ -5612,11 +6017,11 @@ paths: type: number early_return: type: string - required: &ref_79 + required: &ref_378 - modules schema: type: object - required: &ref_62 + required: &ref_69 - summary - value /apps/hub/list: @@ -5669,7 +6074,7 @@ paths: - name: id in: path required: true - schema: *ref_34 + schema: *ref_41 responses: '200': description: app @@ -5699,7 +6104,7 @@ paths: - name: custom_path in: path required: true - schema: &ref_74 + schema: &ref_81 type: string responses: '200': @@ -5709,7 +6114,7 @@ paths: schema: allOf: - type: object - properties: &ref_69 + properties: &ref_76 id: type: integer workspace_id: @@ -5731,7 +6136,7 @@ paths: type: object policy: type: object - properties: &ref_68 + properties: &ref_75 triggerables: type: object additionalProperties: @@ -5775,7 +6180,7 @@ paths: type: boolean custom_path: type: string - required: &ref_70 + required: &ref_77 - id - workspace_id - path @@ -5801,7 +6206,7 @@ paths: - name: path in: path required: true - schema: &ref_41 + schema: &ref_48 type: string responses: '200': @@ -5820,7 +6225,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: script details @@ -5891,7 +6296,7 @@ paths: type: number kind: type: string - enum: &ref_42 + enum: &ref_49 - script - failure - trigger @@ -5962,7 +6367,7 @@ paths: type: string kind: type: string - enum: *ref_42 + enum: *ref_49 score: type: number required: @@ -6005,6 +6410,7 @@ paths: get: summary: list all scripts operationId: listScripts + x-mcp-tool: true tags: - script parameters: @@ -6023,12 +6429,12 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: &ref_59 + schema: &ref_66 type: boolean - name: created_by description: mask to filter exact matching user creator in: query - schema: &ref_60 + schema: &ref_67 type: string - name: path_start description: mask to filter matching starting path @@ -6132,6 +6538,13 @@ paths: in: query schema: type: boolean + - name: languages + in: query + description: | + Filter to only include scripts written in the given languages. + Accepts multiple values as a comma-separated list. + schema: + type: string responses: '200': description: All scripts @@ -6141,7 +6554,7 @@ paths: type: array items: type: object - properties: &ref_44 + properties: &ref_51 workspace_id: type: string hash: @@ -6184,7 +6597,7 @@ paths: type: string language: type: string - enum: &ref_43 + enum: &ref_50 - python3 - deno - go @@ -6205,6 +6618,7 @@ paths: - csharp - nu - java + - duckdb kind: type: string enum: @@ -6256,7 +6670,7 @@ paths: type: boolean on_behalf_of_email: type: string - required: &ref_45 + required: &ref_52 - hash - path - summary @@ -6354,7 +6768,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: draft deleted @@ -6380,7 +6794,7 @@ paths: application/json: schema: type: object - properties: &ref_49 + properties: &ref_56 path: type: string parent_hash: @@ -6399,7 +6813,7 @@ paths: type: string language: type: string - enum: *ref_43 + enum: *ref_50 kind: type: string enum: @@ -6449,7 +6863,35 @@ paths: type: boolean on_behalf_of_email: type: string - required: &ref_50 + assets: + type: array + items: + type: object + required: + - path + - kind + properties: + path: + type: string + kind: + type: string + enum: &ref_202 + - s3object + - resource + - ducklake + access_type: + type: string + enum: + - r + - w + - rw + alt_access_type: + type: string + enum: + - r + - w + - rw + required: &ref_57 - path - summary - description @@ -6476,7 +6918,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 requestBody: description: Workspace error handler enabled required: true @@ -6564,7 +7006,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: script archived @@ -6586,7 +7028,7 @@ paths: - name: hash in: path required: true - schema: &ref_46 + schema: &ref_53 type: string responses: '200': @@ -6595,8 +7037,8 @@ paths: application/json: schema: type: object - properties: *ref_44 - required: *ref_45 + properties: *ref_51 + required: *ref_52 /w/{workspace}/scripts/delete/h/{hash}: post: summary: delete script by hash (erase content but keep hash, require admin) @@ -6611,7 +7053,7 @@ paths: - name: hash in: path required: true - schema: *ref_46 + schema: *ref_53 responses: '200': description: script details @@ -6619,8 +7061,8 @@ paths: application/json: schema: type: object - properties: *ref_44 - required: *ref_45 + properties: *ref_51 + required: *ref_52 /w/{workspace}/scripts/delete/p/{path}: post: summary: delete script at a given path (require admin) @@ -6635,7 +7077,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 - name: keep_captures description: keep captures in: query @@ -6652,6 +7094,7 @@ paths: get: summary: get script by path operationId: getScriptByPath + x-mcp-tool: true tags: - script parameters: @@ -6662,7 +7105,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 - name: with_starred_info in: query schema: @@ -6674,8 +7117,8 @@ paths: application/json: schema: type: object - properties: *ref_44 - required: *ref_45 + properties: *ref_51 + required: *ref_52 /w/{workspace}/scripts/get_triggers_count/{path}: get: summary: get triggers count of script @@ -6690,7 +7133,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: triggers count @@ -6698,7 +7141,7 @@ paths: application/json: schema: type: object - properties: &ref_66 + properties: &ref_73 primary_schedule: type: object properties: @@ -6740,7 +7183,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: tokens list @@ -6750,8 +7193,8 @@ paths: type: array items: type: object - properties: *ref_47 - required: *ref_48 + properties: *ref_54 + required: *ref_55 /w/{workspace}/scripts/get/draft/{path}: get: summary: get script by path with draft @@ -6766,23 +7209,23 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: script details content: application/json: schema: - allOf: &ref_202 + allOf: &ref_243 - type: object - properties: *ref_49 - required: *ref_50 + properties: *ref_56 + required: *ref_57 - type: object properties: draft: type: object - properties: *ref_49 - required: *ref_50 + properties: *ref_56 + required: *ref_57 hash: type: string required: @@ -6801,7 +7244,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: script history @@ -6811,12 +7254,12 @@ paths: type: array items: type: object - properties: &ref_51 + properties: &ref_58 script_hash: type: string deployment_msg: type: string - required: &ref_52 + required: &ref_59 - script_hash /w/{workspace}/scripts/list_paths_from_workspace_runnable/{path}: get: @@ -6832,7 +7275,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: list of script paths @@ -6854,7 +7297,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 tags: - script responses: @@ -6864,8 +7307,8 @@ paths: application/json: schema: type: object - properties: *ref_51 - required: *ref_52 + properties: *ref_58 + required: *ref_59 /w/{workspace}/scripts/history_update/h/{hash}/p/{path}: post: summary: update history of a script @@ -6880,11 +7323,11 @@ paths: - name: hash in: path required: true - schema: *ref_46 + schema: *ref_53 - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 requestBody: description: Script deployment message required: true @@ -6916,7 +7359,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: script content @@ -6940,12 +7383,12 @@ paths: - name: token in: path required: true - schema: &ref_187 + schema: &ref_207 type: string - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: script content @@ -6967,7 +7410,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: does it exists @@ -6989,11 +7432,15 @@ paths: - name: hash in: path required: true - schema: *ref_46 + schema: *ref_53 - name: with_starred_info in: query schema: type: boolean + - name: authed + in: query + schema: + type: boolean responses: '200': description: script details @@ -7001,8 +7448,8 @@ paths: application/json: schema: type: object - properties: *ref_44 - required: *ref_45 + properties: *ref_51 + required: *ref_52 /w/{workspace}/scripts/raw/h/{path}: get: summary: raw script by hash @@ -7017,7 +7464,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: script content @@ -7039,7 +7486,7 @@ paths: - name: hash in: path required: true - schema: *ref_46 + schema: *ref_53 responses: '200': description: script details @@ -7128,7 +7575,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 - name: scheduled_for description: when to schedule this job (leave empty for immediate run) in: query @@ -7150,20 +7597,20 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: &ref_53 + schema: &ref_60 type: string format: uuid - name: tag description: Override the tag to use in: query - schema: &ref_55 + schema: &ref_62 type: string - name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: &ref_56 + schema: &ref_63 type: string - name: job_id description: >- @@ -7172,7 +7619,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: &ref_54 + schema: &ref_61 type: string format: uuid - name: invisible_to_owner @@ -7187,7 +7634,8 @@ paths: application/json: schema: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 responses: '201': description: job created @@ -7210,13 +7658,13 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_53 + schema: *ref_60 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -7224,7 +7672,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_54 + schema: *ref_61 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -7233,14 +7681,14 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: &ref_57 + schema: &ref_64 type: string - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: &ref_58 + schema: &ref_65 type: string requestBody: description: script args @@ -7249,7 +7697,8 @@ paths: application/json: schema: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 responses: '200': description: job result @@ -7270,23 +7719,23 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_53 + schema: *ref_60 - name: tag description: Override the tag to use in: query - schema: *ref_55 + schema: *ref_62 - name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: *ref_56 + schema: *ref_63 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -7294,7 +7743,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_54 + schema: *ref_61 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -7303,13 +7752,13 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_57 + schema: *ref_64 - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_58 + schema: *ref_65 requestBody: description: script args required: true @@ -7317,7 +7766,8 @@ paths: application/json: schema: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 responses: '200': description: job result @@ -7337,23 +7787,23 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_53 + schema: *ref_60 - name: tag description: Override the tag to use in: query - schema: *ref_55 + schema: *ref_62 - name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: *ref_56 + schema: *ref_63 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -7361,7 +7811,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_54 + schema: *ref_61 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -7370,13 +7820,13 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_57 + schema: *ref_64 - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_58 + schema: *ref_65 - name: payload description: > The base64 encoded payload that has been encoded as a JSON. e.g how @@ -7384,7 +7834,7 @@ paths: `encodeURIComponent(btoa(JSON.stringify({a: 2})))` in: query - schema: &ref_113 + schema: &ref_126 type: string responses: '200': @@ -7406,7 +7856,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -7415,13 +7865,13 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_57 + schema: *ref_64 - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_58 + schema: *ref_65 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -7429,7 +7879,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_54 + schema: *ref_61 requestBody: description: script args required: true @@ -7437,7 +7887,8 @@ paths: application/json: schema: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 responses: '200': description: job result @@ -7458,7 +7909,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -7467,13 +7918,13 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_57 + schema: *ref_64 - name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_58 + schema: *ref_65 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -7481,7 +7932,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_54 + schema: *ref_61 requestBody: description: script args required: true @@ -7489,7 +7940,8 @@ paths: application/json: schema: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 responses: '200': description: job result @@ -7574,6 +8026,7 @@ paths: get: summary: list all flows operationId: listFlows + x-mcp-tool: true tags: - flow parameters: @@ -7592,11 +8045,11 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_59 + schema: *ref_66 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_60 + schema: *ref_67 - name: path_start description: mask to filter matching starting path in: query @@ -7650,12 +8103,12 @@ paths: type: array items: allOf: - - allOf: &ref_65 + - allOf: &ref_72 - type: object - properties: *ref_61 - required: *ref_62 + properties: *ref_68 + required: *ref_69 - type: object - properties: &ref_285 + properties: &ref_330 workspace_id: type: string path: @@ -7669,7 +8122,7 @@ paths: type: boolean extra_perms: type: object - additionalProperties: &ref_284 + additionalProperties: &ref_329 type: boolean starred: type: boolean @@ -7689,7 +8142,7 @@ paths: type: boolean on_behalf_of_email: type: string - required: &ref_286 + required: &ref_331 - path - edited_by - edited_at @@ -7717,7 +8170,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 tags: - flow responses: @@ -7729,7 +8182,7 @@ paths: type: array items: type: object - properties: &ref_63 + properties: &ref_70 id: type: integer created_at: @@ -7737,7 +8190,7 @@ paths: format: date-time deployment_msg: type: string - required: &ref_64 + required: &ref_71 - id - created_at /w/{workspace}/flows/get_latest_version/{path}: @@ -7752,7 +8205,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 tags: - flow responses: @@ -7762,8 +8215,8 @@ paths: application/json: schema: type: object - properties: *ref_63 - required: *ref_64 + properties: *ref_70 + required: *ref_71 /w/{workspace}/flows/list_paths_from_workspace_runnable/{runnable_kind}/{path}: get: summary: list flow paths from workspace runnable @@ -7778,7 +8231,7 @@ paths: - name: runnable_kind in: path required: true - schema: &ref_73 + schema: &ref_80 type: string enum: - script @@ -7786,7 +8239,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: list of flow paths @@ -7813,7 +8266,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 tags: - flow responses: @@ -7822,7 +8275,7 @@ paths: content: application/json: schema: - allOf: *ref_65 + allOf: *ref_72 /w/{workspace}/flows/history_update/v/{version}/p/{path}: post: summary: update flow history @@ -7840,7 +8293,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 requestBody: description: Flow deployment message required: true @@ -7866,6 +8319,7 @@ paths: get: summary: get flow by path operationId: getFlowByPath + x-mcp-tool: true tags: - flow parameters: @@ -7876,7 +8330,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 - name: with_starred_info in: query schema: @@ -7887,7 +8341,7 @@ paths: content: application/json: schema: - allOf: *ref_65 + allOf: *ref_72 /w/{workspace}/flows/deployment_status/p/{path}: get: summary: get flow deployment status @@ -7902,7 +8356,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: flow status @@ -7927,7 +8381,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: triggers count @@ -7935,7 +8389,7 @@ paths: application/json: schema: type: object - properties: *ref_66 + properties: *ref_73 /w/{workspace}/flows/list_tokens/{path}: get: summary: get tokens with flow scope @@ -7950,7 +8404,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: tokens list @@ -7960,8 +8414,8 @@ paths: type: array items: type: object - properties: *ref_47 - required: *ref_48 + properties: *ref_54 + required: *ref_55 /w/{workspace}/flows/toggle_workspace_error_handler/{path}: post: summary: Toggle ON and OFF the workspace error handler for a given flow @@ -7976,7 +8430,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 requestBody: description: Workspace error handler enabled required: true @@ -8008,7 +8462,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: flow details with draft @@ -8016,11 +8470,11 @@ paths: application/json: schema: allOf: - - allOf: *ref_65 + - allOf: *ref_72 - type: object properties: draft: - allOf: *ref_65 + allOf: *ref_72 /w/{workspace}/flows/exists/{path}: get: summary: exists flow by path @@ -8035,7 +8489,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: flow details @@ -8061,10 +8515,10 @@ paths: application/json: schema: allOf: - - allOf: &ref_67 + - allOf: &ref_74 - type: object - properties: *ref_61 - required: *ref_62 + properties: *ref_68 + required: *ref_69 - type: object properties: path: @@ -8112,7 +8566,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 requestBody: description: Partially filled flow required: true @@ -8120,7 +8574,7 @@ paths: application/json: schema: allOf: - - allOf: *ref_67 + - allOf: *ref_74 - type: object properties: deployment_message: @@ -8146,7 +8600,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 requestBody: description: archiveFlow required: true @@ -8178,7 +8632,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 - name: keep_captures description: keep captures in: query @@ -8213,11 +8667,11 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_59 + schema: *ref_66 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_60 + schema: *ref_67 - name: path_start description: mask to filter matching starting path in: query @@ -8244,7 +8698,7 @@ paths: type: array items: type: object - properties: &ref_292 + properties: &ref_341 workspace_id: type: string path: @@ -8262,7 +8716,7 @@ paths: edited_at: type: string format: date-time - required: &ref_293 + required: &ref_342 - workspace_id - path - summary @@ -8283,7 +8737,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: app exists @@ -8305,12 +8759,12 @@ paths: - name: version in: path required: true - schema: &ref_186 + schema: &ref_206 type: number - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: app details @@ -8367,11 +8821,11 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_59 + schema: *ref_66 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_60 + schema: *ref_67 - name: path_start description: mask to filter matching starting path in: query @@ -8412,7 +8866,7 @@ paths: type: array items: type: object - properties: &ref_290 + properties: &ref_335 id: type: integer workspace_id: @@ -8440,7 +8894,7 @@ paths: - anonymous raw_app: type: boolean - required: &ref_291 + required: &ref_336 - id - workspace_id - path @@ -8475,7 +8929,7 @@ paths: type: string policy: type: object - properties: *ref_68 + properties: *ref_75 draft_only: type: boolean deployment_message: @@ -8523,7 +8977,7 @@ paths: type: string policy: type: object - properties: *ref_68 + properties: *ref_75 draft_only: type: boolean deployment_message: @@ -8560,7 +9014,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: app exists @@ -8582,7 +9036,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 - name: with_starred_info in: query schema: @@ -8594,8 +9048,8 @@ paths: application/json: schema: type: object - properties: *ref_69 - required: *ref_70 + properties: *ref_76 + required: *ref_77 /w/{workspace}/apps/get/lite/{path}: get: summary: get app lite by path @@ -8610,7 +9064,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: app lite details @@ -8618,8 +9072,8 @@ paths: application/json: schema: type: object - properties: *ref_69 - required: *ref_70 + properties: *ref_76 + required: *ref_77 /w/{workspace}/apps/get/draft/{path}: get: summary: get app by path with draft @@ -8634,17 +9088,17 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: app details with draft content: application/json: schema: - allOf: &ref_294 + allOf: &ref_343 - type: object - properties: *ref_69 - required: *ref_70 + properties: *ref_76 + required: *ref_77 - type: object properties: draft_only: @@ -8664,7 +9118,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: app history @@ -8674,12 +9128,12 @@ paths: type: array items: type: object - properties: &ref_71 + properties: &ref_78 version: type: integer deployment_msg: type: string - required: &ref_72 + required: &ref_79 - version /w/{workspace}/apps/get_latest_version/{path}: get: @@ -8693,7 +9147,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 tags: - app responses: @@ -8703,8 +9157,8 @@ paths: application/json: schema: type: object - properties: *ref_71 - required: *ref_72 + properties: *ref_78 + required: *ref_79 /w/{workspace}/apps/list_paths_from_workspace_runnable/{runnable_kind}/{path}: get: summary: list app paths from workspace runnable @@ -8719,11 +9173,11 @@ paths: - name: runnable_kind in: path required: true - schema: *ref_73 + schema: *ref_80 - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 responses: '200': description: list of app paths @@ -8747,11 +9201,11 @@ paths: - name: id in: path required: true - schema: *ref_34 + schema: *ref_41 - name: version in: path required: true - schema: &ref_188 + schema: &ref_208 type: integer requestBody: description: App deployment message @@ -8784,7 +9238,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: app details @@ -8792,8 +9246,8 @@ paths: application/json: schema: type: object - properties: *ref_69 - required: *ref_70 + properties: *ref_76 + required: *ref_77 /w/{workspace}/apps_u/public_resource/{path}: get: summary: get public resource @@ -8808,7 +9262,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: resource value @@ -8829,7 +9283,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: app secret @@ -8851,7 +9305,7 @@ paths: - name: id in: path required: true - schema: *ref_34 + schema: *ref_41 responses: '200': description: app details @@ -8859,8 +9313,8 @@ paths: application/json: schema: type: object - properties: *ref_69 - required: *ref_70 + properties: *ref_76 + required: *ref_77 /w/{workspace}/raw_apps/create: post: summary: create raw app @@ -8911,7 +9365,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 requestBody: description: updateraw app required: true @@ -8947,7 +9401,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: app deleted @@ -8969,7 +9423,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: app deleted @@ -8991,7 +9445,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 requestBody: description: update app required: true @@ -9007,7 +9461,7 @@ paths: value: {} policy: type: object - properties: *ref_68 + properties: *ref_75 deployment_message: type: string custom_path: @@ -9033,7 +9487,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 requestBody: description: update app required: true @@ -9052,7 +9506,7 @@ paths: value: {} policy: type: object - properties: *ref_68 + properties: *ref_75 deployment_message: type: string custom_path: @@ -9082,7 +9536,7 @@ paths: - name: custom_path in: path required: true - schema: *ref_74 + schema: *ref_81 responses: '200': description: custom path exists @@ -9113,7 +9567,7 @@ paths: type: array items: type: object - properties: &ref_75 + properties: &ref_82 s3: type: string filename: @@ -9122,7 +9576,7 @@ paths: type: string presigned: type: string - required: &ref_76 + required: &ref_83 - s3 required: - s3_objects @@ -9135,8 +9589,8 @@ paths: type: array items: type: object - properties: *ref_75 - required: *ref_76 + properties: *ref_82 + required: *ref_83 /w/{workspace}/apps_u/execute_component/{path}: post: summary: executeComponent @@ -9151,7 +9605,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 requestBody: description: update app required: true @@ -9217,7 +9671,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: file_key in: query required: false @@ -9310,7 +9764,7 @@ paths: - name: path in: path required: true - schema: *ref_41 + schema: *ref_48 - name: scheduled_for description: when to schedule this job (leave empty for immediate run) in: query @@ -9332,11 +9786,11 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_53 + schema: *ref_60 - name: tag description: Override the tag to use in: query - schema: *ref_55 + schema: *ref_62 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -9344,7 +9798,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_54 + schema: *ref_61 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -9353,7 +9807,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_57 + schema: *ref_64 - name: invisible_to_owner description: make the run invisible to the the flow owner (default false) in: query @@ -9366,7 +9820,8 @@ paths: application/json: schema: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 responses: '201': description: job created @@ -9410,9 +9865,18 @@ paths: input_transforms: type: object additionalProperties: - allOf: &ref_77 - - oneOf: *ref_35 - discriminator: *ref_36 + oneOf: &ref_88 + - type: object + properties: *ref_84 + required: *ref_85 + - type: object + properties: *ref_86 + required: *ref_87 + discriminator: &ref_89 + propertyName: type + mapping: + static: '#/components/schemas/schemas-StaticTransform' + javascript: '#/components/schemas/schemas-JavascriptTransform' use_latest_version: type: boolean flow_options_by_path: @@ -9423,7 +9887,8 @@ paths: input_transforms: type: object additionalProperties: - allOf: *ref_77 + oneOf: *ref_88 + discriminator: *ref_89 use_latest_version: type: boolean responses: @@ -9449,7 +9914,7 @@ paths: - name: id in: path required: true - schema: &ref_110 + schema: &ref_123 type: string format: uuid - name: step_id @@ -9482,11 +9947,11 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_53 + schema: *ref_60 - name: tag description: Override the tag to use in: query - schema: *ref_55 + schema: *ref_62 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -9494,7 +9959,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_54 + schema: *ref_61 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -9503,7 +9968,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_57 + schema: *ref_64 - name: invisible_to_owner description: make the run invisible to the the flow owner (default false) in: query @@ -9516,7 +9981,8 @@ paths: application/json: schema: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 responses: '201': description: job created @@ -9539,7 +10005,7 @@ paths: - name: hash in: path required: true - schema: *ref_46 + schema: *ref_53 - name: scheduled_for description: when to schedule this job (leave empty for immediate run) in: query @@ -9561,17 +10027,17 @@ paths: The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_53 + schema: *ref_60 - name: tag description: Override the tag to use in: query - schema: *ref_55 + schema: *ref_62 - name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: *ref_56 + schema: *ref_63 - name: job_id description: >- The job id to assign to the created job. if missing, job is chosen @@ -9579,7 +10045,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_54 + schema: *ref_61 - name: include_header description: > List of headers's keys (separated with ',') whove value are added to @@ -9588,7 +10054,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_57 + schema: *ref_64 - name: invisible_to_owner description: make the run invisible to the the script owner (default false) in: query @@ -9628,7 +10094,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_57 + schema: *ref_64 - name: invisible_to_owner description: make the run invisible to the the script owner (default false) in: query @@ -9641,7 +10107,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_54 + schema: *ref_61 requestBody: description: preview required: true @@ -9649,7 +10115,7 @@ paths: application/json: schema: type: object - properties: &ref_221 + properties: &ref_262 content: type: string path: @@ -9658,10 +10124,11 @@ paths: type: string args: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 language: type: string - enum: *ref_43 + enum: *ref_50 tag: type: string kind: @@ -9674,7 +10141,7 @@ paths: type: boolean lock: type: string - required: &ref_222 + required: &ref_263 - args responses: '201': @@ -9712,11 +10179,12 @@ paths: application/json: schema: type: object - properties: &ref_223 + properties: &ref_264 args: type: object - additionalProperties: *ref_21 - required: &ref_224 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + required: &ref_265 - args responses: '201': @@ -9749,15 +10217,15 @@ paths: type: array items: type: object - properties: &ref_312 + properties: &ref_357 raw_code: type: string path: type: string language: type: string - enum: *ref_43 - required: &ref_313 + enum: *ref_50 + required: &ref_358 - raw_code - path - language @@ -9797,7 +10265,7 @@ paths: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_57 + schema: *ref_64 - name: invisible_to_owner description: make the run invisible to the the script owner (default false) in: query @@ -9810,7 +10278,7 @@ paths: queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_54 + schema: *ref_61 requestBody: description: preview required: true @@ -9818,21 +10286,53 @@ paths: application/json: schema: type: object - properties: &ref_287 + properties: &ref_332 value: type: object - properties: *ref_78 - required: *ref_79 + properties: &ref_92 + modules: + type: array + items: + type: object + properties: *ref_44 + required: *ref_45 + failure_module: + type: object + properties: *ref_44 + required: *ref_45 + preprocessor_module: + type: object + properties: *ref_44 + required: *ref_45 + same_worker: + type: boolean + concurrent_limit: + type: number + concurrency_key: + type: string + concurrency_time_window_s: + type: number + skip_expr: + type: string + cache_ttl: + type: number + priority: + type: number + early_return: + type: string + required: &ref_93 + - modules path: type: string args: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 tag: type: string restarted_from: type: object - properties: &ref_289 + properties: &ref_334 flow_job_id: type: string format: uuid @@ -9840,7 +10340,7 @@ paths: type: string branch_or_iteration_n: type: integer - required: &ref_288 + required: &ref_333 - value - content - args @@ -9856,6 +10356,7 @@ paths: get: summary: list all queued jobs operationId: listQueue + x-mcp-tool: true tags: - job parameters: @@ -9866,104 +10367,104 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_59 + schema: *ref_66 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_60 + schema: *ref_67 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_53 + schema: *ref_60 - name: worker description: worker this job was ran on in: query - schema: &ref_82 + schema: &ref_94 type: string - name: script_path_exact description: mask to filter exact matching path in: query - schema: &ref_83 + schema: &ref_95 type: string - name: script_path_start description: mask to filter matching starting path in: query - schema: &ref_84 + schema: &ref_96 type: string - name: schedule_path description: mask to filter by schedule path in: query - schema: &ref_85 + schema: &ref_97 type: string - name: script_hash description: mask to filter exact matching path in: query - schema: &ref_86 + schema: &ref_98 type: string - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: &ref_87 + schema: &ref_99 type: string format: date-time - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: &ref_88 + schema: &ref_100 type: string format: date-time - name: success description: filter on successful jobs in: query - schema: &ref_96 + schema: &ref_108 type: boolean - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: &ref_90 + schema: &ref_102 type: boolean - name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: &ref_91 + schema: &ref_103 type: string - name: suspended description: filter on suspended jobs in: query - schema: &ref_92 + schema: &ref_104 type: boolean - name: running description: filter on running jobs in: query - schema: &ref_89 + schema: &ref_101 type: boolean - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: &ref_93 + schema: &ref_105 type: string - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: &ref_95 + schema: &ref_107 type: string - name: allow_wildcards description: allow wildcards (*) in the filter of label, tag, worker in: query - schema: &ref_97 + schema: &ref_109 type: boolean - name: tag description: filter on jobs with a given tag/worker group in: query - schema: &ref_94 + schema: &ref_106 type: string - name: page description: which page to return (start at 1, default 1) @@ -9994,7 +10495,7 @@ paths: type: array items: type: object - properties: &ref_108 + properties: &ref_121 workspace_id: type: string id: @@ -10022,7 +10523,8 @@ paths: type: string args: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 logs: type: string raw_code: @@ -10064,14 +10566,14 @@ paths: by extension its DT_TOKEN. flow_status: type: object - properties: &ref_99 + properties: &ref_111 step: type: integer modules: type: array items: type: object - properties: &ref_80 + properties: &ref_90 type: type: string enum: @@ -10148,20 +10650,20 @@ paths: format: uuid skipped: type: boolean - required: &ref_81 + required: &ref_91 - type user_states: additionalProperties: true preprocessor_module: allOf: - type: object - properties: *ref_80 - required: *ref_81 + properties: *ref_90 + required: *ref_91 failure_module: allOf: - type: object - properties: *ref_80 - required: *ref_81 + properties: *ref_90 + required: *ref_91 - type: object properties: parent_module: @@ -10176,19 +10678,32 @@ paths: items: type: string format: uuid - required: &ref_100 + required: &ref_112 - step - modules - failure_module + workflow_as_code_status: + type: object + properties: &ref_113 + scheduled_for: + type: string + format: date-time + started_at: + type: string + format: date-time + duration_ms: + type: number + name: + type: string raw_flow: type: object - properties: *ref_78 - required: *ref_79 + properties: *ref_92 + required: *ref_93 is_flow_step: type: boolean language: type: string - enum: *ref_43 + enum: *ref_50 email: type: string visible_to_owner: @@ -10209,7 +10724,7 @@ paths: type: boolean worker: type: string - required: &ref_109 + required: &ref_122 - id - running - canceled @@ -10322,59 +10837,59 @@ paths: - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_60 + schema: *ref_67 - name: label description: >- mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') in: query - schema: &ref_98 + schema: &ref_110 type: string - name: worker description: worker this job was ran on in: query - schema: *ref_82 + schema: *ref_94 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_53 + schema: *ref_60 - name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_83 + schema: *ref_95 - name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_84 + schema: *ref_96 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_85 + schema: *ref_97 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_86 + schema: *ref_98 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_87 + schema: *ref_99 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_88 + schema: *ref_100 - name: created_before description: filter on created before (inclusive) timestamp in: query - schema: &ref_101 + schema: &ref_114 type: string format: date-time - name: created_after description: filter on created after (exclusive) timestamp in: query - schema: &ref_102 + schema: &ref_115 type: string format: date-time - name: created_or_started_before @@ -10382,23 +10897,23 @@ paths: filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp in: query - schema: &ref_103 + schema: &ref_116 type: string format: date-time - name: running description: filter on running jobs in: query - schema: *ref_89 + schema: *ref_101 - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_90 + schema: *ref_102 - name: created_or_started_after description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp in: query - schema: &ref_104 + schema: &ref_117 type: string format: date-time - name: created_or_started_after_completed_jobs @@ -10407,7 +10922,7 @@ paths: otherwise after (exclusive) timestamp but only for the completed jobs in: query - schema: &ref_105 + schema: &ref_118 type: string format: date-time - name: job_kinds @@ -10415,27 +10930,27 @@ paths: filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_91 + schema: *ref_103 - name: suspended description: filter on suspended jobs in: query - schema: *ref_92 + schema: *ref_104 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_93 + schema: *ref_105 - name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_94 + schema: *ref_106 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_95 + schema: *ref_107 - name: page description: which page to return (start at 1, default 1) in: query @@ -10499,83 +11014,83 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_59 + schema: *ref_66 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_60 + schema: *ref_67 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_53 + schema: *ref_60 - name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_83 + schema: *ref_95 - name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_84 + schema: *ref_96 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_85 + schema: *ref_97 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_86 + schema: *ref_98 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_87 + schema: *ref_99 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_88 + schema: *ref_100 - name: success description: filter on successful jobs in: query - schema: *ref_96 + schema: *ref_108 - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_90 + schema: *ref_102 - name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_91 + schema: *ref_103 - name: suspended description: filter on suspended jobs in: query - schema: *ref_92 + schema: *ref_104 - name: running description: filter on running jobs in: query - schema: *ref_89 + schema: *ref_101 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_93 + schema: *ref_105 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_95 + schema: *ref_107 - name: allow_wildcards description: allow wildcards (*) in the filter of label, tag, worker in: query - schema: *ref_97 + schema: *ref_109 - name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_94 + schema: *ref_106 - name: page description: which page to return (start at 1, default 1) in: query @@ -10653,82 +11168,82 @@ paths: - name: order_desc description: order by desc order (default true) in: query - schema: *ref_59 + schema: *ref_66 - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_60 + schema: *ref_67 - name: label description: >- mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') in: query - schema: *ref_98 + schema: *ref_110 - name: worker description: worker this job was ran on in: query - schema: *ref_82 + schema: *ref_94 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_53 + schema: *ref_60 - name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_83 + schema: *ref_95 - name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_84 + schema: *ref_96 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_85 + schema: *ref_97 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_86 + schema: *ref_98 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_87 + schema: *ref_99 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_88 + schema: *ref_100 - name: success description: filter on successful jobs in: query - schema: *ref_96 + schema: *ref_108 - name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_91 + schema: *ref_103 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_93 + schema: *ref_105 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_95 + schema: *ref_107 - name: allow_wildcards description: allow wildcards (*) in the filter of label, tag, worker in: query - schema: *ref_97 + schema: *ref_109 - name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_94 + schema: *ref_106 - name: page description: which page to return (start at 1, default 1) in: query @@ -10766,7 +11281,7 @@ paths: type: array items: type: object - properties: &ref_106 + properties: &ref_119 workspace_id: type: string id: @@ -10793,7 +11308,8 @@ paths: type: string args: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 result: {} logs: type: string @@ -10835,17 +11351,20 @@ paths: by extension its DT_TOKEN. flow_status: type: object - properties: *ref_99 - required: *ref_100 + properties: *ref_111 + required: *ref_112 + workflow_as_code_status: + type: object + properties: *ref_113 raw_flow: type: object - properties: *ref_78 - required: *ref_79 + properties: *ref_92 + required: *ref_93 is_flow_step: type: boolean language: type: string - enum: *ref_43 + enum: *ref_50 is_skipped: type: boolean email: @@ -10870,7 +11389,7 @@ paths: type: boolean worker: type: string - required: &ref_107 + required: &ref_120 - id - created_by - duration_ms @@ -10889,6 +11408,7 @@ paths: get: summary: list all jobs operationId: listJobs + x-mcp-tool: true tags: - job parameters: @@ -10899,113 +11419,113 @@ paths: - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_60 + schema: *ref_67 - name: label description: >- mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') in: query - schema: *ref_98 + schema: *ref_110 - name: worker description: worker this job was ran on in: query - schema: *ref_82 + schema: *ref_94 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_53 + schema: *ref_60 - name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_83 + schema: *ref_95 - name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_84 + schema: *ref_96 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_85 + schema: *ref_97 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_86 + schema: *ref_98 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_87 + schema: *ref_99 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_88 + schema: *ref_100 - name: created_before description: filter on created before (inclusive) timestamp in: query - schema: *ref_101 + schema: *ref_114 - name: created_after description: filter on created after (exclusive) timestamp in: query - schema: *ref_102 + schema: *ref_115 - name: created_or_started_before description: >- filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp in: query - schema: *ref_103 + schema: *ref_116 - name: running description: filter on running jobs in: query - schema: *ref_89 + schema: *ref_101 - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_90 + schema: *ref_102 - name: created_or_started_after description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp in: query - schema: *ref_104 + schema: *ref_117 - name: created_or_started_after_completed_jobs description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs in: query - schema: *ref_105 + schema: *ref_118 - name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_91 + schema: *ref_103 - name: suspended description: filter on suspended jobs in: query - schema: *ref_92 + schema: *ref_104 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_93 + schema: *ref_105 - name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_94 + schema: *ref_106 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_95 + schema: *ref_107 - name: allow_wildcards description: allow wildcards (*) in the filter of label, tag, worker in: query - schema: *ref_97 + schema: *ref_109 - name: page description: which page to return (start at 1, default 1) in: query @@ -11054,11 +11574,11 @@ paths: schema: type: array items: - oneOf: &ref_111 + oneOf: &ref_124 - allOf: - type: object - properties: *ref_106 - required: *ref_107 + properties: *ref_119 + required: *ref_120 - type: object properties: type: @@ -11067,15 +11587,15 @@ paths: - CompletedJob - allOf: - type: object - properties: *ref_108 - required: *ref_109 + properties: *ref_121 + required: *ref_122 - type: object properties: type: type: string enum: - QueuedJob - discriminator: &ref_112 + discriminator: &ref_125 propertyName: type /jobs/db_clock: get: @@ -11142,19 +11662,23 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: no_logs in: query schema: type: boolean + - name: no_code + in: query + schema: + type: boolean responses: '200': description: job details content: application/json: schema: - oneOf: *ref_111 - discriminator: *ref_112 + oneOf: *ref_124 + discriminator: *ref_125 /w/{workspace}/jobs_u/get_root_job_id/{id}: get: summary: get root job id @@ -11169,7 +11693,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 responses: '200': description: get root job id @@ -11191,7 +11715,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 responses: '200': description: job details @@ -11213,7 +11737,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 responses: '200': description: job args @@ -11234,7 +11758,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: running in: query schema: @@ -11243,10 +11767,18 @@ paths: in: query schema: type: integer + - name: stream_offset + in: query + schema: + type: integer - name: get_progress in: query schema: type: boolean + - name: no_logs + in: query + schema: + type: boolean responses: '200': description: job details @@ -11267,21 +11799,63 @@ paths: type: integer progress: type: integer + stream_offset: + type: integer + new_result_stream: + type: string flow_status: type: object - additionalProperties: &ref_225 - type: object - properties: &ref_226 - scheduled_for: - type: string - format: date-time - started_at: - type: string - format: date-time - duration_ms: - type: number - name: - type: string + properties: *ref_111 + required: *ref_112 + workflow_as_code_status: + type: object + properties: *ref_113 + /w/{workspace}/jobs_u/getupdate_sse/{id}: + get: + summary: get job updates via server-sent events + operationId: getJobUpdatesSSE + tags: + - job + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: id + in: path + required: true + schema: *ref_123 + - name: running + in: query + schema: + type: boolean + - name: log_offset + in: query + schema: + type: integer + - name: stream_offset + in: query + schema: + type: integer + - name: get_progress + in: query + schema: + type: boolean + - name: only_result + in: query + schema: + type: boolean + - name: no_logs + in: query + schema: + type: boolean + responses: + '200': + description: server-sent events stream of job updates + content: + text/event-stream: + schema: + type: string /w/{workspace}/jobs_u/get_log_file/{path}: get: summary: get log file from object store @@ -11319,7 +11893,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 responses: '200': description: flow debug info details @@ -11340,7 +11914,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 responses: '200': description: job details @@ -11348,8 +11922,8 @@ paths: application/json: schema: type: object - properties: *ref_106 - required: *ref_107 + properties: *ref_119 + required: *ref_120 /w/{workspace}/jobs_u/completed/get_result/{id}: get: summary: get completed job result @@ -11364,7 +11938,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: suspended_job in: query schema: @@ -11401,10 +11975,10 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: get_started in: query - schema: &ref_194 + schema: &ref_214 type: boolean responses: '200': @@ -11438,7 +12012,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 responses: '200': description: job details @@ -11446,8 +12020,8 @@ paths: application/json: schema: type: object - properties: *ref_106 - required: *ref_107 + properties: *ref_119 + required: *ref_120 /w/{workspace}/jobs_u/queue/cancel/{id}: post: summary: cancel queued or running job @@ -11462,7 +12036,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 requestBody: description: reason required: true @@ -11494,7 +12068,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: reason required: true @@ -11526,7 +12100,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 requestBody: description: reason required: true @@ -11558,7 +12132,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: resume_id in: path required: true @@ -11589,7 +12163,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: resume_id in: path required: true @@ -11631,7 +12205,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: approver in: query schema: @@ -11682,7 +12256,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: approver in: query schema: @@ -11733,7 +12307,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: payload description: > The base64 encoded payload that has been encoded as a JSON. e.g how @@ -11741,7 +12315,7 @@ paths: `encodeURIComponent(btoa(JSON.stringify({a: 2})))` in: query - schema: *ref_113 + schema: *ref_126 - name: resume_id in: path required: true @@ -11776,7 +12350,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: resume_id in: path required: true @@ -11818,7 +12392,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: key in: path required: true @@ -11850,7 +12424,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: key in: path required: true @@ -11876,7 +12450,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 requestBody: required: true content: @@ -11904,7 +12478,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: resume_id in: path required: true @@ -11939,7 +12513,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: resume_id in: path required: true @@ -11981,7 +12555,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 - name: resume_id in: path required: true @@ -12005,8 +12579,8 @@ paths: type: object properties: job: - oneOf: *ref_111 - discriminator: *ref_112 + oneOf: *ref_124 + discriminator: *ref_125 approvers: type: array items: @@ -12059,6 +12633,14 @@ paths: post: summary: create schedule operationId: createSchedule + x-mcp-tool: true + x-mcp-instructions: > + Creates a new schedule. + + The schedule should include seconds. + + You should get the schema of the script or flow before creating the + schedule to correctly specify the arguments needed. tags: - schedule parameters: @@ -12073,62 +12655,110 @@ paths: application/json: schema: type: object - properties: &ref_236 + properties: &ref_275 path: type: string + description: The path where the schedule will be created schedule: type: string + description: >- + The cron schedule to trigger the script or flow. Should + include seconds. timezone: type: string + description: The timezone to use for the cron schedule script_path: type: string + description: The path to the script or flow to trigger is_flow: type: boolean + description: Whether the schedule is for a flow args: + description: The arguments to pass to the script or flow type: object - additionalProperties: *ref_21 + additionalProperties: *ref_22 enabled: type: boolean + description: Whether the schedule is enabled on_failure: type: string + description: The path to the script or flow to trigger on failure on_failure_times: type: number + description: The number of times to retry on failure on_failure_exact: type: boolean + description: Whether the schedule should only run on the exact time on_failure_extra_args: + description: The arguments to pass to the script or flow type: object - additionalProperties: *ref_21 + additionalProperties: *ref_22 on_recovery: type: string + description: The path to the script or flow to trigger on recovery on_recovery_times: type: number + description: The number of times to retry on recovery on_recovery_extra_args: + description: The arguments to pass to the script or flow type: object - additionalProperties: *ref_21 + additionalProperties: *ref_22 on_success: type: string + description: The path to the script or flow to trigger on success on_success_extra_args: + description: The arguments to pass to the script or flow type: object - additionalProperties: *ref_21 + additionalProperties: *ref_22 ws_error_handler_muted: type: boolean + description: Whether the WebSocket error handler is muted retry: + description: The retry configuration for the schedule type: object - properties: *ref_114 + properties: &ref_127 + constant: + type: object + properties: + attempts: + type: integer + seconds: + type: integer + exponential: + type: object + properties: + attempts: + type: integer + multiplier: + type: integer + seconds: + type: integer + random_factor: + type: integer + minimum: 0 + maximum: 100 no_flow_overlap: type: boolean + description: >- + Whether the schedule should not run if a flow is already + running summary: type: string + description: The summary of the schedule description: type: string + description: The description of the schedule tag: type: string + description: The tag of the schedule paused_until: type: string + description: The date and time the schedule will be paused until format: date-time cron_version: type: string - required: &ref_237 + description: The version of the cron schedule to use (last is v2) + required: &ref_276 - path - schedule - timezone @@ -12146,6 +12776,14 @@ paths: post: summary: update schedule operationId: updateSchedule + x-mcp-tool: true + x-mcp-instructions: > + Updates a schedule. + + The schedule should include seconds. + + You should get the schema of the script or flow before updating the + schedule to correctly specify the arguments needed. tags: - schedule parameters: @@ -12156,7 +12794,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated schedule required: true @@ -12164,58 +12802,80 @@ paths: application/json: schema: type: object - properties: &ref_238 + properties: &ref_277 schedule: type: string + description: >- + The cron schedule to trigger the script or flow. Should + include seconds. timezone: type: string + description: The timezone to use for the cron schedule args: + description: The arguments to pass to the script or flow type: object - additionalProperties: *ref_21 + additionalProperties: *ref_22 on_failure: type: string + description: The path to the script or flow to trigger on failure on_failure_times: type: number + description: The number of times to retry on failure on_failure_exact: type: boolean + description: Whether the schedule should only run on the exact time on_failure_extra_args: + description: The arguments to pass to the script or flow type: object - additionalProperties: *ref_21 + additionalProperties: *ref_22 on_recovery: type: string + description: The path to the script or flow to trigger on recovery on_recovery_times: type: number + description: The number of times to retry on recovery on_recovery_extra_args: + description: The arguments to pass to the script or flow type: object - additionalProperties: *ref_21 + additionalProperties: *ref_22 on_success: type: string + description: The path to the script or flow to trigger on success on_success_extra_args: + description: The arguments to pass to the script or flow type: object - additionalProperties: *ref_21 + additionalProperties: *ref_22 ws_error_handler_muted: type: boolean + description: Whether the WebSocket error handler is muted retry: + description: The retry configuration for the schedule type: object - properties: *ref_114 + properties: *ref_127 no_flow_overlap: type: boolean + description: >- + Whether the schedule should not run if a flow is already + running summary: type: string + description: The summary of the schedule description: type: string + description: The description of the schedule tag: type: string + description: The tag of the schedule paused_until: type: string + description: The date and time the schedule will be paused until format: date-time cron_version: type: string - required: &ref_239 + description: The version of the cron schedule to use (last is v2) + required: &ref_278 - schedule - timezone - - script_path - - is_flow - args responses: '200': @@ -12238,7 +12898,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated schedule enable required: true @@ -12262,6 +12922,7 @@ paths: delete: summary: delete schedule operationId: deleteSchedule + x-mcp-tool: true tags: - schedule parameters: @@ -12272,7 +12933,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: schedule deleted @@ -12284,6 +12945,7 @@ paths: get: summary: get schedule operationId: getSchedule + x-mcp-tool: true tags: - schedule parameters: @@ -12294,7 +12956,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: schedule deleted @@ -12302,7 +12964,7 @@ paths: application/json: schema: type: object - properties: &ref_115 + properties: &ref_128 path: type: string edited_by: @@ -12322,7 +12984,8 @@ paths: type: boolean args: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 extra_perms: type: object additionalProperties: @@ -12339,24 +13002,27 @@ paths: type: boolean on_failure_extra_args: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 on_recovery: type: string on_recovery_times: type: number on_recovery_extra_args: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 on_success: type: string on_success_extra_args: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 ws_error_handler_muted: type: boolean retry: type: object - properties: *ref_114 + properties: *ref_127 summary: type: string description: @@ -12370,7 +13036,7 @@ paths: format: date-time cron_version: type: string - required: &ref_116 + required: &ref_129 - path - edited_by - edited_at @@ -12395,7 +13061,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: schedule exists @@ -12407,6 +13073,7 @@ paths: get: summary: list schedules operationId: listSchedules + x-mcp-tool: true tags: - schedule parameters: @@ -12427,17 +13094,19 @@ paths: filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_93 + schema: *ref_105 - name: path description: filter by path in: query schema: type: string - name: is_flow + description: filter schedules by whether they target a flow in: query schema: type: boolean - name: path_start + description: filter schedules by path prefix in: query schema: type: string @@ -12450,8 +13119,8 @@ paths: type: array items: type: object - properties: *ref_115 - required: *ref_116 + properties: *ref_128 + required: *ref_129 /w/{workspace}/schedules/list_with_jobs: get: summary: list schedules with last 20 jobs @@ -12479,10 +13148,10 @@ paths: schema: type: array items: - allOf: &ref_235 + allOf: &ref_274 - type: object - properties: *ref_115 - required: *ref_116 + properties: *ref_128 + required: *ref_129 - type: object properties: jobs: @@ -12543,6 +13212,236 @@ paths: responses: '201': description: default error handler set + /w/{workspace}/openapi/generate: + post: + summary: generate openapi spec from http routes/webhook + operationId: generateOpenapiSpec + tags: + - openapi + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: openapi spec info and url + content: + application/json: + schema: + type: object + properties: &ref_130 + info: + type: object + properties: &ref_285 + title: + type: string + version: + type: string + description: + type: string + terms_of_service: + type: string + contact: + type: object + properties: + name: + type: string + url: + type: string + email: + type: string + license: + type: object + properties: + name: + type: string + identifier: + type: string + url: + type: string + required: + - name + required: &ref_286 + - title + - version + url: + type: string + openapi_spec_format: + type: string + enum: &ref_280 + - yaml + - json + http_route_filters: + type: array + items: + type: object + properties: &ref_281 + folder_regex: + type: string + path_regex: + type: string + route_path_regex: + type: string + required: &ref_282 + - folder_regex + - path_regex + - route_path_regex + webhook_filters: + type: array + items: + type: object + properties: &ref_283 + user_or_folder_regex: + type: string + enum: + - '*' + - u + - f + user_or_folder_regex_value: + type: string + path: + type: string + runnable_kind: + type: string + enum: &ref_279 + - script + - flow + required: &ref_284 + - user_or_folder_regex + - user_or_folder_regex_value + - path + - runnable_kind + responses: + '200': + description: openapi spec + content: + text/plain: + schema: + type: string + /w/{workspace}/openapi/download: + post: + summary: Download the OpenAPI v3.1 spec as a file + operationId: DownloadOpenapiSpec + tags: + - openapi + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: openapi spec info and url + content: + application/json: + schema: + type: object + properties: *ref_130 + responses: + '200': + description: Downloaded OpenAPI spec + content: + application/octet-stream: + schema: + type: string + format: binary + /w/{workspace}/http_triggers/create_many: + post: + summary: create many HTTP triggers + operationId: createHttpTriggers + tags: + - http_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: new http trigger + required: true + content: + application/json: + schema: + type: array + items: + type: object + properties: &ref_131 + path: + type: string + script_path: + type: string + route_path: + type: string + workspaced_route: + type: boolean + summary: + type: string + description: + type: string + static_asset_config: + type: object + properties: + s3: + type: string + storage: + type: string + filename: + type: string + required: + - s3 + is_flow: + type: boolean + http_method: + type: string + enum: &ref_133 + - get + - post + - put + - delete + - patch + authentication_resource_path: + type: string + is_async: + type: boolean + authentication_method: + type: string + enum: &ref_134 + - none + - windmill + - api_key + - basic_http + - custom_script + - signature + is_static_website: + type: boolean + wrap_body: + type: boolean + raw_string: + type: boolean + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_132 + - path + - script_path + - route_path + - is_flow + - is_async + - authentication_method + - http_method + - is_static_website + responses: + '201': + description: http trigger created + content: + text/plain: + schema: + type: string /w/{workspace}/http_triggers/create: post: summary: create http trigger @@ -12561,64 +13460,8 @@ paths: application/json: schema: type: object - properties: &ref_240 - path: - type: string - script_path: - type: string - route_path: - type: string - workspaced_route: - type: boolean - static_asset_config: - type: object - properties: - s3: - type: string - storage: - type: string - filename: - type: string - required: - - s3 - is_flow: - type: boolean - http_method: - type: string - enum: - - get - - post - - put - - delete - - patch - authentication_resource_path: - type: string - is_async: - type: boolean - authentication_method: - type: string - enum: &ref_117 - - none - - windmill - - api_key - - basic_http - - custom_script - - signature - is_static_website: - type: boolean - wrap_body: - type: boolean - raw_string: - type: boolean - required: &ref_241 - - path - - script_path - - route_path - - is_flow - - is_async - - authentication_method - - http_method - - is_static_website + properties: *ref_131 + required: *ref_132 responses: '201': description: http trigger created @@ -12640,7 +13483,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated trigger required: true @@ -12648,13 +13491,17 @@ paths: application/json: schema: type: object - properties: &ref_242 + properties: &ref_287 path: type: string script_path: type: string route_path: type: string + summary: + type: string + description: + type: string workspaced_route: type: boolean static_asset_config: @@ -12674,24 +13521,28 @@ paths: type: boolean http_method: type: string - enum: - - get - - post - - put - - delete - - patch + enum: *ref_133 is_async: type: boolean authentication_method: type: string - enum: *ref_117 + enum: *ref_134 is_static_website: type: boolean wrap_body: type: boolean raw_string: type: boolean - required: &ref_243 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_288 - path - script_path - is_flow @@ -12721,7 +13572,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: http trigger deleted @@ -12743,16 +13594,16 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: http trigger deleted content: application/json: schema: - allOf: &ref_118 + allOf: &ref_135 - type: object - properties: &ref_122 + properties: &ref_139 path: type: string script_path: @@ -12772,7 +13623,7 @@ paths: format: date-time is_flow: type: boolean - required: &ref_123 + required: &ref_140 - path - script_path - email @@ -12782,7 +13633,7 @@ paths: - edited_at - is_flow type: object - properties: &ref_119 + properties: &ref_136 route_path: type: string static_asset_config: @@ -12798,19 +13649,18 @@ paths: - s3 http_method: type: string - enum: - - get - - post - - put - - delete - - patch + enum: *ref_133 authentication_resource_path: type: string + summary: + type: string + description: + type: string is_async: type: boolean authentication_method: type: string - enum: *ref_117 + enum: *ref_134 is_static_website: type: boolean workspaced_route: @@ -12819,7 +13669,16 @@ paths: type: boolean raw_string: type: boolean - required: &ref_120 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_137 - route_path - is_async - authentication_method @@ -12868,10 +13727,10 @@ paths: schema: type: array items: - allOf: *ref_118 + allOf: *ref_135 type: object - properties: *ref_119 - required: *ref_120 + properties: *ref_136 + required: *ref_137 /w/{workspace}/http_triggers/exists/{path}: get: summary: does http trigger exists @@ -12886,7 +13745,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: http trigger exists @@ -12917,12 +13776,7 @@ paths: type: string http_method: type: string - enum: - - get - - post - - put - - delete - - patch + enum: *ref_133 trigger_path: type: string workspaced_route: @@ -12955,7 +13809,7 @@ paths: application/json: schema: type: object - properties: &ref_244 + properties: &ref_289 path: type: string script_path: @@ -12980,7 +13834,7 @@ paths: initial_messages: type: array items: - anyOf: &ref_121 + anyOf: &ref_138 - type: object properties: raw_message: @@ -12996,7 +13850,8 @@ paths: type: string args: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 is_flow: type: boolean required: @@ -13007,10 +13862,20 @@ paths: - runnable_result url_runnable_args: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 can_return_message: type: boolean - required: &ref_245 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_290 - path - script_path - url @@ -13038,7 +13903,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated trigger required: true @@ -13046,7 +13911,7 @@ paths: application/json: schema: type: object - properties: &ref_246 + properties: &ref_291 url: type: string path: @@ -13069,13 +13934,23 @@ paths: initial_messages: type: array items: - anyOf: *ref_121 + anyOf: *ref_138 url_runnable_args: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 can_return_message: type: boolean - required: &ref_247 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_292 - path - script_path - url @@ -13103,7 +13978,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: websocket trigger deleted @@ -13125,19 +14000,19 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: websocket trigger deleted content: application/json: schema: - allOf: &ref_124 + allOf: &ref_141 - type: object - properties: *ref_122 - required: *ref_123 + properties: *ref_139 + required: *ref_140 type: object - properties: &ref_125 + properties: &ref_142 url: type: string server_id: @@ -13163,13 +14038,23 @@ paths: initial_messages: type: array items: - anyOf: *ref_121 + anyOf: *ref_138 url_runnable_args: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 can_return_message: type: boolean - required: &ref_126 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_143 - url - enabled - filters @@ -13214,10 +14099,10 @@ paths: schema: type: array items: - allOf: *ref_124 + allOf: *ref_141 type: object - properties: *ref_125 - required: *ref_126 + properties: *ref_142 + required: *ref_143 /w/{workspace}/websocket_triggers/exists/{path}: get: summary: does websocket trigger exists @@ -13232,7 +14117,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: websocket trigger exists @@ -13254,7 +14139,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated websocket trigger enable required: true @@ -13297,7 +14182,8 @@ paths: type: string url_runnable_args: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 can_return_message: type: boolean required: @@ -13328,7 +14214,7 @@ paths: application/json: schema: type: object - properties: &ref_270 + properties: &ref_315 path: type: string script_path: @@ -13345,7 +14231,16 @@ paths: type: string enabled: type: boolean - required: &ref_271 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_316 - path - script_path - is_flow @@ -13373,7 +14268,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated trigger required: true @@ -13381,7 +14276,7 @@ paths: application/json: schema: type: object - properties: &ref_272 + properties: &ref_317 kafka_resource_path: type: string group_id: @@ -13396,7 +14291,16 @@ paths: type: string is_flow: type: boolean - required: &ref_273 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_318 - path - script_path - kafka_resource_path @@ -13424,7 +14328,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: kafka trigger deleted @@ -13446,19 +14350,19 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: kafka trigger deleted content: application/json: schema: - allOf: &ref_127 + allOf: &ref_144 - type: object - properties: *ref_122 - required: *ref_123 + properties: *ref_139 + required: *ref_140 type: object - properties: &ref_128 + properties: &ref_145 kafka_resource_path: type: string group_id: @@ -13476,7 +14380,16 @@ paths: type: string enabled: type: boolean - required: &ref_129 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_146 - kafka_resource_path - group_id - topics @@ -13521,10 +14434,10 @@ paths: schema: type: array items: - allOf: *ref_127 + allOf: *ref_144 type: object - properties: *ref_128 - required: *ref_129 + properties: *ref_145 + required: *ref_146 /w/{workspace}/kafka_triggers/exists/{path}: get: summary: does kafka trigger exists @@ -13539,7 +14452,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: kafka trigger exists @@ -13561,7 +14474,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated kafka trigger enable required: true @@ -13629,7 +14542,7 @@ paths: application/json: schema: type: object - properties: &ref_274 + properties: &ref_319 path: type: string script_path: @@ -13650,7 +14563,16 @@ paths: type: string enabled: type: boolean - required: &ref_275 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_320 - path - script_path - is_flow @@ -13678,7 +14600,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated trigger required: true @@ -13686,7 +14608,7 @@ paths: application/json: schema: type: object - properties: &ref_276 + properties: &ref_321 nats_resource_path: type: string use_jetstream: @@ -13705,7 +14627,16 @@ paths: type: string is_flow: type: boolean - required: &ref_277 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_322 - path - script_path - nats_resource_path @@ -13733,7 +14664,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: nats trigger deleted @@ -13755,19 +14686,19 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: nats trigger deleted content: application/json: schema: - allOf: &ref_130 + allOf: &ref_147 - type: object - properties: *ref_122 - required: *ref_123 + properties: *ref_139 + required: *ref_140 type: object - properties: &ref_131 + properties: &ref_148 nats_resource_path: type: string use_jetstream: @@ -13789,7 +14720,16 @@ paths: type: string enabled: type: boolean - required: &ref_132 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_149 - nats_resource_path - use_jetstream - subjects @@ -13834,10 +14774,10 @@ paths: schema: type: array items: - allOf: *ref_130 + allOf: *ref_147 type: object - properties: *ref_131 - required: *ref_132 + properties: *ref_148 + required: *ref_149 /w/{workspace}/nats_triggers/exists/{path}: get: summary: does nats trigger exists @@ -13852,7 +14792,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: nats trigger exists @@ -13874,7 +14814,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated nats trigger enable required: true @@ -13942,12 +14882,12 @@ paths: application/json: schema: type: object - properties: &ref_257 + properties: &ref_302 queue_url: type: string aws_auth_resource_type: type: string - enum: &ref_133 + enum: &ref_150 - oidc - credentials aws_resource_path: @@ -13964,7 +14904,16 @@ paths: type: boolean enabled: type: boolean - required: &ref_258 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_303 - queue_url - aws_resource_path - path @@ -13992,7 +14941,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated trigger required: true @@ -14000,12 +14949,12 @@ paths: application/json: schema: type: object - properties: &ref_259 + properties: &ref_304 queue_url: type: string aws_auth_resource_type: type: string - enum: *ref_133 + enum: *ref_150 aws_resource_path: type: string message_attributes: @@ -14020,7 +14969,16 @@ paths: type: boolean enabled: type: boolean - required: &ref_260 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_305 - queue_url - aws_resource_path - path @@ -14049,7 +15007,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: sqs trigger deleted @@ -14071,24 +15029,24 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: sqs trigger deleted content: application/json: schema: - allOf: &ref_134 + allOf: &ref_151 - type: object - properties: *ref_122 - required: *ref_123 + properties: *ref_139 + required: *ref_140 type: object - properties: &ref_135 + properties: &ref_152 queue_url: type: string aws_auth_resource_type: type: string - enum: *ref_133 + enum: *ref_150 aws_resource_path: type: string message_attributes: @@ -14104,7 +15062,16 @@ paths: type: string enabled: type: boolean - required: &ref_136 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_153 - queue_url - aws_resource_path - enabled @@ -14149,10 +15116,10 @@ paths: schema: type: array items: - allOf: *ref_134 + allOf: *ref_151 type: object - properties: *ref_135 - required: *ref_136 + properties: *ref_152 + required: *ref_153 /w/{workspace}/sqs_triggers/exists/{path}: get: summary: does sqs trigger exists @@ -14167,7 +15134,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: sqs trigger exists @@ -14189,7 +15156,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated sqs trigger enable required: true @@ -14257,44 +15224,44 @@ paths: application/json: schema: type: object - properties: &ref_249 + properties: &ref_294 mqtt_resource_path: type: string subscribe_topics: type: array items: type: object - properties: &ref_137 + properties: &ref_154 qos: type: string - enum: &ref_248 + enum: &ref_293 - qos0 - qos1 - qos2 topic: type: string - required: &ref_138 + required: &ref_155 - qos - topic client_id: type: string v3_config: type: object - properties: &ref_139 + properties: &ref_156 clean_session: type: boolean v5_config: type: object - properties: &ref_140 + properties: &ref_157 clean_start: type: boolean - topic_alias: + topic_alias_maximum: type: number session_expiry_interval: type: number client_version: type: string - enum: &ref_141 + enum: &ref_158 - v3 - v5 path: @@ -14305,7 +15272,16 @@ paths: type: boolean enabled: type: boolean - required: &ref_250 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_295 - path - script_path - is_flow @@ -14332,7 +15308,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated trigger required: true @@ -14340,26 +15316,26 @@ paths: application/json: schema: type: object - properties: &ref_251 + properties: &ref_296 mqtt_resource_path: type: string subscribe_topics: type: array items: type: object - properties: *ref_137 - required: *ref_138 + properties: *ref_154 + required: *ref_155 client_id: type: string v3_config: type: object - properties: *ref_139 + properties: *ref_156 v5_config: type: object - properties: *ref_140 + properties: *ref_157 client_version: type: string - enum: *ref_141 + enum: *ref_158 path: type: string script_path: @@ -14368,7 +15344,16 @@ paths: type: boolean enabled: type: boolean - required: &ref_252 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_297 - path - script_path - is_flow @@ -14396,7 +15381,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: mqtt trigger deleted @@ -14418,38 +15403,38 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: mqtt trigger deleted content: application/json: schema: - allOf: &ref_142 + allOf: &ref_159 - type: object - properties: *ref_122 - required: *ref_123 + properties: *ref_139 + required: *ref_140 type: object - properties: &ref_143 + properties: &ref_160 mqtt_resource_path: type: string subscribe_topics: type: array items: type: object - properties: *ref_137 - required: *ref_138 + properties: *ref_154 + required: *ref_155 v3_config: type: object - properties: *ref_139 + properties: *ref_156 v5_config: type: object - properties: *ref_140 + properties: *ref_157 client_id: type: string client_version: type: string - enum: *ref_141 + enum: *ref_158 server_id: type: string last_server_ping: @@ -14459,7 +15444,16 @@ paths: type: string enabled: type: boolean - required: &ref_144 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_161 - enabled - subscribe_topics - mqtt_resource_path @@ -14503,10 +15497,10 @@ paths: schema: type: array items: - allOf: *ref_142 + allOf: *ref_159 type: object - properties: *ref_143 - required: *ref_144 + properties: *ref_160 + required: *ref_161 /w/{workspace}/mqtt_triggers/exists/{path}: get: summary: does mqtt trigger exists @@ -14521,7 +15515,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: mqtt trigger exists @@ -14543,7 +15537,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated mqtt trigger enable required: true @@ -14611,12 +15605,12 @@ paths: application/json: schema: type: object - properties: &ref_145 + properties: &ref_162 gcp_resource_path: type: string subscription_mode: type: string - enum: &ref_150 + enum: &ref_167 - existing - create_update description: >- @@ -14631,17 +15625,17 @@ paths: type: string delivery_type: type: string - enum: &ref_147 + enum: &ref_164 - push - pull delivery_config: type: object - properties: &ref_148 + properties: &ref_165 audience: type: string authenticate: type: boolean - required: &ref_149 + required: &ref_166 - authenticate - base_endpoint path: @@ -14652,7 +15646,18 @@ paths: type: boolean enabled: type: boolean - required: &ref_146 + auto_acknowledge_msg: + type: boolean + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_163 - path - script_path - is_flow @@ -14680,7 +15685,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated trigger required: true @@ -14688,8 +15693,8 @@ paths: application/json: schema: type: object - properties: *ref_145 - required: *ref_146 + properties: *ref_162 + required: *ref_163 responses: '200': description: gcp trigger updated @@ -14711,7 +15716,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: gcp trigger deleted @@ -14733,19 +15738,19 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: gcp trigger deleted content: application/json: schema: - allOf: &ref_151 + allOf: &ref_168 - type: object - properties: *ref_122 - required: *ref_123 + properties: *ref_139 + required: *ref_140 type: object - properties: &ref_152 + properties: &ref_169 gcp_resource_path: type: string topic_id: @@ -14756,14 +15761,14 @@ paths: type: string delivery_type: type: string - enum: *ref_147 + enum: *ref_164 delivery_config: type: object - properties: *ref_148 - required: *ref_149 + properties: *ref_165 + required: *ref_166 subscription_mode: type: string - enum: *ref_150 + enum: *ref_167 description: >- The mode of subscription. 'existing' means using an existing GCP subscription, while 'create_update' involves @@ -14775,7 +15780,16 @@ paths: type: string enabled: type: boolean - required: &ref_153 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_170 - gcp_resource_path - topic_id - subscription_id @@ -14822,10 +15836,10 @@ paths: schema: type: array items: - allOf: *ref_151 + allOf: *ref_168 type: object - properties: *ref_152 - required: *ref_153 + properties: *ref_169 + required: *ref_170 /w/{workspace}/gcp_triggers/exists/{path}: get: summary: does gcp trigger exists @@ -14840,7 +15854,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: gcp trigger exists @@ -14862,7 +15876,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated gcp trigger enable required: true @@ -14926,7 +15940,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: args to delete subscription from google cloud required: true @@ -14934,10 +15948,10 @@ paths: application/json: schema: type: object - properties: &ref_255 + properties: &ref_300 subscription_id: type: string - required: &ref_256 + required: &ref_301 - subscription_id responses: '200': @@ -14960,7 +15974,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: get all google topics @@ -14984,7 +15998,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: args to get subscription's topic from google cloud required: true @@ -14992,10 +16006,10 @@ paths: application/json: schema: type: object - properties: &ref_253 + properties: &ref_298 topic_id: type: string - required: &ref_254 + required: &ref_299 - topic_id responses: '200': @@ -15006,6 +16020,28 @@ paths: type: array items: type: string + /w/{workspace}/postgres_triggers/postgres/version/{path}: + get: + summary: get postgres version + operationId: getPostgresVersion + tags: + - postgres_trigger + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + - name: path + in: path + required: true + schema: *ref_31 + responses: + '200': + description: postgres version + content: + application/json: + schema: + type: string /w/{workspace}/postgres_triggers/is_valid_postgres_configuration/{path}: get: summary: check if postgres configuration is set to logical @@ -15020,7 +16056,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: boolean that indicates if postgres is set to logical level or not @@ -15046,19 +16082,19 @@ paths: application/json: schema: type: object - properties: &ref_264 + properties: &ref_309 postgres_resource_path: type: string relations: type: array items: type: object - properties: &ref_155 + properties: &ref_172 schema_name: type: string table_to_track: type: array - items: &ref_262 + items: &ref_307 type: object properties: table_name: @@ -15071,14 +16107,14 @@ paths: type: string required: - table_name - required: &ref_156 + required: &ref_173 - schema_name - table_to_track language: type: string - enum: &ref_263 + enum: &ref_308 - Typescript - required: &ref_265 + required: &ref_310 - postgres_resource_path - relations - language @@ -15103,7 +16139,7 @@ paths: - name: id in: path required: true - schema: &ref_185 + schema: &ref_205 type: string responses: '200': @@ -15126,7 +16162,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: list postgres slot @@ -15136,7 +16172,7 @@ paths: type: array items: type: object - properties: &ref_261 + properties: &ref_306 slot_name: type: string active: @@ -15155,7 +16191,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: new slot for postgres required: true @@ -15163,7 +16199,7 @@ paths: application/json: schema: type: object - properties: &ref_154 + properties: &ref_171 name: type: string responses: @@ -15187,7 +16223,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: replication slot of postgres required: true @@ -15195,7 +16231,7 @@ paths: application/json: schema: type: object - properties: *ref_154 + properties: *ref_171 responses: '200': description: postgres replication slot deleted @@ -15217,7 +16253,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: database publication list @@ -15241,11 +16277,12 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: publication in: path required: true - schema: &ref_157 + description: The name of the publication + schema: &ref_174 type: string responses: '200': @@ -15254,18 +16291,18 @@ paths: application/json: schema: type: object - properties: &ref_158 + properties: &ref_175 table_to_track: type: array items: type: object - properties: *ref_155 - required: *ref_156 + properties: *ref_172 + required: *ref_173 transaction_to_track: type: array items: type: string - required: &ref_159 + required: &ref_176 - transaction_to_track /w/{workspace}/postgres_triggers/publication/create/{publication}/{path}: post: @@ -15281,11 +16318,12 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: publication in: path required: true - schema: *ref_157 + description: The name of the publication + schema: *ref_174 requestBody: description: new publication for postgres required: true @@ -15293,8 +16331,8 @@ paths: application/json: schema: type: object - properties: *ref_158 - required: *ref_159 + properties: *ref_175 + required: *ref_176 responses: '201': description: publication created @@ -15316,11 +16354,12 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: publication in: path required: true - schema: *ref_157 + description: The name of the publication + schema: *ref_174 requestBody: description: update publication for postgres required: true @@ -15328,8 +16367,8 @@ paths: application/json: schema: type: object - properties: *ref_158 - required: *ref_159 + properties: *ref_175 + required: *ref_176 responses: '201': description: publication updated @@ -15351,11 +16390,12 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: publication in: path required: true - schema: *ref_157 + description: The name of the publication + schema: *ref_174 responses: '200': description: postgres publication deleted @@ -15381,7 +16421,7 @@ paths: application/json: schema: type: object - properties: &ref_266 + properties: &ref_311 replication_slot_name: type: string publication_name: @@ -15398,9 +16438,18 @@ paths: type: string publication: type: object - properties: *ref_158 - required: *ref_159 - required: &ref_267 + properties: *ref_175 + required: *ref_176 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_312 - path - script_path - is_flow @@ -15427,7 +16476,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated trigger required: true @@ -15435,7 +16484,7 @@ paths: application/json: schema: type: object - properties: &ref_268 + properties: &ref_313 replication_slot_name: type: string publication_name: @@ -15452,9 +16501,18 @@ paths: type: string publication: type: object - properties: *ref_158 - required: *ref_159 - required: &ref_269 + properties: *ref_175 + required: *ref_176 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_314 - path - script_path - is_flow @@ -15483,7 +16541,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: postgres trigger deleted @@ -15505,19 +16563,19 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: get postgres trigger content: application/json: schema: - allOf: &ref_160 + allOf: &ref_177 - type: object - properties: *ref_122 - required: *ref_123 + properties: *ref_139 + required: *ref_140 type: object - properties: &ref_161 + properties: &ref_178 enabled: type: boolean postgres_resource_path: @@ -15533,7 +16591,16 @@ paths: last_server_ping: type: string format: date-time - required: &ref_162 + error_handler_path: + type: string + error_handler_args: + type: object + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 + retry: + type: object + properties: *ref_127 + required: &ref_179 - enabled - postgres_resource_path - replication_slot_name @@ -15578,10 +16645,10 @@ paths: schema: type: array items: - allOf: *ref_160 + allOf: *ref_177 type: object - properties: *ref_161 - required: *ref_162 + properties: *ref_178 + required: *ref_179 /w/{workspace}/postgres_triggers/exists/{path}: get: summary: does postgres trigger exists @@ -15596,7 +16663,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: postgres trigger exists @@ -15618,7 +16685,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: updated postgres trigger enable required: true @@ -15683,7 +16750,7 @@ paths: type: array items: type: object - properties: &ref_164 + properties: &ref_181 name: type: string summary: @@ -15692,7 +16759,7 @@ paths: type: array items: type: string - required: &ref_165 + required: &ref_182 - name /groups/get/{name}: get: @@ -15704,7 +16771,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 responses: '200': description: instance group @@ -15712,8 +16779,8 @@ paths: application/json: schema: type: object - properties: *ref_164 - required: *ref_165 + properties: *ref_181 + required: *ref_182 /groups/create: post: summary: create instance group @@ -15751,7 +16818,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 requestBody: description: update instance group required: true @@ -15781,7 +16848,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 responses: '200': description: instance group deleted @@ -15799,7 +16866,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 requestBody: description: user to add to instance group required: true @@ -15829,7 +16896,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 requestBody: description: user to remove from instance group required: true @@ -15864,7 +16931,7 @@ paths: type: array items: type: object - properties: &ref_166 + properties: &ref_183 name: type: string summary: @@ -15879,7 +16946,7 @@ paths: type: string external_id: type: string - required: &ref_167 + required: &ref_184 - name /groups/overwrite: post: @@ -15896,8 +16963,8 @@ paths: type: array items: type: object - properties: *ref_166 - required: *ref_167 + properties: *ref_183 + required: *ref_184 responses: '200': description: success message @@ -15933,7 +17000,7 @@ paths: type: array items: type: object - properties: &ref_168 + properties: &ref_185 name: type: string summary: @@ -15946,7 +17013,7 @@ paths: type: object additionalProperties: type: boolean - required: &ref_169 + required: &ref_186 - name /w/{workspace}/groups/listnames: get: @@ -16019,7 +17086,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 requestBody: description: updated group required: true @@ -16051,7 +17118,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 responses: '200': description: group deleted @@ -16073,7 +17140,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 responses: '200': description: group @@ -16081,8 +17148,8 @@ paths: application/json: schema: type: object - properties: *ref_168 - required: *ref_169 + properties: *ref_185 + required: *ref_186 /w/{workspace}/groups/adduser/{name}: post: summary: add user to group @@ -16097,7 +17164,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 requestBody: description: added user to group required: true @@ -16129,7 +17196,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 requestBody: description: added user to group required: true @@ -16175,7 +17242,7 @@ paths: type: array items: type: object - properties: &ref_170 + properties: &ref_187 name: type: string owners: @@ -16193,7 +17260,7 @@ paths: edited_at: type: string format: date-time - required: &ref_171 + required: &ref_188 - name - owners - extra_perms @@ -16275,7 +17342,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 requestBody: description: update folder required: true @@ -16314,7 +17381,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 responses: '200': description: folder deleted @@ -16336,7 +17403,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 responses: '200': description: folder @@ -16344,8 +17411,8 @@ paths: application/json: schema: type: object - properties: *ref_170 - required: *ref_171 + properties: *ref_187 + required: *ref_188 /w/{workspace}/folders/exists/{name}: get: summary: exists folder @@ -16360,7 +17427,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 responses: '200': description: folder exists @@ -16382,7 +17449,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 responses: '200': description: folder @@ -16424,7 +17491,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 requestBody: description: owner user to folder required: true @@ -16458,7 +17525,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 requestBody: description: added owner to folder required: true @@ -16484,6 +17551,7 @@ paths: get: summary: list workers operationId: listWorkers + x-mcp-tool: true tags: - worker parameters: @@ -16512,7 +17580,7 @@ paths: type: array items: type: object - properties: &ref_278 + properties: &ref_323 worker: type: string worker_instance: @@ -16554,7 +17622,7 @@ paths: type: number wm_memory_usage: type: number - required: &ref_279 + required: &ref_324 - worker - worker_instance - ping_at @@ -16662,7 +17730,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 responses: '200': description: a config @@ -16671,12 +17739,12 @@ paths: schema: type: object nullable: true - properties: &ref_201 + properties: &ref_242 alerts: type: array items: type: object - properties: &ref_199 + properties: &ref_240 name: type: string tags_to_monitor: @@ -16689,7 +17757,7 @@ paths: type: integer alert_time_threshold_seconds: type: integer - required: &ref_200 + required: &ref_241 - name - tags_to_monitor - jobs_num_threshold @@ -16705,7 +17773,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 requestBody: description: worker group required: true @@ -16728,7 +17796,7 @@ paths: - name: name in: path required: true - schema: *ref_163 + schema: *ref_180 responses: '200': description: Delete config @@ -16751,12 +17819,12 @@ paths: type: array items: type: object - properties: &ref_320 + properties: &ref_365 name: type: string config: type: object - required: &ref_321 + required: &ref_366 - name /configs/list_autoscaling_events/{worker_group}: get: @@ -16779,7 +17847,7 @@ paths: type: array items: type: object - properties: &ref_324 + properties: &ref_369 id: type: integer format: int64 @@ -16842,6 +17910,96 @@ paths: application/json: schema: type: string + /agent_workers/blacklist_token: + post: + summary: blacklist agent token (requires super admin) + operationId: blacklistAgentToken + tags: + - agent_workers + requestBody: + description: token to blacklist + required: true + content: + application/json: + schema: + type: object + properties: + token: + type: string + description: The agent token to blacklist + expires_at: + type: string + format: date-time + description: Optional expiration date for the blacklist entry + required: + - token + responses: + '200': + description: token blacklisted successfully + /agent_workers/remove_blacklist_token: + post: + summary: remove agent token from blacklist (requires super admin) + operationId: removeBlacklistAgentToken + tags: + - agent_workers + requestBody: + description: token to remove from blacklist + required: true + content: + application/json: + schema: + type: object + properties: + token: + type: string + description: The agent token to remove from blacklist + required: + - token + responses: + '200': + description: token removed from blacklist successfully + /agent_workers/list_blacklisted_tokens: + get: + summary: list blacklisted agent tokens (requires super admin) + operationId: listBlacklistedAgentTokens + tags: + - agent_workers + parameters: + - name: include_expired + in: query + description: Whether to include expired blacklisted tokens + schema: + type: boolean + default: false + responses: + '200': + description: list of blacklisted tokens + content: + application/json: + schema: + type: array + items: + type: object + properties: + token: + type: string + description: The blacklisted token (without prefix) + expires_at: + type: string + format: date-time + description: When the blacklist entry expires + blacklisted_at: + type: string + format: date-time + description: When the token was blacklisted + blacklisted_by: + type: string + description: Email of the user who blacklisted the token + required: + - token + - expires_at + - blacklisted_at + - blacklisted_by /w/{workspace}/acls/get/{kind}/{path}: get: summary: get granular acls @@ -16856,7 +18014,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: kind in: path required: true @@ -16903,7 +18061,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: kind in: path required: true @@ -16962,7 +18120,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: kind in: path required: true @@ -17026,7 +18184,7 @@ paths: properties: trigger_kind: type: string - enum: &ref_172 + enum: &ref_189 - webhook - http - websocket @@ -17070,15 +18228,15 @@ paths: required: true schema: type: string - enum: *ref_172 + enum: *ref_189 - name: runnable_kind in: path required: true - schema: *ref_73 + schema: *ref_80 - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: capture config pinged @@ -17096,11 +18254,11 @@ paths: - name: runnable_kind in: path required: true - schema: *ref_73 + schema: *ref_80 - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: capture configs for a script or flow @@ -17110,17 +18268,17 @@ paths: type: array items: type: object - properties: &ref_325 + properties: &ref_370 trigger_config: {} trigger_kind: type: string - enum: *ref_172 + enum: *ref_189 error: type: string last_server_ping: type: string format: date-time - required: &ref_326 + required: &ref_371 - trigger_kind /w/{workspace}/capture/list/{runnable_kind}/{path}: get: @@ -17136,16 +18294,16 @@ paths: - name: runnable_kind in: path required: true - schema: *ref_73 + schema: *ref_80 - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: trigger_kind in: query schema: type: string - enum: *ref_172 + enum: *ref_189 - name: page description: which page to return (start at 1, default 1) in: query @@ -17163,10 +18321,10 @@ paths: type: array items: type: object - properties: &ref_173 + properties: &ref_190 trigger_kind: type: string - enum: *ref_172 + enum: *ref_189 main_args: {} preprocessor_args: {} id: @@ -17174,7 +18332,7 @@ paths: created_at: type: string format: date-time - required: &ref_174 + required: &ref_191 - trigger_kind - main_args - preprocessor_args @@ -17194,11 +18352,11 @@ paths: - name: runnable_kind in: path required: true - schema: *ref_73 + schema: *ref_80 - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 requestBody: description: move captures and configs to a new path required: true @@ -17239,8 +18397,8 @@ paths: application/json: schema: type: object - properties: *ref_173 - required: *ref_174 + properties: *ref_190 + required: *ref_191 delete: summary: delete a capture operationId: deleteCapture @@ -17330,13 +18488,13 @@ paths: schema: *ref_0 - name: runnable_id in: query - schema: &ref_175 + schema: &ref_192 type: string - name: runnable_type in: query - schema: &ref_176 + schema: &ref_193 type: string - enum: &ref_207 + enum: &ref_248 - ScriptHash - ScriptPath - FlowPath @@ -17353,7 +18511,7 @@ paths: filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_93 + schema: *ref_105 - name: include_preview in: query schema: @@ -17367,7 +18525,7 @@ paths: type: array items: type: object - properties: &ref_177 + properties: &ref_194 id: type: string name: @@ -17381,7 +18539,7 @@ paths: type: boolean success: type: boolean - required: &ref_178 + required: &ref_195 - id - name - args @@ -17431,10 +18589,10 @@ paths: schema: *ref_0 - name: runnable_id in: query - schema: *ref_175 + schema: *ref_192 - name: runnable_type in: query - schema: *ref_176 + schema: *ref_193 - name: page description: which page to return (start at 1, default 1) in: query @@ -17452,8 +18610,8 @@ paths: type: array items: type: object - properties: *ref_177 - required: *ref_178 + properties: *ref_194 + required: *ref_195 /w/{workspace}/inputs/create: post: summary: Create an Input for future use in a script or flow @@ -17467,10 +18625,10 @@ paths: schema: *ref_0 - name: runnable_id in: query - schema: *ref_175 + schema: *ref_192 - name: runnable_type in: query - schema: *ref_176 + schema: *ref_193 requestBody: description: Input required: true @@ -17478,12 +18636,12 @@ paths: application/json: schema: type: object - properties: &ref_203 + properties: &ref_244 name: type: string args: type: object - required: &ref_204 + required: &ref_245 - name - args - created_by @@ -17513,14 +18671,14 @@ paths: application/json: schema: type: object - properties: &ref_205 + properties: &ref_246 id: type: string name: type: string is_public: type: boolean - required: &ref_206 + required: &ref_247 - id - name - is_public @@ -17546,7 +18704,7 @@ paths: - name: input in: path required: true - schema: &ref_193 + schema: &ref_213 type: string responses: '200': @@ -17579,7 +18737,7 @@ paths: properties: s3_resource: type: object - properties: &ref_179 + properties: &ref_196 bucket: type: string region: @@ -17594,7 +18752,7 @@ paths: type: string pathStyle: type: boolean - required: &ref_180 + required: &ref_197 - bucket - region - endPoint @@ -17672,8 +18830,8 @@ paths: properties: s3_resource: type: object - properties: *ref_179 - required: *ref_180 + properties: *ref_196 + required: *ref_197 responses: '200': description: Connection settings @@ -17694,10 +18852,10 @@ paths: type: boolean client_kwargs: type: object - properties: &ref_181 + properties: &ref_198 region_name: type: string - required: &ref_182 + required: &ref_199 - region_name required: - endpoint_url @@ -17752,8 +18910,8 @@ paths: type: boolean client_kwargs: type: object - properties: *ref_181 - required: *ref_182 + properties: *ref_198 + required: *ref_199 required: - endpoint_url - use_ssl @@ -17811,8 +18969,8 @@ paths: application/json: schema: type: object - properties: *ref_179 - required: *ref_180 + properties: *ref_196 + required: *ref_197 /w/{workspace}/job_helpers/test_connection: get: summary: Test connection to the workspace object storage @@ -17876,10 +19034,10 @@ paths: type: array items: type: object - properties: &ref_297 + properties: &ref_344 s3: type: string - required: &ref_298 + required: &ref_345 - s3 restricted_access: type: boolean @@ -17912,7 +19070,7 @@ paths: application/json: schema: type: object - properties: &ref_299 + properties: &ref_346 mime_type: type: string size_in_bytes: @@ -17976,7 +19134,7 @@ paths: application/json: schema: type: object - properties: &ref_300 + properties: &ref_347 msg: type: string content: @@ -17988,7 +19146,7 @@ paths: - Csv - Parquet - Unknown - required: &ref_301 + required: &ref_348 - content_type /w/{workspace}/job_helpers/load_parquet_preview/{path}: get: @@ -18004,7 +19162,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: offset in: query schema: @@ -18037,8 +19195,7 @@ paths: '200': description: Parquet Preview content: - application/json: - schema: {} + application/json: {} /w/{workspace}/job_helpers/load_table_count/{path}: get: summary: Load the table row count @@ -18053,7 +19210,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: search_col in: query schema: @@ -18090,7 +19247,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 - name: offset in: query schema: @@ -18127,8 +19284,7 @@ paths: '200': description: Csv Preview content: - application/json: - schema: {} + application/json: {} /w/{workspace}/job_helpers/delete_s3_file: delete: summary: Permanently delete file from S3 @@ -18334,7 +19490,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 requestBody: description: parameters for statistics retrieval required: true @@ -18363,46 +19519,46 @@ paths: type: array items: type: object - properties: &ref_304 + properties: &ref_349 id: type: string name: type: string - required: &ref_305 + required: &ref_350 - id scalar_metrics: type: array items: type: object - properties: &ref_306 + properties: &ref_351 metric_id: type: string value: type: number - required: &ref_307 + required: &ref_352 - id - value timeseries_metrics: type: array items: type: object - properties: &ref_308 + properties: &ref_353 metric_id: type: string values: type: array items: type: object - properties: &ref_310 + properties: &ref_355 timestamp: type: string format: date-time value: type: number - required: &ref_311 + required: &ref_356 - timestamp - value - required: &ref_309 + required: &ref_354 - id - values /w/{workspace}/job_metrics/set_progress/{id}: @@ -18419,7 +19575,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 requestBody: description: parameters for statistics retrieval required: true @@ -18453,7 +19609,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 responses: '200': description: job progress between 0 and 99 @@ -18471,11 +19627,11 @@ paths: - name: before description: filter on started before (inclusive) timestamp in: query - schema: *ref_183 + schema: *ref_200 - name: after description: filter on created after (exclusive) timestamp in: query - schema: *ref_184 + schema: *ref_201 - name: with_error in: query required: false @@ -18524,7 +19680,7 @@ paths: - name: path in: path required: true - schema: *ref_26 + schema: *ref_31 responses: '200': description: log stream @@ -18547,12 +19703,12 @@ paths: type: array items: type: object - properties: &ref_314 + properties: &ref_359 concurrency_key: type: string total_running: type: number - required: &ref_315 + required: &ref_360 - concurrency_key - total_running /concurrency_groups/prune/{concurrency_id}: @@ -18565,7 +19721,7 @@ paths: - name: concurrency_id in: path required: true - schema: &ref_195 + schema: &ref_215 type: string responses: '200': @@ -18585,7 +19741,7 @@ paths: - name: id in: path required: true - schema: *ref_110 + schema: *ref_123 responses: '200': description: concurrency key for given job @@ -18618,97 +19774,97 @@ paths: - name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_60 + schema: *ref_67 - name: label description: >- mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels') in: query - schema: *ref_98 + schema: *ref_110 - name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_53 + schema: *ref_60 - name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_83 + schema: *ref_95 - name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_84 + schema: *ref_96 - name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_85 + schema: *ref_97 - name: script_hash description: mask to filter exact matching path in: query - schema: *ref_86 + schema: *ref_98 - name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_87 + schema: *ref_99 - name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_88 + schema: *ref_100 - name: created_or_started_before description: >- filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp in: query - schema: *ref_103 + schema: *ref_116 - name: running description: filter on running jobs in: query - schema: *ref_89 + schema: *ref_101 - name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_90 + schema: *ref_102 - name: created_or_started_after description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp in: query - schema: *ref_104 + schema: *ref_117 - name: created_or_started_after_completed_jobs description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs in: query - schema: *ref_105 + schema: *ref_118 - name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_91 + schema: *ref_103 - name: args description: >- filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_93 + schema: *ref_105 - name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_94 + schema: *ref_106 - name: result description: >- filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_95 + schema: *ref_107 - name: allow_wildcards description: allow wildcards (*) in the filter of label, tag, worker in: query - schema: *ref_97 + schema: *ref_109 - name: page description: which page to return (start at 1, default 1) in: query @@ -18756,17 +19912,17 @@ paths: application/json: schema: type: object - properties: &ref_316 + properties: &ref_361 jobs: type: array items: - oneOf: *ref_111 - discriminator: *ref_112 + oneOf: *ref_124 + discriminator: *ref_125 obscured_jobs: type: array items: type: object - properties: &ref_208 + properties: &ref_249 typ: type: string started_at: @@ -18779,7 +19935,7 @@ paths: Obscured jobs omitted for security because of too specific filtering type: boolean - required: &ref_317 + required: &ref_362 - jobs - obscured_jobs /srch/w/{workspace}/index/search/job: @@ -18823,7 +19979,7 @@ paths: type: array items: type: object - properties: &ref_322 + properties: &ref_367 dancer: type: string hit_count: @@ -18899,7 +20055,7 @@ paths: type: array items: type: object - properties: &ref_323 + properties: &ref_368 dancer: type: string /srch/index/search/count_service_logs: @@ -18966,6 +20122,173 @@ paths: text/plain: schema: type: string + /w/{workspace}/assets/list: + get: + summary: List all assets in the workspace + operationId: listAssets + tags: + - asset + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + responses: + '200': + description: all assets in the workspace + content: + application/json: + schema: + type: array + items: + type: object + required: + - path + - kind + - usages + properties: + path: + type: string + kind: + type: string + enum: *ref_202 + usages: + type: array + items: + type: object + required: + - path + - kind + properties: + path: + type: string + kind: + type: string + enum: &ref_203 + - script + - flow + access_type: + type: string + enum: &ref_204 + - r + - w + - rw + metadata: + type: object + properties: + resource_type: + type: string + /w/{workspace}/assets/list_by_usages: + post: + summary: List all assets used by given usages paths + operationId: listAssetsByUsage + tags: + - asset + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + requestBody: + description: list assets by usages + required: true + content: + application/json: + schema: + type: object + required: + - usages + properties: + usages: + type: array + items: + type: object + required: + - path + - kind + properties: + path: + type: string + kind: + type: string + enum: *ref_203 + responses: + '200': + description: all assets used by the given usage paths, in the same order + content: + application/json: + schema: + type: array + items: + type: array + items: + type: object + required: + - path + - kind + properties: + path: + type: string + kind: + type: string + enum: *ref_202 + access_type: + type: string + enum: *ref_204 + /mcp/w/{workspace}/list_tools: + get: + summary: list available MCP tools + operationId: listMcpTools + tags: + - mcp + parameters: + - name: workspace + in: path + required: true + schema: *ref_0 + responses: + '200': + description: list of MCP tools available for the workspace + content: + application/json: + schema: + type: array + items: + type: object + required: &ref_235 + - name + - description + - instructions + - path + - method + properties: &ref_236 + name: + type: string + description: The tool name/operation ID + description: + type: string + description: Short description of the tool + instructions: + type: string + description: Detailed instructions for using the tool + path: + type: string + description: API endpoint path + method: + type: string + description: HTTP method (GET, POST, etc.) + path_params_schema: + type: object + description: JSON schema for path parameters + nullable: true + query_params_schema: + type: object + description: JSON schema for query parameters + nullable: true + body_schema: + type: object + description: JSON schema for request body + nullable: true components: securitySchemes: bearerAuth: @@ -18980,7 +20303,7 @@ components: name: id in: path required: true - schema: *ref_185 + schema: *ref_205 Key: name: key in: path @@ -18995,67 +20318,68 @@ components: name: publication in: path required: true - schema: *ref_157 + description: The name of the publication + schema: *ref_174 VersionId: name: version in: path required: true - schema: *ref_186 + schema: *ref_206 Token: name: token in: path required: true - schema: *ref_187 + schema: *ref_207 AccountId: name: id in: path required: true - schema: *ref_31 + schema: *ref_38 ClientName: name: client_name in: path required: true - schema: *ref_30 + schema: *ref_35 ScriptPath: name: path in: path required: true - schema: *ref_41 + schema: *ref_48 ScriptHash: name: hash in: path required: true - schema: *ref_46 + schema: *ref_53 JobId: name: id in: path required: true - schema: *ref_110 + schema: *ref_123 Path: name: path in: path required: true - schema: *ref_26 + schema: *ref_31 CustomPath: name: custom_path in: path required: true - schema: *ref_74 + schema: *ref_81 PathId: name: id in: path required: true - schema: *ref_34 + schema: *ref_41 PathVersion: name: version in: path required: true - schema: *ref_188 + schema: *ref_208 Name: name: name in: path required: true - schema: *ref_163 + schema: *ref_180 Page: name: page description: which page to return (start at 1, default 1) @@ -19070,12 +20394,12 @@ components: name: order_desc description: order by desc order (default true) in: query - schema: *ref_59 + schema: *ref_66 CreatedBy: name: created_by description: mask to filter exact matching user creator in: query - schema: *ref_60 + schema: *ref_67 Label: name: label description: >- @@ -19083,31 +20407,31 @@ components: with as a result an object containing a string in the array at key 'wm_labels') in: query - schema: *ref_98 + schema: *ref_110 Worker: name: worker description: worker this job was ran on in: query - schema: *ref_82 + schema: *ref_94 ParentJob: name: parent_job description: >- The parent job that is at the origin and responsible for the execution of this script if any in: query - schema: *ref_53 + schema: *ref_60 WorkerTag: name: tag description: Override the tag to use in: query - schema: *ref_55 + schema: *ref_62 CacheTtl: name: cache_ttl description: >- Override the cache time to live (in seconds). Can not be used to disable caching, only override with a new cache ttl in: query - schema: *ref_56 + schema: *ref_63 NewJobId: name: job_id description: >- @@ -19115,7 +20439,7 @@ components: randomly using the ULID scheme. If a job id already exists in the queue or as a completed job, the request to create one will fail (Bad Request) in: query - schema: *ref_54 + schema: *ref_61 IncludeHeader: name: include_header description: > @@ -19125,14 +20449,14 @@ components: Header's key lowercased and '-'' replaced to '_' such that 'Content-Type' becomes the 'content_type' arg key in: query - schema: *ref_57 + schema: *ref_64 QueueLimit: name: queue_limit description: > The maximum size of the queue for which the request would get rejected if that job would push it above that limit in: query - schema: *ref_58 + schema: *ref_65 Payload: name: payload description: > @@ -19141,283 +20465,780 @@ components: `encodeURIComponent(btoa(JSON.stringify({a: 2})))` in: query - schema: *ref_113 + schema: *ref_126 ScriptStartPath: name: script_path_start description: mask to filter matching starting path in: query - schema: *ref_84 + schema: *ref_96 SchedulePath: name: schedule_path description: mask to filter by schedule path in: query - schema: *ref_85 + schema: *ref_97 ScriptExactPath: name: script_path_exact description: mask to filter exact matching path in: query - schema: *ref_83 + schema: *ref_95 ScriptExactHash: name: script_hash description: mask to filter exact matching path in: query - schema: *ref_86 + schema: *ref_98 CreatedBefore: name: created_before description: filter on created before (inclusive) timestamp in: query - schema: *ref_101 + schema: *ref_114 CreatedAfter: name: created_after description: filter on created after (exclusive) timestamp in: query - schema: *ref_102 + schema: *ref_115 StartedBefore: name: started_before description: filter on started before (inclusive) timestamp in: query - schema: *ref_87 + schema: *ref_99 StartedAfter: name: started_after description: filter on started after (exclusive) timestamp in: query - schema: *ref_88 + schema: *ref_100 Before: name: before description: filter on started before (inclusive) timestamp in: query - schema: *ref_183 + schema: *ref_200 CreatedOrStartedAfter: name: created_or_started_after description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp in: query - schema: *ref_104 + schema: *ref_117 CreatedOrStartedAfterCompletedJob: name: created_or_started_after_completed_jobs description: >- filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs in: query - schema: *ref_105 + schema: *ref_118 CreatedOrStartedBefore: name: created_or_started_before description: >- filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp in: query - schema: *ref_103 + schema: *ref_116 Success: name: success description: filter on successful jobs in: query - schema: *ref_96 + schema: *ref_108 ScheduledForBeforeNow: name: scheduled_for_before_now description: filter on jobs scheduled_for before now (hence waitinf for a worker) in: query - schema: *ref_90 + schema: *ref_102 Suspended: name: suspended description: filter on suspended jobs in: query - schema: *ref_92 + schema: *ref_104 Running: name: running description: filter on running jobs in: query - schema: *ref_89 + schema: *ref_101 AllowWildcards: name: allow_wildcards description: allow wildcards (*) in the filter of label, tag, worker in: query - schema: *ref_97 + schema: *ref_109 ArgsFilter: name: args description: filter on jobs containing those args as a json subset (@> in postgres) in: query - schema: *ref_93 + schema: *ref_105 Tag: name: tag description: filter on jobs with a given tag/worker group in: query - schema: *ref_94 + schema: *ref_106 ResultFilter: name: result description: filter on jobs containing those result as a json subset (@> in postgres) in: query - schema: *ref_95 + schema: *ref_107 After: name: after description: filter on created after (exclusive) timestamp in: query - schema: *ref_184 + schema: *ref_201 Username: name: username description: filter on exact username of user in: query - schema: *ref_189 + schema: *ref_209 Operation: name: operation description: filter on exact or prefix name of operation in: query - schema: *ref_190 + schema: *ref_210 ResourceName: name: resource description: filter on exact or prefix name of resource in: query - schema: *ref_191 + schema: *ref_211 ActionKind: name: action_kind description: filter on type of operation in: query - schema: *ref_192 + schema: *ref_212 JobKinds: name: job_kinds description: >- filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by, in: query - schema: *ref_91 + schema: *ref_103 RunnableId: name: runnable_id in: query - schema: *ref_175 + schema: *ref_192 RunnableTypeQuery: name: runnable_type in: query - schema: *ref_176 + schema: *ref_193 InputId: name: input in: path required: true - schema: *ref_193 + schema: *ref_213 GetStarted: name: get_started in: query - schema: *ref_194 + schema: *ref_214 ConcurrencyId: name: concurrency_id in: path required: true - schema: *ref_195 + schema: *ref_215 RunnableKind: name: runnable_kind in: path required: true - schema: *ref_73 + schema: *ref_80 schemas: + OpenFlow: + type: object + properties: *ref_68 + required: *ref_69 + FlowValue: + type: object + properties: *ref_92 + required: *ref_93 + Retry: + type: object + properties: *ref_127 + StopAfterIf: + type: object + properties: + skip_if_stopped: + type: boolean + expr: + type: string + error_message: + type: string + required: + - expr + FlowModule: + type: object + properties: + id: + type: string + value: + oneOf: *ref_216 + discriminator: *ref_217 + stop_after_if: + type: object + properties: *ref_46 + required: *ref_47 + stop_after_all_iters_if: + type: object + properties: *ref_46 + required: *ref_47 + skip_if: + type: object + properties: + expr: + type: string + required: + - expr + sleep: + oneOf: *ref_42 + discriminator: *ref_43 + cache_ttl: + type: number + timeout: + type: number + delete_after_use: + type: boolean + summary: + type: string + mock: + type: object + properties: + enabled: + type: boolean + return_value: {} + suspend: + type: object + properties: + required_events: + type: integer + timeout: + type: integer + resume_form: + type: object + properties: + schema: + type: object + user_auth_required: + type: boolean + user_groups_required: + oneOf: *ref_42 + discriminator: *ref_43 + self_approval_disabled: + type: boolean + hide_cancel: + type: boolean + continue_on_disapprove_timeout: + type: boolean + priority: + type: number + continue_on_error: + type: boolean + retry: + type: object + properties: *ref_218 + required: + - value + - id InputTransform: - allOf: *ref_77 + oneOf: *ref_88 + discriminator: *ref_89 + StaticTransform: + type: object + properties: + value: {} + type: + type: string + enum: + - static + required: + - value + - type + JavascriptTransform: + type: object + properties: + expr: + type: string + type: + type: string + enum: + - javascript + required: + - expr + - type + FlowModuleValue: + oneOf: + - type: object + properties: *ref_219 + required: *ref_220 + - type: object + properties: *ref_221 + required: *ref_222 + - type: object + properties: *ref_223 + required: *ref_224 + - type: object + properties: *ref_225 + required: *ref_226 + - type: object + properties: *ref_227 + required: *ref_228 + - type: object + properties: *ref_229 + required: *ref_230 + - type: object + properties: *ref_231 + required: *ref_232 + - type: object + properties: *ref_233 + required: *ref_234 + discriminator: + propertyName: type + mapping: + rawscript: '#/components/schemas/schemas-RawScript' + script: '#/components/schemas/schemas-PathScript' + flow: '#/components/schemas/schemas-PathFlow' + forloopflow: '#/components/schemas/schemas-ForloopFlow' + whileloopflow: '#/components/schemas/schemas-WhileloopFlow' + branchone: '#/components/schemas/schemas-BranchOne' + branchall: '#/components/schemas/schemas-BranchAll' + identity: '#/components/schemas/schemas-Identity' + RawScript: + type: object + properties: + input_transforms: + type: object + additionalProperties: + oneOf: *ref_42 + discriminator: *ref_43 + content: + type: string + language: + type: string + enum: + - deno + - bun + - python3 + - go + - bash + - powershell + - postgresql + - mysql + - bigquery + - snowflake + - mssql + - oracledb + - graphql + - nativets + - php + path: + type: string + lock: + type: string + type: + type: string + enum: + - rawscript + tag: + type: string + concurrent_limit: + type: number + concurrency_time_window_s: + type: number + custom_concurrency_key: + type: string + is_trigger: + type: boolean + assets: + type: array + items: + type: object + required: + - path + - kind + properties: + path: + type: string + kind: + type: string + enum: + - s3object + - resource + - ducklake + access_type: + type: string + enum: + - r + - w + - rw + alt_access_type: + type: string + enum: + - r + - w + - rw + required: + - type + - content + - language + - input_transforms + PathScript: + type: object + properties: + input_transforms: + type: object + additionalProperties: + oneOf: *ref_42 + discriminator: *ref_43 + path: + type: string + hash: + type: string + type: + type: string + enum: + - script + tag_override: + type: string + is_trigger: + type: boolean + required: + - type + - path + - input_transforms + PathFlow: + type: object + properties: + input_transforms: + type: object + additionalProperties: + oneOf: *ref_42 + discriminator: *ref_43 + path: + type: string + type: + type: string + enum: + - flow + required: + - type + - path + - input_transforms + ForloopFlow: + type: object + properties: + modules: + type: array + items: + type: object + properties: *ref_44 + required: *ref_45 + iterator: + oneOf: *ref_42 + discriminator: *ref_43 + skip_failures: + type: boolean + type: + type: string + enum: + - forloopflow + parallel: + type: boolean + parallelism: + type: integer + required: + - modules + - iterator + - skip_failures + - type + WhileloopFlow: + type: object + properties: + modules: + type: array + items: + type: object + properties: *ref_44 + required: *ref_45 + skip_failures: + type: boolean + type: + type: string + enum: + - whileloopflow + parallel: + type: boolean + parallelism: + type: integer + required: + - modules + - skip_failures + - type + BranchOne: + type: object + properties: + branches: + type: array + items: + type: object + properties: + summary: + type: string + expr: + type: string + modules: + type: array + items: + type: object + properties: *ref_44 + required: *ref_45 + required: + - modules + - expr + default: + type: array + items: + type: object + properties: *ref_44 + required: *ref_45 + required: + - modules + type: + type: string + enum: + - branchone + required: + - branches + - default + - type + BranchAll: + type: object + properties: + branches: + type: array + items: + type: object + properties: + summary: + type: string + skip_failure: + type: boolean + modules: + type: array + items: + type: object + properties: *ref_44 + required: *ref_45 + required: + - modules + type: + type: string + enum: + - branchall + parallel: + type: boolean + required: + - branches + - type + Identity: + type: object + properties: + type: + type: string + enum: + - identity + flow: + type: boolean + required: + - type + FlowStatus: + type: object + properties: *ref_111 + required: *ref_112 + FlowStatusModule: + type: object + properties: + type: + type: string + enum: + - WaitingForPriorSteps + - WaitingForEvents + - WaitingForExecutor + - InProgress + - Success + - Failure + id: + type: string + job: + type: string + format: uuid + count: + type: integer + progress: + type: integer + iterator: + type: object + properties: + index: + type: integer + itered: + type: array + items: {} + args: {} + flow_jobs: + type: array + items: + type: string + flow_jobs_success: + type: array + items: + type: boolean + branch_chosen: + type: object + properties: + type: + type: string + enum: + - branch + - default + branch: + type: integer + required: + - type + branchall: + type: object + properties: + branch: + type: integer + len: + type: integer + required: + - branch + - len + approvers: + type: array + items: + type: object + properties: + resume_id: + type: integer + approver: + type: string + required: + - resume_id + - approver + failed_retries: + type: array + items: + type: string + format: uuid + skipped: + type: boolean + required: + - type + EndpointTool: + type: object + required: *ref_235 + properties: *ref_236 AIProvider: type: string - enum: *ref_196 + enum: *ref_237 + GitSyncObjectType: + type: string + enum: *ref_20 AIProviderModel: type: object properties: *ref_18 required: *ref_19 AIProviderConfig: type: object - properties: *ref_197 - required: *ref_198 + properties: *ref_238 + required: *ref_239 AIConfig: type: object - properties: *ref_20 + properties: *ref_21 Alert: type: object - properties: *ref_199 - required: *ref_200 + properties: *ref_240 + required: *ref_241 Configs: type: object nullable: true - properties: *ref_201 + properties: *ref_242 Script: - type: object - properties: *ref_44 - required: *ref_45 - NewScript: - type: object - properties: *ref_49 - required: *ref_50 - NewScriptWithDraft: - allOf: *ref_202 - ScriptHistory: type: object properties: *ref_51 required: *ref_52 + NewScript: + type: object + properties: *ref_56 + required: *ref_57 + NewScriptWithDraft: + allOf: *ref_243 + ScriptHistory: + type: object + properties: *ref_58 + required: *ref_59 ScriptArgs: type: object - additionalProperties: *ref_21 + description: The arguments to pass to the script or flow + additionalProperties: *ref_22 Input: type: object - properties: *ref_177 - required: *ref_178 + properties: *ref_194 + required: *ref_195 CreateInput: type: object - properties: *ref_203 - required: *ref_204 + properties: *ref_244 + required: *ref_245 UpdateInput: type: object - properties: *ref_205 - required: *ref_206 + properties: *ref_246 + required: *ref_247 RunnableType: type: string - enum: *ref_207 + enum: *ref_248 QueuedJob: type: object - properties: *ref_108 - required: *ref_109 + properties: *ref_121 + required: *ref_122 CompletedJob: type: object - properties: *ref_106 - required: *ref_107 + properties: *ref_119 + required: *ref_120 ObscuredJob: type: object - properties: *ref_208 + properties: *ref_249 Job: - oneOf: *ref_111 - discriminator: *ref_112 + oneOf: *ref_124 + discriminator: *ref_125 User: type: object properties: *ref_10 required: *ref_11 UserUsage: type: object - properties: *ref_209 + properties: *ref_250 Login: type: object - properties: *ref_210 - required: *ref_211 + properties: *ref_251 + required: *ref_252 EditWorkspaceUser: type: object - properties: *ref_212 + properties: *ref_253 TruncatedToken: type: object - properties: *ref_47 - required: *ref_48 + properties: *ref_54 + required: *ref_55 NewToken: type: object - properties: *ref_213 + properties: *ref_254 NewTokenImpersonate: type: object - properties: *ref_214 - required: *ref_215 + properties: *ref_255 + required: *ref_256 ListableVariable: type: object - properties: *ref_27 - required: *ref_28 + properties: *ref_32 + required: *ref_33 ContextualVariable: type: object - properties: *ref_216 - required: *ref_217 + properties: *ref_257 + required: *ref_258 CreateVariable: type: object - properties: *ref_218 - required: *ref_219 + properties: *ref_259 + required: *ref_260 EditVariable: type: object - properties: *ref_220 + properties: *ref_261 AuditLog: type: object properties: *ref_1 @@ -19475,33 +21296,38 @@ components: - type: object properties: object: - type: array - items: - type: object - properties: - key: - type: string - typ: - oneOf: - - type: string - enum: - - float - - int - - bool - - email - - unknown - - bytes - - dict - - datetime - - sql - - type: object - properties: - str: {} - required: - - str - required: - - key - - typ + type: object + properties: + name: + type: string + props: + type: array + items: + type: object + properties: + key: + type: string + typ: + oneOf: + - type: string + enum: + - float + - int + - bool + - email + - unknown + - bytes + - dict + - datetime + - sql + - type: object + properties: + str: {} + required: + - str + required: + - key + - typ required: - object - type: object @@ -19549,258 +21375,291 @@ components: - has_preprocessor ScriptLang: type: string - enum: *ref_43 + enum: *ref_50 Preview: type: object - properties: *ref_221 - required: *ref_222 + properties: *ref_262 + required: *ref_263 WorkflowTask: type: object - properties: *ref_223 - required: *ref_224 + properties: *ref_264 + required: *ref_265 WorkflowStatusRecord: type: object - additionalProperties: *ref_225 + additionalProperties: + type: object + properties: *ref_113 WorkflowStatus: type: object - properties: *ref_226 + properties: *ref_113 CreateResource: type: object - properties: *ref_227 - required: *ref_228 + properties: *ref_266 + required: *ref_267 EditResource: type: object - properties: *ref_229 + properties: *ref_268 Resource: type: object - properties: *ref_230 - required: *ref_231 + properties: *ref_269 + required: *ref_270 ListableResource: type: object - properties: *ref_232 - required: *ref_233 + properties: *ref_271 + required: *ref_272 ResourceType: type: object - properties: *ref_32 - required: *ref_33 + properties: *ref_39 + required: *ref_40 EditResourceType: type: object - properties: *ref_234 + properties: *ref_273 Schedule: type: object - properties: *ref_115 - required: *ref_116 + properties: *ref_128 + required: *ref_129 ScheduleWJobs: - allOf: *ref_235 + allOf: *ref_274 + ErrorHandler: + type: string + enum: + - custom + - slack + - teams + - email NewSchedule: type: object - properties: *ref_236 - required: *ref_237 + properties: *ref_275 + required: *ref_276 EditSchedule: type: object - properties: *ref_238 - required: *ref_239 + properties: *ref_277 + required: *ref_278 TriggerExtraProperty: type: object - properties: *ref_122 - required: *ref_123 + properties: *ref_139 + required: *ref_140 AuthenticationMethod: type: string - enum: *ref_117 - HttpTrigger: - allOf: *ref_118 + enum: *ref_134 + RunnableKind: + type: string + enum: *ref_279 + OpenapiSpecFormat: + type: string + enum: *ref_280 + OpenapiHttpRouteFilters: type: object - properties: *ref_119 - required: *ref_120 + properties: *ref_281 + required: *ref_282 + WebhookFilters: + type: object + properties: *ref_283 + required: *ref_284 + OpenapiV3Info: + type: object + properties: *ref_285 + required: *ref_286 + GenerateOpenapiSpec: + type: object + properties: *ref_130 + HttpMethod: + type: string + enum: *ref_133 + HttpTrigger: + allOf: *ref_135 + type: object + properties: *ref_136 + required: *ref_137 NewHttpTrigger: type: object - properties: *ref_240 - required: *ref_241 + properties: *ref_131 + required: *ref_132 EditHttpTrigger: type: object - properties: *ref_242 - required: *ref_243 + properties: *ref_287 + required: *ref_288 TriggersCount: type: object - properties: *ref_66 + properties: *ref_73 WebsocketTrigger: - allOf: *ref_124 + allOf: *ref_141 type: object - properties: *ref_125 - required: *ref_126 + properties: *ref_142 + required: *ref_143 NewWebsocketTrigger: type: object - properties: *ref_244 - required: *ref_245 + properties: *ref_289 + required: *ref_290 EditWebsocketTrigger: type: object - properties: *ref_246 - required: *ref_247 + properties: *ref_291 + required: *ref_292 WebsocketTriggerInitialMessage: - anyOf: *ref_121 + anyOf: *ref_138 MqttQoS: type: string - enum: *ref_248 + enum: *ref_293 MqttV3Config: type: object - properties: *ref_139 + properties: *ref_156 MqttV5Config: type: object - properties: *ref_140 + properties: *ref_157 MqttSubscribeTopic: type: object - properties: *ref_137 - required: *ref_138 + properties: *ref_154 + required: *ref_155 MqttClientVersion: type: string - enum: *ref_141 + enum: *ref_158 MqttTrigger: - allOf: *ref_142 + allOf: *ref_159 type: object - properties: *ref_143 - required: *ref_144 + properties: *ref_160 + required: *ref_161 NewMqttTrigger: type: object - properties: *ref_249 - required: *ref_250 + properties: *ref_294 + required: *ref_295 EditMqttTrigger: type: object - properties: *ref_251 - required: *ref_252 + properties: *ref_296 + required: *ref_297 DeliveryType: type: string - enum: *ref_147 + enum: *ref_164 PushConfig: type: object - properties: *ref_148 - required: *ref_149 + properties: *ref_165 + required: *ref_166 GcpTrigger: - allOf: *ref_151 + allOf: *ref_168 type: object - properties: *ref_152 - required: *ref_153 + properties: *ref_169 + required: *ref_170 SubscriptionMode: type: string - enum: *ref_150 + enum: *ref_167 description: >- The mode of subscription. 'existing' means using an existing GCP subscription, while 'create_update' involves creating or updating a new subscription. GcpTriggerData: type: object - properties: *ref_145 - required: *ref_146 + properties: *ref_162 + required: *ref_163 GetAllTopicSubscription: type: object - properties: *ref_253 - required: *ref_254 + properties: *ref_298 + required: *ref_299 DeleteGcpSubscription: type: object - properties: *ref_255 - required: *ref_256 + properties: *ref_300 + required: *ref_301 AwsAuthResourceType: type: string - enum: *ref_133 + enum: *ref_150 SqsTrigger: - allOf: *ref_134 + allOf: *ref_151 type: object - properties: *ref_135 - required: *ref_136 + properties: *ref_152 + required: *ref_153 NewSqsTrigger: type: object - properties: *ref_257 - required: *ref_258 + properties: *ref_302 + required: *ref_303 EditSqsTrigger: type: object - properties: *ref_259 - required: *ref_260 + properties: *ref_304 + required: *ref_305 Slot: type: object - properties: *ref_154 + properties: *ref_171 SlotList: type: object - properties: *ref_261 + properties: *ref_306 PublicationData: type: object - properties: *ref_158 - required: *ref_159 + properties: *ref_175 + required: *ref_176 TableToTrack: type: array - items: *ref_262 + items: *ref_307 Relations: type: object - properties: *ref_155 - required: *ref_156 + properties: *ref_172 + required: *ref_173 Language: type: string - enum: *ref_263 + enum: *ref_308 TemplateScript: type: object - properties: *ref_264 - required: *ref_265 + properties: *ref_309 + required: *ref_310 PostgresTrigger: - allOf: *ref_160 + allOf: *ref_177 type: object - properties: *ref_161 - required: *ref_162 + properties: *ref_178 + required: *ref_179 NewPostgresTrigger: type: object - properties: *ref_266 - required: *ref_267 + properties: *ref_311 + required: *ref_312 EditPostgresTrigger: type: object - properties: *ref_268 - required: *ref_269 + properties: *ref_313 + required: *ref_314 KafkaTrigger: - allOf: *ref_127 + allOf: *ref_144 type: object - properties: *ref_128 - required: *ref_129 + properties: *ref_145 + required: *ref_146 NewKafkaTrigger: type: object - properties: *ref_270 - required: *ref_271 + properties: *ref_315 + required: *ref_316 EditKafkaTrigger: type: object - properties: *ref_272 - required: *ref_273 + properties: *ref_317 + required: *ref_318 NatsTrigger: - allOf: *ref_130 + allOf: *ref_147 type: object - properties: *ref_131 - required: *ref_132 + properties: *ref_148 + required: *ref_149 NewNatsTrigger: type: object - properties: *ref_274 - required: *ref_275 + properties: *ref_319 + required: *ref_320 EditNatsTrigger: type: object - properties: *ref_276 - required: *ref_277 + properties: *ref_321 + required: *ref_322 Group: type: object - properties: *ref_168 - required: *ref_169 + properties: *ref_185 + required: *ref_186 InstanceGroup: type: object - properties: *ref_164 - required: *ref_165 + properties: *ref_181 + required: *ref_182 Folder: type: object - properties: *ref_170 - required: *ref_171 + properties: *ref_187 + required: *ref_188 WorkerPing: type: object - properties: *ref_278 - required: *ref_279 + properties: *ref_323 + required: *ref_324 UserWorkspaceList: type: object - properties: *ref_280 - required: *ref_281 + properties: *ref_325 + required: *ref_326 CreateWorkspace: type: object - properties: *ref_282 - required: *ref_283 + properties: *ref_327 + required: *ref_328 Workspace: type: object properties: *ref_7 @@ -19814,48 +21673,56 @@ components: properties: *ref_14 required: *ref_15 Flow: - allOf: *ref_65 + allOf: *ref_72 ExtraPerms: type: object - additionalProperties: *ref_284 + additionalProperties: *ref_329 FlowMetadata: type: object - properties: *ref_285 - required: *ref_286 + properties: *ref_330 + required: *ref_331 OpenFlowWPath: - allOf: *ref_67 + allOf: *ref_74 FlowPreview: type: object - properties: *ref_287 - required: *ref_288 + properties: *ref_332 + required: *ref_333 RestartedFrom: type: object - properties: *ref_289 + properties: *ref_334 Policy: type: object - properties: *ref_68 + properties: *ref_75 ListableApp: type: object - properties: *ref_290 - required: *ref_291 + properties: *ref_335 + required: *ref_336 + ScopeDefinition: + type: object + properties: *ref_337 + required: *ref_338 + ScopeDomain: + type: object + properties: *ref_339 + required: *ref_340 ListableRawApp: type: object - properties: *ref_292 - required: *ref_293 + properties: *ref_341 + required: *ref_342 AppWithLastVersion: type: object - properties: *ref_69 - required: *ref_70 + properties: *ref_76 + required: *ref_77 AppWithLastVersionWDraft: - allOf: *ref_294 + allOf: *ref_343 AppHistory: type: object - properties: *ref_71 - required: *ref_72 + properties: *ref_78 + required: *ref_79 FlowVersion: type: object - properties: *ref_63 - required: *ref_64 + properties: *ref_70 + required: *ref_71 SlackToken: type: object properties: @@ -19877,46 +21744,50 @@ components: - bot TokenResponse: type: object - properties: *ref_295 - required: *ref_296 + properties: *ref_36 + required: *ref_37 HubScriptKind: type: string - enum: *ref_42 + enum: *ref_49 PolarsClientKwargs: type: object - properties: *ref_181 - required: *ref_182 + properties: *ref_198 + required: *ref_199 LargeFileStorage: type: object - properties: *ref_22 + properties: *ref_23 + DucklakeSettings: + type: object + required: *ref_24 + properties: *ref_25 WindmillLargeFile: type: object - properties: *ref_297 - required: *ref_298 + properties: *ref_344 + required: *ref_345 WindmillFileMetadata: type: object - properties: *ref_299 + properties: *ref_346 WindmillFilePreview: type: object - properties: *ref_300 - required: *ref_301 + properties: *ref_347 + required: *ref_348 S3Resource: type: object - properties: *ref_179 - required: *ref_180 + properties: *ref_196 + required: *ref_197 WorkspaceGitSyncSettings: type: object - properties: *ref_23 + properties: *ref_26 WorkspaceDeployUISettings: type: object - properties: *ref_24 + properties: *ref_29 WorkspaceDefaultScripts: type: object - properties: *ref_25 + properties: *ref_30 GitRepositorySettings: type: object - properties: *ref_302 - required: *ref_303 + properties: *ref_27 + required: *ref_28 UploadFilePart: type: object properties: @@ -19929,71 +21800,71 @@ components: - tag MetricMetadata: type: object - properties: *ref_304 - required: *ref_305 + properties: *ref_349 + required: *ref_350 ScalarMetric: type: object - properties: *ref_306 - required: *ref_307 + properties: *ref_351 + required: *ref_352 TimeseriesMetric: type: object - properties: *ref_308 - required: *ref_309 + properties: *ref_353 + required: *ref_354 MetricDataPoint: type: object - properties: *ref_310 - required: *ref_311 + properties: *ref_355 + required: *ref_356 RawScriptForDependencies: type: object - properties: *ref_312 - required: *ref_313 + properties: *ref_357 + required: *ref_358 ConcurrencyGroup: type: object - properties: *ref_314 - required: *ref_315 + properties: *ref_359 + required: *ref_360 ExtendedJobs: type: object - properties: *ref_316 - required: *ref_317 + properties: *ref_361 + required: *ref_362 ExportedUser: type: object properties: *ref_3 required: *ref_4 GlobalSetting: type: object - properties: *ref_318 - required: *ref_319 + properties: *ref_363 + required: *ref_364 Config: type: object - properties: *ref_320 - required: *ref_321 + properties: *ref_365 + required: *ref_366 ExportedInstanceGroup: type: object - properties: *ref_166 - required: *ref_167 + properties: *ref_183 + required: *ref_184 JobSearchHit: type: object - properties: *ref_322 + properties: *ref_367 LogSearchHit: type: object - properties: *ref_323 + properties: *ref_368 AutoscalingEvent: type: object - properties: *ref_324 + properties: *ref_369 CriticalAlert: type: object - properties: *ref_29 + properties: *ref_34 CaptureTriggerKind: type: string - enum: *ref_172 + enum: *ref_189 Capture: type: object - properties: *ref_173 - required: *ref_174 + properties: *ref_190 + required: *ref_191 CaptureConfig: type: object - properties: *ref_325 - required: *ref_326 + properties: *ref_370 + required: *ref_371 OperatorSettings: nullable: true type: object @@ -20001,15 +21872,15 @@ components: properties: *ref_13 TeamInfo: type: object - required: *ref_327 - properties: *ref_328 + required: *ref_372 + properties: *ref_373 ChannelInfo: type: object - required: *ref_329 - properties: *ref_330 + required: *ref_374 + properties: *ref_375 GithubInstallations: type: array - items: *ref_331 + items: *ref_376 WorkspaceGithubInstallation: type: object properties: @@ -20022,8 +21893,8 @@ components: - installation_id S3Object: type: object - properties: *ref_75 - required: *ref_76 + properties: *ref_82 + required: *ref_83 TeamsChannel: type: object required: @@ -20048,76 +21919,88 @@ components: type: string description: Microsoft Teams channel name minLength: 1 - StaticTransform: + AssetUsageKind: + type: string + enum: *ref_203 + AssetUsageAccessType: + type: string + enum: *ref_204 + AssetKind: + type: string + enum: *ref_202 + Asset: type: object - properties: *ref_332 - required: *ref_333 - JavascriptTransform: + properties: + path: + type: string + kind: + type: string + enum: *ref_202 + required: + - path + - kind + schemas-StaticTransform: type: object - properties: *ref_334 - required: *ref_335 + properties: *ref_84 + required: *ref_85 + schemas-JavascriptTransform: + type: object + properties: *ref_86 + required: *ref_87 schemas-InputTransform: - oneOf: *ref_35 - discriminator: *ref_36 - RawScript: + oneOf: *ref_42 + discriminator: *ref_43 + schemas-RawScript: type: object - properties: *ref_336 - required: *ref_337 - PathScript: + properties: *ref_219 + required: *ref_220 + schemas-PathScript: type: object - properties: *ref_338 - required: *ref_339 - PathFlow: + properties: *ref_221 + required: *ref_222 + schemas-PathFlow: type: object - properties: *ref_340 - required: *ref_341 - FlowModule: + properties: *ref_223 + required: *ref_224 + schemas-FlowModule: type: object - properties: *ref_37 - required: *ref_38 - ForloopFlow: + properties: *ref_44 + required: *ref_45 + schemas-ForloopFlow: type: object - properties: *ref_342 - required: *ref_343 - WhileloopFlow: + properties: *ref_225 + required: *ref_226 + schemas-WhileloopFlow: type: object - properties: *ref_344 - required: *ref_345 - BranchOne: + properties: *ref_227 + required: *ref_228 + schemas-BranchOne: type: object - properties: *ref_346 - required: *ref_347 - BranchAll: + properties: *ref_229 + required: *ref_230 + schemas-BranchAll: type: object - properties: *ref_348 - required: *ref_349 - Identity: + properties: *ref_231 + required: *ref_232 + schemas-Identity: type: object - properties: *ref_350 - required: *ref_351 - FlowModuleValue: - oneOf: *ref_352 - discriminator: *ref_353 - StopAfterIf: + properties: *ref_233 + required: *ref_234 + schemas-FlowModuleValue: + oneOf: *ref_216 + discriminator: *ref_217 + schemas-StopAfterIf: type: object - properties: *ref_39 - required: *ref_40 - Retry: + properties: *ref_46 + required: *ref_47 + schemas-Retry: type: object - properties: *ref_114 - FlowValue: + properties: *ref_218 + schemas-FlowValue: type: object - properties: *ref_78 - required: *ref_79 - OpenFlow: + properties: *ref_377 + required: *ref_378 + schemas-FlowStatusModule: type: object - properties: *ref_61 - required: *ref_62 - FlowStatusModule: - type: object - properties: *ref_80 - required: *ref_81 - FlowStatus: - type: object - properties: *ref_99 - required: *ref_100 + properties: *ref_90 + required: *ref_91 diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index e7c3ef37b9..053fabe663 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.3" info: - version: 1.518.2 + version: 1.527.1 title: Windmill API contact: @@ -1944,6 +1944,14 @@ paths: type: boolean auto_add: type: boolean + auto_add_instance_groups: + type: array + items: + type: string + auto_add_instance_groups_roles: + type: object + additionalProperties: + type: string plan: type: string customer_id: @@ -2351,6 +2359,39 @@ paths: schema: type: string + /w/{workspace}/workspaces/edit_instance_groups: + post: + summary: edit instance groups + operationId: editInstanceGroups + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: Instance Groups Configuration + required: true + content: + application/json: + schema: + type: object + properties: + groups: + type: array + items: + type: string + roles: + type: object + additionalProperties: + type: string + + responses: + "200": + description: status + content: + text/plain: + schema: + type: string + /w/{workspace}/workspaces/edit_webhook: post: summary: edit webhook @@ -2440,7 +2481,6 @@ paths: $ref: "#/components/schemas/ScriptArgs" error_handler_muted_on_cancel: type: boolean - responses: "200": description: status @@ -2474,7 +2514,6 @@ paths: application/json: schema: {} - /w/{workspace}/workspaces/list_ducklakes: get: summary: list ducklakes @@ -3069,11 +3108,13 @@ paths: post: summary: create variable operationId: createVariable + x-mcp-tool: true tags: - variable parameters: - $ref: "#/components/parameters/WorkspaceId" - name: already_encrypted + description: whether the variable is already encrypted (default false) in: query schema: type: boolean @@ -3119,6 +3160,7 @@ paths: delete: summary: delete variable operationId: deleteVariable + x-mcp-tool: true tags: - variable parameters: @@ -3136,12 +3178,14 @@ paths: post: summary: update variable operationId: updateVariable + x-mcp-tool: true tags: - variable parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/Path" - name: already_encrypted + description: whether the variable is already encrypted (default false) in: query schema: type: boolean @@ -3164,6 +3208,7 @@ paths: get: summary: get variable operationId: getVariable + x-mcp-tool: true tags: - variable parameters: @@ -3228,11 +3273,13 @@ paths: get: summary: list variables operationId: listVariable + x-mcp-tool: true tags: - variable parameters: - $ref: "#/components/parameters/WorkspaceId" - name: path_start + description: filter variables by path prefix in: query schema: type: string @@ -3833,11 +3880,13 @@ paths: post: summary: create resource operationId: createResource + x-mcp-tool: true tags: - resource parameters: - $ref: "#/components/parameters/WorkspaceId" - name: update_if_exists + description: update the resource if it already exists (default false) in: query schema: type: boolean @@ -3860,6 +3909,7 @@ paths: delete: summary: delete resource operationId: deleteResource + x-mcp-tool: true tags: - resource parameters: @@ -3877,6 +3927,7 @@ paths: post: summary: update resource operationId: updateResource + x-mcp-tool: true tags: - resource parameters: @@ -3927,6 +3978,7 @@ paths: get: summary: get resource operationId: getResource + x-mcp-tool: true tags: - resource parameters: @@ -3999,6 +4051,7 @@ paths: get: summary: list resources operationId: listResource + x-mcp-tool: true tags: - resource parameters: @@ -4016,6 +4069,7 @@ paths: schema: type: string - name: path_start + description: filter resources by path prefix in: query schema: type: string @@ -4198,6 +4252,7 @@ paths: get: summary: list resource_types operationId: listResourceType + x-mcp-tool: true tags: - resource parameters: @@ -4651,6 +4706,7 @@ paths: get: summary: list all scripts operationId: listScripts + x-mcp-tool: true tags: - script parameters: @@ -5026,6 +5082,7 @@ paths: get: summary: get script by path operationId: getScriptByPath + x-mcp-tool: true tags: - script parameters: @@ -5616,6 +5673,7 @@ paths: get: summary: list all flows operationId: listFlows + x-mcp-tool: true tags: - flow parameters: @@ -5797,6 +5855,7 @@ paths: get: summary: get flow by path operationId: getFlowByPath + x-mcp-tool: true tags: - flow parameters: @@ -7247,6 +7306,7 @@ paths: get: summary: list all queued jobs operationId: listQueue + x-mcp-tool: true tags: - job parameters: @@ -7586,6 +7646,7 @@ paths: get: summary: list all jobs operationId: listJobs + x-mcp-tool: true tags: - job parameters: @@ -7757,6 +7818,10 @@ paths: parameters: - $ref: "#/components/parameters/WorkspaceId" - $ref: "#/components/parameters/JobId" + - name: remove_ansi_warnings + in: query + schema: + type: boolean responses: "200": description: job details @@ -7765,6 +7830,23 @@ paths: schema: type: string + /w/{workspace}/jobs_u/get_completed_logs_tail/{id}: + get: + summary: get completed job logs tail + operationId: getCompletedJobLogsTail + tags: + - job + parameters: + - $ref: "#/components/parameters/WorkspaceId" + - $ref: "#/components/parameters/JobId" + responses: + "200": + description: completed job logs tail + content: + text/plain: + schema: + type: string + /w/{workspace}/jobs_u/get_args/{id}: get: summary: get job args @@ -7798,10 +7880,18 @@ paths: in: query schema: type: integer + - name: stream_offset + in: query + schema: + type: integer - name: get_progress in: query schema: type: boolean + - name: no_logs + in: query + schema: + type: boolean responses: "200": @@ -7823,6 +7913,10 @@ paths: type: integer progress: type: integer + stream_offset: + type: integer + new_result_stream: + type: string flow_status: $ref: "../../openflow.openapi.yaml#/components/schemas/FlowStatus" workflow_as_code_status: @@ -7845,6 +7939,10 @@ paths: in: query schema: type: integer + - name: stream_offset + in: query + schema: + type: integer - name: get_progress in: query schema: @@ -7853,6 +7951,10 @@ paths: in: query schema: type: boolean + - name: no_logs + in: query + schema: + type: boolean responses: "200": @@ -8526,6 +8628,11 @@ paths: post: summary: create schedule operationId: createSchedule + x-mcp-tool: true + x-mcp-instructions: | + Creates a new schedule. + The schedule should include seconds. + You should get the schema of the script or flow before creating the schedule to correctly specify the arguments needed. tags: - schedule parameters: @@ -8549,6 +8656,11 @@ paths: post: summary: update schedule operationId: updateSchedule + x-mcp-tool: true + x-mcp-instructions: | + Updates a schedule. + The schedule should include seconds. + You should get the schema of the script or flow before updating the schedule to correctly specify the arguments needed. tags: - schedule parameters: @@ -8603,6 +8715,7 @@ paths: delete: summary: delete schedule operationId: deleteSchedule + x-mcp-tool: true tags: - schedule parameters: @@ -8620,6 +8733,7 @@ paths: get: summary: get schedule operationId: getSchedule + x-mcp-tool: true tags: - schedule parameters: @@ -8654,6 +8768,7 @@ paths: get: summary: list schedules operationId: listSchedules + x-mcp-tool: true tags: - schedule parameters: @@ -8667,10 +8782,12 @@ paths: schema: type: string - name: is_flow + description: filter schedules by whether they target a flow in: query schema: type: boolean - name: path_start + description: filter schedules by path prefix in: query schema: type: string @@ -10637,6 +10754,21 @@ paths: type: array items: $ref: "#/components/schemas/InstanceGroup" + /groups/list_with_workspaces: + get: + summary: list instance groups with workspace information + operationId: listInstanceGroupsWithWorkspaces + tags: + - group + responses: + "200": + description: instance group list with workspaces + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/InstanceGroupWithWorkspaces" /groups/get/{name}: get: @@ -11275,6 +11407,7 @@ paths: get: summary: list workers operationId: listWorkers + x-mcp-tool: true tags: - worker parameters: @@ -11296,25 +11429,28 @@ paths: items: $ref: "#/components/schemas/WorkerPing" - /workers/exists_worker_with_tag: + /workers/exists_workers_with_tags: get: - summary: exists worker with tag - operationId: existsWorkerWithTag + summary: exists workers with tags + operationId: existsWorkersWithTags tags: - worker parameters: - - name: tag + - name: tags in: query required: true + description: comma separated list of tags schema: type: string responses: "200": - description: whether a worker with the tag exists + description: map of tags to whether at least one worker with the tag exists content: application/json: schema: - type: boolean + type: object + additionalProperties: + type: boolean /workers/queue_metrics: get: @@ -13323,6 +13459,24 @@ paths: access_type: $ref: "#/components/schemas/AssetUsageAccessType" + /mcp/w/{workspace}/list_tools: + get: + summary: list available MCP tools + operationId: listMcpTools + tags: + - mcp + parameters: + - $ref: "#/components/parameters/WorkspaceId" + responses: + "200": + description: list of MCP tools available for the workspace + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/EndpointTool" + components: securitySchemes: bearerAuth: @@ -13356,6 +13510,7 @@ components: name: publication in: path required: true + description: The name of the publication schema: type: string VersionId: @@ -13783,6 +13938,38 @@ components: # -- INLINE END -- # Do not change line above + EndpointTool: + type: object + required: [name, description, instructions, path, method] + properties: + name: + type: string + description: The tool name/operation ID + description: + type: string + description: Short description of the tool + instructions: + type: string + description: Detailed instructions for using the tool + path: + type: string + description: API endpoint path + method: + type: string + description: HTTP method (GET, POST, etc.) + path_params_schema: + type: object + description: JSON schema for path parameters + nullable: true + query_params_schema: + type: object + description: JSON schema for query parameters + nullable: true + body_schema: + type: object + description: JSON schema for request body + nullable: true + AIProvider: type: string enum: @@ -14106,6 +14293,7 @@ components: ScriptArgs: type: object + description: The arguments to pass to the script or flow additionalProperties: {} Input: @@ -14451,6 +14639,10 @@ components: type: array items: type: string + added_via: + nullable: true + allOf: + - $ref: "#/components/schemas/UserSource" required: - email - username @@ -14462,6 +14654,22 @@ components: - folders - folders_owners + UserSource: + type: object + properties: + source: + type: string + enum: [domain, instance_group, manual] + description: "How the user was added to the workspace" + domain: + type: string + description: "The domain used for auto-invite (when source is 'domain')" + group: + type: string + description: "The instance group name (when source is 'instance_group')" + required: + - source + UserUsage: type: object properties: @@ -14608,18 +14816,25 @@ components: properties: path: type: string + description: The path to the variable value: type: string + description: The value of the variable is_secret: type: boolean + description: Whether the variable is a secret description: type: string + description: The description of the variable account: type: integer + description: The account identifier is_oauth: type: boolean + description: Whether the variable is an OAuth variable expires_at: type: string + description: The expiration date of the variable format: date-time required: - path @@ -14632,12 +14847,16 @@ components: properties: path: type: string + description: The path to the variable value: type: string + description: The new value of the variable is_secret: type: boolean + description: Whether the variable is a secret description: type: string + description: The new description of the variable AuditLog: type: object @@ -14840,7 +15059,7 @@ components: - key - typ required: - - object + - object - type: object properties: list: @@ -14909,7 +15128,8 @@ components: csharp, nu, java, - duckdb, + ruby, + duckdb # for related places search: ADD_NEW_LANG ] @@ -14970,11 +15190,14 @@ components: properties: path: type: string + description: The path to the resource value: {} description: type: string + description: The description of the resource resource_type: type: string + description: The resource_type associated with the resource required: - path - value @@ -14985,9 +15208,14 @@ components: properties: path: type: string + description: The path to the resource description: type: string + description: The new description of the resource value: {} + resource_type: + type: string + description: The new resource_type to be associated with the resource Resource: type: object @@ -15183,59 +15411,91 @@ components: - success - duration_ms + ErrorHandler: + type: string + enum: + - custom + - slack + - teams + - email + NewSchedule: type: object properties: path: type: string + description: The path where the schedule will be created schedule: type: string + description: The cron schedule to trigger the script or flow. Should include seconds. timezone: type: string + description: The timezone to use for the cron schedule script_path: type: string + description: The path to the script or flow to trigger is_flow: type: boolean + description: Whether the schedule is for a flow args: $ref: "#/components/schemas/ScriptArgs" + description: The arguments to pass to the script or flow enabled: type: boolean + description: Whether the schedule is enabled on_failure: # a reference to a script path, flow path, or webhook (script/, flow/) type: string + description: The path to the script or flow to trigger on failure on_failure_times: type: number + description: The number of times to retry on failure on_failure_exact: type: boolean + description: Whether the schedule should only run on the exact time on_failure_extra_args: $ref: "#/components/schemas/ScriptArgs" + description: The arguments to pass to the script or flow on failure on_recovery: type: string + description: The path to the script or flow to trigger on recovery on_recovery_times: type: number + description: The number of times to retry on recovery on_recovery_extra_args: $ref: "#/components/schemas/ScriptArgs" + description: The arguments to pass to the script or flow on recovery on_success: type: string + description: The path to the script or flow to trigger on success on_success_extra_args: $ref: "#/components/schemas/ScriptArgs" + description: The arguments to pass to the script or flow on success ws_error_handler_muted: type: boolean + description: Whether the WebSocket error handler is muted retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" + description: The retry configuration for the schedule no_flow_overlap: type: boolean + description: Whether the schedule should not run if a flow is already running summary: type: string + description: The summary of the schedule description: type: string + description: The description of the schedule tag: type: string + description: The tag of the schedule paused_until: type: string + description: The date and time the schedule will be paused until format: date-time cron_version: type: string + description: The version of the cron schedule to use (last is v2) required: - path - schedule @@ -15249,51 +15509,69 @@ components: properties: schedule: type: string + description: The cron schedule to trigger the script or flow. Should include seconds. timezone: type: string + description: The timezone to use for the cron schedule args: $ref: "#/components/schemas/ScriptArgs" + description: The arguments to pass to the script or flow on_failure: # a reference to a script path, flow path, or webhook (script/, flow/) type: string + description: The path to the script or flow to trigger on failure on_failure_times: type: number + description: The number of times to retry on failure on_failure_exact: type: boolean + description: Whether the schedule should only run on the exact time on_failure_extra_args: $ref: "#/components/schemas/ScriptArgs" + description: The arguments to pass to the script or flow on failure on_recovery: type: string + description: The path to the script or flow to trigger on recovery on_recovery_times: type: number + description: The number of times to retry on recovery on_recovery_extra_args: $ref: "#/components/schemas/ScriptArgs" + description: The arguments to pass to the script or flow on recovery on_success: type: string + description: The path to the script or flow to trigger on success on_success_extra_args: $ref: "#/components/schemas/ScriptArgs" + description: The arguments to pass to the script or flow on success ws_error_handler_muted: type: boolean + description: Whether the WebSocket error handler is muted retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" + description: The retry configuration for the schedule no_flow_overlap: type: boolean + description: Whether the schedule should not run if a flow is already running summary: type: string + description: The summary of the schedule description: type: string + description: The description of the schedule tag: type: string + description: The tag of the schedule paused_until: type: string + description: The date and time the schedule will be paused until format: date-time cron_version: type: string + description: The version of the cron schedule to use (last is v2) required: - schedule - timezone - - script_path - - is_flow - args TriggerExtraProperty: @@ -16056,6 +16334,8 @@ components: type: boolean enabled: type: boolean + auto_acknowledge_msg: + type: boolean error_handler_path: type: string error_handler_args: @@ -16608,6 +16888,8 @@ components: InstanceGroup: type: object + required: + - name properties: name: type: string @@ -16617,6 +16899,34 @@ components: type: array items: type: string + + InstanceGroupWithWorkspaces: + type: object + required: + - name + properties: + name: + type: string + summary: + type: string + emails: + type: array + items: + type: string + workspaces: + type: array + items: + $ref: "#/components/schemas/WorkspaceInfo" + + WorkspaceInfo: + type: object + properties: + workspace_id: + type: string + workspace_name: + type: string + role: + type: string required: - name @@ -16820,7 +17130,6 @@ components: additionalProperties: type: boolean - FlowMetadata: type: object properties: diff --git a/backend/windmill-api/src/approvals.rs b/backend/windmill-api/src/approvals.rs index f16ed00abe..e03b587f10 100644 --- a/backend/windmill-api/src/approvals.rs +++ b/backend/windmill-api/src/approvals.rs @@ -1,18 +1,24 @@ -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use uuid::Uuid; -use std::str::FromStr; -use regex::Regex; -use serde_json::Value; use crate::auth::OptTokened; use crate::db::{ApiAuthed, DB}; -use crate::jobs::{cancel_suspended_job, resume_suspended_job, QueryApprover, QueryOrBody, ResumeUrls, get_resume_urls_internal}; -use axum::{extract::{Path, Query}, Extension}; -use windmill_common::error::Error; +use crate::jobs::{ + cancel_suspended_job, get_resume_urls_internal, resume_suspended_job, QueryApprover, + QueryOrBody, ResumeUrls, +}; +use axum::{ + extract::{Path, Query}, + Extension, +}; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use serde_json::value::RawValue; +use serde_json::Value; +use std::collections::HashMap; +use std::str::FromStr; +use uuid::Uuid; use windmill_common::cache; +use windmill_common::error::Error; use windmill_common::jobs::JobKind; use windmill_common::scripts::ScriptHash; -use serde_json::value::RawValue; #[derive(Debug, Deserialize, Serialize)] pub struct ResumeSchema { @@ -234,7 +240,7 @@ pub async fn get_approval_form_details( .ok_or_else(|| Error::BadRequest("This workflow is no longer running and has either already timed out or been cancelled or completed.".to_string())) .map(|r| (r.job_kind, r.script_hash, r.raw_flow, r.parent_job, r.created_at, r.created_by, r.script_path, r.args))?; - let flow_data = match cache::job::fetch_flow(&db, job_kind, script_hash).await { + let flow_data = match cache::job::fetch_flow(&db, &job_kind, script_hash).await { Ok(data) => data, Err(_) => { if let Some(parent_job_id) = parent_job_id.as_ref() { diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 188e6e3a81..dc128997ee 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -52,7 +52,7 @@ use std::str; use windmill_audit::audit_oss::audit_log; use windmill_audit::ActionKind; use windmill_common::{ - apps::{AppScriptId, ListAppQuery}, + apps::{AppScriptId, ListAppQuery, APP_WORKSPACED_ROUTE}, auth::TOKEN_PREFIX_LEN, cache::{self, future::FutureCachedExt}, db::UserDB, @@ -157,7 +157,7 @@ pub struct AppVersion { pub created_at: chrono::DateTime, } -#[derive(Serialize, Deserialize, FromRow)] +#[derive(Debug, Serialize, Deserialize, FromRow)] pub struct AppWithLastVersion { pub id: i64, pub path: String, @@ -182,7 +182,7 @@ pub struct AppWithLastVersionAndStarred { } #[cfg(feature = "enterprise")] -#[derive(Serialize, FromRow)] +#[derive(Debug, Serialize, FromRow)] pub struct AppWithLastVersionAndWorkspace { #[sqlx(flatten)] #[serde(flatten)] @@ -524,21 +524,36 @@ async fn get_app_w_draft( let mut tx = user_db.begin(&authed).await?; let app_o = sqlx::query_as::<_, AppWithLastVersionAndDraft>( - r#"SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path, - app.extra_perms, app_version.value, - app_version.created_at, app_version.created_by, - app.draft_only, draft.value as "draft" - from app - INNER JOIN app_version ON - app_version.id = app.versions[array_upper(app.versions, 1)] - LEFT JOIN draft ON - app.path = draft.path AND draft.workspace_id = $2 AND draft.typ = 'app' - WHERE app.path = $1 AND app.workspace_id = $2"#, + r#" + SELECT + app.id, + app.path, + app.summary, + app.versions, + app.policy, + app.custom_path, + app.extra_perms, + app_version.value, + app_version.created_at, + app_version.created_by, + app.draft_only, + draft.value AS "draft" + FROM app + INNER JOIN app_version + ON app_version.id = app.versions[array_upper(app.versions, 1)] + LEFT JOIN draft + ON app.path = draft.path + AND draft.workspace_id = $2 + AND draft.typ = 'app' + WHERE app.path = $1 + AND app.workspace_id = $2 + "#, ) .bind(path.to_owned()) .bind(&w_id) .fetch_optional(&mut *tx) .await?; + tx.commit().await?; let app = not_found_if_none(app_o, "App", path)?; @@ -642,11 +657,13 @@ async fn custom_path_exists( Extension(db): Extension, Path((w_id, custom_path)): Path<(String, String)>, ) -> JsonResult { + let as_workspaced_route = *APP_WORKSPACED_ROUTE.read().await; + let exists = sqlx::query_scalar!( "SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2))", custom_path, - if *CLOUD_HOSTED { Some(&w_id) } else { None } + if *CLOUD_HOSTED || as_workspaced_route { Some(&w_id) } else { None } ) .fetch_one(&db) .await?.unwrap_or(false); @@ -976,11 +993,12 @@ async fn create_app_internal<'a>( } if let Some(custom_path) = &app.custom_path { require_admin(authed.is_admin, &authed.username)?; + let as_workspaced_route = *APP_WORKSPACED_ROUTE.read().await; let exists = sqlx::query_scalar!( "SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2))", custom_path, - if *CLOUD_HOSTED { Some(w_id) } else { None } + if *CLOUD_HOSTED || as_workspaced_route { Some(w_id) } else { None } ) .fetch_one(&mut *tx) .await?.unwrap_or(false); @@ -1272,6 +1290,7 @@ async fn update_app_internal<'a>( ) -> Result<(sqlx::Transaction<'a, sqlx::Postgres>, String, i64)> { use sql_builder::prelude::*; let mut tx = user_db.clone().begin(&authed).await?; + let npath = if ns.policy.is_some() || ns.path.is_some() || ns.summary.is_some() @@ -1311,6 +1330,7 @@ async fn update_app_internal<'a>( if let Some(ncustom_path) = &ns.custom_path { require_admin(authed.is_admin, &authed.username)?; + let as_workspaced_route = *APP_WORKSPACED_ROUTE.read().await; if ncustom_path.is_empty() { sqlb.set("custom_path", "NULL"); @@ -1318,7 +1338,7 @@ async fn update_app_internal<'a>( let exists = sqlx::query_scalar!( "SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2) AND NOT (path = $3 AND workspace_id = $4))", ncustom_path, - if *CLOUD_HOSTED { Some(w_id) } else { None }, + if *CLOUD_HOSTED || as_workspaced_route { Some(w_id) } else { None }, path, w_id ) diff --git a/backend/windmill-api/src/capture.rs b/backend/windmill-api/src/capture.rs index 54ad2c35d4..4bd2161248 100644 --- a/backend/windmill-api/src/capture.rs +++ b/backend/windmill-api/src/capture.rs @@ -176,6 +176,7 @@ pub struct GcpTriggerConfig { #[serde(flatten)] pub create_update: Option, pub topic_id: String, + pub auto_acknowledge_msg: Option, } #[cfg(all(feature = "enterprise", feature = "nats"))] diff --git a/backend/windmill-api/src/db.rs b/backend/windmill-api/src/db.rs index bf04b515a5..7fa7c61386 100644 --- a/backend/windmill-api/src/db.rs +++ b/backend/windmill-api/src/db.rs @@ -854,6 +854,22 @@ impl From for Authed { } } +impl From for ApiAuthed { + fn from(value: Authed) -> Self { + Self { + email: value.email, + username: value.username, + is_admin: value.is_admin, + is_operator: value.is_operator, + groups: value.groups, + folders: value.folders, + scopes: value.scopes, + username_override: None, // Authed doesn't have this field, so default to None + token_prefix: value.token_prefix, + } + } +} + impl From<&ApiAuthed> for AuditAuthor { fn from(value: &ApiAuthed) -> Self { Self { diff --git a/backend/windmill-api/src/gcp_triggers_oss.rs b/backend/windmill-api/src/gcp_triggers_oss.rs index 17363e28c0..b18e61db95 100644 --- a/backend/windmill-api/src/gcp_triggers_oss.rs +++ b/backend/windmill-api/src/gcp_triggers_oss.rs @@ -2,22 +2,23 @@ #[allow(unused)] pub use crate::gcp_triggers_ee::*; +use serde_json::value::RawValue; +use sqlx::prelude::FromRow; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use sqlx::types::Json as SqlxJson; +use windmill_common::worker::to_raw_value; +use windmill_common::triggers::TriggerKind; +use crate::trigger_helpers::TriggerJobArgs; + #[cfg(not(feature = "private"))] use { crate::db::{ApiAuthed, DB}, - crate::trigger_helpers::TriggerJobArgs, axum::{extract::Request, Router}, http::HeaderMap, - serde::{Deserialize, Serialize}, - serde_json::value::RawValue, - sqlx::prelude::FromRow, - sqlx::types::Json as SqlxJson, - std::collections::HashMap, windmill_common::db::UserDB, - windmill_common::worker::to_raw_value, windmill_common::{ error::{Error as WindmillError, Result as WindmillResult}, - triggers::TriggerKind, utils::empty_as_none, }, }; @@ -134,7 +135,6 @@ pub fn gcp_push_route_handler() -> Router { } #[derive(FromRow, Deserialize, Serialize, Debug)] -#[cfg(not(feature = "private"))] pub struct GcpTrigger { pub gcp_resource_path: String, pub subscription_id: String, @@ -160,8 +160,10 @@ pub struct GcpTrigger { pub error_handler_args: Option>>>, #[serde(skip_serializing_if = "Option::is_none")] pub retry: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub auto_acknowledge_msg: Option, } -#[cfg(not(feature = "private"))] + impl TriggerJobArgs for GcpTrigger { fn v1_payload_fn(payload: String) -> HashMap> { HashMap::from([("payload".to_string(), to_raw_value(&payload))]) @@ -170,4 +172,4 @@ impl TriggerJobArgs for GcpTrigger { fn trigger_kind() -> TriggerKind { TriggerKind::Gcp } -} +} \ No newline at end of file diff --git a/backend/windmill-api/src/groups.rs b/backend/windmill-api/src/groups.rs index d5da9c25f5..4c526ffe26 100644 --- a/backend/windmill-api/src/groups.rs +++ b/backend/windmill-api/src/groups.rs @@ -44,6 +44,7 @@ pub fn workspaced_service() -> Router { pub fn global_service() -> Router { Router::new() .route("/list", get(list_igroups)) + .route("/list_with_workspaces", get(list_igroups_with_workspaces)) .route("/get/:name", get(get_igroup)) .route("/create", post(create_igroup)) .route("/update/:name", post(update_igroup)) @@ -187,7 +188,7 @@ pub async fn require_is_owner( ) -> Result<()> { let is_owner = query_scalar!( "SELECT EXISTS(SELECT 1 FROM group_ WHERE (group_.extra_perms ->> CONCAT('u/', $1::text))::boolean AND name = $2 AND workspace_id = $4) OR exists( - SELECT 1 FROM group_ g, jsonb_each_text(g.extra_perms) f + SELECT 1 FROM group_ g, jsonb_each_text(g.extra_perms) f WHERE $2 = g.name AND $4 = g.workspace_id AND SPLIT_PART(key, '/', 1) = 'g' AND key = ANY($3::text[]) AND value::boolean)", username, @@ -418,7 +419,7 @@ async fn get_group( let group = not_found_if_none(get_group_opt(&mut tx, &w_id, &name).await?, "Group", &name)?; let members = sqlx::query_scalar!( - "SELECT usr.username + "SELECT usr.username FROM usr_to_group LEFT JOIN usr ON usr_to_group.usr = usr.username AND usr_to_group.workspace_id = $2 WHERE group_ = $1 AND usr.workspace_id = $2 AND usr_to_group.workspace_id = $2", name, @@ -638,6 +639,21 @@ struct IGroup { summary: Option, emails: Option>, } + +#[derive(Serialize)] +struct IGroupWithWorkspaces { + name: String, + summary: Option, + emails: Option>, + workspaces: Vec, +} + +#[derive(Serialize, Clone)] +struct WorkspaceInfo { + workspace_id: String, + workspace_name: String, + role: String, +} async fn list_igroups(Extension(db): Extension) -> JsonResult> { let mut tx: Transaction<'_, Postgres> = db.begin().await?; @@ -652,6 +668,70 @@ async fn list_igroups(Extension(db): Extension) -> JsonResult> { return Ok(Json(groups)); } +async fn list_igroups_with_workspaces(Extension(db): Extension) -> JsonResult> { + let mut tx: Transaction<'_, Postgres> = db.begin().await?; + + // Get all instance groups with their emails first + let groups = sqlx::query_as!( + IGroup, + "SELECT name, summary, array_remove(array_agg(email_to_igroup.email), null) as emails FROM email_to_igroup RIGHT JOIN instance_group ON instance_group.name = email_to_igroup.igroup GROUP BY name, summary" + ) + .fetch_all(&mut *tx) + .await?; + + // Get all workspace mappings for instance groups in a single query + let workspace_mappings = sqlx::query!( + r#" + SELECT + ig.name as group_name, + ws.workspace_id, + w.name as workspace_name, + ws.auto_add_instance_groups_roles->ig.name as role + FROM instance_group ig + INNER JOIN workspace_settings ws ON ws.auto_add_instance_groups IS NOT NULL + AND ig.name = ANY(ws.auto_add_instance_groups) + INNER JOIN workspace w ON w.id = ws.workspace_id AND w.deleted = false + ORDER BY ig.name, ws.workspace_id + "# + ) + .fetch_all(&mut *tx) + .await?; + + // Create a map of group_name -> Vec + let mut workspaces_by_group: std::collections::HashMap> = std::collections::HashMap::new(); + for mapping in workspace_mappings { + let role = mapping.role + .and_then(|r| r.as_str().map(|s| s.to_string())) + .unwrap_or_else(|| "developer".to_string()); + + let workspace_info = WorkspaceInfo { + workspace_id: mapping.workspace_id.clone(), + workspace_name: mapping.workspace_name, + role, + }; + + workspaces_by_group + .entry(mapping.group_name) + .or_insert_with(Vec::new) + .push(workspace_info); + } + + let mut result = Vec::new(); + for group in groups { + let workspaces = workspaces_by_group.get(&group.name).cloned().unwrap_or_default(); + + result.push(IGroupWithWorkspaces { + name: group.name, + summary: group.summary, + emails: group.emails, + workspaces, + }); + } + + tx.commit().await?; + return Ok(Json(result)); +} + async fn get_igroup(Path(name): Path, Extension(db): Extension) -> JsonResult { let group = sqlx::query_as!( IGroup, diff --git a/backend/windmill-api/src/http_triggers.rs b/backend/windmill-api/src/http_triggers.rs index b6d5fff7f0..b8a52a58ba 100644 --- a/backend/windmill-api/src/http_triggers.rs +++ b/backend/windmill-api/src/http_triggers.rs @@ -12,7 +12,6 @@ use crate::{ users::fetch_api_authed, utils::{check_scopes, non_empty_str, ExpiringCacheEntry}, }; - use anyhow::anyhow; use axum::response::Response; use axum::{ @@ -197,38 +196,37 @@ async fn list_triggers( ) -> error::JsonResult> { let mut tx = user_db.begin(&authed).await?; let (per_page, offset) = paginate(Pagination { per_page: lst.per_page, page: lst.page }); - let mut sqlb = SqlBuilder::select_from("http_trigger") - .fields(&[ - "workspace_id", - "path", - "route_path", - "route_path_key", - "workspaced_route", - "wrap_body", - "raw_string", - "script_path", - "summary", - "description", - "is_flow", - "http_method", - "edited_by", - "email", - "edited_at", - "extra_perms", - "is_async", - "authentication_method", - "static_asset_config", - "is_static_website", - "authentication_resource_path", - "error_handler_path", - "error_handler_args", - "retry", - ]) - .order_by("edited_at", true) - .and_where("workspace_id = ?".bind(&w_id)) - .offset(offset) - .limit(per_page) - .clone(); + let mut sqlb = SqlBuilder::select_from("http_trigger"); + sqlb.fields(&[ + "workspace_id", + "path", + "route_path", + "route_path_key", + "workspaced_route", + "wrap_body", + "raw_string", + "script_path", + "summary", + "description", + "is_flow", + "http_method", + "edited_by", + "email", + "edited_at", + "extra_perms", + "is_async", + "authentication_method", + "static_asset_config", + "is_static_website", + "authentication_resource_path", + "error_handler_path", + "error_handler_args", + "retry", + ]) + .order_by("edited_at", true) + .and_where("workspace_id = ?".bind(&w_id)) + .offset(offset) + .limit(per_page); if let Some(path) = lst.path { sqlb.and_where_eq("script_path", "?".bind(&path)); } @@ -391,7 +389,7 @@ async fn create_trigger_inner( new_http_trigger.is_static_website, new_http_trigger.error_handler_path, new_http_trigger.error_handler_args as _, - new_http_trigger.retry as _, + new_http_trigger.retry as _ ) .execute(&mut *tx) .await?; diff --git a/backend/windmill-api/src/jobs.rs b/backend/windmill-api/src/jobs.rs index 8f247aaf75..2e1296c7e1 100644 --- a/backend/windmill-api/src/jobs.rs +++ b/backend/windmill-api/src/jobs.rs @@ -16,6 +16,7 @@ use http::{HeaderMap, HeaderName}; use itertools::Itertools; use quick_cache::sync::Cache; use serde_json::value::RawValue; +use serde_json::Value; use sqlx::Pool; use std::collections::HashMap; use std::hash::{DefaultHasher, Hash, Hasher}; @@ -24,6 +25,8 @@ use std::str::FromStr; use std::time::Instant; use tokio::io::AsyncReadExt; use tower::ServiceBuilder; +#[cfg(all(feature = "enterprise", feature = "smtp"))] +use windmill_common::auth::is_super_admin_email; use windmill_common::auth::TOKEN_PREFIX_LEN; use windmill_common::error::JsonResult; use windmill_common::flow_status::{JobResult, RestartedFrom}; @@ -33,6 +36,8 @@ use windmill_common::jobs::{ }; use windmill_common::utils::WarnAfterExt; use windmill_common::worker::{Connection, CLOUD_HOSTED, TMP_DIR}; +#[cfg(all(feature = "enterprise", feature = "smtp"))] +use windmill_common::{email_oss::send_email_html, server::load_smtp_config}; use windmill_common::scripts::PREVIEW_IS_CODEBASE_HASH; use windmill_common::variables::get_workspace_key; @@ -66,7 +71,9 @@ use tower_http::cors::{Any, CorsLayer}; use urlencoding::encode; use windmill_audit::audit_oss::{audit_log, AuditAuthor}; use windmill_audit::ActionKind; + use windmill_common::worker::to_raw_value; + use windmill_common::{ cache, db::UserDB, @@ -85,7 +92,7 @@ use windmill_common::{ use windmill_common::{ get_latest_deployed_hash_for_path, get_latest_flow_version_info_for_path, - get_script_info_for_hash, FlowVersionInfo, ScriptHashInfo, BASE_URL, + get_script_info_for_hash, utils::empty_as_none, FlowVersionInfo, ScriptHashInfo, BASE_URL, }; use windmill_queue::{ cancel_job, get_result_and_success_by_id_from_flow, job_is_complete, push, PushArgs, @@ -234,6 +241,10 @@ pub fn workspaced_service() -> Router { ) .route("/run/dependencies", post(run_dependencies_job)) .route("/run/flow_dependencies", post(run_flow_dependencies_job)) + .route( + "/send_email_with_instance_smtp", + post(send_email_with_instance_smtp), + ) } pub fn workspace_unauthed_service() -> Router { @@ -261,6 +272,7 @@ pub fn workspace_unauthed_service() -> Router { .route("/get_root_job_id/:id", get(get_root_job)) .route("/get/:id", get(get_job)) .route("/get_logs/:id", get(get_job_logs)) + .route("/get_completed_logs_tail/:id", get(get_completed_job_logs_tail)) .route("/get_args/:id", get(get_args)) .route("/get_flow_debug_info/:id", get(get_flow_job_debug_info)) .route("/completed/get/:id", get(get_completed_job)) @@ -900,11 +912,10 @@ impl<'a> GetQuery<'a> { /// when pushed from an un-updated workers. /// This function is used to make the above change transparent for the API, as the returned jobs /// will have the raw values as if they were still in the tables. - async fn resolve_raw_values( + async fn resolve_raw_values( &self, db: &DB, id: Uuid, - kind: JobKind, hash: Option, job: &mut JobExtended, ) { @@ -917,18 +928,18 @@ impl<'a> GetQuery<'a> { // Try to fetch the flow from the cache, fallback to the preview flow. // NOTE: This could check for the job kinds instead of the `or_else` but it's not // necessary as `fetch_flow` return early if the job kind is not a preview one. - cache::job::fetch_flow(db, kind, hash) + cache::job::fetch_flow(db, job.job_kind(), hash) .or_else(|_| cache::job::fetch_preview_flow(db, &id, raw_flow)) .await .ok() .inspect(|data| job.raw_flow = Some(sqlx::types::Json(data.raw_flow.clone()))); } - if self.with_code { + if self.with_code && job.job_kind() == &JobKind::Preview { // Try to fetch the code from the cache, fallback to the preview code. // NOTE: This could check for the job kinds instead of the `or_else` but it's not // necessary as `fetch_script` return early if the job kind is not a preview one. let conn = Connection::from(db.clone()); - cache::job::fetch_script(db.clone(), kind, hash) + cache::job::fetch_script(db.clone(), job.job_kind(), hash) .or_else(|_| cache::job::fetch_preview_script(&conn, &id, raw_lock, raw_code)) .await .ok() @@ -958,7 +969,7 @@ impl<'a> GetQuery<'a> { self.check_auth(job.as_ref().map(|job| job.created_by.as_str()))?; if let Some(job) = job.as_mut() { - self.resolve_raw_values(&db, job.id, job.job_kind, job.script_hash, job) + self.resolve_raw_values(&db, job.id, job.script_hash, job) .await; } if self.with_flow { @@ -992,7 +1003,7 @@ impl<'a> GetQuery<'a> { self.check_auth(cjob.as_ref().map(|job| job.created_by.as_str()))?; if let Some(job) = cjob.as_mut() { - self.resolve_raw_values(db, job.id, job.job_kind, job.script_hash, job) + self.resolve_raw_values(db, job.id, job.script_hash, job) .await; } @@ -1038,6 +1049,240 @@ impl<'a> GetQuery<'a> { } } +#[cfg(all(feature = "smtp", feature = "enterprise"))] +async fn send_workspace_trigger_failure_email_notification( + db: &Pool, + w_id: &str, + job_id: &Uuid, + trigger_path: Option<&str>, + runnable_path: Option<&str>, + email_recipients: &Vec, + error: &Value, +) -> Result { + let smtp_config = match load_smtp_config(db).await? { + Some(config) => config, + None => { + tracing::info!( + "SMTP not configured, skipping workspace trigger failure email notification" + ); + return Err(anyhow::anyhow!( + "SMTP not configured, skipping workspace trigger failure email notification" + ) + .into()); + } + }; + + let runnable_path = runnable_path.as_deref().unwrap_or("Unknown"); + + let (trigger_kind, trigger_path) = if let Some(trigger_path) = trigger_path.as_deref() { + match trigger_path.split_once('/') { + Some((trigger_kind, trigger_path)) => { + tracing::debug!( + "Workspace trigger job {} is a {:?} trigger", + &job_id, + trigger_kind + ); + (Some(trigger_kind), Some(trigger_path)) + } + _ => (None, None), + } + } else { + (None, None) + }; + + let base_url = BASE_URL.read().await; + let job_url = format!("{}/run/{}?workspace={}", base_url, &job_id, w_id); + let trigger_kind_str = trigger_kind.unwrap_or("Unknown").to_string().to_uppercase(); + + let subject = format!( + "Windmill Job Failed: {} in workspace {}", + runnable_path, w_id + ); + + let error_details = serde_json::to_string_pretty(&error) + .unwrap_or_else(|_| format!("Unable to serialize error: {:?}", error)); + + let trigger_info = if trigger_kind.is_some() && trigger_path.is_some() { + format!( + r#" +
+ Trigger path: {} +
+ +
+ Trigger Type: {} +
"#, + trigger_path.unwrap(), + trigger_kind_str + ) + } else { + String::new() + }; + + let email_title = if trigger_kind.is_some() { + format!("Windmill Trigger Job {} Failed", &job_id) + } else { + format!("Windmill Job {} Failed", &job_id) + }; + + let content = format!( + r#" + + + + + + + +

{}

+ +
+ Workspace: {} +
+{} +
+ Script/Flow Path: {} +
+ +
+ Job ID: {} +
+ +
+ Error Details: +
{}
+
+ + View Job Details + + +"#, + email_title, + w_id, + trigger_info, + runnable_path, + &job_id, + error_details, + job_url + ); + + if let Err(e) = send_email_html( + &subject, + &content, + email_recipients.to_owned(), + smtp_config, + None, + ) + .await + { + let err_msg = format!( + "Failed to send workspace email notification for trigger failure (trigger kind: {}, job ID: {}): {}", + trigger_kind_str, + &job_id, + e + ); + return Err(Error::internal_err(err_msg)); + } + let success_msg = format!( + "Job ID '{}' failed. An email with error details has been sent to {:?}.", + &job_id, email_recipients + ); + tracing::info!("{}", &success_msg); + Ok(success_msg) +} + +#[derive(Debug, Serialize, Deserialize)] +struct SendEmail { + job_id: Uuid, + #[serde(default, deserialize_with = "empty_as_none")] + trigger_path: Option, + #[serde(default, deserialize_with = "empty_as_none")] + runnable_path: Option, + #[serde(default, deserialize_with = "empty_as_none")] + email_recipients: Option>, + error: Value, +} + +#[cfg(all(feature = "enterprise", feature = "smtp"))] +async fn send_email_with_instance_smtp( + authed: ApiAuthed, + Extension(db): Extension, + Path(w_id): Path, + Json(send_email): Json, +) -> error::Result> { + use windmill_common::jobs::EMAIL_ERROR_HANDLER_USER_EMAIL; + + if *CLOUD_HOSTED { + tracing::warn!( + "Workspace trigger failure email notification is not available for cloud hosted Windmill", + ); + return Err(anyhow::anyhow!("Feature not supported in cloud hosted windmill").into()); + } + + if send_email.email_recipients.is_none() { + use windmill_common::utils::report_critical_error; + + tracing::error!("No recipient to send the error"); + report_critical_error( + "No recipient to send the error".to_string(), + db.clone(), + Some(&w_id), + None, + ) + .await; + return Err(anyhow::anyhow!("No recipient to send the error").into()); + } + + if authed.email == EMAIL_ERROR_HANDLER_USER_EMAIL + || is_super_admin_email(&db, &authed.email).await? + { + let resp = send_workspace_trigger_failure_email_notification( + &db, + &w_id, + &send_email.job_id, + send_email.trigger_path.as_deref(), + send_email.runnable_path.as_deref(), + &send_email.email_recipients.unwrap(), + &send_email.error, + ) + .await?; + + return Ok(Json(resp)); + } + + return Err(Error::NotAuthorized( + "Only super admin or whitelisted token can access email workspace error handler feature" + .to_string(), + )); +} + +#[cfg(not(all(feature = "enterprise", feature = "smtp")))] +async fn send_email_with_instance_smtp( + _authed: ApiAuthed, + Extension(_db): Extension, + Path(_w_id): Path, + Json(_send_email): Json, +) -> error::Result> { + tracing::warn!("SMTP is not enabled, skipping workspace trigger failure email notification",); + + return Err(anyhow::anyhow!("SMTP is not enabled").into()); +} + #[cfg(all(feature = "enterprise", feature = "parquet"))] async fn get_logs_from_store( log_offset: i32, @@ -1117,11 +1362,55 @@ async fn get_logs_from_disk( return None; } +async fn get_completed_job_logs_tail( + OptAuthed(opt_authed): OptAuthed, + Extension(db): Extension, + Path((w_id, id)): Path<(String, Uuid)>, +) -> error::JsonResult { + + let tags = opt_authed + .as_ref() + .map(|authed| get_scope_tags(authed).map(|v| v.iter().map(|s| s.to_string()).collect_vec())) + .flatten(); + + let record = sqlx::query!( + "SELECT created_by AS \"created_by!\", coalesce(job_logs.logs, '') as logs + FROM v2_job + LEFT JOIN job_logs ON job_logs.job_id = v2_job.id + WHERE v2_job.id = $1 AND v2_job.workspace_id = $2 AND ($3::text[] IS NULL OR v2_job.tag = ANY($3)) + ORDER BY job_logs.log_offset DESC + LIMIT 100", + id, + w_id, + tags.as_ref().map(|v| v.as_slice()) + ) + .fetch_optional(&db) + .await?; + + if let Some(record) = record { + if opt_authed.is_none() && record.created_by != "anonymous" { + return Err(Error::BadRequest( + "As a non logged in user, you can only see jobs ran by anonymous users".to_string(), + )); + } + + let logs = record.logs.unwrap_or_default(); + Ok(Json(logs)) + } else { + Err(Error::NotFound("Job not found".to_string()).into()) + } +} +#[derive(Debug, Deserialize)] +struct QueryJobLogs { + remove_ansi_warnings: Option, +} + async fn get_job_logs( OptAuthed(opt_authed): OptAuthed, opt_tokened: OptTokened, Extension(db): Extension, Path((w_id, id)): Path<(String, Uuid)>, + Query(query_job_logs): Query, ) -> error::Result { // let audit_author: AuditAuthor = match opt_authed { // Some(authed) => (&authed).into(), @@ -1175,10 +1464,14 @@ async fn get_job_logs( { return r.map(content_plain); } - let logs = format!( - "to remove ansi colors, use: | sed 's/\\x1B\\[[0-9;]\\{{1,\\}}[A-Za-z]//g'\n{}", + let logs = if query_job_logs.remove_ansi_warnings.unwrap_or(false) { logs - ); + } else { + format!( + "to remove ansi colors, use: | sed 's/\\x1B\\[[0-9;]\\{{1,\\}}[A-Za-z]//g'\n{}", + logs + ) + }; Ok(content_plain(Body::from(logs))) } else { let text = sqlx::query!( @@ -2642,7 +2935,7 @@ pub async fn get_resume_urls_internal( } #[derive(sqlx::FromRow, Debug, Serialize)] -pub struct JobExtended { +pub struct JobExtended { #[sqlx(flatten)] #[serde(flatten)] inner: T, @@ -2665,7 +2958,23 @@ pub struct JobExtended { pub aggregate_wait_time_ms: Option, } -impl JobExtended { +pub trait JobCommon { + fn job_kind(&self) -> &JobKind; +} + +impl JobCommon for QueuedJob { + fn job_kind(&self) -> &JobKind { + &self.job_kind + } +} + +impl JobCommon for CompletedJob { + fn job_kind(&self) -> &JobKind { + &self.job_kind + } +} + +impl JobExtended { pub fn new( self_wait_time_ms: Option, aggregate_wait_time_ms: Option, @@ -2683,7 +2992,7 @@ impl JobExtended { } } -impl Deref for JobExtended { +impl Deref for JobExtended { type Target = T; fn deref(&self) -> &Self::Target { @@ -2691,7 +3000,7 @@ impl Deref for JobExtended { } } -impl DerefMut for JobExtended { +impl DerefMut for JobExtended { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } @@ -4538,8 +4847,13 @@ pub async fn run_wait_result_script_by_path_internal( check_queue_too_long(&db, QUEUE_LIMIT_WAIT_RESULT.or(run_query.queue_limit)).await?; let mut tx = user_db.clone().begin(&authed).await?; - let (job_payload, tag, delete_after_use, timeout, on_behalf_of) = - script_path_to_payload(script_path.to_path(), &mut *tx, &w_id, run_query.skip_preprocessor).await?; + let (job_payload, tag, delete_after_use, timeout, on_behalf_of) = script_path_to_payload( + script_path.to_path(), + &mut *tx, + &w_id, + run_query.skip_preprocessor, + ) + .await?; drop(tx); let tag = run_query.tag.clone().or(tag); @@ -5680,22 +5994,38 @@ pub async fn run_job_by_hash_inner( pub struct JobUpdateQuery { pub running: Option, pub log_offset: Option, + pub stream_offset: Option, pub get_progress: Option, + pub no_logs: Option, pub only_result: Option, pub fast: Option, } #[derive(Serialize, Debug)] pub struct JobUpdate { + #[serde(skip_serializing_if = "Option::is_none")] pub running: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub completed: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub new_logs: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub new_result_stream: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub log_offset: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub stream_offset: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub mem_peak: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub progress: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub flow_status: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub workflow_as_code_status: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub job: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub only_result: Option>, } @@ -5714,6 +6044,7 @@ impl Hash for JobUpdate { self.log_offset.hash(state); self.mem_peak.hash(state); self.progress.hash(state); + self.stream_offset.hash(state); if !self.completed.unwrap_or(false) { self.flow_status.as_ref().map(|x| x.get().hash(state)); self.workflow_as_code_status @@ -5779,7 +6110,7 @@ async fn get_job_update( opt_tokened: OptTokened, Extension(db): Extension, Path((w_id, job_id)): Path<(String, Uuid)>, - Query(JobUpdateQuery { log_offset, get_progress, running, only_result, .. }): Query< + Query(JobUpdateQuery { log_offset, stream_offset, get_progress, running, only_result, no_logs, .. }): Query< JobUpdateQuery, >, ) -> JsonResult { @@ -5791,11 +6122,13 @@ async fn get_job_update( &w_id, &job_id, log_offset, + stream_offset, get_progress, running, true, false, only_result, + no_logs, ) .await?, )) @@ -5806,7 +6139,7 @@ async fn get_job_update_sse( opt_tokened: OptTokened, Extension(db): Extension, Path((w_id, job_id)): Path<(String, Uuid)>, - Query(JobUpdateQuery { log_offset, get_progress, running, only_result, fast }): Query< + Query(JobUpdateQuery { log_offset, stream_offset, get_progress, running, no_logs, only_result, fast }): Query< JobUpdateQuery, >, ) -> Response { @@ -5817,10 +6150,12 @@ async fn get_job_update_sse( w_id, job_id, log_offset, + stream_offset, get_progress, running, only_result, fast, + no_logs, ) .map(|x| { format!( @@ -5857,19 +6192,24 @@ fn get_job_update_sse_stream( w_id: String, job_id: Uuid, initial_log_offset: Option, + initial_stream_offset: Option, get_progress: Option, running: Option, only_result: Option, fast: Option, + no_logs: Option, ) -> impl futures::Stream { let (tx, rx) = tokio::sync::mpsc::channel(32); tokio::spawn(async move { let mut log_offset = initial_log_offset; + let mut stream_offset = initial_stream_offset; let mut last_update_hash: Option = None; // Send initial update immediately let mut running = running; + let mut mem_peak = 0; + match get_job_update_data( &opt_authed, &opt_tokened, @@ -5877,22 +6217,38 @@ fn get_job_update_sse_stream( &w_id, &job_id, log_offset, + stream_offset, get_progress, running, true, true, only_result, + no_logs, ) .await { - Ok(update) => { + Ok(mut update) => { last_update_hash = Some(update.hash_str()); let completion_sent = update.completed.unwrap_or(false); if running.is_some() && update.running.is_some_and(|x| x) { running = Some(true); } + if let Some(new_mem_peak) = update.mem_peak { + mem_peak = new_mem_peak; + } if let Some(new_offset) = update.log_offset { - log_offset = Some(new_offset); + if new_offset != log_offset.unwrap_or(0) { + log_offset = Some(new_offset); + } else { + update.log_offset = None; + } + } + if let Some(new_stream_offset) = update.stream_offset { + if new_stream_offset != stream_offset.unwrap_or(0) { + stream_offset = Some(new_stream_offset); + } else { + update.stream_offset = None; + } } if tx.send(JobUpdateSSEStream::Update(update)).await.is_err() { tracing::warn!("Failed to send initial job update for job {job_id}"); @@ -5918,6 +6274,7 @@ fn get_job_update_sse_stream( let mut i = 0; let start = Instant::now(); let mut last_ping = Instant::now(); + loop { i += 1; let ms_duration = if i > 100 || !fast.unwrap_or(false) { @@ -5950,18 +6307,27 @@ fn get_job_update_sse_stream( &w_id, &job_id, log_offset, + stream_offset, get_progress, running, false, true, only_result, + no_logs, ) .await { - Ok(update) => { + Ok(mut update) => { if running.is_some() && update.running.is_some_and(|x| x) { running = Some(true); } + if update.completed.is_some_and(|x| !x) { + update.completed = None; + } + if update.new_logs.as_ref().is_some_and(|x| x.is_empty()) { + update.new_logs = None; + } + // if !only_result.unwrap_or(false) { // tracing::error!("update {:?}", update); // } @@ -5970,7 +6336,25 @@ fn get_job_update_sse_stream( if last_update_hash.as_ref() != Some(&update_last_status) { // Update log offset if available if let Some(new_offset) = update.log_offset { - log_offset = Some(new_offset); + if new_offset != log_offset.unwrap_or(0) { + log_offset = Some(new_offset); + } else { + update.log_offset = None; + } + } + if let Some(new_stream_offset) = update.stream_offset { + if new_stream_offset != stream_offset.unwrap_or(0) { + stream_offset = Some(new_stream_offset); + } else { + update.stream_offset = None; + } + } + if let Some(new_mem_peak) = update.mem_peak { + if new_mem_peak != mem_peak { + mem_peak = new_mem_peak; + } else { + update.mem_peak = None; + } } let completed = update.completed.unwrap_or(false); if tx.send(JobUpdateSSEStream::Update(update)).await.is_err() { @@ -5983,7 +6367,8 @@ fn get_job_update_sse_stream( last_update_hash = Some(update_last_status); } } - Err(_) => { + Err(e) => { + tracing::error!("Error getting job update: {:?}", e); if tx.send(JobUpdateSSEStream::NotFound).await.is_err() { tracing::warn!("Failed to send job not found for job {job_id}"); } @@ -6003,11 +6388,13 @@ async fn get_job_update_data( w_id: &str, job_id: &Uuid, log_offset: Option, + stream_offset: Option, get_progress: Option, running: Option, log_view: bool, get_full_job_on_completion: bool, only_result: Option, + no_logs: Option, ) -> error::Result { let tags = if log_view { log_job_view( @@ -6028,19 +6415,22 @@ async fn get_job_update_data( if only_result.unwrap_or(false) { let result = if let Some(tags) = tags { - let r = sqlx::query!( - "SELECT result as \"result: sqlx::types::Json>\", v2_job.tag, - v2_job_queue.running as \"running: Option\" + let r = + sqlx::query!( + "SELECT result as \"result: sqlx::types::Json>\", v2_job.tag, + v2_job_queue.running as \"running: Option\", SUBSTR(rs.stream, $3) AS \"result_stream: Option\", CHAR_LENGTH(rs.stream) AS stream_offset FROM v2_job LEFT JOIN v2_job_queue USING (id) LEFT JOIN v2_job_completed USING (id) + LEFT JOIN job_result_stream rs ON rs.job_id = $2 WHERE v2_job.id = $2 AND v2_job.workspace_id = $1", - w_id, - job_id, - ) - .fetch_optional(db) - .await? - .ok_or_else(|| Error::NotFound(format!("Job not found: {}", job_id)))?; + w_id, + job_id, + stream_offset.unwrap_or(0), + ) + .fetch_optional(db) + .await? + .ok_or_else(|| Error::NotFound(format!("Job not found: {}", job_id)))?; if !tags.contains(&r.tag.as_str()) { return Err(Error::NotAuthorized(format!( @@ -6050,27 +6440,55 @@ async fn get_job_update_data( ))); } let running = r.running.as_ref().map(|x| *x); - (r.result.map(|x| x.0), running) + (r.result.map(|x| x.0), running, r.result_stream.flatten(), r.stream_offset) } else { if running.is_some_and(|x| !x) { let r = sqlx::query!( - "SELECT result as \"result: sqlx::types::Json>\", v2_job_queue.running as \"running: Option\" FROM v2_job_completed FULL OUTER JOIN v2_job_queue USING (id) WHERE (v2_job_queue.id = $1 AND v2_job_queue.workspace_id = $2) OR (v2_job_completed.id = $1 AND v2_job_completed.workspace_id = $2)", + "SELECT + COALESCE(jc.result, jc.result) as \"result: sqlx::types::Json>\", + jq.running as \"running: Option\", + SUBSTR(rs.stream, $3) AS \"result_stream: Option\", + CHAR_LENGTH(rs.stream) + 1 AS stream_offset + FROM ( + SELECT $1::uuid as job_id, $2::text as workspace_id + ) base + LEFT JOIN v2_job_completed jc ON jc.id = base.job_id AND jc.workspace_id = base.workspace_id + LEFT JOIN v2_job_queue jq ON jq.id = base.job_id AND jq.workspace_id = base.workspace_id + LEFT JOIN job_result_stream rs ON rs.job_id = base.job_id + WHERE base.job_id = $1", job_id, w_id, + stream_offset.unwrap_or(0), ).fetch_optional(db).await?; if let Some(r) = r { let running = r.running.as_ref().map(|x| *x); - (r.result.map(|x| x.0), running) + (r.result.map(|x| x.0), running, r.result_stream.flatten(), r.stream_offset) } else { - (None, None) + (None, None, None, None) } } else { - (sqlx::query_scalar!( - "SELECT result as \"result: sqlx::types::Json>\" FROM v2_job_completed WHERE id = $2 AND workspace_id = $1", + let q = sqlx::query!( + "SELECT + COALESCE(jc.result, NULL) as \"result: sqlx::types::Json>\", + SUBSTR(rs.stream, $3) AS \"result_stream: Option\", + CHAR_LENGTH(rs.stream) + 1 AS stream_offset + FROM ( + SELECT $2::uuid as job_id, $1::text as workspace_id + ) base + LEFT JOIN v2_job_completed jc ON jc.id = base.job_id AND jc.workspace_id = base.workspace_id + LEFT JOIN job_result_stream rs ON rs.job_id = base.job_id + WHERE base.job_id = $2", w_id, job_id, - ).fetch_optional(db).await?.flatten() - .map(|x| x.0), running) + stream_offset.unwrap_or(0), + ) + .fetch_optional(db) + .await?; + if let Some(r) = q { + (r.result.map(|x| x.0), running, r.result_stream.flatten(), r.stream_offset) + } else { + (None, None, None, None) + } } }; Ok(JobUpdate { @@ -6078,6 +6496,8 @@ async fn get_job_update_data( completed: if result.0.is_some() { Some(true) } else { None }, log_offset: None, new_logs: None, + new_result_stream: result.2, + stream_offset: result.3, mem_peak: None, progress: None, job: None, @@ -6093,20 +6513,24 @@ async fn get_job_update_data( WHEN q.id IS NOT NULL THEN (CASE WHEN NOT $5 AND q.running THEN true ELSE null END) ELSE false END AS running, - SUBSTR(logs, GREATEST($1 - log_offset, 0)) AS logs, + CASE WHEN $7::BOOLEAN THEN NULL ELSE SUBSTR(logs, GREATEST($1 - log_offset, 0)) END AS logs, + SUBSTR(rs.stream, $8) AS new_result_stream, COALESCE(r.memory_peak, c.memory_peak) AS mem_peak, COALESCE(c.flow_status, f.flow_status) AS \"flow_status: sqlx::types::Json>\", COALESCE(c.workflow_as_code_status, f.workflow_as_code_status) AS \"workflow_as_code_status: sqlx::types::Json>\", - job_logs.log_offset + CHAR_LENGTH(job_logs.logs) + 1 AS log_offset, + CASE WHEN $7::BOOLEAN THEN NULL ELSE job_logs.log_offset + CHAR_LENGTH(job_logs.logs) + 1 END AS log_offset, + CHAR_LENGTH(rs.stream) + 1 AS stream_offset, created_by AS \"created_by!\", CASE WHEN $4::BOOLEAN THEN ( SELECT scalar_int FROM job_stats WHERE job_id = $3 AND metric_id = 'progress_perc' - ) END AS progress + ) END AS progress, + rs.stream AS \"result_stream: Option\" FROM v2_job j LEFT JOIN v2_job_queue q USING (id) LEFT JOIN v2_job_runtime r USING (id) LEFT JOIN v2_job_status f USING (id) LEFT JOIN v2_job_completed c USING (id) + LEFT JOIN job_result_stream rs ON rs.job_id = $3 LEFT JOIN job_logs ON job_logs.job_id = $3 WHERE j.workspace_id = $2 AND j.id = $3 AND ($6::text[] IS NULL OR j.tag = ANY($6))", @@ -6116,6 +6540,8 @@ async fn get_job_update_data( get_progress.unwrap_or(false), running, tags.as_ref().map(|v| v.as_slice()) as Option<&[&str]>, + no_logs.unwrap_or(false), + stream_offset.unwrap_or(0), ) .fetch_optional(db) .await? @@ -6139,6 +6565,8 @@ async fn get_job_update_data( completed: record.completed, log_offset: record.log_offset, new_logs: record.logs, + new_result_stream: record.new_result_stream, + stream_offset: record.stream_offset, mem_peak: record.mem_peak, progress: record.progress, workflow_as_code_status: record diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index e2a74b5cb5..b90a7420da 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -544,6 +544,18 @@ pub async fn run_server( (Router::new(), Option::<()>::None) }; + let mcp_list_tools_service = { + #[cfg(feature = "mcp")] + { + mcp::list_tools_service() + } + + #[cfg(not(feature = "mcp"))] + { + Router::new() + } + }; + #[cfg(feature = "agent_worker_server")] let (agent_workers_router, agent_workers_bg_processor, agent_workers_killpill_tx) = if server_mode { @@ -632,6 +644,8 @@ pub async fn run_server( .nest("/embeddings", embeddings::global_service()) .nest("/ai", ai::global_service()) .nest("/inkeep", inkeep_oss::global_service()) + .nest("/mcp/w/:workspace_id/sse", mcp_router) + .nest("/mcp/w/:workspace_id/list_tools", mcp_list_tools_service) .route_layer(from_extractor::()) .route_layer(from_extractor::()) .nest("/jobs", jobs::global_root_service()) @@ -670,10 +684,6 @@ pub async fn run_server( .layer(from_extractor::()) .layer(cors.clone()), ) - .nest( - "/mcp/w/:workspace_id/sse", - mcp_router.layer(from_extractor::()), - ) .layer(from_extractor::()) .nest("/agent_workers", { #[cfg(feature = "agent_worker_server")] diff --git a/backend/windmill-api/src/mcp.rs b/backend/windmill-api/src/mcp.rs deleted file mode 100644 index ca345ba934..0000000000 --- a/backend/windmill-api/src/mcp.rs +++ /dev/null @@ -1,1233 +0,0 @@ -use std::borrow::Cow; -use std::collections::HashMap; -use std::sync::Arc; - -use axum::body::to_bytes; -use axum::Router; -use axum::{extract::Path, http::Request, middleware::Next, response::Response}; -use rmcp::{ - handler::server::ServerHandler, - model::*, - service::{RequestContext, RoleServer}, - Error, -}; -use serde::{Deserialize, Serialize}; -use serde_json::Value; -use sql_builder::prelude::*; -use sqlx::FromRow; -use tokio::try_join; -use windmill_common::db::UserDB; -use windmill_common::worker::to_raw_value; -use windmill_common::{DB, HUB_BASE_URL}; - -use windmill_common::scripts::{get_full_hub_script_by_path, Schema}; - -use crate::db::ApiAuthed; -use crate::jobs::{ - run_wait_result_flow_by_path_internal, run_wait_result_script_by_path_internal, RunJobQuery, -}; -use crate::HTTP_CLIENT; -use rmcp::transport::streamable_http_server::{ - session::local::LocalSessionManager, SessionManager, StreamableHttpService, -}; -use windmill_common::utils::{query_elems_from_hub, StripPath}; - -/// Transforms the path for workspace scripts/flows. -/// -/// This function takes a path and a type string. -/// It then formats the transformed path with the type prefix. -/// This is used when listing, because we can't have names with slashes. -/// Because we replace slashes with underscores, we also need to escape underscores. -/// -/// # Parameters -/// - `path`: The path to transform. -/// - `type_str`: The type of the item (script or flow). -/// -/// # Returns -/// - `String`: The transformed path. -fn transform_path(path: &str, type_str: &str) -> String { - // Only apply special underscore escaping for paths starting with "f/" - let transformed = if path.starts_with("f/") { - let escaped_path = path.replace('_', "__"); - escaped_path.replace('/', "_") - } else { - path.replace('/', "_") - }; - - // first letter of type_str is used as prefix, only one letter to avoid reaching 60 char name limit - format!("{}-{}", &type_str[..1], transformed) -} - -fn convert_schema_to_schema_type(schema: Option) -> SchemaType { - let schema_obj = if let Some(ref s) = schema { - match serde_json::from_str::(s.0.get()) { - Ok(val) => val, - Err(_) => SchemaType::default(), - } - } else { - SchemaType::default() - }; - schema_obj -} - -trait ToolableItem { - fn get_path_or_id(&self) -> String; - fn get_summary(&self) -> &str; - fn get_description(&self) -> &str; - fn get_schema(&self) -> SchemaType; - fn is_hub(&self) -> bool; - fn item_type(&self) -> &'static str; - fn get_integration_type(&self) -> Option; -} - -impl ToolableItem for ScriptInfo { - fn get_path_or_id(&self) -> String { - transform_path(&self.path, "script") - } - fn get_summary(&self) -> &str { - self.summary.as_deref().unwrap_or("No summary") - } - fn get_description(&self) -> &str { - self.description.as_deref().unwrap_or("No description") - } - fn get_schema(&self) -> SchemaType { - convert_schema_to_schema_type(self.schema.clone()) - } - fn is_hub(&self) -> bool { - false - } - fn item_type(&self) -> &'static str { - "script" - } - fn get_integration_type(&self) -> Option { - None - } -} - -impl ToolableItem for FlowInfo { - fn get_path_or_id(&self) -> String { - transform_path(&self.path, "flow") - } - fn get_summary(&self) -> &str { - self.summary.as_deref().unwrap_or("No summary") - } - fn get_description(&self) -> &str { - self.description.as_deref().unwrap_or("No description") - } - fn get_schema(&self) -> SchemaType { - convert_schema_to_schema_type(self.schema.clone()) - } - fn is_hub(&self) -> bool { - false - } - fn item_type(&self) -> &'static str { - "flow" - } - fn get_integration_type(&self) -> Option { - None - } -} - -impl ToolableItem for HubScriptInfo { - fn get_path_or_id(&self) -> String { - let id = self.version_id; - let summary = self.summary.as_deref().unwrap_or("No summary"); - format!("hs-{}-{}", id, summary.replace(" ", "_")) - } - fn get_summary(&self) -> &str { - self.summary.as_deref().unwrap_or("No summary") - } - fn get_description(&self) -> &str { - self.description.as_deref().unwrap_or("No description") - } - fn get_schema(&self) -> SchemaType { - match serde_json::from_value::(self.schema.clone().unwrap_or_default()) { - Ok(schema_type) => schema_type, - Err(_) => SchemaType::default(), - } - } - fn is_hub(&self) -> bool { - true - } - fn item_type(&self) -> &'static str { - "script" - } - fn get_integration_type(&self) -> Option { - self.app.clone() - } -} - -#[derive(Clone)] -pub struct Runner {} - -#[derive(Serialize, Deserialize, Debug)] -struct HubResponse { - asks: Vec, -} - -#[derive(Serialize, Deserialize, Debug)] -struct HubScriptInfo { - version_id: u64, - summary: Option, - description: Option, - schema: Option, - app: Option, -} - -#[derive(Serialize, FromRow, Deserialize, Debug, Clone)] -struct SchemaType { - r#type: String, - properties: std::collections::HashMap, - required: Vec, -} - -impl Default for SchemaType { - fn default() -> Self { - Self { - r#type: "object".to_string(), - properties: std::collections::HashMap::new(), - required: vec![], - } - } -} - -#[derive(Serialize, FromRow, Debug)] -struct ScriptInfo { - path: String, - summary: Option, - description: Option, - schema: Option, -} - -#[derive(Serialize, FromRow)] -struct ItemSchema { - schema: Option, -} - -#[derive(Serialize, FromRow, Debug)] -struct FlowInfo { - path: String, - summary: Option, - description: Option, - schema: Option, -} - -#[derive(Serialize, FromRow, Debug)] -struct ResourceInfo { - path: String, - description: Option, - resource_type: String, -} - -#[derive(Serialize, FromRow, Debug, Clone)] -struct ResourceType { - name: String, - description: Option, -} - -impl Runner { - pub fn new() -> Self { - Self {} - } - - fn check_scopes(authed: &ApiAuthed) -> Result<(), Error> { - let scopes = authed.scopes.as_ref(); - if scopes.is_none() - || scopes - .unwrap() - .iter() - .all(|scope| scope != "mcp:all" && scope != "mcp:favorites" && !scope.starts_with("mcp:hub:")) - { - tracing::error!("Unauthorized: missing mcp scope"); - return Err(Error::internal_error("Unauthorized: missing mcp scope".to_string(), None)); - } - Ok(()) - } - - async fn get_item_schema( - path: &str, - user_db: &UserDB, - authed: &ApiAuthed, - workspace_id: &str, - item_type: &str, - ) -> Result, Error> { - let mut sqlb = SqlBuilder::select_from(&format!("{} as o", item_type)); - sqlb.fields(&["o.schema"]); - sqlb.and_where("o.path = ?".bind(&path)); - sqlb.and_where("o.workspace_id = ?".bind(&workspace_id)); - sqlb.and_where("o.archived = false"); - sqlb.and_where("o.draft_only IS NOT TRUE"); - let sql = sqlb.sql().map_err(|_e| { - tracing::error!("failed to build sql: {}", _e); - Error::internal_error("failed to build sql", None) - })?; - let mut tx = user_db - .clone() - .begin(authed) - .await - .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; - let item = sqlx::query_as::<_, ItemSchema>(&sql) - .fetch_one(&mut *tx) - .await - .map_err(|_e| { - tracing::error!("failed to fetch item schema: {}", _e); - Error::internal_error("failed to fetch item schema", None) - })?; - tx.commit() - .await - .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; - Ok(item.schema) - } - - /// Reverses the transformation of a path. - /// - /// This function takes a transformed path and reverses the transformation applied by `transform_path`. - /// It checks if the path starts with "h" (indicating a Hub script) and removes the prefix if present. - /// It then determines the type of the item (script or flow) based on the prefix. - /// This is used in call_tool to get the original path, and the type of the item. - /// - /// # Parameters - /// - `transformed_path`: The transformed path to reverse. - /// - /// # Returns - /// - `Result<(&str, String, bool), String>`: A tuple containing the original path, the type of the item, and a boolean indicating if it's a Hub script. - /// - `Err(String)`: If the path is invalid. - fn reverse_transform(transformed_path: &str) -> Result<(&str, String, bool), String> { - let is_hub = transformed_path.starts_with("h"); - let transformed_path = if is_hub { - transformed_path[1..].to_string() - } else { - transformed_path.to_string() - }; - let type_str = if transformed_path.starts_with("s-") { - "script" - } else if transformed_path.starts_with("f-") { - "flow" - } else { - return Err(format!( - "Invalid prefix in transformed path: {}", - transformed_path - )); - }; - - let mangled_path = &transformed_path[2..]; - - // Check if this path was previously transformed with special underscore handling - let is_special_path = mangled_path.starts_with("f_"); - - let original_path = if is_hub { - let parts = mangled_path.split("-").collect::>(); - parts[0].to_string() - } else if is_special_path { - const TEMP_PLACEHOLDER: &str = "@@UNDERSCORE@@"; - let path_with_placeholder = mangled_path.replace("__", TEMP_PLACEHOLDER); - let path_with_slashes = path_with_placeholder.replace('_', "/"); - path_with_slashes.replace(TEMP_PLACEHOLDER, "_") - } else { - mangled_path.replacen('_', "/", 2) - }; - - Ok((type_str, original_path, is_hub)) - } - - async fn inner_get_resources_types( - user_db: &UserDB, - authed: &ApiAuthed, - workspace_id: &str, - ) -> Result, Error> { - let mut sqlb = SqlBuilder::select_from("resource_type as o"); - sqlb.fields(&["o.name", "o.description"]); - sqlb.and_where("o.workspace_id = ?".bind(&workspace_id)); - let sql = sqlb.sql().map_err(|_e| { - tracing::error!("failed to build sql: {}", _e); - Error::internal_error("failed to build sql", None) - })?; - let mut tx = user_db - .clone() - .begin(authed) - .await - .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; - let rows = sqlx::query_as::<_, ResourceType>(&sql) - .fetch_all(&mut *tx) - .await - .map_err(|_e| { - tracing::error!("Failed to fetch resource types: {}", _e); - Error::internal_error("failed to fetch resource types", None) - })?; - tx.commit() - .await - .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; - Ok(rows) - } - - async fn inner_get_resources( - user_db: &UserDB, - authed: &ApiAuthed, - workspace_id: &str, - resource_type: &str, - ) -> Result, Error> { - let mut sqlb = SqlBuilder::select_from("resource as o"); - sqlb.fields(&["o.path", "o.description", "o.resource_type"]); - sqlb.and_where("o.workspace_id = ?".bind(&workspace_id)); - sqlb.and_where("o.resource_type = ?".bind(&resource_type)); - let sql = sqlb.sql().map_err(|_e| { - tracing::error!("failed to build sql: {}", _e); - Error::internal_error("failed to build sql", None) - })?; - let mut tx = user_db - .clone() - .begin(authed) - .await - .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; - let rows = sqlx::query_as::<_, ResourceInfo>(&sql) - .fetch_all(&mut *tx) - .await - .map_err(|_e| { - tracing::error!("Failed to fetch resources: {}", _e); - Error::internal_error("failed to fetch resources", None) - })?; - tx.commit() - .await - .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; - - Ok(rows) - } - - async fn inner_get_items sqlx::FromRow<'a, sqlx::postgres::PgRow> + Send + Unpin>( - user_db: &UserDB, - authed: &ApiAuthed, - workspace_id: &str, - scope_type: &str, - item_type: &str, - ) -> Result, Error> { - let mut sqlb = SqlBuilder::select_from(&format!("{} as o", item_type)); - let fields = vec!["o.path", "o.summary", "o.description", "o.schema"]; - sqlb.fields(&fields); - if scope_type == "favorites" { - sqlb.join("favorite") - .on("favorite.favorite_kind = ? AND favorite.workspace_id = o.workspace_id AND favorite.path = o.path AND favorite.usr = ?".bind(&item_type) - .bind(&authed.username)); - } - sqlb.and_where("o.workspace_id = ?".bind(&workspace_id)) - .and_where("o.archived = false") - .and_where("o.draft_only IS NOT TRUE"); - - if item_type == "script" { - sqlb.and_where("(o.no_main_func IS NOT TRUE OR o.no_main_func IS NULL)"); - } - - sqlb.order_by( - if item_type == "flow" { - "o.edited_at" - } else { - "o.created_at" - }, - false, - ) - .limit(100); - let sql = sqlb.sql().map_err(|_e| { - tracing::error!("failed to build sql: {}", _e); - Error::internal_error("failed to build sql", None) - })?; - let mut tx = user_db - .clone() - .begin(authed) - .await - .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; - let rows = sqlx::query_as::<_, T>(&sql) - .fetch_all(&mut *tx) - .await - .map_err(|_e| { - tracing::error!("Failed to fetch {}: {}", item_type, _e); - Error::internal_error(format!("failed to fetch {}", item_type), None) - })?; - tx.commit() - .await - .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; - Ok(rows) - } - - async fn inner_get_scripts_from_hub( - db: &DB, - scope_integrations: Option<&str>, - ) -> Result, Error> { - let query_params = Some(vec![ - ("limit", "100".to_string()), - ("with_schema", "true".to_string()), - ("apps", scope_integrations.unwrap_or("").to_string()), - ]); - let url = format!("{}/scripts/top", *HUB_BASE_URL.read().await); - let (_status_code, _headers, response) = - query_elems_from_hub(&HTTP_CLIENT, &url, query_params, &db) - .await - .map_err(|e| { - tracing::error!("Failed to get items from hub: {}", e); - Error::internal_error(format!("Failed to get items from hub: {}", e), None) - })?; - let body_bytes = to_bytes(response, usize::MAX).await.map_err(|e| { - tracing::error!("Failed to read response body: {}", e); - Error::internal_error(format!("Failed to read response body: {}", e), None) - })?; - let body_str = String::from_utf8(body_bytes.to_vec()).map_err(|e| { - tracing::error!("Failed to decode response body: {}", e); - Error::internal_error(format!("Failed to decode response body: {}", e), None) - })?; - let hub_response: HubResponse = serde_json::from_str(&body_str).map_err(|e| { - tracing::error!("Failed to parse hub response: {}", e); - Error::internal_error(format!("Failed to parse hub response: {}", e), None) - })?; - - Ok(hub_response.asks) - } - - /// Transforms a value if it's an object. - /// - /// This function takes a key and a value, and a schema object. - /// If the value is a string that starts with "$res:", it returns the value as is. - /// Otherwise, it checks if the key is defined in the schema and if it's an object type. - /// If it is, it transforms the value to a string. This is because some clients do not support object types. - /// # Parameters - /// - `key`: The key of the value to transform. - /// - `value`: The value to transform. - /// - `schema_obj`: The schema object. - /// - /// # Returns - /// - `Value`: The transformed value. - fn transform_value_if_object( - key: &str, - value: &Value, - schema_obj: &Option, - ) -> Value { - if value.is_string() && value.as_str().unwrap().starts_with("$res:") { - return value.clone(); - } - - let schema_obj = match schema_obj { - Some(s) => s, - None => return value.clone(), - }; - - // Check if property is defined in schema and is an object type - let is_obj_type = match schema_obj.properties.get(key) { - Some(property) => { - let prop_type = property.get("type").and_then(|t| t.as_str()); - prop_type == Some("object") - } - None => false, - }; - - // If it's an object type and we received a string, try to parse it - if is_obj_type && value.is_string() { - if let Some(str_val) = value.as_str() { - if let Ok(obj_val) = serde_json::from_str::(str_val) { - return obj_val; - } - } - } - - value.clone() - } - - /// Reverses the transformation of a key. - /// - /// This function takes a transformed key and a schema object. - /// It then reverses the transformation applied by `apply_key_transformation`. This can be subject to collisions, but it's unlikely and is ok for our use case. - /// # Parameters - /// - `transformed_key`: The transformed key to reverse. - /// - `schema_obj`: The schema object. - /// - /// # Returns - /// - `String`: The original key. - fn reverse_transform_key(transformed_key: &str, schema_obj: &Option) -> String { - let schema_obj = match schema_obj { - Some(s) => s, - None => { - // No schema available, return the key as is (best guess) - return transformed_key.to_string(); - } - }; - - for original_key_in_schema in schema_obj.properties.keys() { - // Apply the SAME forward transformation to the schema key - let potential_transformed_key = - Runner::apply_key_transformation(original_key_in_schema); - - // If it matches the key we received, we found the likely original - if potential_transformed_key == transformed_key { - return original_key_in_schema.clone(); - } - } - - transformed_key.to_string() - } - - /// Applies a key transformation to a key. - /// - /// This function takes a key and replaces spaces with underscores. - /// It also removes any characters that are not alphanumeric or underscores. - /// This is used when listing, because we can't have names with spaces or special characters in the schema properties. - /// # Parameters - /// - `key`: The key to transform. - /// - /// # Returns - /// - `String`: The transformed key. - fn apply_key_transformation(key: &str) -> String { - key.replace(' ', "_") - .chars() - .filter(|c| c.is_alphanumeric() || *c == '_') - .collect::() - } - - /// Transforms the schema for resources. - /// - /// This function takes a schema and a database connection, and attempts to transform the schema for resources. - /// It replaces invalid characters in property keys with underscores and converts object properties to strings. - /// It also fetches resource type information and adds it to the description of resource properties. - /// - /// # Parameters - /// - `schema`: The schema to transform. - /// - `user_db`: The database connection. - /// - `authed`: The authenticated user. - /// - `w_id`: The workspace ID. - /// - `resources_cache`: A mutable reference to the resources cache. - /// - `resources_types`: A reference to the resource types. - /// - /// # Returns - /// - `Result`: The transformed schema. - /// - `Err(Error)`: If the transformation fails. - async fn transform_schema_for_resources( - schema: &SchemaType, - user_db: &UserDB, - authed: &ApiAuthed, - w_id: &str, - resources_cache: &mut HashMap>, - resources_types: &Vec, - ) -> Result { - let mut schema_obj: SchemaType = schema.clone(); - - // replace invalid char in property key with underscore - let replacements: Vec<(String, String, serde_json::Value)> = schema_obj - .properties - .iter() - .filter_map(|(key, value)| { - if key.chars().any(|c| !c.is_alphanumeric() && c != '_') { - let new_key = Runner::apply_key_transformation(key); - Some((key.clone(), new_key, value.clone())) - } else { - None - } - }) - .collect(); - - for (old_key, new_key, value) in replacements { - schema_obj.properties.remove(&old_key); - schema_obj.properties.insert(new_key, value); - } - - for (_key, prop_value) in schema_obj.properties.iter_mut() { - if let serde_json::Value::Object(prop_map) = prop_value { - // transform object properties to string because some client does not support object, might change in the future - if let Some(type_value) = prop_map.get("type") { - if let serde_json::Value::String(type_str) = type_value { - if type_str == "object" { - prop_map.insert( - "type".to_string(), - serde_json::Value::String("string".to_string()), - ); - } - } - } - // if property is a resource, fetch the resource type infos, and add each available resource to the description - if let Some(format_value) = prop_map.get("format") { - if let serde_json::Value::String(format_str) = format_value { - if format_str.starts_with("resource-") { - let resource_type_key = - format_str.split("-").last().unwrap_or_default().to_string(); - let resource_type = resources_types - .iter() - .find(|rt| rt.name == resource_type_key); - let resource_type_obj = resource_type.cloned().unwrap_or_else(|| { - tracing::info!("Resource type not found: {}", resource_type_key); - ResourceType { name: resource_type_key.clone(), description: None } - }); - - if !resources_cache.contains_key(&resource_type_key) { - let available_resources = Runner::inner_get_resources( - user_db, - authed, - &w_id, - &resource_type_key, - ) - .await; - - match available_resources { - Ok(cache_data) => { - resources_cache - .insert(resource_type_key.clone(), cache_data); - } - Err(e) => { - tracing::error!( - "Failed to fetch resource cache data: {}", - e - ); - continue; // Skip this property if fetching failed - } - } - } - - if let Some(resource_cache) = resources_cache.get(&resource_type_key) { - let resources_count = resource_cache.len(); - let description = format!( - "This is a resource named `{}` with the following description: `{}`.\nThe path of the resource should be used to specify the resource.\n{}", - resource_type_obj.name, - resource_type_obj.description.as_deref().unwrap_or("No description"), - if resources_count == 0 { - "This resource does not have any available instances, you should create one from your windmill workspace." - } else if resources_count > 1 { - "This resource has multiple available instances, you should precisely select the one you want to use." - } else { - "There is 1 resource available." - } - ); - prop_map.insert( - "type".to_string(), - serde_json::Value::String("string".to_string()), - ); - prop_map.insert( - "description".to_string(), - serde_json::Value::String(description), - ); - if resources_count > 0 { - let resources_description = resource_cache - .iter() - .map(|resource| { - format!( - "{}: $res:{}", - resource - .description - .as_deref() - .unwrap_or("No title"), - resource.path - ) - }) - .collect::>() - .join("\n"); - - prop_map.insert( - "description".to_string(), - serde_json::Value::String(format!( - "{}\nHere are the available resources, in the format title:path. Title can be empty. Path should be used to specify the resource:\n{}", - prop_map.get("description").unwrap_or(&serde_json::Value::String("No description".to_string())), - resources_description - )), - ); - } - } - } - } - } - } else { - tracing::warn!( - "Schema property value is not a JSON object: {:?}", - prop_value - ); - } - } - - Ok(schema_obj) - } - - /// Fetches the schema for a Hub script. - /// - /// This function takes a script path and a database connection, and attempts to fetch the schema for the script. - /// It strips the path to remove any leading slashes, and then attempts to retrieve the full script using `get_full_hub_script_by_path`. - /// If successful, it converts the schema string to a `Schema` object. - /// If the schema cannot be converted, it logs a warning and returns `None`. - /// - /// # Parameters - /// - `path`: The path of the script to fetch the schema for. - /// - `db`: The database connection. - /// - /// # Returns - /// - `Ok(Option)`: The schema if found, otherwise `None`. - /// - `Err(Error)`: If the request fails. - async fn get_hub_script_schema(path: &str, db: &DB) -> Result, Error> { - let strip_path = StripPath(path.to_string()); - let res = get_full_hub_script_by_path(strip_path, &HTTP_CLIENT, Some(db)) - .await - .map_err(|e| { - tracing::error!("Failed to get hub script: {}", e); - Error::internal_error(format!("Failed to get hub script: {}", e), None) - })?; - match serde_json::from_str::(res.schema.get()) { - Ok(schema) => Ok(Some(schema)), - Err(e) => { - tracing::warn!("Failed to convert schema: {}", e); - Ok(None) - } - } - } - - /// Creates a `Tool` from a `ToolableItem`. - /// - /// This function takes an item that implements the `ToolableItem` trait and converts it into an RMCP `Tool`. - /// It handles both workspace scripts/flows and Hub scripts differently, depending on the item type. - /// - /// # Parameters - /// - `item`: The item to convert to a `Tool`. - /// - `user_db`: The database connection. - /// - `authed`: The authenticated user. - /// - `workspace_id`: The workspace ID. - /// - `resources_cache`: A mutable reference to the resources cache. - /// - `resources_types`: A reference to the resource types. - /// - /// # Returns - /// - `Ok(Tool)`: The created `Tool`. - async fn create_tool_from_item( - item: &T, - user_db: &UserDB, - authed: &ApiAuthed, - workspace_id: &str, - resources_cache: &mut HashMap>, - resources_types: &Vec, - ) -> Result { - let is_hub = item.is_hub(); - let path = item.get_path_or_id(); - let item_type = item.item_type(); - let description = format!( - "This is a {} named `{}` with the following description: `{}`.{}", - item_type, - item.get_summary(), - item.get_description(), - if is_hub { - format!( - " It is a tool used for the following app: {}", - item.get_integration_type() - .unwrap_or("No integration type".to_string()) - ) - } else { - "".to_string() - } - ); - let schema_obj = Runner::transform_schema_for_resources( - &item.get_schema(), - user_db, - authed, - &workspace_id, - resources_cache, - &resources_types, - ) - .await?; - let input_schema_map = match serde_json::to_value(schema_obj) { - Ok(Value::Object(map)) => map, - Ok(_) => { - tracing::warn!("Schema object for tool '{}' did not serialize to a JSON object, using empty schema.", path); - serde_json::Map::new() - } - Err(e) => { - tracing::error!( - "Failed to serialize schema object for tool '{}': {}. Using empty schema.", - path, - e - ); - serde_json::Map::new() - } - }; - Ok(Tool { - name: Cow::Owned(path), - description: Some(Cow::Owned(description)), - input_schema: Arc::new(input_schema_map), - annotations: None, - }) - } -} - -impl ServerHandler for Runner { - /// Handles the `CallTool` request from the MCP client. - /// - /// This involves: - /// 1. Parsing arguments and extracting context (DB, Auth). - /// 2. Reversing the tool name (`request.name`) to get the original path and type using `reverse_transform`. - /// 3. Handling Hub scripts: If identified as a Hub script, searches the Hub for the actual script ID. - /// 4. Fetching the schema for the item (needed for argument transformation). - /// 5. Transforming incoming arguments: - /// - Reversing key transformations (e.g., `user_input` back to `user input`). - /// - Parsing stringified JSON objects back into JSON values based on schema type. - /// 6. Executing the corresponding script or flow using internal Windmill runners. - /// 7. Formatting the execution result into an RMCP `CallToolResult`. - /// - /// # Parameters - /// - `request`: The `CallToolRequestParam` containing the tool name and arguments. - /// - `context`: The `RequestContext` providing access to workspace ID, DB connections, auth info. - /// - /// # Returns - /// - `Ok(CallToolResult)`: On successful execution, containing the output. - /// - `Err(Error)`: If any step fails (parsing, DB access, execution, reversing transform, hub search). - async fn call_tool( - &self, - request: CallToolRequestParam, - context: RequestContext, - ) -> Result { - - let http_parts = context - .extensions - .get::() - .ok_or_else(|| { - tracing::error!("http::request::Parts not found"); - Error::internal_error("http::request::Parts not found", None) - })?; - - let authed = http_parts.extensions.get::().ok_or_else(|| { - tracing::error!("ApiAuthed Axum extension not found"); - Error::internal_error("ApiAuthed Axum extension not found", None) - })?; - - Runner::check_scopes(authed)?; - - let db = http_parts.extensions.get::().ok_or_else(|| { - tracing::error!("DB Axum extension not found"); - Error::internal_error("DB Axum extension not found", None) - })?; - - let user_db = http_parts.extensions.get::().ok_or_else(|| { - tracing::error!("UserDB Axum extension not found"); - Error::internal_error("UserDB Axum extension not found", None) - })?; - - let args = request.arguments.map(Value::Object).ok_or_else(|| { - Error::invalid_params("Missing arguments for tool", Some(request.name.clone().into())) - })?; - - let workspace_id = http_parts - .extensions - .get::() - .ok_or_else(|| { - tracing::error!("WorkspaceId not found"); - Error::internal_error("WorkspaceId not found", None) - }) - .map(|w_id| w_id.0.clone())?; - - let (tool_type, path, is_hub) = - Runner::reverse_transform(&request.name).unwrap_or_default(); - - let item_schema = if is_hub { - Runner::get_hub_script_schema(&format!("hub/{}", path), db).await? - } else { - Runner::get_item_schema(&path, user_db, authed, &workspace_id, &tool_type).await? - }; - - let schema_obj = if let Some(ref s) = item_schema { - match serde_json::from_str::(s.0.get()) { - Ok(val) => Some(val), - Err(e) => { - tracing::warn!("Failed to parse schema: {}", e); - None - } - } - } else { - None - }; - - let push_args = if let Value::Object(map) = args.clone() { - let mut args_hash = HashMap::new(); - for (k, v) in map { - // need to transform back the key without invalid characters to the original key - let original_key = Runner::reverse_transform_key(&k, &schema_obj); - - // object properties are transformed to string because some client does not support object, might change in the future - let transformed_v = Runner::transform_value_if_object(&k, &v, &schema_obj); - args_hash.insert(original_key, to_raw_value(&transformed_v)); - } - windmill_queue::PushArgsOwned { extra: None, args: args_hash } - } else { - windmill_queue::PushArgsOwned::default() - }; - let script_or_flow_path = if is_hub { - StripPath(format!("hub/{}", path)) - } else { - StripPath(path) - }; - let run_query = RunJobQuery::default(); - - let result = if tool_type == "script" { - run_wait_result_script_by_path_internal( - db.clone(), - run_query, - script_or_flow_path, - authed.clone(), - user_db.clone(), - workspace_id.clone(), - push_args, - ) - .await - } else { - run_wait_result_flow_by_path_internal( - db.clone(), - run_query, - script_or_flow_path, - authed.clone(), - user_db.clone(), - push_args, - workspace_id.clone(), - ) - .await - }; - - match result { - Ok(response) => { - let body_bytes = to_bytes(response.into_body(), usize::MAX) - .await - .map_err(|e| { - Error::internal_error(format!("Failed to read response body: {}", e), None) - })?; - let body_str = String::from_utf8(body_bytes.to_vec()).map_err(|e| { - Error::internal_error(format!("Failed to decode response body: {}", e), None) - })?; - Ok(CallToolResult::success(vec![Content::text(body_str)])) - } - Err(e) => Err(Error::internal_error( - format!("Failed to run script: {}", e), - None, - )), - } - } - - /// Fetches available tools (scripts, flows, hub scripts) based on the user's scope. - /// - /// - Determines scope (all, favorites, hub-specific) from auth token. - /// - Fetches relevant items (workspace scripts/flows, hub scripts) concurrently. - /// - Fetches resource type information needed for schema enrichment. - /// - Transforms each item into an RMCP `Tool` definition, including schema adjustments - /// (like resource description enrichment and object->string conversion). - /// - /// # Parameters - /// - `_request`: Optional pagination parameters (currently ignored). - /// - `_context`: The `RequestContext` providing workspace ID, DB, auth. - /// - /// # Returns - /// - `Ok(ListToolsResult)`: A list of `Tool` definitions. Pagination is not yet implemented. - /// - `Err(Error)`: If fetching data from DB or Hub fails. - async fn list_tools( - &self, - _request: Option, - mut _context: RequestContext, - ) -> Result { - let http_parts = _context - .extensions - .get::() - .ok_or_else(|| { - tracing::error!("http::request::Parts not found"); - Error::internal_error("http::request::Parts not found", None) - })?; - - let authed = http_parts.extensions.get::().ok_or_else(|| { - tracing::error!("ApiAuthed Axum extension not found"); - Error::internal_error("ApiAuthed Axum extension not found", None) - })?; - - Runner::check_scopes(authed)?; - - let db = http_parts.extensions.get::().ok_or_else(|| { - tracing::error!("DB Axum extension not found"); - Error::internal_error("DB Axum extension not found", None) - })?; - - let user_db = http_parts.extensions.get::().ok_or_else(|| { - tracing::error!("UserDB Axum extension not found"); - Error::internal_error("UserDB Axum extension not found", None) - })?; - - let workspace_id = http_parts - .extensions - .get::() - .ok_or_else(|| { - tracing::error!("WorkspaceId not found"); - Error::internal_error("WorkspaceId not found", None) - }) - .map(|w_id| w_id.0.clone())?; - - let scopes = authed.scopes.as_ref(); - let owned_scope = scopes.and_then(|scopes| { - scopes - .iter() - .find(|scope| scope.starts_with("mcp:") && !scope.contains("hub")) - }); - let hub_scope = scopes.and_then(|scopes| scopes.iter().find(|scope| scope.starts_with("mcp:hub"))); - let scope_type = owned_scope.map_or("all", |scope| { - let parts = scope.split(":").collect::>(); - parts[1] - }); - let scope_integrations = hub_scope.and_then(|scope| { - let parts = scope.split(":").collect::>(); - if parts.len() == 3 { - Some(parts[2]) - } else { - None - } - }); - - let scripts_fn = Runner::inner_get_items::( - user_db, - authed, - &workspace_id, - scope_type, - "script", - ); - let flows_fn = - Runner::inner_get_items::(user_db, authed, &workspace_id, scope_type, "flow"); - let resources_types_fn = Runner::inner_get_resources_types(user_db, authed, &workspace_id); - let hub_scripts_fn = Runner::inner_get_scripts_from_hub(db, scope_integrations.as_deref()); - let (scripts, flows, resources_types, hub_scripts) = if scope_integrations.is_some() { - let (scripts, flows, resources_types, hub_scripts) = - try_join!(scripts_fn, flows_fn, resources_types_fn, hub_scripts_fn)?; - (scripts, flows, resources_types, hub_scripts) - } else { - let (scripts, flows, resources_types) = - try_join!(scripts_fn, flows_fn, resources_types_fn)?; - (scripts, flows, resources_types, vec![]) - }; - - let mut resources_cache: HashMap> = HashMap::new(); - let mut tools: Vec = Vec::new(); - - for script in scripts { - tools.push( - Runner::create_tool_from_item( - &script, - user_db, - authed, - &workspace_id, - &mut resources_cache, - &resources_types, - ) - .await?, - ); - } - - for flow in flows { - tools.push( - Runner::create_tool_from_item( - &flow, - user_db, - authed, - &workspace_id, - &mut resources_cache, - &resources_types, - ) - .await?, - ); - } - - for hub_script in hub_scripts { - tools.push( - Runner::create_tool_from_item( - &hub_script, - user_db, - authed, - &workspace_id, - &mut resources_cache, - &resources_types, - ) - .await?, - ); - } - - Ok(ListToolsResult { tools, next_cursor: None }) - } - - fn get_info(&self) -> ServerInfo { - ServerInfo { - protocol_version: Default::default(), - capabilities: ServerCapabilities::builder() - .enable_tools() - .enable_tool_list_changed() - .build(), - server_info: Implementation::from_build_env(), - instructions: Some("This server provides a list of scripts and flows the user can run on Windmill. Each flow and script is a tool callable with their respective arguments.".to_string()), - } - } - - async fn initialize( - &self, - _request: InitializeRequestParam, - _context: RequestContext, - ) -> Result { - Ok(self.get_info()) - } - - async fn list_resources( - &self, - _request: Option, - _context: RequestContext, - ) -> Result { - Ok(ListResourcesResult { resources: vec![], next_cursor: None }) - } - - async fn list_prompts( - &self, - _request: Option, - _context: RequestContext, - ) -> Result { - Ok(ListPromptsResult::default()) - } - - async fn list_resource_templates( - &self, - _request: Option, - _context: RequestContext, - ) -> Result { - Ok(ListResourceTemplatesResult::default()) - } -} - -#[derive(Clone, Debug)] -pub struct WorkspaceId(pub String); - -pub async fn extract_and_store_workspace_id( - Path(params): Path, - mut request: Request, - next: Next, -) -> Response { - let workspace_id = params; - request.extensions_mut().insert(WorkspaceId(workspace_id)); - next.run(request).await -} - -pub async fn setup_mcp_server() -> anyhow::Result<(Router, Arc)> { - let session_manager = Arc::new(LocalSessionManager::default()); - let service_config = Default::default(); - let service = StreamableHttpService::new( - || Ok(Runner::new()), - session_manager.clone(), - service_config, - ); - - let router = axum::Router::new().nest_service("/", service); - Ok((router, session_manager)) -} - -pub async fn shutdown_mcp_server(session_manager: Arc) { - let session_ids_to_close = { - let sessions_map = session_manager.sessions.read().await; - sessions_map.keys().cloned().collect::>() - }; - - if !session_ids_to_close.is_empty() { - tracing::info!( - "Closing {} active MCP session(s)...", - session_ids_to_close.len() - ); - let close_futures = session_ids_to_close - .iter() - .map(|session_id| { - let manager_clone = session_manager.clone(); - async move { - if let Err(_) = manager_clone.close_session(session_id).await { - tracing::warn!("Error closing MCP session"); - } - } - }) - .collect::>(); - futures::future::join_all(close_futures).await; - } -} diff --git a/backend/windmill-api/src/mcp/mod.rs b/backend/windmill-api/src/mcp/mod.rs new file mode 100644 index 0000000000..e7223aac8d --- /dev/null +++ b/backend/windmill-api/src/mcp/mod.rs @@ -0,0 +1,11 @@ +//! Model Context Protocol (MCP) implementation for Windmill +//! +//! This module provides the MCP server implementation that exposes Windmill scripts, +//! flows, and API endpoints as MCP tools for AI assistants to interact with. + +pub mod server; +pub mod tools; +pub mod utils; + +// Re-export main components +pub use server::{extract_and_store_workspace_id, setup_mcp_server, shutdown_mcp_server, list_tools_service}; \ No newline at end of file diff --git a/backend/windmill-api/src/mcp/server.rs b/backend/windmill-api/src/mcp/server.rs new file mode 100644 index 0000000000..8bfe35a009 --- /dev/null +++ b/backend/windmill-api/src/mcp/server.rs @@ -0,0 +1,517 @@ +//! MCP Server implementation +//! +//! Contains the core MCP server handler that implements the Model Context Protocol +//! specification. This is a thin orchestration layer that delegates to the appropriate +//! modules for tool management, database operations, and schema transformation. + +use std::borrow::Cow; +use std::collections::HashMap; +use std::sync::Arc; + +use axum::body::to_bytes; +use rmcp::{ + handler::server::ServerHandler, + model::*, + service::{RequestContext, RoleServer}, + Error, +}; +use serde_json::Value; +use tokio::try_join; +use windmill_common::db::UserDB; +use windmill_common::worker::to_raw_value; +use windmill_common::{utils::StripPath, DB}; + +use crate::db::ApiAuthed; +use crate::jobs::{ + run_wait_result_flow_by_path_internal, run_wait_result_script_by_path_internal, RunJobQuery, +}; + +use super::utils::{ + database::{ + check_scopes, get_items, get_resources_types, get_scripts_from_hub, get_item_schema, get_hub_script_schema + }, + models::{ScriptInfo, FlowInfo, ResourceInfo, ResourceType, SchemaType, ToolableItem, WorkspaceId}, + schema::transform_schema_for_resources, + transform::{reverse_transform, reverse_transform_key}, +}; +use super::tools::{ + endpoint_tools::{all_endpoint_tools, endpoint_tools_to_mcp_tools, call_endpoint_tool, EndpointTool}, +}; + +use axum::{ + extract::Path, + http::Request, + middleware::Next, + response::Response, + routing::get, + Json, + Router, +}; +use rmcp::transport::streamable_http_server::{ + session::local::LocalSessionManager, + SessionManager, + StreamableHttpService, +}; +use windmill_common::error::JsonResult; + + +/// MCP Server Runner - implements the core MCP protocol handlers +#[derive(Clone)] +pub struct Runner {} + +impl Runner { + pub fn new() -> Self { + Self {} + } + + /// Creates a Tool from a ToolableItem + async fn create_tool_from_item( + item: &T, + user_db: &UserDB, + authed: &ApiAuthed, + workspace_id: &str, + resources_cache: &mut HashMap>, + resources_types: &Vec, + ) -> Result { + let is_hub = item.is_hub(); + let path = item.get_path_or_id(); + let item_type = item.item_type(); + let description = format!( + "This is a {} named `{}` with the following description: `{}`.{}", + item_type, + item.get_summary(), + item.get_description(), + if is_hub { + format!( + " It is a tool used for the following app: {}", + item.get_integration_type() + .unwrap_or("No integration type".to_string()) + ) + } else { + "".to_string() + } + ); + let schema_obj = transform_schema_for_resources( + &item.get_schema(), + user_db, + authed, + &workspace_id, + resources_cache, + &resources_types, + ) + .await?; + let input_schema_map = match serde_json::to_value(schema_obj) { + Ok(Value::Object(map)) => map, + Ok(_) => { + tracing::warn!("Schema object for tool '{}' did not serialize to a JSON object, using empty schema.", path); + serde_json::Map::new() + } + Err(e) => { + tracing::error!( + "Failed to serialize schema object for tool '{}': {}. Using empty schema.", + path, + e + ); + serde_json::Map::new() + } + }; + + Ok(Tool { + name: Cow::Owned(path), + description: Some(Cow::Owned(description)), + input_schema: Arc::new(input_schema_map), + annotations: Some(ToolAnnotations { + title: Some(item.get_summary().to_string()), + read_only_hint: Some(false), // Can modify environment + destructive_hint: Some(true), // Can potentially be destructive + idempotent_hint: Some(false), // Are not guaranteed to be idempotent + open_world_hint: Some(true), // Can interact with external services + }), + }) + } +} + + +impl ServerHandler for Runner { + /// Handles the `CallTool` request from the MCP client + async fn call_tool( + &self, + request: CallToolRequestParam, + context: RequestContext, + ) -> Result { + let http_parts = context + .extensions + .get::() + .ok_or_else(|| { + tracing::error!("http::request::Parts not found"); + Error::internal_error("http::request::Parts not found", None) + })?; + + let authed = http_parts.extensions.get::().ok_or_else(|| { + tracing::error!("ApiAuthed Axum extension not found"); + Error::internal_error("ApiAuthed Axum extension not found", None) + })?; + + check_scopes(authed)?; + + let db = http_parts.extensions.get::().ok_or_else(|| { + tracing::error!("DB Axum extension not found"); + Error::internal_error("DB Axum extension not found", None) + })?; + + let user_db = http_parts.extensions.get::().ok_or_else(|| { + tracing::error!("UserDB Axum extension not found"); + Error::internal_error("UserDB Axum extension not found", None) + })?; + + let args = request.arguments.map(Value::Object).ok_or_else(|| { + Error::invalid_params("Missing arguments for tool", Some(request.name.clone().into())) + })?; + + let workspace_id = http_parts + .extensions + .get::() + .ok_or_else(|| { + tracing::error!("WorkspaceId not found"); + Error::internal_error("WorkspaceId not found", None) + }) + .map(|w_id| w_id.0.clone())?; + + // Check if this is a generated endpoint tool + let endpoint_tools = all_endpoint_tools(); + for endpoint_tool in endpoint_tools { + if endpoint_tool.name.as_ref() == request.name { + // This is an endpoint tool, forward to the actual HTTP endpoint + let result = call_endpoint_tool(&endpoint_tool, args.clone(), &workspace_id, &authed).await?; + return Ok(CallToolResult::success(vec![Content::text( + serde_json::to_string_pretty(&result).unwrap_or_else(|_| "{}".to_string()) + )])); + } + } + + // Continue with script/flow logic + let (tool_type, path, is_hub) = + reverse_transform(&request.name).unwrap_or_default(); + + let item_schema = if is_hub { + get_hub_script_schema(&format!("hub/{}", path), db).await? + } else { + get_item_schema(&path, user_db, authed, &workspace_id, &tool_type).await? + }; + + let schema_obj = if let Some(ref s) = item_schema { + match serde_json::from_str::(s.0.get()) { + Ok(val) => Some(val), + Err(e) => { + tracing::warn!("Failed to parse schema: {}", e); + None + } + } + } else { + None + }; + + let push_args = if let Value::Object(map) = args.clone() { + let mut args_hash = HashMap::new(); + for (k, v) in map { + // need to transform back the key without invalid characters to the original key + let original_key = reverse_transform_key(&k, &schema_obj); + args_hash.insert(original_key, to_raw_value(&v)); + } + windmill_queue::PushArgsOwned { extra: None, args: args_hash } + } else { + windmill_queue::PushArgsOwned::default() + }; + let script_or_flow_path = if is_hub { + StripPath(format!("hub/{}", path)) + } else { + StripPath(path) + }; + let run_query = RunJobQuery::default(); + + let result = if tool_type == "script" { + run_wait_result_script_by_path_internal( + db.clone(), + run_query, + script_or_flow_path, + authed.clone(), + user_db.clone(), + workspace_id.clone(), + push_args, + ) + .await + } else { + run_wait_result_flow_by_path_internal( + db.clone(), + run_query, + script_or_flow_path, + authed.clone(), + user_db.clone(), + push_args, + workspace_id.clone(), + ) + .await + }; + + match result { + Ok(response) => { + let body_bytes = to_bytes(response.into_body(), usize::MAX) + .await + .map_err(|e| { + Error::internal_error(format!("Failed to read response body: {}", e), None) + })?; + let body_str = String::from_utf8(body_bytes.to_vec()).map_err(|e| { + Error::internal_error(format!("Failed to decode response body: {}", e), None) + })?; + Ok(CallToolResult::success(vec![Content::text(body_str)])) + } + Err(e) => Err(Error::internal_error( + format!("Failed to run script: {}", e), + None, + )), + } + } + + /// Fetches available tools (scripts, flows, hub scripts) based on the user's scope + async fn list_tools( + &self, + _request: Option, + mut _context: RequestContext, + ) -> Result { + let http_parts = _context + .extensions + .get::() + .ok_or_else(|| { + tracing::error!("http::request::Parts not found"); + Error::internal_error("http::request::Parts not found", None) + })?; + + let authed = http_parts.extensions.get::().ok_or_else(|| { + tracing::error!("ApiAuthed Axum extension not found"); + Error::internal_error("ApiAuthed Axum extension not found", None) + })?; + + check_scopes(authed)?; + + let db = http_parts.extensions.get::().ok_or_else(|| { + tracing::error!("DB Axum extension not found"); + Error::internal_error("DB Axum extension not found", None) + })?; + + let user_db = http_parts.extensions.get::().ok_or_else(|| { + tracing::error!("UserDB Axum extension not found"); + Error::internal_error("UserDB Axum extension not found", None) + })?; + + let workspace_id = http_parts + .extensions + .get::() + .ok_or_else(|| { + tracing::error!("WorkspaceId not found"); + Error::internal_error("WorkspaceId not found", None) + }) + .map(|w_id| w_id.0.clone())?; + + let scopes = authed.scopes.as_ref(); + let owned_scope = scopes.and_then(|scopes| { + scopes + .iter() + .find(|scope| scope.starts_with("mcp:") && !scope.contains("hub")) + }); + let hub_scope = scopes.and_then(|scopes| scopes.iter().find(|scope| scope.starts_with("mcp:hub"))); + let (scope_type, scope_path) = owned_scope.map_or(("all", None), |scope| { + let parts = scope.split(":").collect::>(); + (parts[1], if parts.len() == 3 { Some(parts[2]) } else { None }) + }); + let scope_integrations = hub_scope.and_then(|scope| { + let parts = scope.split(":").collect::>(); + if parts.len() == 3 { + Some(parts[2]) + } else { + None + } + }); + + let scripts_fn = get_items::( + user_db, + authed, + &workspace_id, + scope_type, + "script", + scope_path.as_deref(), + ); + let flows_fn = + get_items::(user_db, authed, &workspace_id, scope_type, "flow", scope_path.as_deref()); + let resources_types_fn = get_resources_types(user_db, authed, &workspace_id); + let hub_scripts_fn = get_scripts_from_hub(db, scope_integrations.as_deref()); + let (scripts, flows, resources_types, hub_scripts) = if scope_integrations.is_some() { + let (scripts, flows, resources_types, hub_scripts) = + try_join!(scripts_fn, flows_fn, resources_types_fn, hub_scripts_fn)?; + (scripts, flows, resources_types, hub_scripts) + } else { + let (scripts, flows, resources_types) = + try_join!(scripts_fn, flows_fn, resources_types_fn)?; + (scripts, flows, resources_types, vec![]) + }; + + let mut resources_cache: HashMap> = HashMap::new(); + let mut tools: Vec = Vec::new(); + + for script in scripts { + tools.push( + Runner::create_tool_from_item( + &script, + user_db, + authed, + &workspace_id, + &mut resources_cache, + &resources_types, + ) + .await?, + ); + } + + for flow in flows { + tools.push( + Runner::create_tool_from_item( + &flow, + user_db, + authed, + &workspace_id, + &mut resources_cache, + &resources_types, + ) + .await?, + ); + } + + for hub_script in hub_scripts { + tools.push( + Runner::create_tool_from_item( + &hub_script, + user_db, + authed, + &workspace_id, + &mut resources_cache, + &resources_types, + ) + .await?, + ); + } + + // Add endpoint tools from the generated MCP tools + let endpoint_tools = all_endpoint_tools(); + let mcp_tools_converted = endpoint_tools_to_mcp_tools(endpoint_tools); + tools.extend(mcp_tools_converted); + + Ok(ListToolsResult { tools, next_cursor: None }) + } + + fn get_info(&self) -> ServerInfo { + ServerInfo { + protocol_version: Default::default(), + capabilities: ServerCapabilities::builder() + .enable_tools() + .enable_tool_list_changed() + .build(), + server_info: Implementation::from_build_env(), + instructions: Some("This server provides a list of scripts and flows the user can run on Windmill. Each flow and script is a tool callable with their respective arguments.".to_string()), + } + } + + async fn initialize( + &self, + _request: InitializeRequestParam, + _context: RequestContext, + ) -> Result { + Ok(self.get_info()) + } + + async fn list_resources( + &self, + _request: Option, + _context: RequestContext, + ) -> Result { + Ok(ListResourcesResult { resources: vec![], next_cursor: None }) + } + + async fn list_prompts( + &self, + _request: Option, + _context: RequestContext, + ) -> Result { + Ok(ListPromptsResult::default()) + } + + async fn list_resource_templates( + &self, + _request: Option, + _context: RequestContext, + ) -> Result { + Ok(ListResourceTemplatesResult::default()) + } +} + +/// Extract workspace ID from path and store it in request extensions +pub async fn extract_and_store_workspace_id( + Path(params): Path, + mut request: Request, + next: Next, +) -> Response { + let workspace_id = params; + request.extensions_mut().insert(WorkspaceId(workspace_id)); + next.run(request).await +} + +/// Setup the MCP server with HTTP transport +pub async fn setup_mcp_server() -> anyhow::Result<(Router, Arc)> { + let session_manager = Arc::new(LocalSessionManager::default()); + let service_config = Default::default(); + let service = StreamableHttpService::new( + || Ok(Runner::new()), + session_manager.clone(), + service_config, + ); + + let router = axum::Router::new().nest_service("/", service); + Ok((router, session_manager)) +} + +/// Shutdown the MCP server gracefully by closing all active sessions +pub async fn shutdown_mcp_server(session_manager: Arc) { + let session_ids_to_close = { + let sessions_map = session_manager.sessions.read().await; + sessions_map.keys().cloned().collect::>() + }; + + if !session_ids_to_close.is_empty() { + tracing::info!( + "Closing {} active MCP session(s)...", + session_ids_to_close.len() + ); + let close_futures = session_ids_to_close + .iter() + .map(|session_id| { + let manager_clone = session_manager.clone(); + async move { + if let Err(_) = manager_clone.close_session(session_id).await { + tracing::warn!("Error closing MCP session"); + } + } + }) + .collect::>(); + futures::future::join_all(close_futures).await; + } +} + +/// HTTP handler to list MCP tools as JSON +async fn list_mcp_tools_handler() -> JsonResult> { + let endpoint_tools = all_endpoint_tools(); + Ok(Json(endpoint_tools)) +} + +/// Creates a router service for listing MCP tools +pub fn list_tools_service() -> Router { + Router::new() + .route("/", get(list_mcp_tools_handler)) +} \ No newline at end of file diff --git a/backend/windmill-api/src/mcp/tools/auto_generated_endpoints.rs b/backend/windmill-api/src/mcp/tools/auto_generated_endpoints.rs new file mode 100644 index 0000000000..9865da06c9 --- /dev/null +++ b/backend/windmill-api/src/mcp/tools/auto_generated_endpoints.rs @@ -0,0 +1,1174 @@ +// Auto-generated MCP tools from OpenAPI specification +// This file is generated by generate_mcp_tools.py - DO NOT EDIT MANUALLY + + +use std::borrow::Cow; +use serde::{Deserialize, Serialize}; + + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct EndpointTool { + pub name: Cow<'static, str>, + pub description: Cow<'static, str>, + pub instructions: Cow<'static, str>, + pub path: Cow<'static, str>, + pub method: Cow<'static, str>, + pub path_params_schema: Option, + pub query_params_schema: Option, + pub body_schema: Option, +} + +pub fn all_tools() -> Vec { + vec![ + EndpointTool { + name: Cow::Borrowed("createVariable"), + description: Cow::Borrowed("create variable"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/variables/create"), + method: Cow::Borrowed("POST"), + path_params_schema: None, + query_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "already_encrypted": { + "type": "boolean", + "description": "whether the variable is already encrypted (default false)" + } + }, + "required": [] +})), + body_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "The path to the variable" + }, + "value": { + "type": "string", + "description": "The value of the variable" + }, + "is_secret": { + "type": "boolean", + "description": "Whether the variable is a secret" + }, + "description": { + "type": "string", + "description": "The description of the variable" + }, + "account": { + "type": "integer", + "description": "The account identifier" + }, + "is_oauth": { + "type": "boolean", + "description": "Whether the variable is an OAuth variable" + }, + "expires_at": { + "type": "string", + "description": "The expiration date of the variable", + "format": "date-time" + } + }, + "required": [ + "path", + "value", + "is_secret", + "description" + ] +})), + }, + EndpointTool { + name: Cow::Borrowed("deleteVariable"), + description: Cow::Borrowed("delete variable"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/variables/delete/{path}"), + method: Cow::Borrowed("DELETE"), + path_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ] +})), + query_params_schema: None, + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("updateVariable"), + description: Cow::Borrowed("update variable"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/variables/update/{path}"), + method: Cow::Borrowed("POST"), + path_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ] +})), + query_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "already_encrypted": { + "type": "boolean", + "description": "whether the variable is already encrypted (default false)" + } + }, + "required": [] +})), + body_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "The path to the variable" + }, + "value": { + "type": "string", + "description": "The new value of the variable" + }, + "is_secret": { + "type": "boolean", + "description": "Whether the variable is a secret" + }, + "description": { + "type": "string", + "description": "The new description of the variable" + } + } +})), + }, + EndpointTool { + name: Cow::Borrowed("getVariable"), + description: Cow::Borrowed("get variable"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/variables/get/{path}"), + method: Cow::Borrowed("GET"), + path_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ] +})), + query_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "decrypt_secret": { + "type": "boolean", + "description": "ask to decrypt secret if this variable is secret\n(if not secret no effect, default: true)\n" + }, + "include_encrypted": { + "type": "boolean", + "description": "ask to include the encrypted value if secret and decrypt secret is not true (default: false)\n" + } + }, + "required": [] +})), + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("listVariable"), + description: Cow::Borrowed("list variables"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/variables/list"), + method: Cow::Borrowed("GET"), + path_params_schema: None, + query_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path_start": { + "type": "string", + "description": "filter variables by path prefix" + }, + "page": { + "type": "integer", + "description": "which page to return (start at 1, default 1)" + }, + "per_page": { + "type": "integer", + "description": "number of items to return for a given page (default 30, max 100)" + } + }, + "required": [] +})), + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("createResource"), + description: Cow::Borrowed("create resource"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/resources/create"), + method: Cow::Borrowed("POST"), + path_params_schema: None, + query_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "update_if_exists": { + "type": "boolean", + "description": "update the resource if it already exists (default false)" + } + }, + "required": [] +})), + body_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "The path to the resource" + }, + "value": {}, + "description": { + "type": "string", + "description": "The description of the resource" + }, + "resource_type": { + "type": "string", + "description": "The resource_type associated with the resource" + } + }, + "required": [ + "path", + "value", + "resource_type" + ] +})), + }, + EndpointTool { + name: Cow::Borrowed("deleteResource"), + description: Cow::Borrowed("delete resource"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/resources/delete/{path}"), + method: Cow::Borrowed("DELETE"), + path_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ] +})), + query_params_schema: None, + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("updateResource"), + description: Cow::Borrowed("update resource"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/resources/update/{path}"), + method: Cow::Borrowed("POST"), + path_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ] +})), + query_params_schema: None, + body_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "The path to the resource" + }, + "description": { + "type": "string", + "description": "The new description of the resource" + }, + "value": {}, + "resource_type": { + "type": "string", + "description": "The new resource_type to be associated with the resource" + } + } +})), + }, + EndpointTool { + name: Cow::Borrowed("getResource"), + description: Cow::Borrowed("get resource"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/resources/get/{path}"), + method: Cow::Borrowed("GET"), + path_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ] +})), + query_params_schema: None, + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("listResource"), + description: Cow::Borrowed("list resources"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/resources/list"), + method: Cow::Borrowed("GET"), + path_params_schema: None, + query_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "page": { + "type": "integer", + "description": "which page to return (start at 1, default 1)" + }, + "per_page": { + "type": "integer", + "description": "number of items to return for a given page (default 30, max 100)" + }, + "resource_type": { + "type": "string", + "description": "resource_types to list from, separated by ','," + }, + "resource_type_exclude": { + "type": "string", + "description": "resource_types to not list from, separated by ','," + }, + "path_start": { + "type": "string", + "description": "filter resources by path prefix" + } + }, + "required": [] +})), + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("listResourceType"), + description: Cow::Borrowed("list resource_types"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/resources/type/list"), + method: Cow::Borrowed("GET"), + path_params_schema: None, + query_params_schema: None, + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("listScripts"), + description: Cow::Borrowed("list all scripts"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/scripts/list"), + method: Cow::Borrowed("GET"), + path_params_schema: None, + query_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "page": { + "type": "integer", + "description": "which page to return (start at 1, default 1)" + }, + "per_page": { + "type": "integer", + "description": "number of items to return for a given page (default 30, max 100)" + }, + "order_desc": { + "type": "boolean", + "description": "order by desc order (default true)" + }, + "created_by": { + "type": "string", + "description": "mask to filter exact matching user creator" + }, + "path_start": { + "type": "string", + "description": "mask to filter matching starting path" + }, + "path_exact": { + "type": "string", + "description": "mask to filter exact matching path" + }, + "first_parent_hash": { + "type": "string", + "description": "mask to filter scripts whom first direct parent has exact hash" + }, + "last_parent_hash": { + "type": "string", + "description": "mask to filter scripts whom last parent in the chain has exact hash.\nBeware that each script stores only a limited number of parents. Hence\nthe last parent hash for a script is not necessarily its top-most parent.\nTo find the top-most parent you will have to jump from last to last hash\n until finding the parent\n" + }, + "parent_hash": { + "type": "string", + "description": "is the hash present in the array of stored parent hashes for this script.\nThe same warning applies than for last_parent_hash. A script only store a\nlimited number of direct parent\n" + }, + "show_archived": { + "type": "boolean", + "description": "(default false)\nshow only the archived files.\nwhen multiple archived hash share the same path, only the ones with the latest create_at\nare\ned.\n" + }, + "include_without_main": { + "type": "boolean", + "description": "(default false)\ninclude scripts without an exported main function\n" + }, + "include_draft_only": { + "type": "boolean", + "description": "(default false)\ninclude scripts that have no deployed version\n" + }, + "is_template": { + "type": "boolean", + "description": "(default regardless)\nif true show only the templates\nif false show only the non templates\nif not defined, show all regardless of if the script is a template\n" + }, + "kinds": { + "type": "string", + "description": "(default regardless)\nscript kinds to filter, split by comma\n" + }, + "starred_only": { + "type": "boolean", + "description": "(default false)\nshow only the starred items\n" + }, + "with_deployment_msg": { + "type": "boolean", + "description": "(default false)\ninclude deployment message\n" + }, + "languages": { + "type": "string", + "description": "Filter to only include scripts written in the given languages.\nAccepts multiple values as a comma-separated list.\n" + } + }, + "required": [] +})), + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("getScriptByPath"), + description: Cow::Borrowed("get script by path"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/scripts/get/p/{path}"), + method: Cow::Borrowed("GET"), + path_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ] +})), + query_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "with_starred_info": { + "type": "boolean" + } + }, + "required": [] +})), + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("listFlows"), + description: Cow::Borrowed("list all flows"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/flows/list"), + method: Cow::Borrowed("GET"), + path_params_schema: None, + query_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "page": { + "type": "integer", + "description": "which page to return (start at 1, default 1)" + }, + "per_page": { + "type": "integer", + "description": "number of items to return for a given page (default 30, max 100)" + }, + "order_desc": { + "type": "boolean", + "description": "order by desc order (default true)" + }, + "created_by": { + "type": "string", + "description": "mask to filter exact matching user creator" + }, + "path_start": { + "type": "string", + "description": "mask to filter matching starting path" + }, + "path_exact": { + "type": "string", + "description": "mask to filter exact matching path" + }, + "show_archived": { + "type": "boolean", + "description": "(default false)\nshow only the archived files.\nwhen multiple archived hash share the same path, only the ones with the latest create_at\nare displayed.\n" + }, + "starred_only": { + "type": "boolean", + "description": "(default false)\nshow only the starred items\n" + }, + "include_draft_only": { + "type": "boolean", + "description": "(default false)\ninclude items that have no deployed version\n" + }, + "with_deployment_msg": { + "type": "boolean", + "description": "(default false)\ninclude deployment message\n" + } + }, + "required": [] +})), + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("getFlowByPath"), + description: Cow::Borrowed("get flow by path"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/flows/get/{path}"), + method: Cow::Borrowed("GET"), + path_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ] +})), + query_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "with_starred_info": { + "type": "boolean" + } + }, + "required": [] +})), + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("listQueue"), + description: Cow::Borrowed("list all queued jobs"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/jobs/queue/list"), + method: Cow::Borrowed("GET"), + path_params_schema: None, + query_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "order_desc": { + "type": "boolean", + "description": "order by desc order (default true)" + }, + "created_by": { + "type": "string", + "description": "mask to filter exact matching user creator" + }, + "parent_job": { + "type": "string", + "format": "uuid", + "description": "The parent job that is at the origin and responsible for the execution of this script if any" + }, + "worker": { + "type": "string", + "description": "worker this job was ran on" + }, + "script_path_exact": { + "type": "string", + "description": "mask to filter exact matching path" + }, + "script_path_start": { + "type": "string", + "description": "mask to filter matching starting path" + }, + "schedule_path": { + "type": "string", + "description": "mask to filter by schedule path" + }, + "script_hash": { + "type": "string", + "description": "mask to filter exact matching path" + }, + "started_before": { + "type": "string", + "format": "date-time", + "description": "filter on started before (inclusive) timestamp" + }, + "started_after": { + "type": "string", + "format": "date-time", + "description": "filter on started after (exclusive) timestamp" + }, + "success": { + "type": "boolean", + "description": "filter on successful jobs" + }, + "scheduled_for_before_now": { + "type": "boolean", + "description": "filter on jobs scheduled_for before now (hence waitinf for a worker)" + }, + "job_kinds": { + "type": "string", + "description": "filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by," + }, + "suspended": { + "type": "boolean", + "description": "filter on suspended jobs" + }, + "running": { + "type": "boolean", + "description": "filter on running jobs" + }, + "args": { + "type": "string", + "description": "filter on jobs containing those args as a json subset (@> in postgres)" + }, + "result": { + "type": "string", + "description": "filter on jobs containing those result as a json subset (@> in postgres)" + }, + "allow_wildcards": { + "type": "boolean", + "description": "allow wildcards (*) in the filter of label, tag, worker" + }, + "tag": { + "type": "string", + "description": "filter on jobs with a given tag/worker group" + }, + "page": { + "type": "integer", + "description": "which page to return (start at 1, default 1)" + }, + "per_page": { + "type": "integer", + "description": "number of items to return for a given page (default 30, max 100)" + }, + "all_workspaces": { + "type": "boolean", + "description": "get jobs from all workspaces (only valid if request come from the `admins` workspace)" + }, + "is_not_schedule": { + "type": "boolean", + "description": "is not a scheduled job" + } + }, + "required": [] +})), + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("listJobs"), + description: Cow::Borrowed("list all jobs"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/jobs/list"), + method: Cow::Borrowed("GET"), + path_params_schema: None, + query_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "created_by": { + "type": "string", + "description": "mask to filter exact matching user creator" + }, + "label": { + "type": "string", + "description": "mask to filter exact matching job's label (job labels are completed jobs with as a result an object containing a string in the array at key 'wm_labels')" + }, + "worker": { + "type": "string", + "description": "worker this job was ran on" + }, + "parent_job": { + "type": "string", + "format": "uuid", + "description": "The parent job that is at the origin and responsible for the execution of this script if any" + }, + "script_path_exact": { + "type": "string", + "description": "mask to filter exact matching path" + }, + "script_path_start": { + "type": "string", + "description": "mask to filter matching starting path" + }, + "schedule_path": { + "type": "string", + "description": "mask to filter by schedule path" + }, + "script_hash": { + "type": "string", + "description": "mask to filter exact matching path" + }, + "started_before": { + "type": "string", + "format": "date-time", + "description": "filter on started before (inclusive) timestamp" + }, + "started_after": { + "type": "string", + "format": "date-time", + "description": "filter on started after (exclusive) timestamp" + }, + "created_before": { + "type": "string", + "format": "date-time", + "description": "filter on created before (inclusive) timestamp" + }, + "created_after": { + "type": "string", + "format": "date-time", + "description": "filter on created after (exclusive) timestamp" + }, + "created_or_started_before": { + "type": "string", + "format": "date-time", + "description": "filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp" + }, + "running": { + "type": "boolean", + "description": "filter on running jobs" + }, + "scheduled_for_before_now": { + "type": "boolean", + "description": "filter on jobs scheduled_for before now (hence waitinf for a worker)" + }, + "created_or_started_after": { + "type": "string", + "format": "date-time", + "description": "filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp" + }, + "created_or_started_after_completed_jobs": { + "type": "string", + "format": "date-time", + "description": "filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs" + }, + "job_kinds": { + "type": "string", + "description": "filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by," + }, + "suspended": { + "type": "boolean", + "description": "filter on suspended jobs" + }, + "args": { + "type": "string", + "description": "filter on jobs containing those args as a json subset (@> in postgres)" + }, + "tag": { + "type": "string", + "description": "filter on jobs with a given tag/worker group" + }, + "result": { + "type": "string", + "description": "filter on jobs containing those result as a json subset (@> in postgres)" + }, + "allow_wildcards": { + "type": "boolean", + "description": "allow wildcards (*) in the filter of label, tag, worker" + }, + "page": { + "type": "integer", + "description": "which page to return (start at 1, default 1)" + }, + "per_page": { + "type": "integer", + "description": "number of items to return for a given page (default 30, max 100)" + }, + "is_skipped": { + "type": "boolean", + "description": "is the job skipped" + }, + "is_flow_step": { + "type": "boolean", + "description": "is the job a flow step" + }, + "has_null_parent": { + "type": "boolean", + "description": "has null parent" + }, + "success": { + "type": "boolean", + "description": "filter on successful jobs" + }, + "all_workspaces": { + "type": "boolean", + "description": "get jobs from all workspaces (only valid if request come from the `admins` workspace)" + }, + "is_not_schedule": { + "type": "boolean", + "description": "is not a scheduled job" + } + }, + "required": [] +})), + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("createSchedule"), + description: Cow::Borrowed("create schedule"), + instructions: Cow::Borrowed("Creates a new schedule. +The schedule should include seconds. +You should get the schema of the script or flow before creating the schedule to correctly specify the arguments needed. +"), + path: Cow::Borrowed("/w/{workspace}/schedules/create"), + method: Cow::Borrowed("POST"), + path_params_schema: None, + query_params_schema: None, + body_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string", + "description": "The path where the schedule will be created" + }, + "schedule": { + "type": "string", + "description": "The cron schedule to trigger the script or flow. Should include seconds." + }, + "timezone": { + "type": "string", + "description": "The timezone to use for the cron schedule" + }, + "script_path": { + "type": "string", + "description": "The path to the script or flow to trigger" + }, + "is_flow": { + "type": "boolean", + "description": "Whether the schedule is for a flow" + }, + "args": { + "type": "object", + "description": "The arguments to pass to the script or flow", + "additionalProperties": {} + }, + "enabled": { + "type": "boolean", + "description": "Whether the schedule is enabled" + }, + "on_failure": { + "type": "string", + "description": "The path to the script or flow to trigger on failure" + }, + "on_failure_times": { + "type": "number", + "description": "The number of times to retry on failure" + }, + "on_failure_exact": { + "type": "boolean", + "description": "Whether the schedule should only run on the exact time" + }, + "on_failure_extra_args": { + "type": "object", + "description": "The arguments to pass to the script or flow", + "additionalProperties": {} + }, + "on_recovery": { + "type": "string", + "description": "The path to the script or flow to trigger on recovery" + }, + "on_recovery_times": { + "type": "number", + "description": "The number of times to retry on recovery" + }, + "on_recovery_extra_args": { + "type": "object", + "description": "The arguments to pass to the script or flow", + "additionalProperties": {} + }, + "on_success": { + "type": "string", + "description": "The path to the script or flow to trigger on success" + }, + "on_success_extra_args": { + "type": "object", + "description": "The arguments to pass to the script or flow", + "additionalProperties": {} + }, + "ws_error_handler_muted": { + "type": "boolean", + "description": "Whether the WebSocket error handler is muted" + }, + "retry": { + "$ref": "../../openflow.openapi.yaml#/components/schemas/Retry", + "description": "The retry configuration for the schedule" + }, + "no_flow_overlap": { + "type": "boolean", + "description": "Whether the schedule should not run if a flow is already running" + }, + "summary": { + "type": "string", + "description": "The summary of the schedule" + }, + "description": { + "type": "string", + "description": "The description of the schedule" + }, + "tag": { + "type": "string", + "description": "The tag of the schedule" + }, + "paused_until": { + "type": "string", + "description": "The date and time the schedule will be paused until", + "format": "date-time" + }, + "cron_version": { + "type": "string", + "description": "The version of the cron schedule to use (last is v2)" + } + }, + "required": [ + "path", + "schedule", + "timezone", + "script_path", + "is_flow", + "args" + ] +})), + }, + EndpointTool { + name: Cow::Borrowed("updateSchedule"), + description: Cow::Borrowed("update schedule"), + instructions: Cow::Borrowed("Updates a schedule. +The schedule should include seconds. +You should get the schema of the script or flow before updating the schedule to correctly specify the arguments needed. +"), + path: Cow::Borrowed("/w/{workspace}/schedules/update/{path}"), + method: Cow::Borrowed("POST"), + path_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ] +})), + query_params_schema: None, + body_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "schedule": { + "type": "string", + "description": "The cron schedule to trigger the script or flow. Should include seconds." + }, + "timezone": { + "type": "string", + "description": "The timezone to use for the cron schedule" + }, + "args": { + "type": "object", + "description": "The arguments to pass to the script or flow", + "additionalProperties": {} + }, + "on_failure": { + "type": "string", + "description": "The path to the script or flow to trigger on failure" + }, + "on_failure_times": { + "type": "number", + "description": "The number of times to retry on failure" + }, + "on_failure_exact": { + "type": "boolean", + "description": "Whether the schedule should only run on the exact time" + }, + "on_failure_extra_args": { + "type": "object", + "description": "The arguments to pass to the script or flow", + "additionalProperties": {} + }, + "on_recovery": { + "type": "string", + "description": "The path to the script or flow to trigger on recovery" + }, + "on_recovery_times": { + "type": "number", + "description": "The number of times to retry on recovery" + }, + "on_recovery_extra_args": { + "type": "object", + "description": "The arguments to pass to the script or flow", + "additionalProperties": {} + }, + "on_success": { + "type": "string", + "description": "The path to the script or flow to trigger on success" + }, + "on_success_extra_args": { + "type": "object", + "description": "The arguments to pass to the script or flow", + "additionalProperties": {} + }, + "ws_error_handler_muted": { + "type": "boolean", + "description": "Whether the WebSocket error handler is muted" + }, + "retry": { + "$ref": "../../openflow.openapi.yaml#/components/schemas/Retry", + "description": "The retry configuration for the schedule" + }, + "no_flow_overlap": { + "type": "boolean", + "description": "Whether the schedule should not run if a flow is already running" + }, + "summary": { + "type": "string", + "description": "The summary of the schedule" + }, + "description": { + "type": "string", + "description": "The description of the schedule" + }, + "tag": { + "type": "string", + "description": "The tag of the schedule" + }, + "paused_until": { + "type": "string", + "description": "The date and time the schedule will be paused until", + "format": "date-time" + }, + "cron_version": { + "type": "string", + "description": "The version of the cron schedule to use (last is v2)" + } + }, + "required": [ + "schedule", + "timezone", + "args" + ] +})), + }, + EndpointTool { + name: Cow::Borrowed("deleteSchedule"), + description: Cow::Borrowed("delete schedule"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/schedules/delete/{path}"), + method: Cow::Borrowed("DELETE"), + path_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ] +})), + query_params_schema: None, + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("getSchedule"), + description: Cow::Borrowed("get schedule"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/schedules/get/{path}"), + method: Cow::Borrowed("GET"), + path_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ] +})), + query_params_schema: None, + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("listSchedules"), + description: Cow::Borrowed("list schedules"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/w/{workspace}/schedules/list"), + method: Cow::Borrowed("GET"), + path_params_schema: None, + query_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "page": { + "type": "integer", + "description": "which page to return (start at 1, default 1)" + }, + "per_page": { + "type": "integer", + "description": "number of items to return for a given page (default 30, max 100)" + }, + "args": { + "type": "string", + "description": "filter on jobs containing those args as a json subset (@> in postgres)" + }, + "path": { + "type": "string", + "description": "filter by path" + }, + "is_flow": { + "type": "boolean", + "description": "filter schedules by whether they target a flow" + }, + "path_start": { + "type": "string", + "description": "filter schedules by path prefix" + } + }, + "required": [] +})), + body_schema: None, + }, + EndpointTool { + name: Cow::Borrowed("listWorkers"), + description: Cow::Borrowed("list workers"), + instructions: Cow::Borrowed(""), + path: Cow::Borrowed("/workers/list"), + method: Cow::Borrowed("GET"), + path_params_schema: None, + query_params_schema: Some(serde_json::json!({ + "type": "object", + "properties": { + "page": { + "type": "integer", + "description": "which page to return (start at 1, default 1)" + }, + "per_page": { + "type": "integer", + "description": "number of items to return for a given page (default 30, max 100)" + }, + "ping_since": { + "type": "integer", + "description": "number of seconds the worker must have had a last ping more recent of (default to 300)" + } + }, + "required": [] +})), + body_schema: None, + } + ] +} diff --git a/backend/windmill-api/src/mcp/tools/endpoint_tools.rs b/backend/windmill-api/src/mcp/tools/endpoint_tools.rs new file mode 100644 index 0000000000..1b8a7f0a66 --- /dev/null +++ b/backend/windmill-api/src/mcp/tools/endpoint_tools.rs @@ -0,0 +1,272 @@ +//! Endpoint tools for MCP server +//! +//! Contains the auto-generated endpoint tools and utilities for converting +//! them to MCP tools and handling HTTP calls to Windmill API endpoints. + +use rmcp::{model::Tool, Error}; +use std::sync::Arc; +use windmill_common::auth::create_jwt_token; +use windmill_common::db::Authed; +use windmill_common::BASE_URL; +use crate::db::ApiAuthed; + +// Import the auto-generated tools +use super::auto_generated_endpoints; +pub use auto_generated_endpoints::{EndpointTool, all_tools}; + +/// Get all available endpoint tools +pub fn all_endpoint_tools() -> Vec { + all_tools() +} + +/// Convert endpoint tools to MCP tools +pub fn endpoint_tools_to_mcp_tools(endpoint_tools: Vec) -> Vec { + endpoint_tools.into_iter().map(|tool| endpoint_tool_to_mcp_tool(&tool)).collect() +} + +/// Convert a single endpoint tool to MCP tool +pub fn endpoint_tool_to_mcp_tool(tool: &EndpointTool) -> Tool { + let mut combined_properties = serde_json::Map::new(); + let mut combined_required = Vec::new(); + + // Combine all parameter schemas + let schemas = [ + &tool.path_params_schema, + &tool.query_params_schema, + &tool.body_schema, + ]; + + for schema in schemas.iter().filter_map(|s| s.as_ref()) { + merge_schema_into(&mut combined_properties, &mut combined_required, schema); + } + + let combined_schema = serde_json::json!({ + "type": "object", + "properties": combined_properties, + "required": combined_required + }); + + let description = format!("{}. {}", tool.description, tool.instructions); + + // Create annotations based on HTTP method and endpoint characteristics + let annotations = create_endpoint_annotations(tool); + + Tool { + name: tool.name.clone(), + description: Some(description.into()), + input_schema: Arc::new(combined_schema.as_object().unwrap().clone()), + annotations: Some(annotations), + } +} + +/// Create appropriate annotations for endpoint tools based on HTTP method +fn create_endpoint_annotations(tool: &EndpointTool) -> rmcp::model::ToolAnnotations { + let method = tool.method.as_ref(); + + // Determine characteristics based on HTTP method + let (read_only, destructive, idempotent, open_world) = match method { + "GET" => (true, false, true, true), // Read-only, safe, idempotent + "POST" => (false, true, false, true), // Can modify, potentially destructive, not idempotent + "PUT" => (false, false, true, true), // Can modify, typically idempotent updates + "DELETE" => (false, true, true, true), // Destructive but idempotent + "PATCH" => (false, false, false, true), // Partial updates, not guaranteed idempotent + _ => (false, true, false, true), // Default: assume can modify and be destructive + }; + + rmcp::model::ToolAnnotations { + title: Some(format!("{} {}", method, tool.path)), + read_only_hint: Some(read_only), + destructive_hint: Some(destructive), + idempotent_hint: Some(idempotent), + open_world_hint: Some(open_world), + } +} + +/// Merge schema into combined properties and required fields +fn merge_schema_into( + combined_properties: &mut serde_json::Map, + combined_required: &mut Vec, + schema: &serde_json::Value, +) { + if let Some(props) = schema.get("properties").and_then(|p| p.as_object()) { + for (key, value) in props { + combined_properties.insert(key.clone(), value.clone()); + } + } + + if let Some(required) = schema.get("required").and_then(|r| r.as_array()) { + for req in required.iter().filter_map(|r| r.as_str()) { + combined_required.push(req.to_string()); + } + } +} + +/// Call an endpoint tool by making HTTP request to Windmill API +pub async fn call_endpoint_tool( + tool: &EndpointTool, + args: serde_json::Value, + workspace_id: &str, + api_authed: &ApiAuthed, +) -> Result { + let args_map = match &args { + serde_json::Value::Object(map) => map, + _ => return Err(Error::invalid_params("Arguments must be an object", Some(tool.name.clone().into()))), + }; + + // Build URL with path substitutions + let path_template = substitute_path_params(&tool.path, workspace_id, args_map, &tool.path_params_schema)?; + let query_string = build_query_string(args_map, &tool.query_params_schema); + let full_url = format!("{}/api{}{}", BASE_URL.read().await, path_template, query_string); + + // Prepare request body + let body_json = build_request_body(&tool.method, args_map, &tool.body_schema); + + // Create and execute request + let response = create_http_request(&tool.method, &full_url, workspace_id, api_authed, body_json).await?; + + let status = response.status(); + let response_text = response.text().await.map_err(|e| { + Error::internal_error(format!("Failed to read response text: {}", e), None) + })?; + + if status.is_success() { + Ok(serde_json::from_str(&response_text).unwrap_or_else(|_| serde_json::Value::String(response_text))) + } else { + Err(Error::internal_error( + format!("HTTP {} {}: {}", status.as_u16(), status.canonical_reason().unwrap_or(""), response_text), + None + )) + } +} + +/// Substitute path parameters in the URL template +fn substitute_path_params( + path: &str, + workspace_id: &str, + args_map: &serde_json::Map, + path_schema: &Option, +) -> Result { + let mut path_template = path.replace("{workspace}", workspace_id); + + if let Some(schema) = path_schema { + if let Some(props) = schema.get("properties").and_then(|p| p.as_object()) { + for (param_name, _) in props { + let placeholder = format!("{{{}}}", param_name); + match args_map.get(param_name) { + Some(param_value) => { + if let Some(str_val) = param_value.as_str() { + path_template = path_template.replace(&placeholder, str_val); + } + }, + None => { + tracing::warn!("Missing required path parameter: {}", param_name); + return Err(Error::invalid_params( + format!("Missing required path parameter: {}", param_name), + None + )); + } + } + } + } + } + + Ok(path_template) +} + +/// Build query string from arguments +fn build_query_string( + args_map: &serde_json::Map, + query_schema: &Option, +) -> String { + let Some(schema) = query_schema else { return String::new() }; + let Some(props) = schema.get("properties").and_then(|p| p.as_object()) else { return String::new() }; + + let query_params: Vec = props + .keys() + .filter_map(|param_name| { + args_map.get(param_name) + .filter(|v| !v.is_null()) + .map(|value| { + let value_str = value.to_string(); + let str_val = value_str.trim_matches('"'); + format!("{}={}", + urlencoding::encode(param_name), + urlencoding::encode(str_val) + ) + }) + }) + .collect(); + + if query_params.is_empty() { + String::new() + } else { + format!("?{}", query_params.join("&")) + } +} + +/// Build request body from arguments +fn build_request_body( + method: &str, + args_map: &serde_json::Map, + body_schema: &Option, +) -> Option { + if method == "GET" { + return None; + } + + let schema = body_schema.as_ref()?; + let props = schema.get("properties")?.as_object()?; + + let body_map: serde_json::Map = props + .keys() + .filter_map(|param_name| { + args_map.get(param_name) + .map(|value| (param_name.clone(), value.clone())) + }) + .collect(); + + if body_map.is_empty() { + None + } else { + Some(serde_json::Value::Object(body_map)) + } +} + +/// Create HTTP request with authentication +async fn create_http_request( + method: &str, + url: &str, + workspace_id: &str, + api_authed: &ApiAuthed, + body_json: Option, +) -> Result { + let client = &crate::HTTP_CLIENT; + let mut request_builder = match method { + "GET" => client.get(url), + "POST" => client.post(url), + "PUT" => client.put(url), + "DELETE" => client.delete(url), + "PATCH" => client.patch(url), + _ => return Err(Error::invalid_params( + format!("Unsupported HTTP method: {}", method), + None + )), + }; + + // Add authorization header + let authed = Authed::from(api_authed.clone()); + let token = create_jwt_token(authed, workspace_id, 3600, None, None, None, None).await + .map_err(|e| Error::internal_error(e.to_string(), None))?; + request_builder = request_builder.header("Authorization", format!("Bearer {}", token)); + + // Add body if present + if let Some(body) = body_json { + request_builder = request_builder + .header("Content-Type", "application/json") + .json(&body); + } + + request_builder.send().await.map_err(|e| { + Error::internal_error(format!("Failed to execute request: {}", e), None) + }) +} \ No newline at end of file diff --git a/backend/windmill-api/src/mcp/tools/flow_tools.rs b/backend/windmill-api/src/mcp/tools/flow_tools.rs new file mode 100644 index 0000000000..03b47472bf --- /dev/null +++ b/backend/windmill-api/src/mcp/tools/flow_tools.rs @@ -0,0 +1,40 @@ +//! Flow tools for MCP server +//! +//! Contains functionality for converting Windmill flows into MCP tools. + +use super::super::utils::{ + models::{FlowInfo, ToolableItem, SchemaType}, + schema::convert_schema_to_schema_type, + transform::transform_path, +}; + +/// Implementation of ToolableItem for FlowInfo +impl ToolableItem for FlowInfo { + fn get_path_or_id(&self) -> String { + transform_path(&self.path, "flow") + } + + fn get_summary(&self) -> &str { + self.summary.as_deref().unwrap_or("No summary") + } + + fn get_description(&self) -> &str { + self.description.as_deref().unwrap_or("No description") + } + + fn get_schema(&self) -> SchemaType { + convert_schema_to_schema_type(self.schema.clone()) + } + + fn is_hub(&self) -> bool { + false + } + + fn item_type(&self) -> &'static str { + "flow" + } + + fn get_integration_type(&self) -> Option { + None + } +} \ No newline at end of file diff --git a/backend/windmill-api/src/mcp/tools/hub_tools.rs b/backend/windmill-api/src/mcp/tools/hub_tools.rs new file mode 100644 index 0000000000..b81b973b30 --- /dev/null +++ b/backend/windmill-api/src/mcp/tools/hub_tools.rs @@ -0,0 +1,43 @@ +//! Hub tools for MCP server +//! +//! Contains functionality for integrating Windmill Hub scripts as MCP tools. + +use super::super::utils::{ + models::{HubScriptInfo, ToolableItem, SchemaType}, +}; + +/// Implementation of ToolableItem for HubScriptInfo +impl ToolableItem for HubScriptInfo { + fn get_path_or_id(&self) -> String { + let id = self.version_id; + let summary = self.summary.as_deref().unwrap_or("No summary"); + format!("hs-{}-{}", id, summary.replace(" ", "_")) + } + + fn get_summary(&self) -> &str { + self.summary.as_deref().unwrap_or("No summary") + } + + fn get_description(&self) -> &str { + self.description.as_deref().unwrap_or("No description") + } + + fn get_schema(&self) -> SchemaType { + match serde_json::from_value::(self.schema.clone().unwrap_or_default()) { + Ok(schema_type) => schema_type, + Err(_) => SchemaType::default(), + } + } + + fn is_hub(&self) -> bool { + true + } + + fn item_type(&self) -> &'static str { + "script" + } + + fn get_integration_type(&self) -> Option { + self.app.clone() + } +} \ No newline at end of file diff --git a/backend/windmill-api/src/mcp/tools/mod.rs b/backend/windmill-api/src/mcp/tools/mod.rs new file mode 100644 index 0000000000..1488b22d8a --- /dev/null +++ b/backend/windmill-api/src/mcp/tools/mod.rs @@ -0,0 +1,10 @@ +//! Tool management for MCP server +//! +//! This module handles the conversion of Windmill scripts, flows, and endpoints +//! into MCP tools that can be used by AI assistants. + +pub mod script_tools; +pub mod flow_tools; +pub mod hub_tools; +pub mod endpoint_tools; +pub mod auto_generated_endpoints; \ No newline at end of file diff --git a/backend/windmill-api/src/mcp/tools/script_tools.rs b/backend/windmill-api/src/mcp/tools/script_tools.rs new file mode 100644 index 0000000000..716fae3a87 --- /dev/null +++ b/backend/windmill-api/src/mcp/tools/script_tools.rs @@ -0,0 +1,40 @@ +//! Script tools for MCP server +//! +//! Contains functionality for converting Windmill scripts into MCP tools. + +use super::super::utils::{ + models::{ScriptInfo, ToolableItem, SchemaType}, + schema::convert_schema_to_schema_type, + transform::transform_path, +}; + +/// Implementation of ToolableItem for ScriptInfo +impl ToolableItem for ScriptInfo { + fn get_path_or_id(&self) -> String { + transform_path(&self.path, "script") + } + + fn get_summary(&self) -> &str { + self.summary.as_deref().unwrap_or("No summary") + } + + fn get_description(&self) -> &str { + self.description.as_deref().unwrap_or("No description") + } + + fn get_schema(&self) -> SchemaType { + convert_schema_to_schema_type(self.schema.clone()) + } + + fn is_hub(&self) -> bool { + false + } + + fn item_type(&self) -> &'static str { + "script" + } + + fn get_integration_type(&self) -> Option { + None + } +} \ No newline at end of file diff --git a/backend/windmill-api/src/mcp/utils/database.rs b/backend/windmill-api/src/mcp/utils/database.rs new file mode 100644 index 0000000000..0a0ea15ca6 --- /dev/null +++ b/backend/windmill-api/src/mcp/utils/database.rs @@ -0,0 +1,252 @@ +//! Database operations for MCP server +//! +//! Contains all database query functions and database-related utilities +//! used by the MCP server implementation. + +use rmcp::Error; +use sql_builder::prelude::*; +use windmill_common::db::UserDB; +use windmill_common::scripts::{get_full_hub_script_by_path, Schema}; +use windmill_common::utils::{query_elems_from_hub, StripPath}; +use windmill_common::{DB, HUB_BASE_URL}; + +use crate::db::ApiAuthed; +use crate::HTTP_CLIENT; +use super::models::*; + +/// Check if the user has proper MCP scopes +pub fn check_scopes(authed: &ApiAuthed) -> Result<(), Error> { + let scopes = authed.scopes.as_ref(); + if scopes.is_none() + || scopes + .unwrap() + .iter() + .all(|scope| !scope.starts_with("mcp:all") && !scope.starts_with("mcp:favorites") && !scope.starts_with("mcp:hub:")) + { + tracing::error!("Unauthorized: missing mcp scope"); + return Err(Error::internal_error("Unauthorized: missing mcp scope".to_string(), None)); + } + Ok(()) +} + +/// Get the schema for a specific item (script or flow) +pub async fn get_item_schema( + path: &str, + user_db: &UserDB, + authed: &ApiAuthed, + workspace_id: &str, + item_type: &str, +) -> Result, Error> { + let mut sqlb = SqlBuilder::select_from(&format!("{} as o", item_type)); + sqlb.fields(&["o.schema"]); + sqlb.and_where("o.path = ?".bind(&path)); + sqlb.and_where("o.workspace_id = ?".bind(&workspace_id)); + sqlb.and_where("o.archived = false"); + sqlb.and_where("o.draft_only IS NOT TRUE"); + let sql = sqlb.sql().map_err(|_e| { + tracing::error!("failed to build sql: {}", _e); + Error::internal_error("failed to build sql", None) + })?; + let mut tx = user_db + .clone() + .begin(authed) + .await + .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; + let item = sqlx::query_as::<_, ItemSchema>(&sql) + .fetch_one(&mut *tx) + .await + .map_err(|_e| { + tracing::error!("failed to fetch item schema: {}", _e); + Error::internal_error("failed to fetch item schema", None) + })?; + tx.commit() + .await + .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; + Ok(item.schema) +} + +/// Get all resource types from the database +pub async fn get_resources_types( + user_db: &UserDB, + authed: &ApiAuthed, + workspace_id: &str, +) -> Result, Error> { + let mut sqlb = SqlBuilder::select_from("resource_type as o"); + sqlb.fields(&["o.name", "o.description"]); + sqlb.and_where("o.workspace_id = ?".bind(&workspace_id)); + let sql = sqlb.sql().map_err(|_e| { + tracing::error!("failed to build sql: {}", _e); + Error::internal_error("failed to build sql", None) + })?; + let mut tx = user_db + .clone() + .begin(authed) + .await + .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; + let rows = sqlx::query_as::<_, ResourceType>(&sql) + .fetch_all(&mut *tx) + .await + .map_err(|_e| { + tracing::error!("Failed to fetch resource types: {}", _e); + Error::internal_error("failed to fetch resource types", None) + })?; + tx.commit() + .await + .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; + Ok(rows) +} + +/// Get resources by type from the database +pub async fn get_resources( + user_db: &UserDB, + authed: &ApiAuthed, + workspace_id: &str, + resource_type: &str, +) -> Result, Error> { + let mut sqlb = SqlBuilder::select_from("resource as o"); + sqlb.fields(&["o.path", "o.description", "o.resource_type"]); + sqlb.and_where("o.workspace_id = ?".bind(&workspace_id)); + sqlb.and_where("o.resource_type = ?".bind(&resource_type)); + let sql = sqlb.sql().map_err(|_e| { + tracing::error!("failed to build sql: {}", _e); + Error::internal_error("failed to build sql", None) + })?; + let mut tx = user_db + .clone() + .begin(authed) + .await + .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; + let rows = sqlx::query_as::<_, ResourceInfo>(&sql) + .fetch_all(&mut *tx) + .await + .map_err(|_e| { + tracing::error!("Failed to fetch resources: {}", _e); + Error::internal_error("failed to fetch resources", None) + })?; + tx.commit() + .await + .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; + + Ok(rows) +} + +/// Generic function to get items (scripts or flows) from the database +pub async fn get_items sqlx::FromRow<'a, sqlx::postgres::PgRow> + Send + Unpin>( + user_db: &UserDB, + authed: &ApiAuthed, + workspace_id: &str, + scope_type: &str, + item_type: &str, + scope_path: Option<&str>, +) -> Result, Error> { + let mut sqlb = SqlBuilder::select_from(&format!("{} as o", item_type)); + let fields = vec!["o.path", "o.summary", "o.description", "o.schema"]; + sqlb.fields(&fields); + if scope_type == "favorites" { + sqlb.join("favorite") + .on("favorite.favorite_kind = ? AND favorite.workspace_id = o.workspace_id AND favorite.path = o.path AND favorite.usr = ?".bind(&item_type) + .bind(&authed.username)); + } + sqlb.and_where("o.workspace_id = ?".bind(&workspace_id)) + .and_where("o.archived = false") + .and_where("o.draft_only IS NOT TRUE"); + + if item_type == "script" { + sqlb.and_where("(o.no_main_func IS NOT TRUE OR o.no_main_func IS NULL)"); + } + + // scope path is always a folder path, format is f/my_folder/* + if let Some(scope_path) = scope_path { + if scope_path.split("/").count() != 3 || !scope_path.starts_with("f/") || !scope_path.ends_with("/*") { + return Err(Error::internal_error( + format!("Invalid folder format: {}, expected format is f/my_folder/*", scope_path), + None, + )); + } + sqlb.and_where_like_left("o.path", &scope_path[..scope_path.len() - 2]); + } + + sqlb.order_by( + if item_type == "flow" { + "o.edited_at" + } else { + "o.created_at" + }, + false, + ) + .limit(100); + let sql = sqlb.sql().map_err(|_e| { + tracing::error!("failed to build sql: {}", _e); + Error::internal_error("failed to build sql", None) + })?; + let mut tx = user_db + .clone() + .begin(authed) + .await + .map_err(|_e| Error::internal_error("failed to begin transaction", None))?; + let rows = sqlx::query_as::<_, T>(&sql) + .fetch_all(&mut *tx) + .await + .map_err(|_e| { + tracing::error!("Failed to fetch {}: {}", item_type, _e); + Error::internal_error(format!("failed to fetch {}", item_type), None) + })?; + tx.commit() + .await + .map_err(|_e| Error::internal_error("failed to commit transaction", None))?; + Ok(rows) +} + +/// Get scripts from the Hub +pub async fn get_scripts_from_hub( + db: &DB, + scope_integrations: Option<&str>, +) -> Result, Error> { + let query_params = Some(vec![ + ("limit", "100".to_string()), + ("with_schema", "true".to_string()), + ("apps", scope_integrations.unwrap_or("").to_string()), + ]); + let url = format!("{}/scripts/top", *HUB_BASE_URL.read().await); + let (_status_code, _headers, response) = + query_elems_from_hub(&HTTP_CLIENT, &url, query_params, &db) + .await + .map_err(|e| { + tracing::error!("Failed to get items from hub: {}", e); + Error::internal_error(format!("Failed to get items from hub: {}", e), None) + })?; + + use axum::body::to_bytes; + let body_bytes = to_bytes(response, usize::MAX).await.map_err(|e| { + tracing::error!("Failed to read response body: {}", e); + Error::internal_error(format!("Failed to read response body: {}", e), None) + })?; + let body_str = String::from_utf8(body_bytes.to_vec()).map_err(|e| { + tracing::error!("Failed to decode response body: {}", e); + Error::internal_error(format!("Failed to decode response body: {}", e), None) + })?; + let hub_response: HubResponse = serde_json::from_str(&body_str).map_err(|e| { + tracing::error!("Failed to parse hub response: {}", e); + Error::internal_error(format!("Failed to parse hub response: {}", e), None) + })?; + + Ok(hub_response.asks) +} + +/// Get the schema for a Hub script +pub async fn get_hub_script_schema(path: &str, db: &DB) -> Result, Error> { + let strip_path = StripPath(path.to_string()); + let res = get_full_hub_script_by_path(strip_path, &HTTP_CLIENT, Some(db)) + .await + .map_err(|e| { + tracing::error!("Failed to get hub script: {}", e); + Error::internal_error(format!("Failed to get hub script: {}", e), None) + })?; + match serde_json::from_str::(res.schema.get()) { + Ok(schema) => Ok(Some(schema)), + Err(e) => { + tracing::warn!("Failed to convert schema: {}", e); + Ok(None) + } + } +} \ No newline at end of file diff --git a/backend/windmill-api/src/mcp/utils/mod.rs b/backend/windmill-api/src/mcp/utils/mod.rs new file mode 100644 index 0000000000..04464be481 --- /dev/null +++ b/backend/windmill-api/src/mcp/utils/mod.rs @@ -0,0 +1,9 @@ +//! Utility functions and helpers for MCP server +//! +//! This module contains various utility functions for schema transformation, +//! database operations, data models, and path transformations. + +pub mod models; +pub mod database; +pub mod schema; +pub mod transform; \ No newline at end of file diff --git a/backend/windmill-api/src/mcp/utils/models.rs b/backend/windmill-api/src/mcp/utils/models.rs new file mode 100644 index 0000000000..453b1731fd --- /dev/null +++ b/backend/windmill-api/src/mcp/utils/models.rs @@ -0,0 +1,98 @@ +//! Data models for MCP server +//! +//! Contains all the data structures used throughout the MCP implementation, +//! including database models, API response models, and utility types. + +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use sqlx::FromRow; +use std::collections::HashMap; +use windmill_common::scripts::Schema; + +/// Workspace ID wrapper for Axum extensions +#[derive(Clone, Debug)] +pub struct WorkspaceId(pub String); + +/// Hub API response structure +#[derive(Serialize, Deserialize, Debug)] +pub struct HubResponse { + pub asks: Vec, +} + +/// Hub script information +#[derive(Serialize, Deserialize, Debug)] +pub struct HubScriptInfo { + pub version_id: u64, + pub summary: Option, + pub description: Option, + pub schema: Option, + pub app: Option, +} + +/// Schema type structure for JSON schemas +#[derive(Serialize, FromRow, Deserialize, Debug, Clone)] +pub struct SchemaType { + pub r#type: String, + pub properties: HashMap, + pub required: Vec, +} + +impl Default for SchemaType { + fn default() -> Self { + Self { + r#type: "object".to_string(), + properties: HashMap::new(), + required: vec![], + } + } +} + +/// Script information from database +#[derive(Serialize, FromRow, Debug)] +pub struct ScriptInfo { + pub path: String, + pub summary: Option, + pub description: Option, + pub schema: Option, +} + +/// Flow information from database +#[derive(Serialize, FromRow, Debug)] +pub struct FlowInfo { + pub path: String, + pub summary: Option, + pub description: Option, + pub schema: Option, +} + +/// Resource information from database +#[derive(Serialize, FromRow, Debug, Clone)] +pub struct ResourceInfo { + pub path: String, + pub description: Option, + pub resource_type: String, +} + +/// Resource type information from database +#[derive(Serialize, FromRow, Debug, Clone)] +pub struct ResourceType { + pub name: String, + pub description: Option, +} + +/// Schema holder for database queries +#[derive(Serialize, FromRow)] +pub struct ItemSchema { + pub schema: Option, +} + +/// Trait for objects that can be converted to MCP tools +pub trait ToolableItem { + fn get_path_or_id(&self) -> String; + fn get_summary(&self) -> &str; + fn get_description(&self) -> &str; + fn get_schema(&self) -> SchemaType; + fn is_hub(&self) -> bool; + fn item_type(&self) -> &'static str; + fn get_integration_type(&self) -> Option; +} \ No newline at end of file diff --git a/backend/windmill-api/src/mcp/utils/schema.rs b/backend/windmill-api/src/mcp/utils/schema.rs new file mode 100644 index 0000000000..b11f2f83ab --- /dev/null +++ b/backend/windmill-api/src/mcp/utils/schema.rs @@ -0,0 +1,160 @@ +//! Schema transformation utilities for MCP server +//! +//! Contains functions for transforming Windmill schemas into MCP-compatible formats, +//! including resource enrichment and schema conversion utilities. + +use rmcp::Error; +use serde_json::Value; +use std::collections::HashMap; +use windmill_common::db::UserDB; +use windmill_common::scripts::Schema; + +use crate::db::ApiAuthed; +use super::models::{SchemaType, ResourceInfo, ResourceType}; +use super::database::get_resources; +use super::transform::apply_key_transformation; + +/// Convert a Windmill Schema to a SchemaType +pub fn convert_schema_to_schema_type(schema: Option) -> SchemaType { + let schema_obj = if let Some(ref s) = schema { + match serde_json::from_str::(s.0.get()) { + Ok(val) => val, + Err(_) => SchemaType::default(), + } + } else { + SchemaType::default() + }; + schema_obj +} + +/// Transform the schema for resources by enriching with resource information +pub async fn transform_schema_for_resources( + schema: &SchemaType, + user_db: &UserDB, + authed: &ApiAuthed, + w_id: &str, + resources_cache: &mut HashMap>, + resources_types: &Vec, +) -> Result { + let mut schema_obj: SchemaType = schema.clone(); + + // replace invalid char in property key with underscore + let replacements: Vec<(String, String, Value)> = schema_obj + .properties + .iter() + .filter_map(|(key, value)| { + if key.chars().any(|c| !c.is_alphanumeric() && c != '_') { + let new_key = apply_key_transformation(key); + Some((key.clone(), new_key, value.clone())) + } else { + None + } + }) + .collect(); + + for (old_key, new_key, value) in replacements { + schema_obj.properties.remove(&old_key); + schema_obj.properties.insert(new_key, value); + } + + for (_key, prop_value) in schema_obj.properties.iter_mut() { + if let Value::Object(prop_map) = prop_value { + // if property is a resource, fetch the resource type infos, and add each available resource to the description + if let Some(format_value) = prop_map.get("format") { + if let Value::String(format_str) = format_value { + if format_str.starts_with("resource-") { + let resource_type_key = + format_str.split("-").last().unwrap_or_default().to_string(); + let resource_type = resources_types + .iter() + .find(|rt| rt.name == resource_type_key); + let resource_type_obj = resource_type.cloned(); + + if !resources_cache.contains_key(&resource_type_key) { + let available_resources = get_resources( + user_db, + authed, + &w_id, + &resource_type_key, + ) + .await; + + match available_resources { + Ok(cache_data) => { + resources_cache + .insert(resource_type_key.clone(), cache_data); + } + Err(e) => { + tracing::error!( + "Failed to fetch resource cache data: {}", + e + ); + continue; // Skip this property if fetching failed + } + } + } + + if let Some(resource_cache) = resources_cache.get(&resource_type_key) { + let resources_count = resource_cache.len(); + let description = match resource_type_obj { + Some(resource_type_obj) => format!( + "This is a resource named `{}` with the following description: `{}`.\\nThe path of the resource should be used to specify the resource.\\n{}", + resource_type_obj.name, + resource_type_obj.description.as_deref().unwrap_or("No description"), + if resources_count == 0 { + "This resource does not have any available instances, you should create one from your windmill workspace." + } else if resources_count > 1 { + "This resource has multiple available instances, you should precisely select the one you want to use." + } else { + "There is 1 resource available." + } + ), + None => "An object parameter.".to_string() + }; + prop_map.insert( + "type".to_string(), + Value::String("string".to_string()), + ); + prop_map.insert( + "description".to_string(), + Value::String(description), + ); + if resources_count > 0 { + let resources_description = resource_cache + .iter() + .map(|resource| { + format!( + "{}: $res:{}", + resource + .description + .as_deref() + .unwrap_or("No title"), + resource.path + ) + }) + .collect::>() + .join("\\n"); + + prop_map.insert( + "description".to_string(), + Value::String(format!( + "{}\\nHere are the available resources, in the format title:path. Title can be empty. Path should be used to specify the resource:\\n{}", + prop_map.get("description").unwrap_or(&Value::String("No description".to_string())), + resources_description + )), + ); + } + } + } + } + } + } else { + tracing::warn!( + "Schema property value is not a JSON object: {:?}", + prop_value + ); + } + } + + Ok(schema_obj) +} \ No newline at end of file diff --git a/backend/windmill-api/src/mcp/utils/transform.rs b/backend/windmill-api/src/mcp/utils/transform.rs new file mode 100644 index 0000000000..6574173bc4 --- /dev/null +++ b/backend/windmill-api/src/mcp/utils/transform.rs @@ -0,0 +1,112 @@ +//! Transformation utilities for MCP server +//! +//! Contains functions for transforming paths, keys, and other identifiers +//! to make them compatible with MCP tool naming requirements. + +use super::models::SchemaType; + +/// Transform the path for workspace scripts/flows +/// +/// This function takes a path and a type string and formats the transformed +/// path with the type prefix. This is used when listing, because we can't +/// have names with slashes. Because we replace slashes with underscores, +/// we also need to escape underscores. +pub fn transform_path(path: &str, type_str: &str) -> String { + // Only apply special underscore escaping for paths starting with "f/" + let transformed = if path.starts_with("f/") { + let escaped_path = path.replace('_', "__"); + escaped_path.replace('/', "_") + } else { + path.replace('/', "_") + }; + + // first letter of type_str is used as prefix, only one letter to avoid reaching 60 char name limit + format!("{}-{}", &type_str[..1], transformed) +} + +/// Reverse the transformation of a path +/// +/// This function takes a transformed path and reverses the transformation +/// applied by `transform_path`. It checks if the path starts with "h" +/// (indicating a Hub script) and removes the prefix if present. +/// It then determines the type of the item (script or flow) based on the prefix. +/// This is used in call_tool to get the original path, and the type of the item. +/// +/// Returns: (type, original_path, is_hub) +pub fn reverse_transform(transformed_path: &str) -> Result<(&str, String, bool), String> { + let is_hub = transformed_path.starts_with("h"); + let transformed_path = if is_hub { + transformed_path[1..].to_string() + } else { + transformed_path.to_string() + }; + let type_str = if transformed_path.starts_with("s-") { + "script" + } else if transformed_path.starts_with("f-") { + "flow" + } else { + return Err(format!( + "Invalid prefix in transformed path: {}", + transformed_path + )); + }; + + let mangled_path = &transformed_path[2..]; + + // Check if this path was previously transformed with special underscore handling + let is_special_path = mangled_path.starts_with("f_"); + + let original_path = if is_hub { + let parts = mangled_path.split("-").collect::>(); + parts[0].to_string() + } else if is_special_path { + const TEMP_PLACEHOLDER: &str = "@@UNDERSCORE@@"; + let path_with_placeholder = mangled_path.replace("__", TEMP_PLACEHOLDER); + let path_with_slashes = path_with_placeholder.replace('_', "/"); + path_with_slashes.replace(TEMP_PLACEHOLDER, "_") + } else { + mangled_path.replacen('_', "/", 2) + }; + + Ok((type_str, original_path, is_hub)) +} + +/// Apply key transformation to a key +/// +/// This function takes a key and replaces spaces with underscores. +/// It also removes any characters that are not alphanumeric or underscores. +/// This is used when listing, because we can't have names with spaces +/// or special characters in the schema properties. +pub fn apply_key_transformation(key: &str) -> String { + key.replace(' ', "_") + .chars() + .filter(|c| c.is_alphanumeric() || *c == '_') + .collect::() +} + +/// Reverse the transformation of a key +/// +/// This function takes a transformed key and a schema object and reverses +/// the transformation applied by `apply_key_transformation`. This can be +/// subject to collisions, but it's unlikely and is ok for our use case. +pub fn reverse_transform_key(transformed_key: &str, schema_obj: &Option) -> String { + let schema_obj = match schema_obj { + Some(s) => s, + None => { + // No schema available, return the key as is (best guess) + return transformed_key.to_string(); + } + }; + + for original_key_in_schema in schema_obj.properties.keys() { + // Apply the SAME forward transformation to the schema key + let potential_transformed_key = apply_key_transformation(original_key_in_schema); + + // If it matches the key we received, we found the likely original + if potential_transformed_key == transformed_key { + return original_key_in_schema.clone(); + } + } + + transformed_key.to_string() +} \ No newline at end of file diff --git a/backend/windmill-api/src/mqtt_triggers.rs b/backend/windmill-api/src/mqtt_triggers.rs index ae62f662e7..5659c9b944 100644 --- a/backend/windmill-api/src/mqtt_triggers.rs +++ b/backend/windmill-api/src/mqtt_triggers.rs @@ -644,35 +644,34 @@ pub async fn list_mqtt_triggers( ) -> error::JsonResult> { let mut tx = user_db.begin(&authed).await?; let (per_page, offset) = paginate(Pagination { per_page: lst.per_page, page: lst.page }); - let mut sqlb = SqlBuilder::select_from("mqtt_trigger") - .fields(&[ - "mqtt_resource_path", - "subscribe_topics", - "v3_config", - "v5_config", - "client_version", - "client_id", - "workspace_id", - "path", - "script_path", - "is_flow", - "edited_by", - "email", - "edited_at", - "server_id", - "last_server_ping", - "extra_perms", - "error", - "enabled", - "error_handler_path", - "error_handler_args", - "retry", - ]) - .order_by("edited_at", true) - .and_where("workspace_id = ?".bind(&w_id)) - .offset(offset) - .limit(per_page) - .clone(); + let mut sqlb = SqlBuilder::select_from("mqtt_trigger"); + sqlb.fields(&[ + "mqtt_resource_path", + "subscribe_topics", + "v3_config", + "v5_config", + "client_version", + "client_id", + "workspace_id", + "path", + "script_path", + "is_flow", + "edited_by", + "email", + "edited_at", + "server_id", + "last_server_ping", + "extra_perms", + "error", + "enabled", + "error_handler_path", + "error_handler_args", + "retry", + ]) + .order_by("edited_at", true) + .and_where("workspace_id = ?".bind(&w_id)) + .offset(offset) + .limit(per_page); if let Some(path) = lst.path { sqlb.and_where_eq("script_path", "?".bind(&path)); } @@ -760,7 +759,9 @@ pub async fn update_mqtt_trigger( Json(mqtt_trigger): Json, ) -> error::Result { let workspace_path = path.to_path(); - check_scopes(&authed, || format!("mqtt_triggers:write:{}", workspace_path))?; + check_scopes(&authed, || { + format!("mqtt_triggers:write:{}", workspace_path) + })?; let EditMqttTrigger { mqtt_resource_path, diff --git a/backend/windmill-api/src/postgres_triggers/handler.rs b/backend/windmill-api/src/postgres_triggers/handler.rs index 44318200f4..71d53dcf8b 100644 --- a/backend/windmill-api/src/postgres_triggers/handler.rs +++ b/backend/windmill-api/src/postgres_triggers/handler.rs @@ -374,7 +374,9 @@ pub async fn create_postgres_trigger( Path(w_id): Path, Json(new_postgres_trigger): Json, ) -> Result<(StatusCode, String)> { - check_scopes(&authed, || format!("postgres_triggers:write:{}", new_postgres_trigger.path))?; + check_scopes(&authed, || { + format!("postgres_triggers:write:{}", new_postgres_trigger.path) + })?; if *CLOUD_HOSTED { return Err(error::Error::BadRequest( @@ -515,32 +517,31 @@ pub async fn list_postgres_triggers( ) -> error::JsonResult> { let mut tx = user_db.begin(&authed).await?; let (per_page, offset) = paginate(Pagination { per_page: lst.per_page, page: lst.page }); - let mut sqlb = SqlBuilder::select_from("postgres_trigger") - .fields(&[ - "workspace_id", - "path", - "script_path", - "is_flow", - "edited_by", - "email", - "edited_at", - "server_id", - "last_server_ping", - "extra_perms", - "error", - "enabled", - "postgres_resource_path", - "replication_slot_name", - "publication_name", - "error_handler_path", - "error_handler_args", - "retry", - ]) - .order_by("edited_at", true) - .and_where("workspace_id = ?".bind(&w_id)) - .offset(offset) - .limit(per_page) - .clone(); + let mut sqlb = SqlBuilder::select_from("postgres_trigger"); + sqlb.fields(&[ + "workspace_id", + "path", + "script_path", + "is_flow", + "edited_by", + "email", + "edited_at", + "server_id", + "last_server_ping", + "extra_perms", + "error", + "enabled", + "postgres_resource_path", + "replication_slot_name", + "publication_name", + "error_handler_path", + "error_handler_args", + "retry", + ]) + .order_by("edited_at", true) + .and_where("workspace_id = ?".bind(&w_id)) + .offset(offset) + .limit(per_page); if let Some(path) = lst.path { sqlb.and_where_eq("script_path", "?".bind(&path)); } @@ -1204,7 +1205,9 @@ pub async fn update_postgres_trigger( Json(postgres_trigger): Json, ) -> Result { let workspace_path = path.to_path(); - check_scopes(&authed, || format!("postgres_triggers:write:{}", workspace_path))?; + check_scopes(&authed, || { + format!("postgres_triggers:write:{}", workspace_path) + })?; let EditPostgresTrigger { replication_slot_name, @@ -1298,7 +1301,7 @@ pub async fn update_postgres_trigger( workspace_path, error_handler_path, error_handler_args as _, - retry as _, + retry as _ ) .execute(&mut *tx) .await?; diff --git a/backend/windmill-api/src/scripts.rs b/backend/windmill-api/src/scripts.rs index d47b1b2c12..ca2cec7ce1 100644 --- a/backend/windmill-api/src/scripts.rs +++ b/backend/windmill-api/src/scripts.rs @@ -734,6 +734,7 @@ async fn create_script_internal<'c>( || ns.language == ScriptLang::Nu || ns.language == ScriptLang::Php || ns.language == ScriptLang::Java + || ns.language == ScriptLang::Ruby // for related places search: ADD_NEW_LANG ) { Some(String::new()) diff --git a/backend/windmill-api/src/settings.rs b/backend/windmill-api/src/settings.rs index 59ec5e3569..245900b1db 100644 --- a/backend/windmill-api/src/settings.rs +++ b/backend/windmill-api/src/settings.rs @@ -27,17 +27,18 @@ use axum::extract::Query; #[cfg(feature = "enterprise")] use crate::utils::require_devops_role; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; #[cfg(feature = "enterprise")] use windmill_common::ee_oss::{send_critical_alert, CriticalAlertKind, CriticalErrorChannel}; use windmill_common::error::to_anyhow; use windmill_common::{ - email_oss::send_email, + email_oss::send_email_plain_text, error::{self, JsonResult, Result}, get_database_url, global_settings::{ - AUTOMATE_USERNAME_CREATION_SETTING, CRITICAL_ALERT_MUTE_UI_SETTING, EMAIL_DOMAIN_SETTING, - ENV_SETTINGS, HUB_ACCESSIBLE_URL_SETTING, HUB_BASE_URL_SETTING, + APP_WORKSPACED_ROUTE_SETTING, AUTOMATE_USERNAME_CREATION_SETTING, + CRITICAL_ALERT_MUTE_UI_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS, + HUB_ACCESSIBLE_URL_SETTING, HUB_BASE_URL_SETTING, }, parse_postgres_url, server::Smtp, @@ -105,7 +106,7 @@ pub async fn test_email( let to = test_email.to; let client_timeout = Duration::from_secs(3); - send_email( + send_email_plain_text( "Test email from Windmill", "Test email content", vec![to], @@ -255,6 +256,69 @@ pub async fn set_global_setting_internal( .await?; } } + APP_WORKSPACED_ROUTE_SETTING => { + let serde_json::Value::Bool(workspaced_route) = &value else { + return Err(error::Error::BadRequest(format!( + "{} setting Expected to be boolean", + APP_WORKSPACED_ROUTE_SETTING + ))); + }; + + if !*workspaced_route { + #[derive(Debug, Deserialize, Serialize)] + #[allow(unused)] + struct DuplicateApp { + custom_path: Option, + path: String, + } + let duplicate_app = sqlx::query_as!( + DuplicateApp, + r#" + SELECT + path, + custom_path + FROM + app + WHERE + custom_path IN ( + SELECT + custom_path + FROM + app + GROUP + BY custom_path + HAVING COUNT(*) > 1 + ) + ORDER BY custom_path + "# + ) + .fetch_all(db) + .await?; + + if !duplicate_app.is_empty() { + tracing::error!( + "Cannot disable {} setting as duplicate app with custom path were found: {:?}", + APP_WORKSPACED_ROUTE_SETTING, + &duplicate_app + ); + + #[derive(Serialize)] + struct ErrorResponse { + error: String, + details: Vec, + } + + let error_response = ErrorResponse { + error: "Duplicate custom paths detected".to_string(), + details: duplicate_app, + }; + + return Err(error::Error::JsonErr( + serde_json::to_value(error_response).unwrap(), + )); + } + } + } _ => {} } diff --git a/backend/windmill-api/src/triggers.rs b/backend/windmill-api/src/triggers.rs index 72d25328b3..82d189df5c 100644 --- a/backend/windmill-api/src/triggers.rs +++ b/backend/windmill-api/src/triggers.rs @@ -212,7 +212,7 @@ pub async fn list_tokens_internal( WHERE workspace_id = $1 AND ( scopes @> ARRAY['jobs:run:flows:' || $2]::text[] - OR scopes @> ARRAY['run:flows/' || $2]::text[] + OR scopes @> ARRAY['run:flow/' || $2]::text[] ) "#, w_id, @@ -235,7 +235,7 @@ pub async fn list_tokens_internal( WHERE workspace_id = $1 AND ( scopes @> ARRAY['jobs:run:scripts:' || $2]::text[] - OR scopes @> ARRAY['run:scripts/' || $2]::text[] + OR scopes @> ARRAY['run:script/' || $2]::text[] ) "#, w_id, diff --git a/backend/windmill-api/src/users.rs b/backend/windmill-api/src/users.rs index 4692eea71a..16d969c236 100644 --- a/backend/windmill-api/src/users.rs +++ b/backend/windmill-api/src/users.rs @@ -9,6 +9,7 @@ #![allow(non_snake_case)] use quick_cache::sync::Cache; +use sqlx::{Postgres, Transaction}; use std::sync::atomic::AtomicBool; use std::sync::Arc; @@ -262,6 +263,8 @@ pub struct User { pub operator: bool, pub disabled: bool, pub role: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub added_via: Option, } #[derive(Serialize)] @@ -494,7 +497,7 @@ async fn list_user_usage( WHERE workspace_id = $1 AND job_kind NOT IN ('flow', 'flowpreview', 'flownode') AND email = usr.email - AND now() - '1 week'::interval < created_at + AND now() - '1 week'::interval < created_at ) usage WHERE workspace_id = $1 ", @@ -786,8 +789,8 @@ async fn get_usage( ) -> Result { let usage = sqlx::query_scalar!( " - SELECT usage.usage FROM usage - WHERE is_workspace = false + SELECT usage.usage FROM usage + WHERE is_workspace = false AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date) AND id = $1", email @@ -810,6 +813,8 @@ pub struct User2 { pub role: Option, pub super_admin: bool, pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub added_via: Option, } async fn get_user(w_id: &str, username: &str, db: &DB) -> Result> { @@ -1315,49 +1320,15 @@ async fn update_workspace_user( require_admin(authed.is_admin, &authed.username)?; - if let Some(a) = eu.is_admin { - sqlx::query_scalar!( - "UPDATE usr SET is_admin = $1 WHERE username = $2 AND workspace_id = $3", - a, - &username_to_update, - &w_id - ) - .execute(&mut *tx) - .await?; - } - - if let Some(a) = eu.operator { - sqlx::query_scalar!( - "UPDATE usr SET operator = $1 WHERE username = $2 AND workspace_id = $3", - a, - &username_to_update, - &w_id - ) - .execute(&mut *tx) - .await?; - } - - if let Some(a) = eu.disabled { - sqlx::query_scalar!( - "UPDATE usr SET disabled = $1 WHERE username = $2 AND workspace_id = $3", - a, - &username_to_update, - &w_id - ) - .execute(&mut *tx) - .await?; - } - - audit_log( - &mut *tx, - &authed, - "users.update", - ActionKind::Update, + update_workspace_user_internal( &w_id, - Some(&username_to_update), - None, - ) - .await?; + &username_to_update, + eu.is_admin, + eu.operator, + eu.disabled, + &mut tx, + Some(&authed) + ).await?; let user_email = sqlx::query_scalar!( "SELECT email FROM usr WHERE username = $1 AND workspace_id = $2", @@ -1500,6 +1471,105 @@ async fn create_user( crate::users_oss::create_user(authed, db, webhook, argon2, nu).await } +/// Internal helper for updating workspace user permissions - used by both API and system operations +pub async fn update_workspace_user_internal( + w_id: &str, + username_to_update: &str, + is_admin: Option, + operator: Option, + disabled: Option, + tx: &mut Transaction<'_, Postgres>, + authed: Option<&ApiAuthed>, // None for system operations +) -> Result<()> { + if let Some(a) = is_admin { + sqlx::query_scalar!( + "UPDATE usr SET is_admin = $1 WHERE username = $2 AND workspace_id = $3", + a, + username_to_update, + w_id + ) + .execute(&mut **tx) + .await?; + } + if let Some(a) = operator { + sqlx::query_scalar!( + "UPDATE usr SET operator = $1 WHERE username = $2 AND workspace_id = $3", + a, + username_to_update, + w_id + ) + .execute(&mut **tx) + .await?; + } + if let Some(a) = disabled { + sqlx::query_scalar!( + "UPDATE usr SET disabled = $1 WHERE username = $2 AND workspace_id = $3", + a, + username_to_update, + w_id + ) + .execute(&mut **tx) + .await?; + } + + // Only audit if we have an authenticated user (API calls) + if let Some(auth) = authed { + audit_log( + &mut **tx, + auth, + "users.update", + ActionKind::Update, + w_id, + Some(username_to_update), + None, + ) + .await?; + } + + Ok(()) +} + +/// Internal helper for deleting workspace users - used by both API and system operations +pub async fn delete_workspace_user_internal( + w_id: &str, + username_to_delete: &str, + email_to_delete: &str, + tx: &mut Transaction<'_, Postgres>, + authed: Option<&ApiAuthed>, // None for system operations +) -> Result<()> { + sqlx::query_scalar!( + "DELETE FROM usr WHERE email = $1 AND workspace_id = $2", + email_to_delete, + w_id + ) + .execute(&mut **tx) + .await?; + + sqlx::query!( + "DELETE FROM usr_to_group WHERE usr = $1 AND workspace_id = $2", + username_to_delete, + w_id + ) + .execute(&mut **tx) + .await?; + + // Only audit if we have an authenticated user (API calls) + if let Some(auth) = authed { + audit_log( + &mut **tx, + auth, + "users.delete", + ActionKind::Delete, + w_id, + Some(username_to_delete), + None, + ) + .await?; + } + + Ok(()) +} + async fn delete_workspace_user( authed: ApiAuthed, Extension(db): Extension, @@ -1519,32 +1589,7 @@ async fn delete_workspace_user( let email_to_delete = not_found_if_none(email_to_delete_o, "User", &username_to_delete)?; - sqlx::query_scalar!( - "DELETE FROM usr WHERE email = $1 AND workspace_id = $2", - email_to_delete, - &w_id - ) - .execute(&mut *tx) - .await?; - - sqlx::query!( - "DELETE FROM usr_to_group WHERE usr = $1 AND workspace_id = $2", - &username_to_delete, - &w_id - ) - .execute(&mut *tx) - .await?; - - audit_log( - &mut *tx, - &authed, - "users.delete", - ActionKind::Delete, - &w_id, - Some(&username_to_delete), - None, - ) - .await?; + delete_workspace_user_internal(&w_id, &username_to_delete, &email_to_delete, &mut tx, Some(&authed)).await?; tx.commit().await?; handle_deployment_metadata( @@ -2096,8 +2141,8 @@ async fn get_all_runnables( })?; let mut tx = db.clone().begin(&nauthed).await?; let flows = sqlx::query!( - "SELECT flow.workspace_id as workspace, flow.path, summary, description, flow_version.schema - FROM flow + "SELECT flow.workspace_id as workspace, flow.path, summary, description, flow_version.schema + FROM flow LEFT JOIN flow_version ON flow_version.id = flow.versions[array_upper(flow.versions, 1)] WHERE flow.workspace_id = $1", workspace @@ -2580,9 +2625,9 @@ async fn update_username_in_workpsace<'c>( // ---- flows ---- sqlx::query!( r#"INSERT INTO flow - (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at) + (workspace_id, path, summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at) SELECT workspace_id, REGEXP_REPLACE(path,'u/' || $2 || '/(.*)','u/' || $1 || '/\1'), summary, description, archived, extra_perms, dependency_job, draft_only, tag, ws_error_handler_muted, dedicated_worker, timeout, visible_to_runner_only, on_behalf_of_email, concurrency_key, versions, value, schema, edited_by, edited_at - FROM flow + FROM flow WHERE path LIKE ('u/' || $2 || '/%') AND workspace_id = $3"#, new_username, old_username, diff --git a/backend/windmill-api/src/websocket_triggers.rs b/backend/windmill-api/src/websocket_triggers.rs index 87ecb87e09..a75652e555 100644 --- a/backend/windmill-api/src/websocket_triggers.rs +++ b/backend/windmill-api/src/websocket_triggers.rs @@ -24,7 +24,9 @@ use windmill_common::{ db::UserDB, error::{self, to_anyhow, JsonResult}, triggers::TriggerKind, - utils::{not_found_if_none, paginate, report_critical_error, Pagination, StripPath}, + utils::{ + empty_as_none, not_found_if_none, paginate, report_critical_error, Pagination, StripPath, + }, worker::{to_raw_value, CLOUD_HOSTED}, INSTANCE_NAME, }; @@ -64,6 +66,7 @@ struct NewWebsocketTrigger { initial_messages: Option>>, url_runnable_args: Option>, can_return_message: bool, + #[serde(default, deserialize_with = "empty_as_none")] error_handler_path: Option, error_handler_args: Option>>>, retry: Option>, @@ -152,13 +155,34 @@ async fn list_websocket_triggers( ) -> error::JsonResult> { let mut tx = user_db.begin(&authed).await?; let (per_page, offset) = paginate(Pagination { per_page: lst.per_page, page: lst.page }); - let mut sqlb = SqlBuilder::select_from("websocket_trigger") - .field("*") - .order_by("edited_at", true) - .and_where("workspace_id = ?".bind(&w_id)) - .offset(offset) - .limit(per_page) - .clone(); + let mut sqlb = SqlBuilder::select_from("websocket_trigger"); + + sqlb.fields(&[ + "workspace_id", + "path", + "url", + "script_path", + "is_flow", + "edited_by", + "email", + "edited_at", + "server_id", + "last_server_ping", + "extra_perms", + "error", + "enabled", + "filters", + "initial_messages", + "url_runnable_args", + "can_return_message", + "error_handler_path", + "error_handler_args", + "retry", + ]) + .order_by("edited_at", true) + .and_where("workspace_id = ?".bind(&w_id)) + .offset(offset) + .limit(per_page); if let Some(path) = lst.path { sqlb.and_where_eq("script_path", "?".bind(&path)); } @@ -188,14 +212,39 @@ async fn get_websocket_trigger( check_scopes(&authed, || format!("websocket_triggers:read:{}", path))?; let mut tx = user_db.begin(&authed).await?; let trigger = sqlx::query_as::<_, WebsocketTrigger>( - r#"SELECT * - FROM websocket_trigger - WHERE workspace_id = $1 AND path = $2"#, + r#" + SELECT + workspace_id, + path, + url, + script_path, + is_flow, + edited_by, + email, + edited_at, + server_id, + last_server_ping, + extra_perms, + error, + enabled, + filters, + initial_messages, + url_runnable_args, + can_return_message, + error_handler_path, + error_handler_args, + retry + FROM + websocket_trigger + WHERE + workspace_id = $1 AND path = $2 + "#, ) .bind(w_id) .bind(path) .fetch_optional(&mut *tx) .await?; + tx.commit().await?; let trigger = not_found_if_none(trigger, "Trigger", path)?; @@ -317,8 +366,29 @@ async fn update_websocket_trigger( // important to update server_id to NULL to stop current websocket listener sqlx::query!( - "UPDATE websocket_trigger SET url = $1, script_path = $2, path = $3, is_flow = $4, filters = $5, initial_messages = $6, url_runnable_args = $7, edited_by = $8, email = $9, can_return_message = $10, edited_at = now(), server_id = NULL, error = NULL, error_handler_path = $13, error_handler_args = $14, retry = $15 - WHERE workspace_id = $11 AND path = $12", + " + UPDATE + websocket_trigger + SET + url = $1, + script_path = $2, + path = $3, + is_flow = $4, + filters = $5, + initial_messages = $6, + url_runnable_args = $7, + edited_by = $8, + email = $9, + can_return_message = $10, + edited_at = now(), + server_id = NULL, + error = NULL, + error_handler_path = $13, + error_handler_args = $14, + retry = $15 + WHERE + workspace_id = $11 AND path = $12 + ", ct.url, ct.script_path, ct.path, @@ -333,9 +403,10 @@ async fn update_websocket_trigger( path, ct.error_handler_path, ct.error_handler_args as _, - ct.retry as _, + ct.retry as _ ) - .execute(&mut *tx).await?; + .execute(&mut *tx) + .await?; audit_log( &mut *tx, @@ -546,18 +617,45 @@ async fn listen_to_unlistened_websockets( db: &DB, killpill_rx: &tokio::sync::broadcast::Receiver<()>, ) { - match sqlx::query_as::<_, WebsocketTrigger>( - r#"SELECT * - FROM websocket_trigger - WHERE enabled IS TRUE AND (last_server_ping IS NULL OR last_server_ping < now() - interval '15 seconds')"# + let websocket_triggers = sqlx::query_as::<_, WebsocketTrigger>( + r#" + SELECT + workspace_id, + path, + url, + script_path, + is_flow, + edited_by, + email, + edited_at, + server_id, + last_server_ping, + extra_perms, + error, + enabled, + filters, + initial_messages, + url_runnable_args, + can_return_message, + error_handler_path, + error_handler_args, + retry + FROM websocket_trigger + WHERE + enabled IS TRUE + AND (last_server_ping IS NULL OR last_server_ping < now() - interval '15 seconds') + "#, ) .fetch_all(db) - .await - { + .await; + + match websocket_triggers { Ok(mut triggers) => { triggers.shuffle(&mut rand::rng()); for trigger in triggers { - trigger.maybe_listen_to_websocket(db.clone(), killpill_rx.resubscribe()).await; + trigger + .maybe_listen_to_websocket(db.clone(), killpill_rx.resubscribe()) + .await; } } Err(err) => { diff --git a/backend/windmill-api/src/workers.rs b/backend/windmill-api/src/workers.rs index 65db431d69..8e6b7a86fd 100644 --- a/backend/windmill-api/src/workers.rs +++ b/backend/windmill-api/src/workers.rs @@ -28,7 +28,7 @@ use crate::{db::ApiAuthed, utils::require_super_admin}; pub fn global_service() -> Router { Router::new() .route("/list", get(list_worker_pings)) - .route("/exists_worker_with_tag", get(exists_worker_with_tag)) + .route("/exists_workers_with_tags", get(exists_workers_with_tags)) .route("/custom_tags", get(get_custom_tags)) .route( "/is_default_tags_per_workspace", @@ -113,24 +113,38 @@ async fn list_worker_pings( } #[derive(Serialize, Deserialize)] -struct TagQuery { - tag: String, +struct TagsQuery { + tags: String, } -async fn exists_worker_with_tag( +async fn exists_workers_with_tags( authed: ApiAuthed, Extension(user_db): Extension, - Query(tag_query): Query, -) -> JsonResult { + Query(tags_query): Query, +) -> JsonResult> { let mut tx = user_db.begin(&authed).await?; - let row = sqlx::query!( - "SELECT EXISTS(SELECT 1 FROM worker_ping WHERE custom_tags @> $1 AND ping_at > now() - interval '1 minute')", - &[tag_query.tag] + let mut result = std::collections::HashMap::new(); + + // Create a query that checks all tags at once using unnest + let tags = tags_query + .tags + .split(',') + .map(|s| s.to_string()) + .collect::>(); + let rows = sqlx::query!( + "SELECT tag::text, EXISTS(SELECT 1 FROM worker_ping WHERE custom_tags @> ARRAY[tag] AND ping_at > now() - interval '1 minute') as exists + FROM unnest($1::text[]) as tag", + tags.as_slice() ) - .fetch_one(&mut *tx) + .fetch_all(&mut *tx) .await?; + + for row in rows { + result.insert(row.tag.unwrap_or_default(), row.exists.unwrap_or(false)); + } + tx.commit().await?; - Ok(Json(row.exists.unwrap_or(false))) + Ok(Json(result)) } #[derive(Deserialize)] diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 6f6650713e..78052b8ed8 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -101,6 +101,7 @@ pub fn workspaced_service() -> Router { ) .route("/edit_webhook", post(edit_webhook)) .route("/edit_auto_invite", post(edit_auto_invite)) + .route("/edit_instance_groups", post(edit_instance_groups)) .route("/edit_deploy_to", post(edit_deploy_to)) .route( "/get_secondary_storage_names", @@ -252,6 +253,10 @@ pub struct WorkspaceSettings { pub operator_settings: Option, #[serde(skip_serializing_if = "Option::is_none")] pub git_app_installations: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub auto_add_instance_groups: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub auto_add_instance_groups_roles: Option, } #[derive(sqlx::Type, Serialize, Deserialize, Debug)] @@ -381,6 +386,10 @@ pub struct EditErrorHandler { pub error_handler_muted_on_cancel: Option, } +lazy_static::lazy_static! { + pub static ref EMAIL_REGEXP: Regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap(); +} + async fn list_pending_invites( authed: ApiAuthed, Extension(user_db): Extension, @@ -457,15 +466,53 @@ async fn get_settings( let mut tx = user_db.begin(&authed).await?; let settings = sqlx::query_as!( WorkspaceSettings, - "SELECT workspace_id, slack_team_id, teams_team_id, teams_team_name, slack_name, slack_command_script, teams_command_script, slack_email, auto_invite_domain, auto_invite_operator, auto_add, customer_id, plan, webhook, deploy_to, ai_config, error_handler, error_handler_extra_args, error_handler_muted_on_cancel, large_file_storage, ducklake, git_sync, deploy_ui, default_app, default_scripts, mute_critical_alerts, color, operator_settings, git_app_installations FROM workspace_settings WHERE workspace_id = $1", + r#" + SELECT + workspace_id, + slack_team_id, + teams_team_id, + teams_team_name, + slack_name, + slack_command_script, + teams_command_script, + slack_email, + auto_invite_domain, + auto_invite_operator, + auto_add, + customer_id, + plan, + webhook, + deploy_to, + ai_config, + error_handler, + error_handler_extra_args, + error_handler_muted_on_cancel, + large_file_storage, + ducklake, + git_sync, + deploy_ui, + default_app, + default_scripts, + mute_critical_alerts, + color, + operator_settings, + git_app_installations, + auto_add_instance_groups, + auto_add_instance_groups_roles + FROM + workspace_settings + WHERE + workspace_id = $1 + "#, &w_id ) .fetch_optional(&mut *tx) .await .map_err(|e| Error::internal_err(format!("getting settings: {e:#}")))?; - tx.commit().await?; - let settings = not_found_if_none(settings, "workspace settings", &w_id)?; + tx.commit().await?; + + let settings = not_found_if_none(settings, "workspace settings", &w_id)?; Ok(Json(settings)) } @@ -686,6 +733,28 @@ async fn edit_auto_invite( crate::workspaces_oss::edit_auto_invite(authed, db, w_id, ea).await } +#[cfg(feature = "private")] +async fn edit_instance_groups( + authed: ApiAuthed, + Extension(db): Extension, + Path(w_id): Path, + Json(config): Json, +) -> Result { + crate::workspaces_ee::edit_instance_groups(authed, db, w_id, config).await +} + +#[cfg(not(feature = "private"))] +async fn edit_instance_groups( + _authed: ApiAuthed, + Extension(_db): Extension, + Path(_w_id): Path, + Json(_config): Json, +) -> Result { + Err(Error::BadRequest( + "Instance groups are only available on Windmill Enterprise Edition".to_string(), + )) +} + async fn edit_webhook( authed: ApiAuthed, Extension(db): Extension, @@ -1645,8 +1714,46 @@ async fn edit_error_handler( .await?; if let Some(error_handler) = &ee.error_handler { + match ee.error_handler_extra_args.as_ref() { + Some(extra_args) if extra_args.is_object() => { + let Ok(email_recipients) = serde_json::from_value::>>( + extra_args["email_recipients"].to_owned(), + ) else { + return Err(Error::BadRequest( + "Field `email_recipients` expected to be JSON array".to_string(), + )); + }; + + if let Some(email_recipients) = email_recipients { + for email in email_recipients { + if !EMAIL_REGEXP.is_match(&email) { + return Err(Error::BadRequest(format!( + "Invalid email format: {}", + email + ))); + } + } + } + } + None => {} + _ => { + return Err(Error::BadRequest( + "Field `error_handler_extra_args` expected to be JSON object".to_string(), + )) + } + } + sqlx::query!( - "UPDATE workspace_settings SET error_handler = $1, error_handler_extra_args = $2, error_handler_muted_on_cancel = $3 WHERE workspace_id = $4", + r#" + UPDATE + workspace_settings + SET + error_handler = $1, + error_handler_extra_args = $2, + error_handler_muted_on_cancel = $3 + WHERE + workspace_id = $4 + "#, error_handler, ee.error_handler_extra_args, ee.error_handler_muted_on_cancel.unwrap_or(false), @@ -1656,12 +1763,22 @@ async fn edit_error_handler( .await?; } else { sqlx::query!( - "UPDATE workspace_settings SET error_handler = NULL, error_handler_extra_args = NULL WHERE workspace_id = $1", - &w_id, + r#" + UPDATE + workspace_settings + SET + error_handler = NULL, + error_handler_extra_args = NULL, + error_handler_muted_on_cancel = NULL + WHERE + workspace_id = $1 + "#, + &w_id ) .execute(&mut *tx) .await?; } + audit_log( &mut *tx, &authed, diff --git a/backend/windmill-api/src/workspaces_export.rs b/backend/windmill-api/src/workspaces_export.rs index 88475dd129..1b1f107be6 100644 --- a/backend/windmill-api/src/workspaces_export.rs +++ b/backend/windmill-api/src/workspaces_export.rs @@ -377,6 +377,7 @@ pub(crate) async fn tarball_workspace( ScriptLang::Nu => "nu", ScriptLang::OracleDB => "odb.sql", ScriptLang::Java => "java", + ScriptLang::Ruby => "rb", // for related places search: ADD_NEW_LANG }; archive @@ -564,8 +565,10 @@ pub(crate) async fn tarball_workspace( error_handler_path, error_handler_args as "error_handler_args: _", retry as "retry: _" - FROM http_trigger - WHERE workspace_id = $1 + FROM + http_trigger + WHERE + workspace_id = $1 "#, &w_id ) @@ -649,7 +652,7 @@ pub(crate) async fn tarball_workspace( enabled, error_handler_path, error_handler_args as "error_handler_args: _", - retry as "retry: _" + retry as "retry: _" FROM kafka_trigger WHERE workspace_id = $1"#, &w_id @@ -737,7 +740,8 @@ pub(crate) async fn tarball_workspace( enabled, error_handler_path, error_handler_args as "error_handler_args: _", - retry as "retry: _" + retry as "retry: _", + auto_acknowledge_msg FROM gcp_trigger WHERE @@ -760,7 +764,8 @@ pub(crate) async fn tarball_workspace( { let nats_triggers = sqlx::query_as!( crate::nats_triggers_oss::NatsTrigger, - r#"SELECT + r#" + SELECT workspace_id, path, nats_resource_path, @@ -781,8 +786,11 @@ pub(crate) async fn tarball_workspace( error_handler_path, error_handler_args as "error_handler_args: _", retry as "retry: _" - FROM nats_trigger - WHERE workspace_id = $1"#, + FROM + nats_trigger + WHERE + workspace_id = $1 + "#, &w_id ) .fetch_all(&mut *tx) @@ -801,7 +809,8 @@ pub(crate) async fn tarball_workspace( { let postgres_triggers = sqlx::query_as!( crate::postgres_triggers::PostgresTrigger, - r#"SELECT + r#" + SELECT workspace_id, path, script_path, @@ -819,9 +828,12 @@ pub(crate) async fn tarball_workspace( postgres_resource_path, error_handler_path, error_handler_args as "error_handler_args: _", - retry as "retry: _" - FROM postgres_trigger - WHERE workspace_id = $1"#, + retry as "retry: _" + FROM + postgres_trigger + WHERE + workspace_id = $1 + "#, &w_id ) .fetch_all(&mut *tx) diff --git a/backend/windmill-common/src/agent_workers.rs b/backend/windmill-common/src/agent_workers.rs index 52c7e2d70c..74626522e6 100644 --- a/backend/windmill-common/src/agent_workers.rs +++ b/backend/windmill-common/src/agent_workers.rs @@ -14,8 +14,6 @@ use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware}; use crate::{jwt::decode_without_verify, worker::HttpClient}; lazy_static! { - pub static ref BASE_INTERNAL_URL: String = - std::env::var("BASE_INTERNAL_URL").unwrap_or("http://localhost:8080".to_string()); pub static ref AGENT_TOKEN: String = std::env::var("AGENT_TOKEN").unwrap_or_default(); pub static ref DECODED_AGENT_TOKEN: Option = { if AGENT_TOKEN.is_empty() { diff --git a/backend/windmill-common/src/apps.rs b/backend/windmill-common/src/apps.rs index c5b22d434c..b4552111e0 100644 --- a/backend/windmill-common/src/apps.rs +++ b/backend/windmill-common/src/apps.rs @@ -6,9 +6,14 @@ * LICENSE-AGPL for a copy of the license. */ -use std::collections::HashMap; +use std::{collections::HashMap, sync::Arc}; use serde::{Deserialize, Serialize}; +use tokio::sync::RwLock; + +lazy_static::lazy_static! { + pub static ref APP_WORKSPACED_ROUTE: Arc> = Arc::new(RwLock::new(false)); +} /// Id in the `app_script` table. #[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Eq, PartialEq)] diff --git a/backend/windmill-common/src/auth.rs b/backend/windmill-common/src/auth.rs index a2de086e22..543d6eb462 100644 --- a/backend/windmill-common/src/auth.rs +++ b/backend/windmill-common/src/auth.rs @@ -297,25 +297,41 @@ pub async fn create_token_for_owner( } }; + create_jwt_token(job_authed, w_id, expires_in, Some(*job_id), Some(label.to_string()), audit_span, None) + .await +} + +pub async fn create_jwt_token( + authed: Authed, + workspace_id: &str, + expires_in_seconds: u64, + job_id: Option, + label: Option, + audit_span: Option, + scopes: Option>, +) -> crate::error::Result { let payload = JWTAuthClaims { - email: job_authed.email, - username: job_authed.username, - is_admin: job_authed.is_admin, - is_operator: job_authed.is_operator, - groups: job_authed.groups, - folders: job_authed.folders, - label: Some(label.to_string()), - workspace_id: w_id.to_string(), - exp: (chrono::Utc::now() + chrono::Duration::seconds(expires_in as i64)).timestamp() - as usize, - job_id: Some(job_id.to_string()), - scopes: None, + email: authed.email.clone(), + username: authed.username.clone(), + is_admin: authed.is_admin, + is_operator: authed.is_operator, + groups: authed.groups.clone(), + folders: authed.folders.clone(), + label, + workspace_id: workspace_id.to_string(), + exp: (chrono::Utc::now() + chrono::Duration::seconds(expires_in_seconds as i64)) + .timestamp() as usize, + job_id: job_id.map(|id| id.to_string()), + scopes, audit_span, }; let token = jwt::encode_with_internal_secret(&payload) .await - .with_context(|| format!("Could not encode JWT token for job {job_id}"))?; + .with_context(|| match job_id { + Some(job_id) => format!("Could not encode JWT token for job {job_id}"), + None => "Could not encode JWT token".to_string(), + })?; Ok(format!("jwt_{}", token)) } diff --git a/backend/windmill-common/src/cache.rs b/backend/windmill-common/src/cache.rs index 03d8ec7f03..4129948268 100644 --- a/backend/windmill-common/src/cache.rs +++ b/backend/windmill-common/src/cache.rs @@ -797,11 +797,12 @@ pub mod job { #[track_caller] pub fn fetch_script( db: DB, - kind: JobKind, + kind: &JobKind, hash: Option, ) -> impl Future>> { use JobKind::*; let loc = Location::caller(); + let kind = kind.clone(); async move { match (kind, hash.map(|ScriptHash(id)| id)) { (FlowScript, Some(id)) => { @@ -825,11 +826,12 @@ pub mod job { #[track_caller] pub fn fetch_flow<'c>( db: &'c DB, - kind: JobKind, + kind: &JobKind, hash: Option, ) -> impl Future>> + 'c { use JobKind::*; let loc = Location::caller(); + let kind = kind.clone(); async move { match (kind, hash.map(|ScriptHash(id)| id)) { (FlowDependencies, Some(id)) => flow::fetch_version(db, id).await, diff --git a/backend/windmill-common/src/email_oss.rs b/backend/windmill-common/src/email_oss.rs index e6a340523e..05eb6bf243 100644 --- a/backend/windmill-common/src/email_oss.rs +++ b/backend/windmill-common/src/email_oss.rs @@ -15,3 +15,25 @@ pub async fn send_email( ) -> crate::error::Result<()> { Ok(()) } + +#[cfg(not(feature = "private"))] +pub async fn send_email_html( + _subject: &str, + _content: &str, + _to: Vec, + _smtp: Smtp, + _client_timeout: Option, +) -> crate::error::Result<()> { + Ok(()) +} + +#[cfg(not(feature = "private"))] +pub async fn send_email_plain_text( + _subject: &str, + _content: &str, + _to: Vec, + _smtp: Smtp, + _client_timeout: Option, +) -> crate::error::Result<()> { + Ok(()) +} diff --git a/backend/windmill-common/src/error.rs b/backend/windmill-common/src/error.rs index e5c95b7d82..e2e4ed4abc 100644 --- a/backend/windmill-common/src/error.rs +++ b/backend/windmill-common/src/error.rs @@ -181,6 +181,13 @@ impl From for Error { } } +impl From for Error { + #[track_caller] + fn from(e: url::ParseError) -> Self { + Self::ArgumentErr(format!("Cannot parse provided url. \ne: {e}")) + } +} + impl Error { /// https://docs.rs/anyhow/1/anyhow/struct.Error.html#display-representations pub fn alt(&self) -> String { diff --git a/backend/windmill-common/src/global_settings.rs b/backend/windmill-common/src/global_settings.rs index 895384e855..c26bdc2f7e 100644 --- a/backend/windmill-common/src/global_settings.rs +++ b/backend/windmill-common/src/global_settings.rs @@ -13,6 +13,7 @@ pub const BUNFIG_INSTALL_SCOPES_SETTING: &str = "bunfig_install_scopes"; pub const NUGET_CONFIG_SETTING: &str = "nuget_config"; pub const MAVEN_REPOS_SETTING: &str = "maven_repos"; pub const NO_DEFAULT_MAVEN_SETTING: &str = "no_default_maven"; +pub const RUBY_REPOS_SETTING: &str = "ruby_repos"; pub const EXTRA_PIP_INDEX_URL_SETTING: &str = "pip_extra_index_url"; pub const PIP_INDEX_URL_SETTING: &str = "pip_index_url"; @@ -42,6 +43,7 @@ pub const DEV_INSTANCE_SETTING: &str = "dev_instance"; pub const JWT_SECRET_SETTING: &str = "jwt_secret"; pub const EMAIL_DOMAIN_SETTING: &str = "email_domain"; pub const OTEL_SETTING: &str = "otel"; +pub const APP_WORKSPACED_ROUTE_SETTING: &str = "app_workspaced_route"; pub const ENV_SETTINGS: &[&str] = &[ "DISABLE_NSJAIL", @@ -65,6 +67,10 @@ pub const ENV_SETTINGS: &[&str] = &[ "DENO_PATH", "GO_PATH", "JAVA_PATH", + "RUBY_PATH", + "BUNDLE_PATH", + "GEM_PATH", + "RUBY_CONCURRENT_DOWNLOADS", // for related places search: ADD_NEW_LANG "GOPRIVATE", "GOPROXY", diff --git a/backend/windmill-common/src/jobs.rs b/backend/windmill-common/src/jobs.rs index 2b4a9a3067..c2be3abf6d 100644 --- a/backend/windmill-common/src/jobs.rs +++ b/backend/windmill-common/src/jobs.rs @@ -12,6 +12,8 @@ use uuid::Uuid; pub const ENTRYPOINT_OVERRIDE: &str = "_ENTRYPOINT_OVERRIDE"; pub const LARGE_LOG_THRESHOLD_SIZE: usize = 9000; +pub const EMAIL_ERROR_HANDLER_USER_EMAIL: &str = "email_error_handler@windmill.dev"; + use crate::{ apps::AppScriptId, auth::is_super_admin_email, diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index a1bb0babf5..4b5408289f 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -82,6 +82,7 @@ pub mod variables; pub mod worker; pub mod workspaces; pub mod triggers; +pub mod result_stream; pub mod stream; pub const DEFAULT_MAX_CONNECTIONS_SERVER: u32 = 50; @@ -134,6 +135,7 @@ lazy_static::lazy_static! { pub static ref BASE_URL: Arc> = Arc::new(RwLock::new("".to_string())); pub static ref IS_READY: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); + pub static ref BASE_INTERNAL_URL: String = std::env::var("BASE_INTERNAL_URL").unwrap_or("http://localhost:8000".to_string()); pub static ref HUB_BASE_URL: Arc> = Arc::new(RwLock::new(DEFAULT_HUB_BASE_URL.to_string())); diff --git a/backend/windmill-common/src/result_stream.rs b/backend/windmill-common/src/result_stream.rs new file mode 100644 index 0000000000..4daa0cbd40 --- /dev/null +++ b/backend/windmill-common/src/result_stream.rs @@ -0,0 +1,33 @@ +use uuid::Uuid; +use crate::{error, DB}; + +pub const STREAM_PREFIX: &str = "WM_STREAM: "; + +pub fn extract_stream_from_logs(line: &str) -> Option { + if line.starts_with(STREAM_PREFIX) { + // Extract the content after "WM_STREAM:" prefix + let stream_content = line.strip_prefix(STREAM_PREFIX).unwrap_or(""); + if !stream_content.is_empty() { + return Some(stream_content.to_string().replace("\\n", "\n")); + } + } + None +} + + + +pub async fn append_result_stream_db(db: &DB, workspace_id: &str, job_id: &Uuid, nstream: &str) -> error::Result<()> { + if !nstream.is_empty() { + sqlx::query!( + r#" + INSERT INTO job_result_stream (workspace_id, job_id, stream) + VALUES ($1, $2, $3) + ON CONFLICT (job_id) DO UPDATE SET stream = job_result_stream.stream || $3 + "#, + workspace_id, + job_id, + nstream, + ).execute(db).await?; + } + Ok(()) +} diff --git a/backend/windmill-common/src/schema.rs b/backend/windmill-common/src/schema.rs index 71ecd220d1..d16b86d507 100644 --- a/backend/windmill-common/src/schema.rs +++ b/backend/windmill-common/src/schema.rs @@ -348,7 +348,7 @@ pub fn should_validate_schema(code: &str, lang: &ScriptLang) -> bool { use ScriptLang::*; let comment = match lang { Nativets | Bun | Bunnative | Deno | Php | CSharp | Java => "//", - Python3 | Go | Bash | Powershell | Graphql | Ansible | Nu => "#", + Python3 | Go | Bash | Powershell | Graphql | Ansible | Nu | Ruby => "#", Postgresql | Mysql | Bigquery | Snowflake | Mssql | OracleDB | DuckDb => "--", Rust => "//!", // for related places search: ADD_NEW_LANG diff --git a/backend/windmill-common/src/scripts.rs b/backend/windmill-common/src/scripts.rs index ff2f1865af..7633e60e25 100644 --- a/backend/windmill-common/src/scripts.rs +++ b/backend/windmill-common/src/scripts.rs @@ -55,7 +55,9 @@ pub enum ScriptLang { Ansible, CSharp, Nu, - Java, // for related places search: ADD_NEW_LANG + Java, + Ruby, + // for related places search: ADD_NEW_LANG } impl ScriptLang { @@ -83,6 +85,7 @@ impl ScriptLang { ScriptLang::CSharp => "csharp", ScriptLang::Nu => "nu", ScriptLang::Java => "java", + ScriptLang::Ruby => "ruby", // for related places search: ADD_NEW_LANG } } @@ -113,6 +116,8 @@ impl FromStr for ScriptLang { "csharp" => ScriptLang::CSharp, "nu" => ScriptLang::Nu, "java" => ScriptLang::Java, + "ruby" => ScriptLang::Ruby, + // for related places search: ADD_NEW_LANG language => { return Err(anyhow::anyhow!("{} is currently not supported", language).into()) } diff --git a/backend/windmill-common/src/utils.rs b/backend/windmill-common/src/utils.rs index 0abed36d9b..6486819e61 100644 --- a/backend/windmill-common/src/utils.rs +++ b/backend/windmill-common/src/utils.rs @@ -49,6 +49,7 @@ use std::sync::atomic::Ordering; use crate::worker::CLOUD_HOSTED; lazy_static::lazy_static! { + pub static ref HTTP_CLIENT: Client = reqwest::ClientBuilder::new() .user_agent("windmill/beta") .timeout(std::time::Duration::from_secs(20)) diff --git a/backend/windmill-common/src/worker.rs b/backend/windmill-common/src/worker.rs index e10a450055..50ed13ff7b 100644 --- a/backend/windmill-common/src/worker.rs +++ b/backend/windmill-common/src/worker.rs @@ -18,21 +18,22 @@ use std::{ path::{Component, Path, PathBuf}, str::FromStr, sync::{atomic::AtomicBool, Arc}, + time::Duration, }; #[cfg(windows)] use sysinfo::System; -use tokio::sync::RwLock; +use tokio::{sync::RwLock, time::timeout}; use uuid::Uuid; use windmill_macros::annotations; use crate::{ - agent_workers::{PingJobStatusResponse, BASE_INTERNAL_URL}, + agent_workers::PingJobStatusResponse, cache::{unwrap_or_error, RawNode, RawScript}, error::{self, to_anyhow}, global_settings::CUSTOM_TAGS_SETTING, indexer::TantivyIndexerSettings, server::Smtp, - KillpillSender, DB, + KillpillSender, BASE_INTERNAL_URL, DB, }; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)] @@ -178,6 +179,7 @@ lazy_static::lazy_static! { "csharp".to_string(), "nu".to_string(), "java".to_string(), + "ruby".to_string(), "duckdb".to_string(), // for related places search: ADD_NEW_LANG "dependency".to_string(), @@ -621,6 +623,12 @@ fn parse_file(path: &str) -> Option { .flatten() } +#[derive(Copy, Clone)] +#[annotations("#")] +pub struct RubyAnnotations { + pub verbose: bool, +} + #[derive(Copy, Clone)] #[annotations("#")] pub struct PythonAnnotations { @@ -1359,7 +1367,7 @@ pub async fn update_worker_ping_main_loop_query( occupancy_rate_30m: Option, db: &DB, ) -> anyhow::Result<()> { - sqlx::query!( + timeout(Duration::from_secs(10), sqlx::query!( "UPDATE worker_ping SET ping_at = now(), jobs_executed = $1, custom_tags = $2, occupancy_rate = $3, memory_usage = $4, wm_memory_usage = $5, vcpus = COALESCE($7, vcpus), memory = COALESCE($8, memory), occupancy_rate_15s = $9, occupancy_rate_5m = $10, occupancy_rate_30m = $11 WHERE worker = $6", @@ -1375,8 +1383,8 @@ pub async fn update_worker_ping_main_loop_query( occupancy_rate_5m, occupancy_rate_30m, ) - .execute(db) - .await?; + .execute(db)) + .await??; Ok(()) } diff --git a/backend/windmill-queue/Cargo.toml b/backend/windmill-queue/Cargo.toml index 4458e8aa99..b50773f9c8 100644 --- a/backend/windmill-queue/Cargo.toml +++ b/backend/windmill-queue/Cargo.toml @@ -15,6 +15,7 @@ enterprise = ["windmill-common/enterprise"] cloud = [] benchmark = ["windmill-common/benchmark"] prometheus = ["dep:prometheus"] +smtp = [] [dependencies] windmill-audit.workspace = true diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 5334a1ac90..1b5a67e99f 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -33,8 +33,11 @@ use windmill_common::add_time; use windmill_common::auth::JobPerms; #[cfg(feature = "benchmark")] use windmill_common::bench::BenchmarkIter; +use windmill_common::jobs::EMAIL_ERROR_HANDLER_USER_EMAIL; use windmill_common::utils::now_from_db; use windmill_common::worker::{Connection, SCRIPT_TOKEN_EXPIRY}; +#[cfg(feature = "enterprise")] +use windmill_common::BASE_URL; use windmill_common::{ auth::{fetch_authed_from_permissioned_as, permissioned_as_to_username}, cache::{self, FlowData}, @@ -63,16 +66,12 @@ use windmill_common::{ use backon::ConstantBuilder; use backon::{BackoffBuilder, Retryable}; -#[cfg(feature = "enterprise")] -use windmill_common::BASE_URL; - -#[cfg(feature = "cloud")] -use windmill_common::users::SUPERADMIN_SYNC_EMAIL; - use crate::flow_status::{update_flow_status_in_progress, update_workflow_as_code_status}; use crate::jobs_oss::update_concurrency_counter; use crate::schedule::{get_schedule_opt, push_scheduled_job}; use crate::tags::per_workspace_tag; +#[cfg(feature = "cloud")] +use windmill_common::users::SUPERADMIN_SYNC_EMAIL; #[cfg(feature = "prometheus")] lazy_static::lazy_static! { @@ -121,6 +120,7 @@ const MAX_FREE_CONCURRENT_RUNS: i32 = 30; const ERROR_HANDLER_USERNAME: &str = "error_handler"; const SCHEDULE_ERROR_HANDLER_USERNAME: &str = "schedule_error_handler"; +const GLOBAL_ERROR_HANDLER_USERNAME: &str = "global"; #[cfg(feature = "enterprise")] const SCHEDULE_RECOVERY_HANDLER_USERNAME: &str = "schedule_recovery_handler"; const ERROR_HANDLER_USER_GROUP: &str = "g/error_handler"; @@ -298,7 +298,9 @@ ORDER BY depth, id .collect_vec(); jobs_to_cancel.reverse(); - tracing::info!("Found {} child jobs to cancel", jobs_to_cancel.len()); + if !jobs_to_cancel.is_empty() { + tracing::info!("Found {} child jobs to cancel", jobs_to_cancel.len()); + } let (ntx, _) = cancel_single_job( username, @@ -1281,7 +1283,7 @@ async fn restart_job_if_perpetual_inner( #[cfg(feature = "enterprise")] async fn has_failure_module(db: &Pool, job: &MiniPulledJob) -> bool { - if let Ok(flow) = cache::job::fetch_flow(db, job.kind, job.runnable_id).await { + if let Ok(flow) = cache::job::fetch_flow(db, &job.kind, job.runnable_id).await { return flow.value().failure_module.is_some(); } sqlx::query_scalar!( @@ -1360,6 +1362,7 @@ async fn apply_completed_job_error_handlers {} - _ => { + err => { + tracing::error!( + "Could not run workspace error handler for job {}: {}", + &queued_job.id, + err + ); let base_url = BASE_URL.read().await; let w_id: &String = &queued_job.workspace_id; report_critical_error(format!( - "Could not push workspace error handler for failed job ({base_url}/run/{}?workspace={w_id}): {}", + "Failed to push workspace error handler job to handle failed job ({base_url}/run/{}?workspace={w_id}): {}", queued_job.id, err ), db.clone(), Some(&w_id), None) @@ -1485,8 +1493,16 @@ pub async fn report_error_to_workspace_handler_or_critical_side_channel( error_message: String, ) -> () { let w_id = &queued_job.workspace_id; - let (error_handler, error_handler_extra_args) = sqlx::query_as::<_, (Option, Option>>)>( - "SELECT error_handler, error_handler_extra_args FROM workspace_settings WHERE workspace_id = $1", + let row_result = sqlx::query_as::<_, (Option, Option>>)>( + r#" + SELECT + error_handler, + error_handler_extra_args + FROM + workspace_settings + WHERE + workspace_id = $1 + "#, ) .bind(&w_id) .fetch_optional(db) @@ -1495,6 +1511,8 @@ pub async fn report_error_to_workspace_handler_or_critical_side_channel( .flatten() .unwrap_or((None, None)); + let (error_handler, error_handler_extra_args) = row_result; + if let Some(error_handler) = error_handler { if let Err(err) = push_error_handler( db, @@ -1538,8 +1556,18 @@ pub async fn send_error_to_workspace_handler<'a, 'c, T: Serialize + Send + Sync> result: Json<&'a T>, ) -> Result<(), Error> { let w_id = &queued_job.workspace_id; - let (error_handler, error_handler_extra_args, error_handler_muted_on_cancel) = sqlx::query_as::<_, (Option, Option>>, bool)>( - "SELECT error_handler, error_handler_extra_args, error_handler_muted_on_cancel FROM workspace_settings WHERE workspace_id = $1", + + let row_result = sqlx::query_as::<_, (Option, Option>>, bool)>( + r#" + SELECT + error_handler, + error_handler_extra_args, + error_handler_muted_on_cancel + FROM + workspace_settings + WHERE + workspace_id = $1 + "#, ) .bind(&w_id) .fetch_optional(db) @@ -1547,6 +1575,8 @@ pub async fn send_error_to_workspace_handler<'a, 'c, T: Serialize + Send + Sync> .context("fetching error handler info from workspace_settings")? .ok_or_else(|| Error::internal_err(format!("no workspace settings for id {w_id}")))?; + let (error_handler, error_handler_extra_args, error_handler_muted_on_cancel) = row_result; + if is_canceled && error_handler_muted_on_cancel { return Ok(()); } @@ -1598,7 +1628,6 @@ pub async fn send_error_to_workspace_handler<'a, 'c, T: Serialize + Send + Sync> .await?; } } - Ok(()) } @@ -1846,6 +1875,71 @@ async fn apply_schedule_handlers<'a, 'c, T: Serialize + Send + Sync>( Ok(()) } +pub const ERROR_HANDLER_PATH_TEAMS: &str = "/workspace-or-schedule-error-handler-teams"; +pub const ERROR_HANDLER_PATH_SLACK: &str = "/workspace-or-schedule-error-handler-slack"; +pub const ERROR_HANDLER_PATH_EMAIL: &str = "/workspace-or-error-handler-email"; + +enum ErrorHandlerType { + Custom, + Teams, + Slack, + Email, +} + +impl ErrorHandlerType { + fn from_error_handler_path(error_handler_path: &str) -> Option { + let error_handler_path = if error_handler_path.starts_with("script/") { + error_handler_path.strip_prefix("script/").unwrap() + } else if error_handler_path.starts_with("flow/") { + error_handler_path.strip_prefix("flow/").unwrap() + } else { + error_handler_path + }; + + if let Some(from_hub) = error_handler_path.strip_prefix("hub/") { + let handler_type = if from_hub.ends_with(ERROR_HANDLER_PATH_TEAMS) { + ErrorHandlerType::Teams + } else if from_hub.ends_with(ERROR_HANDLER_PATH_SLACK) { + ErrorHandlerType::Slack + } else if from_hub.ends_with(ERROR_HANDLER_PATH_EMAIL) { + ErrorHandlerType::Email + } else { + return None; + }; + + return Some(handler_type); + } + + Some(ErrorHandlerType::Custom) + } +} + +fn get_email_and_permissioned_as( + error_handler_path: &str, + is_global_error_handler: bool, + is_schedule_error_handler: bool, +) -> (&'static str, String) { + let res = if is_global_error_handler { + (SUPERADMIN_SECRET_EMAIL, SUPERADMIN_SECRET_EMAIL.to_string()) + } else if is_schedule_error_handler { + ( + SCHEDULE_ERROR_HANDLER_USER_EMAIL, + ERROR_HANDLER_USER_GROUP.to_string(), + ) + } else { + let handler_type = ErrorHandlerType::from_error_handler_path(error_handler_path); + + let email = match handler_type { + Some(ErrorHandlerType::Email) => EMAIL_ERROR_HANDLER_USER_EMAIL, + _ => ERROR_HANDLER_USER_EMAIL, + }; + + (email, ERROR_HANDLER_USER_GROUP.to_string()) + }; + + res +} + pub async fn push_error_handler<'a, 'c, T: Serialize + Send + Sync>( db: &Pool, job_id: Uuid, @@ -1881,6 +1975,7 @@ pub async fn push_error_handler<'a, 'c, T: Serialize + Send + Sync>( extra.insert("is_flow".to_string(), to_raw_value(&is_flow)); extra.insert("started_at".to_string(), to_raw_value(&started_at)); extra.insert("email".to_string(), to_raw_value(&email)); + if let Some(failed_times) = failed_times { extra.insert("failed_times".to_string(), to_raw_value(&failed_times)); } @@ -1902,17 +1997,11 @@ pub async fn push_error_handler<'a, 'c, T: Serialize + Send + Sync>( on_behalf_of.email.as_str(), on_behalf_of.permissioned_as.clone(), ) - } else if is_global_error_handler { - (SUPERADMIN_SECRET_EMAIL, SUPERADMIN_SECRET_EMAIL.to_string()) - } else if is_schedule_error_handler { - ( - SCHEDULE_ERROR_HANDLER_USER_EMAIL, - ERROR_HANDLER_USER_GROUP.to_string(), - ) } else { - ( - ERROR_HANDLER_USER_EMAIL, - ERROR_HANDLER_USER_GROUP.to_string(), + get_email_and_permissioned_as( + on_failure_path, + is_global_error_handler, + is_schedule_error_handler, ) }; @@ -1924,7 +2013,7 @@ pub async fn push_error_handler<'a, 'c, T: Serialize + Send + Sync>( payload, PushArgs { extra: Some(extra), args: &result }, if is_global_error_handler { - "global" + GLOBAL_ERROR_HANDLER_USERNAME } else if is_schedule_error_handler { SCHEDULE_ERROR_HANDLER_USERNAME } else { @@ -2172,6 +2261,25 @@ pub enum JobTriggerKind { Gcp, } +impl std::fmt::Display for JobTriggerKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let kind = match self { + JobTriggerKind::Webhook => "webhook", + JobTriggerKind::Http => "http", + JobTriggerKind::Websocket => "websocket", + JobTriggerKind::Kafka => "kafka", + JobTriggerKind::Email => "email", + JobTriggerKind::Nats => "nats", + JobTriggerKind::Mqtt => "mqtt", + JobTriggerKind::Sqs => "sqs", + JobTriggerKind::Postgres => "postgres", + JobTriggerKind::Schedule => "schedule", + JobTriggerKind::Gcp => "gcp", + }; + write!(f, "{}", kind) + } +} + #[derive(sqlx::FromRow, Debug, Clone, Serialize, Deserialize)] pub struct MiniPulledJob { pub workspace_id: String, @@ -4532,6 +4640,12 @@ pub async fn push<'c, 'd>( // tracing::error!("Could not insert job_perms for job {job_id}: {err:#}"); // } + let trigger_kind = if schedule_path.is_some() { + Some(JobTriggerKind::Schedule) + } else { + None + }; + sqlx::query!( "WITH inserted_job AS ( INSERT INTO v2_job (id, workspace_id, raw_code, raw_lock, raw_flow, tag, parent_job, @@ -4540,8 +4654,7 @@ pub async fn push<'c, 'd>( flow_innermost_root_job, root_job, concurrent_limit, concurrency_time_window_s, timeout, flow_step_id, cache_ttl, priority, trigger_kind, script_entrypoint_override, preprocessed) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, - $19, $20, $38, $21, $22, $23, $24, $25, $26, - CASE WHEN $14::VARCHAR IS NOT NULL THEN 'schedule'::job_trigger_kind END, + $19, $20, $38, $21, $22, $23, $24, $25, $26, $39::job_trigger_kind, ($12::JSONB)->>'_ENTRYPOINT_OVERRIDE', $27) ), inserted_runtime AS ( @@ -4596,7 +4709,8 @@ pub async fn push<'c, 'd>( job_authed.is_operator, folders.as_slice(), job_authed.groups.as_slice(), - root_job.or(parent_job) + root_job.or(parent_job), + trigger_kind as Option, ) .execute(&mut *tx) .warn_after_seconds(1) @@ -4785,7 +4899,7 @@ async fn restarted_flows_resolution( )) })?; - let flow_data = cache::job::fetch_flow(db, row.job_kind, row.script_hash) + let flow_data = cache::job::fetch_flow(db, &row.job_kind, row.script_hash) .or_else(|_| cache::job::fetch_preview_flow(db.into(), &completed_flow_id, row.raw_flow)) .await?; let flow_value = flow_data.value(); diff --git a/backend/windmill-worker/Cargo.toml b/backend/windmill-worker/Cargo.toml index d78d7cff27..3805171024 100644 --- a/backend/windmill-worker/Cargo.toml +++ b/backend/windmill-worker/Cargo.toml @@ -32,6 +32,7 @@ csharp = ["dep:windmill-parser-csharp"] rust = ["dep:windmill-parser-rust"] nu = ["dep:windmill-parser-nu"] java = ["dep:windmill-parser-java"] +ruby = ["dep:windmill-parser-ruby"] duckdb = ["dep:duckdb"] [dependencies] @@ -46,6 +47,7 @@ windmill-parser-rust = { workspace = true, optional = true } windmill-parser-csharp = { workspace = true, optional = true } windmill-parser-nu = { workspace = true, optional = true } windmill-parser-java = { workspace = true, optional = true } +windmill-parser-ruby = { workspace = true, optional = true } windmill-parser-py = { workspace = true, optional = true } windmill-parser-yaml.workspace = true windmill-parser-py-imports = { workspace = true, optional = true } diff --git a/backend/windmill-worker/nsjail/download.ruby.config.proto b/backend/windmill-worker/nsjail/download.ruby.config.proto new file mode 100644 index 0000000000..7b9a04f912 --- /dev/null +++ b/backend/windmill-worker/nsjail/download.ruby.config.proto @@ -0,0 +1,86 @@ +name: "gem install" + +mode: ONCE +hostname: "ruby" +log_level: ERROR +time_limit: 900 + +rlimit_as: 2048 +rlimit_cpu: 1000 +rlimit_fsize: 1024 +rlimit_nofile: 64 + +envar: "HOME=/user" +envar: "LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH" + +cwd: "/tmp" + +clone_newnet: false +clone_newuser: {CLONE_NEWUSER} + +skip_setsid: true +keep_caps: true +keep_env: true +mount_proc: true + + +mount { + src: "/bin" + dst: "/bin" + is_bind: true +} + +mount { + src: "/lib" + dst: "/lib" + is_bind: true +} + +mount { + src: "/lib64" + dst: "/lib64" + is_bind: true + mandatory: false +} + +mount { + src: "/usr" + dst: "/usr" + is_bind: true +} + +mount { + src: "/etc" + dst: "/etc" + is_bind: true +} + +mount { + src: "/dev/null" + dst: "/dev/null" + is_bind: true + rw: true +} + +mount { + dst: "/tmp" + fstype: "tmpfs" + rw: true + options: "size=500000000" +} + +mount { + src: "{TARGET}" + dst: "{TARGET}" + is_bind: true + mandatory: false + rw: true +} + +mount { + src: "/dev/urandom" + dst: "/dev/urandom" + is_bind: true +} + +{DEV} diff --git a/backend/windmill-worker/nsjail/lock.ruby.config.proto b/backend/windmill-worker/nsjail/lock.ruby.config.proto new file mode 100644 index 0000000000..4d6035a772 --- /dev/null +++ b/backend/windmill-worker/nsjail/lock.ruby.config.proto @@ -0,0 +1,79 @@ +name: "bundle lock" + +mode: ONCE +hostname: "ruby" +log_level: ERROR +time_limit: 900 + +rlimit_as: 2048 +rlimit_cpu: 1000 +rlimit_fsize: 1024 +rlimit_nofile: 64 + +envar: "HOME=/tmp" +envar: "LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH" + +cwd: "/tmp" + +clone_newnet: false +clone_newuser: {CLONE_NEWUSER} + +skip_setsid: true +keep_caps: true +keep_env: true +mount_proc: true + + +mount { + src: "/bin" + dst: "/bin" + is_bind: true +} + +mount { + src: "/lib" + dst: "/lib" + is_bind: true +} + +mount { + src: "/lib64" + dst: "/lib64" + is_bind: true + mandatory: false +} + +mount { + src: "/usr" + dst: "/usr" + is_bind: true +} + +mount { + src: "/etc" + dst: "/etc" + is_bind: true +} + +mount { + src: "/dev/null" + dst: "/dev/null" + is_bind: true + rw: true +} + +mount { + src: "{JOB_DIR}" + dst: "/tmp" + is_bind: true + mandatory: false + rw: true +} + +mount { + src: "/dev/urandom" + dst: "/dev/urandom" + is_bind: true +} + +{DEV} diff --git a/backend/windmill-worker/nsjail/run.ruby.config.proto b/backend/windmill-worker/nsjail/run.ruby.config.proto new file mode 100644 index 0000000000..539b87e147 --- /dev/null +++ b/backend/windmill-worker/nsjail/run.ruby.config.proto @@ -0,0 +1,113 @@ +name: "ruby run script" + +mode: ONCE +hostname: "ruby" +log_level: ERROR + +disable_rl: true + +cwd: "/tmp" + +clone_newnet: false +clone_newuser: {CLONE_NEWUSER} + +skip_setsid: true +keep_caps: false +keep_env: true +# mount_proc: true + +mount { + src: "/bin" + dst: "/bin" + is_bind: true +} + +mount { + src: "/lib" + dst: "/lib" + is_bind: true +} + + +mount { + src: "/lib64" + dst: "/lib64" + is_bind: true + mandatory: false +} + + +mount { + src: "/usr" + dst: "/usr" + is_bind: true +} + +mount { + src: "/dev/null" + dst: "/dev/null" + is_bind: true + rw: true +} + +mount { + dst: "/tmp" + fstype: "tmpfs" + rw: true + options: "size=500000000" +} + + +mount { + src: "{JOB_DIR}/main.rb" + dst: "/tmp/main.rb" + is_bind: true + mandatory: false +} + +mount { + src: "{JOB_DIR}/args.json" + dst: "/tmp/args.json" + is_bind: true +} + +mount { + src: "{JOB_DIR}/result.json" + dst: "/tmp/result.json" + rw: true + is_bind: true +} + +#mount { +# src: "{CACHE_DIR}" +# dst: "{CACHE_DIR}" +# is_bind: true +# mandatory: false +# } + +mount { + src: "/etc" + dst: "/etc" + is_bind: true +} + +mount { + src: "/dev/random" + dst: "/dev/random" + is_bind: true +} + +mount { + src: "/dev/urandom" + dst: "/dev/urandom" + is_bind: true +} + +iface_no_lo: true + +{SHARED_MOUNT} + +{SHARED_DEPENDENCIES} + + +{DEV} diff --git a/backend/windmill-worker/src/ansible_executor.rs b/backend/windmill-worker/src/ansible_executor.rs index 1783641da3..5038495af1 100644 --- a/backend/windmill-worker/src/ansible_executor.rs +++ b/backend/windmill-worker/src/ansible_executor.rs @@ -78,7 +78,7 @@ async fn clone_repo( clone_cmd.arg(&repo.url); clone_cmd.arg(&target_path); - let clone_cmd_child = start_child_process(clone_cmd, GIT_PATH.as_str()).await?; + let clone_cmd_child = start_child_process(clone_cmd, GIT_PATH.as_str(), false).await?; handle_child( job_id, conn, @@ -112,7 +112,7 @@ async fn clone_repo( .stdout(Stdio::piped()) .stderr(Stdio::piped()); - let checkout_cmd_child = start_child_process(checkout_cmd, GIT_PATH.as_str()).await?; + let checkout_cmd_child = start_child_process(checkout_cmd, GIT_PATH.as_str(), false).await?; handle_child( job_id, conn, @@ -217,7 +217,7 @@ async fn clone_repo_without_history( init_cmd.args(["--initial-branch", branch]); } - let init_cmd_child = start_child_process(init_cmd, GIT_PATH.as_str()).await?; + let init_cmd_child = start_child_process(init_cmd, GIT_PATH.as_str(), false).await?; handle_child( job_id, conn, @@ -249,7 +249,7 @@ async fn clone_repo_without_history( .stdout(Stdio::piped()) .stderr(Stdio::piped()); - let add_remote_cmd_child = start_child_process(add_remote_cmd, GIT_PATH.as_str()).await?; + let add_remote_cmd_child = start_child_process(add_remote_cmd, GIT_PATH.as_str(), false).await?; handle_child( job_id, conn, @@ -281,7 +281,7 @@ async fn clone_repo_without_history( .stdout(Stdio::piped()) .stderr(Stdio::piped()); - let fetch_cmd_child = start_child_process(fetch_cmd, GIT_PATH.as_str()).await?; + let fetch_cmd_child = start_child_process(fetch_cmd, GIT_PATH.as_str(), false).await?; handle_child( job_id, conn, @@ -313,7 +313,7 @@ async fn clone_repo_without_history( .stdout(Stdio::piped()) .stderr(Stdio::piped()); - let checkout_cmd_child = start_child_process(checkout_cmd, GIT_PATH.as_str()).await?; + let checkout_cmd_child = start_child_process(checkout_cmd, GIT_PATH.as_str(), false).await?; handle_child( job_id, conn, @@ -447,7 +447,7 @@ pub async fn install_galaxy_collections( .stdout(Stdio::piped()) .stderr(Stdio::piped()); - let child = start_child_process(galaxy_roles_cmd, ANSIBLE_GALAXY_PATH.as_str()).await?; + let child = start_child_process(galaxy_roles_cmd, ANSIBLE_GALAXY_PATH.as_str(), false).await?; handle_child( job_id, conn, @@ -484,7 +484,7 @@ pub async fn install_galaxy_collections( .stdout(Stdio::piped()) .stderr(Stdio::piped()); - let child = start_child_process(galaxy_collections_cmd, ANSIBLE_GALAXY_PATH.as_str()).await?; + let child = start_child_process(galaxy_collections_cmd, ANSIBLE_GALAXY_PATH.as_str(), false).await?; handle_child( job_id, conn, @@ -1096,7 +1096,7 @@ fi ) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await? + start_child_process(nsjail_cmd, NSJAIL_PATH.as_str(), false).await? } else { let mut ansible_cmd = Command::new(ANSIBLE_PLAYBOOK_PATH.as_str()); ansible_cmd @@ -1115,7 +1115,7 @@ fi #[cfg(windows)] ansible_cmd.env("USERPROFILE", crate::USERPROFILE_ENV.as_str()); - start_child_process(ansible_cmd, ANSIBLE_PLAYBOOK_PATH.as_str()).await? + start_child_process(ansible_cmd, ANSIBLE_PLAYBOOK_PATH.as_str(), false).await? }; handle_child( diff --git a/backend/windmill-worker/src/bash_executor.rs b/backend/windmill-worker/src/bash_executor.rs index 201717d7ad..7fda8ee783 100644 --- a/backend/windmill-worker/src/bash_executor.rs +++ b/backend/windmill-worker/src/bash_executor.rs @@ -198,7 +198,7 @@ exit $exit_status .args(cmd_args) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await? + start_child_process(nsjail_cmd, NSJAIL_PATH.as_str(), false).await? } else { let mut cmd_args = vec!["wrapper.sh"]; cmd_args.extend(&args); @@ -214,7 +214,7 @@ exit $exit_status .args(cmd_args) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(bash_cmd, BIN_BASH.as_str()).await? + start_child_process(bash_cmd, BIN_BASH.as_str(), false).await? }; handle_child( &job.id, @@ -630,7 +630,7 @@ pub async fn handle_powershell_job( .stdout(Stdio::piped()) .stderr(Stdio::piped()); - let child = start_child_process(cmd, POWERSHELL_PATH.as_str()).await?; + let child = start_child_process(cmd, POWERSHELL_PATH.as_str(), false).await?; handle_child( &job.id, @@ -779,7 +779,7 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"", .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(cmd, NSJAIL_PATH.as_str()).await? + start_child_process(cmd, NSJAIL_PATH.as_str(), false).await? } else { let mut cmd; let mut cmd_args; @@ -852,7 +852,7 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"", .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()); } - start_child_process(cmd, POWERSHELL_PATH.as_str()).await? + start_child_process(cmd, POWERSHELL_PATH.as_str(), false).await? }; handle_child( diff --git a/backend/windmill-worker/src/bun_executor.rs b/backend/windmill-worker/src/bun_executor.rs index a508f3084c..b9083a5119 100644 --- a/backend/windmill-worker/src/bun_executor.rs +++ b/backend/windmill-worker/src/bun_executor.rs @@ -20,9 +20,9 @@ use crate::{ read_file_content, read_result, start_child_process, write_file_binary, OccupancyMetrics, }, handle_child::handle_child, - BUNFIG_INSTALL_SCOPES, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, BUN_PATH, DISABLE_NSJAIL, - DISABLE_NUSER, HOME_ENV, NODE_BIN_PATH, NODE_PATH, NPM_CONFIG_REGISTRY, NPM_PATH, NSJAIL_PATH, - PATH_ENV, PROXY_ENVS, TZ_ENV, + BUNFIG_INSTALL_SCOPES, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, BUN_NO_CACHE, BUN_PATH, + DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NODE_BIN_PATH, NODE_PATH, NPM_CONFIG_REGISTRY, + NPM_PATH, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, TZ_ENV, }; use windmill_common::client::AuthedClient; @@ -151,7 +151,7 @@ pub async fn gen_bun_lockfile( #[cfg(windows)] child_cmd.env("SystemRoot", SYSTEM_ROOT.as_str()); - let mut child_process = start_child_process(child_cmd, &*BUN_PATH).await?; + let mut child_process = start_child_process(child_cmd, &*BUN_PATH, false).await?; if let Some(db) = db { handle_child( @@ -293,12 +293,21 @@ pub async fn install_bun_lockfile( occupancy_metrics: &mut Option<&mut OccupancyMetrics>, ) -> Result<()> { let mut child_cmd = Command::new(if npm_mode { &*NPM_PATH } else { &*BUN_PATH }); + + let mut args = vec!["install", "--save-text-lockfile"]; + + let no_cache = !npm_mode && *BUN_NO_CACHE; + + if no_cache { + args.push("--no-cache"); + } + child_cmd .current_dir(job_dir) .env_clear() .envs(PROXY_ENVS.clone()) .envs(common_bun_proc_envs) - .args(vec!["install", "--save-text-lockfile"]) + .args(args) .stdout(Stdio::piped()) .stderr(Stdio::piped()); @@ -307,6 +316,8 @@ pub async fn install_bun_lockfile( let mut npm_logs = if npm_mode { "NPM mode\n".to_string() + } else if no_cache { + "Bun install with --no-cache flag (BUN_NO_CACHE=true)\n".to_string() } else { "".to_string() }; @@ -339,13 +350,13 @@ pub async fn install_bun_lockfile( false }; - if npm_mode { + if npm_mode || no_cache { if let Some(db) = db { append_logs(&job_id.clone(), w_id, npm_logs, db).await; } } - let mut child_process = start_child_process(child_cmd, &*BUN_PATH).await?; + let mut child_process = start_child_process(child_cmd, &*BUN_PATH, false).await?; gen_bunfig(job_dir).await?; if let Some(db) = db { @@ -364,7 +375,7 @@ pub async fn install_bun_lockfile( occupancy_metrics, None, ) - .await? + .await?; } else { Box::into_pin(child_process.wait()).await?; } @@ -520,7 +531,7 @@ pub async fn generate_wrapper_mjs( #[cfg(windows)] child.env("SystemRoot", SYSTEM_ROOT.as_str()); - let child_process = start_child_process(child, &*BUN_PATH).await?; + let child_process = start_child_process(child, &*BUN_PATH, false).await?; handle_child( job_id, db, @@ -570,7 +581,7 @@ pub async fn generate_bun_bundle( #[cfg(windows)] child.env("SystemRoot", SYSTEM_ROOT.as_str()); - let mut child_process = start_child_process(child, &*BUN_PATH).await?; + let mut child_process = start_child_process(child, &*BUN_PATH, false).await?; if let Some(db) = db { handle_child( job_id, @@ -1081,6 +1092,10 @@ function argsObjToArr({{ {spread} }}) {{ return [ {spread} ]; }} +function isAsyncIterable(obj) {{ + return obj != null && typeof obj[Symbol.asyncIterator] === 'function'; +}} + BigInt.prototype.toJSON = function () {{ return this.toString(); }}; @@ -1093,6 +1108,12 @@ async function run() {{ throw new Error("{main_name} function is missing"); }} let res = await Main.{main_name}(...argsArr); + if (isAsyncIterable(res)) {{ + for await (const chunk of res) {{ + console.log("WM_STREAM: " + chunk.replace('\n', '\\n')); + }} + res = null; + }} const res_json = JSON.stringify(res ?? null, (key, value) => typeof value === 'undefined' ? null : value); await fs.writeFile("result.json", res_json); process.exit(0); @@ -1378,7 +1399,7 @@ try {{ .args(args) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await? + start_child_process(nsjail_cmd, NSJAIL_PATH.as_str(), false).await? } else { let cmd = if annotation.nodejs { let script_path = format!("{job_dir}/wrapper.mjs"); @@ -1437,11 +1458,12 @@ try {{ } else { &*BUN_PATH }, + false, ) .await? }; - handle_child( + let handle_result = handle_child( &job.id, conn, mem_peak, @@ -1474,7 +1496,7 @@ try {{ })?; *new_args = Some(args.clone()); } - read_result(job_dir).await + read_result(job_dir, handle_result.result_stream).await } pub async fn get_common_bun_proc_envs(base_internal_url: Option<&str>) -> HashMap { @@ -1677,7 +1699,7 @@ BigInt.prototype.toJSON = function () {{ return this.toString(); }}; -console.log('start'); +console.log('start'); for await (const line of Readline.createInterface({{ input: process.stdin }})) {{ {print_lines} @@ -1686,7 +1708,7 @@ for await (const line of Readline.createInterface({{ input: process.stdin }})) { process.exit(0); }} try {{ - let {{ {spread} }} = JSON.parse(line) + let {{ {spread} }} = JSON.parse(line) {dates} let res = await Main.main(...[ {spread} ]); console.log("wm_res[success]:" + JSON.stringify(res ?? null, (key, value) => typeof value === 'undefined' ? null : value)); diff --git a/backend/windmill-worker/src/common.rs b/backend/windmill-worker/src/common.rs index c29f00c963..051101d727 100644 --- a/backend/windmill-worker/src/common.rs +++ b/backend/windmill-worker/src/common.rs @@ -12,7 +12,6 @@ use sha2::Digest; use sqlx::types::Json; use sqlx::{Pool, Postgres}; use tokio::process::Command; -use tokio::sync::{RwLock, Semaphore}; use tokio::{fs::File, io::AsyncReadExt}; #[cfg(feature = "parquet")] @@ -31,11 +30,10 @@ use windmill_common::{ variables::ContextualVariable, }; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, Result}; use windmill_parser_sql::{s3_mode_extension, S3ModeArgs, S3ModeFormat}; use windmill_queue::MiniPulledJob; -use std::ops::AsyncFn; use std::path::Path; use std::{collections::HashMap, sync::Arc, time::Duration}; @@ -45,7 +43,7 @@ use windmill_common::{variables, DB}; use tokio::{io::AsyncWriteExt, time::Instant}; use crate::agent_workers::UPDATE_PING_URL; -use crate::{DISABLE_NSJAIL, JOB_DEFAULT_TIMEOUT, MAX_RESULT_SIZE, MAX_TIMEOUT_DURATION, PATH_ENV}; +use crate::{JOB_DEFAULT_TIMEOUT, MAX_RESULT_SIZE, MAX_TIMEOUT_DURATION, PATH_ENV}; use windmill_common::client::AuthedClient; pub async fn build_args_map<'a>( @@ -359,11 +357,41 @@ pub async fn read_file(path: &str) -> error::Result> { return Ok(r); } +pub async fn merge_result_stream( + result: error::Result>, + result_stream: Option, +) -> error::Result> { + if let Some(result_stream) = result_stream { + result.and_then(|x| { + let mut value: Value = serde_json::from_str(x.get())?; + + // Insert the string at the "wm_stream" field + if let Value::Object(ref mut map) = value { + map.insert("wm_stream".to_string(), Value::String(result_stream)); + } else if value.is_null() { + // return Ok(unsafe_raw(json)) + return Ok(to_raw_value(&json!(result_stream))); + } else { + return Ok(x); + } + + // Convert back to RawValue + let json_string = serde_json::to_string(&value)?; + Ok(RawValue::from_string(json_string)?) + }) + } else { + result + } +} /// Read the `result.json` file. This function assumes that the file contains valid json and will /// result in undefined behaviour if it isn't. If the result.json is user generated or otherwise /// not guaranteed to be valid, use `read_and_check_result` -pub async fn read_result(job_dir: &str) -> error::Result> { - return read_file(&format!("{job_dir}/result.json")).await; +pub async fn read_result( + job_dir: &str, + result_stream: Option, +) -> error::Result> { + let rf = read_file(&format!("{job_dir}/result.json")).await; + merge_result_stream(rf, result_stream).await } pub async fn read_and_check_file(path: &str) -> error::Result> { @@ -612,22 +640,31 @@ impl OccupancyMetrics { } } +lazy_static! { + static ref DISABLE_PROCESS_GROUP: bool = std::env::var("DISABLE_PROCESS_GROUP").is_ok(); +} + pub async fn start_child_process( cmd: Command, executable: &str, + disable_process_group: bool, ) -> Result, Error> { use process_wrap::tokio::*; let mut cmd = TokioCommandWrap::from(cmd); - #[cfg(unix)] - { - use process_wrap::tokio::ProcessGroup; - cmd.wrap(ProcessGroup::leader()); - } - #[cfg(windows)] - { - cmd.wrap(JobObject); + if !*DISABLE_PROCESS_GROUP && !disable_process_group { + #[cfg(unix)] + { + use process_wrap::tokio::ProcessGroup; + + cmd.wrap(ProcessGroup::leader()); + } + #[cfg(windows)] + { + cmd.wrap(JobObject); + } } + return cmd .spawn() .map_err(|err| tentatively_improve_error(err.into(), executable)); @@ -1033,561 +1070,6 @@ pub fn build_http_client(timeout_duration: std::time::Duration) -> error::Result .map_err(|e| Error::internal_err(format!("Error building http client: {e:#}"))) } -#[derive(Clone)] -pub struct RequiredDependency { - /// Expected directory of dependency in cache - /// For example: - /// /tmp/windmill/cache/python_311/rich==0.0.0 - /// IMPORTANT!: path should not end with '/' - pub path: String, - /// Name to use for S3 tars - /// If not specified will use top level directory of path. - pub custom_name: Option, - /// Display name - /// Name that will be used for console output and logging - /// If not specified will either use custom_name or top level directory of path. - pub short_name: Option, -} - -pub enum InstallStrategy { - /// Will invoke callback to install single dependency - Single(Arc Result + Send + Sync>), - /// Will try to pull S3 first and will invoke closure to install the rest - AllAtOnce(Arc) -> Result + Send + Sync>), -} -/// # General -/// -/// Languages that compile usually include dependencies in final executable. -/// When dynamic languages do not and runtime dependencies provided separately. -/// -/// This helper implies that the language is dynamic. -/// Python, Ruby, Java are dynamic and they can use this helper. -/// -/// # Features -/// -/// This helper will install all specified dependencies in parallel and if it is EE, cache to S3 -/// It has atomic success file, allowing to distinguish failed installations from succesfull. -/// -/// Besides that it provides console output and does logging. -/// -/// # Usage -/// -/// Most important arguments in this helper are `deps` and `install_fn` -/// -/// In `deps` you specify all dependencies that are needed to be on worker in order to execute script. -/// You don't know which are actually installed and which are not. -/// -/// `deps` is a vector of RequiredDependency. Check [RequiredDependency] for more context. -/// -/// After `deps` are provided helper will check each dependency and check if it is in cache, if not it will try to pull from S3 -/// and if it does not work either, it will invoke `install_fn` closure. -/// Closure arguments has dependency name as well as it`s expected path in cache. -/// Closure should return Command that will install dependency to asked place. -pub async fn par_install_language_dependencies<'a>( - deps: Vec, - language_name: &'a str, - installer_executable_name: &'a str, - platform_agnostic: bool, - concurrent_downloads: usize, - stdout_on_err: bool, - install_fn: InstallStrategy, - postinstall_cb: impl AsyncFn(Vec) -> Result<(), error::Error>, - job_id: &'a Uuid, - w_id: &'a str, - worker_name: &'a str, - conn: &Connection, -) -> anyhow::Result<()> { - #[cfg(not(all(feature = "enterprise", feature = "parquet")))] - let _ = (platform_agnostic, language_name); - - let total_time = std::time::Instant::now(); - - // Total to install - let mut not_installed = vec![]; - let total_to_install; - // let mut not_installed = vec![]; - let counter_arc = Arc::new(tokio::sync::Mutex::new(0)); - // Append logs with line like this: - // [9/21] + requests==2.32.3 << (S3) | in 57ms - #[allow(unused_assignments)] - async fn print_success( - mut s3_pull: bool, - mut s3_push: bool, - job_id: &Uuid, - w_id: &str, - req: &str, - req_tl: usize, - counter_arc: Arc>, - total_to_install: usize, - instant: std::time::Instant, - conn: &Connection, - ) { - #[cfg(not(all(feature = "enterprise", feature = "parquet")))] - { - (s3_pull, s3_push) = (false, false); - } - - #[cfg(all(feature = "enterprise", feature = "parquet"))] - if windmill_common::s3_helpers::OBJECT_STORE_SETTINGS - .read() - .await - .is_none() - { - (s3_pull, s3_push) = (false, false); - } - - let mut counter = counter_arc.lock().await; - *counter += 1; - - windmill_queue::append_logs( - job_id, - w_id, - format!( - "\n{}+ {}{}{}| in {}ms", - windmill_common::worker::pad_string( - &format!("[{}/{total_to_install}]", counter), - 9 - ), - // Because we want to align to max len [999/999] we take 9 - // 123456789 - windmill_common::worker::pad_string(&req, req_tl + 1), - // Margin to the right ^ - if s3_pull { "<< (S3) " } else { "" }, - if s3_push { " > (S3) " } else { "" }, - instant.elapsed().as_millis(), - ), - conn, - ) - .await; - // Drop lock, so next print success can fire - } - - let mut name_tl = 0; - struct NotInstalledDependency { - path: String, - custom_name: Option, - short_name: Option, - display_name: String, - } - { - let mut to_be_installed_is_used = false; - for RequiredDependency { - path, // - custom_name, - short_name, - } in deps.into_iter() - { - if path.ends_with("/") { - anyhow::bail!("Internal error: path should not end with '/'") - } - let display_name = short_name - .as_ref() - .or(custom_name.as_ref()) - .or(path.split("/").last().map(|e| e.to_owned()).as_ref()) - .unwrap_or_else(|| { - tracing::warn!( - workspace_id = %w_id, - job_id = %job_id, - "failed to parse top level directory name for {path}, fallback to full path.", - ); - - &path - }) - .to_owned(); - { - // Later will help us align text in log console - if display_name.len() > name_tl { - name_tl = display_name.len(); - } - } - // Will look like: /tmp/windmill/cache/lang/dependency.valid.windmill - if tokio::fs::metadata(path.clone() + ".valid.windmill") - .await - .is_err() - { - if !to_be_installed_is_used { - windmill_queue::append_logs( - job_id, - w_id, - format!("\n--- INSTALLATION ---\n\nTo be installed:\n\n"), - conn, - ) - .await; - to_be_installed_is_used = true; - } - windmill_queue::append_logs(job_id, w_id, format!("- {display_name}\n"), conn) - .await; - not_installed.push(NotInstalledDependency { - path, - custom_name, - short_name, - display_name, - }); - } - } - } - total_to_install = not_installed.len(); - if total_to_install == 0 { - return Ok(()); - } - #[cfg(all(feature = "enterprise", feature = "parquet"))] - let is_not_pro = !matches!( - windmill_common::ee_oss::get_license_plan().await, - windmill_common::ee_oss::LicensePlan::Pro - ); - #[cfg(all(feature = "enterprise", feature = "parquet"))] - if is_not_pro && matches!(install_fn, InstallStrategy::AllAtOnce(_)) { - windmill_queue::append_logs( - job_id, - w_id, - format!("\nLooking for packages on S3:\n"), - conn, - ) - .await; - } - - // Parallelism level (N) - let parallel_limit = // Semaphore will panic if value less then 1 - concurrent_downloads.clamp(1, 30); - - tracing::info!( - workspace_id = %w_id, - "Install parallel limit: {}, job: {}", - parallel_limit, - job_id - ); - - let mut handles = vec![]; - let semaphore = Arc::new(Semaphore::new(parallel_limit)); - let not_pulled = Arc::new(RwLock::new(vec![])); - // let mut handles = Vec::with_capacity(total_to_install); - for NotInstalledDependency { - // - path, - custom_name, - short_name, - display_name, - } in not_installed - { - let permit = semaphore.clone().acquire_owned().await; // Acquire a permit - - if let Err(_) = permit { - tracing::error!( - workspace_id = %w_id, - "Cannot acquire permit on semaphore, that can only mean that semaphore has been closed." - ); - break; - } - - let permit = permit.unwrap(); - - #[cfg(all(feature = "enterprise", feature = "parquet"))] - let s3_pull_future = if is_not_pro { - if let Some(os) = windmill_common::s3_helpers::get_object_store().await { - Some(crate::global_cache::pull_from_tar( - os, - path.clone(), - language_name.to_owned(), - custom_name.clone(), - platform_agnostic, - )) - } else { - None - } - } else { - None - }; - let child = { - if let InstallStrategy::Single(ref callback, ..) = install_fn { - let cmd = callback(RequiredDependency { - path: path.clone(), - custom_name: custom_name.clone(), - short_name: short_name.clone(), - })?; - tracing::debug!("{:?}", &cmd); - Some(start_child_process(cmd, &installer_executable_name).await?) - } else { - None - } - }; - - let ( - worker_name_2, - path_2, - display_name_2, - custom_name, - job_id_2, - w_id_2, - conn_2, - counter_arc, - language_name, - installer_executable_name, - not_pulled, - ) = ( - worker_name.to_owned(), - path.clone(), - display_name.clone(), - custom_name.clone(), - job_id.clone(), - w_id.to_owned(), - conn.clone(), - counter_arc.clone(), - language_name.to_owned(), - installer_executable_name.to_owned(), - not_pulled.clone(), - ); - #[cfg(not(all(feature = "enterprise", feature = "parquet")))] - let _ = language_name; - - let start = std::time::Instant::now(); - let handle = tokio::spawn(async move { - let _permit = permit; - - #[cfg(all(feature = "enterprise", feature = "parquet"))] - if let Some(s3_pull_future) = s3_pull_future { - if let Err(e) = s3_pull_future.await { - tracing::info!( - workspace_id = %w_id_2, - "No tarball was found for {:?} on S3 or different problem occured {job_id_2}:\n{e}", - &custom_name.clone().unwrap_or(path) - ); - } else { - // TODO: Refactor - // Create a file to indicate that installation was successfull - let valid_path = path_2.clone() + ".valid.windmill"; - // This is atomic operation, meaning, that it either completes and dependency is valid, - // or it does not and dependency is invalid and will be reinstalled next run - if let Err(e) = File::create(&valid_path).await { - tracing::error!( - workspace_id = %w_id_2, - job_id = %job_id_2, - "Failed to create {}!\n{e}\n - This file needed for jobs to function", - valid_path - ); - }; - print_success( - true, - false, - &job_id_2, - &w_id_2, - &display_name_2, - name_tl, - counter_arc, - total_to_install, - start, - &conn_2, - ) - .await; - return; - } - } - - let Some(child) = child else { - let mut lock = not_pulled.write().await; - lock.push(RequiredDependency { - path: path_2.clone(), - custom_name: custom_name.clone(), - short_name: short_name.clone(), - }); - return; - }; - if let Err(e) = crate::handle_child::handle_child( - &job_id_2, - &conn_2, - // TODO: Return mem_peak - &mut 0, - // TODO: Return canceld_by_ref - &mut None, - child, - !*DISABLE_NSJAIL, - &worker_name_2, - &w_id_2, - &installer_executable_name, - None, - false, - &mut None, - None, - ) - .await - { - windmill_queue::append_logs( - &job_id_2, - &w_id_2, - format!("error while installing {}: {e:?}", &display_name_2), - &conn_2, - ) - .await; - } else { - // if let Some(cb) = postinstall_cb { - // if let Err(e) = cb(vec![RequiredDependency { - // path: path_2.clone(), - // custom_name: custom_name.clone(), - // short_name: short_name.clone(), - // }]) - // .await - // { - // tracing::error!( - // workspace_id = %w_id_2, - // job_id = %job_id_2, - // "Postinstall callback failed!\n{e}\n - // This might affect execution", - // ); - // } - // } - print_success( - false, - true, - &job_id_2, - &w_id_2, - &display_name_2, - name_tl, - counter_arc, - total_to_install, - start, - &conn_2, - ) - .await; - // TODO: Refactor - // Create a file to indicate that installation was successfull - let valid_path = path_2.clone() + ".valid.windmill"; - // This is atomic operation, meaning, that it either completes and dependency is valid, - // or it does not and dependency is invalid and will be reinstalled next run - if let Err(e) = File::create(&valid_path).await { - tracing::error!( - workspace_id = %w_id_2, - job_id = %job_id_2, - "Failed to create {}!\n{e}\n - This file needed for jobs to function", - valid_path - ); - }; - #[cfg(all(feature = "enterprise", feature = "parquet"))] - { - if let Some(os) = windmill_common::s3_helpers::get_object_store().await { - tokio::spawn(async move { - if let Err(e) = crate::global_cache::build_tar_and_push( - os, - path_2, - language_name, - custom_name, - platform_agnostic, - ) - .await - { - tracing::warn!("failed to build tar and push: {e:?}"); - } - }); - } - } - } - }); - handles.push(handle); - } - - for handle in handles { - if let Err(e) = handle.await { - tracing::error!("Error joining handles: {e:?}"); - } - } - if !not_pulled.read().await.is_empty() { - if let InstallStrategy::AllAtOnce(ref callback, ..) = install_fn { - let not_pulled_copy = not_pulled.read().await.clone(); - windmill_queue::append_logs( - job_id, - w_id, - format!("\n\nFetching {} packages...\n", not_pulled_copy.len()), - &conn, - ) - .await; - let cmd = callback(not_pulled_copy.clone())?; - tracing::debug!("{:?}", &cmd); - let child = start_child_process(cmd, &installer_executable_name).await?; - let mut buf = "".to_owned(); - let pipe_stdout = if stdout_on_err { Some(&mut buf) } else { None }; - if let Err(e) = crate::handle_child::handle_child( - // &job_id, - &Uuid::nil(), - &conn, - // TODO: Return mem_peak - &mut 0, - // TODO: Return canceld_by_ref - &mut None, - child, - !*DISABLE_NSJAIL, - &worker_name, - &w_id, - &installer_executable_name, - None, - false, - &mut None, - pipe_stdout, - ) - .await - { - bail!(format!( - "error while installing dependencies: {e:?}\n{}", - buf - )); - } - { - postinstall_cb(not_pulled_copy.clone()).await?; - } - for RequiredDependency { path, custom_name: _custom_name, .. } in - not_pulled_copy.into_iter() - { - // TODO: Refactor - // Create a file to indicate that installation was successfull - let valid_path = path.clone() + ".valid.windmill"; - // This is atomic operation, meaning, that it either completes and dependency is valid, - // or it does not and dependency is invalid and will be reinstalled next run - if let Err(e) = File::create(&valid_path).await { - tracing::error!( - workspace_id = %w_id, - job_id = %job_id, - "Failed to create {}!\n{e}\n - This file needed for jobs to function", - valid_path - ); - }; - #[cfg(all(feature = "enterprise", feature = "parquet"))] - { - if let Some(os) = windmill_common::s3_helpers::get_object_store().await { - let language_name = language_name.to_owned(); - tokio::spawn(async move { - if let Err(e) = crate::global_cache::build_tar_and_push( - os, - path, - language_name, - _custom_name, - platform_agnostic, - ) - .await - { - tracing::warn!("failed to build tar and push: {e:?}"); - } - }); - } - } - } - } - } - { - let total_time = total_time.elapsed().as_millis(); - windmill_queue::append_logs( - &job_id, - w_id, - format!( - "\nDone. Time spent on installation phase: {}ms\n", - total_time - ), - conn, - ) - .await; - } - Ok(()) -} - #[derive(Clone)] pub struct S3ModeWorkerData { pub client: AuthedClient, diff --git a/backend/windmill-worker/src/csharp_executor.rs b/backend/windmill-worker/src/csharp_executor.rs index a8ed06a2e2..aff6b575ea 100644 --- a/backend/windmill-worker/src/csharp_executor.rs +++ b/backend/windmill-worker/src/csharp_executor.rs @@ -85,6 +85,7 @@ pub async fn generate_nuget_lockfile( let mut gen_lockfile_cmd = Command::new(DOTNET_PATH.as_str()); gen_lockfile_cmd .current_dir(job_dir) + .env("MSBUILDDISABLENODEREUSE", "1") .args(vec!["restore", "--use-lock-file"]) .stdout(Stdio::piped()) .stderr(Stdio::piped()); @@ -112,7 +113,8 @@ pub async fn generate_nuget_lockfile( .unwrap_or_else(|_| format!("{}\\AppData\\Local", HOME_ENV.as_str())), ); - let gen_lockfile_process = start_child_process(gen_lockfile_cmd, DOTNET_PATH.as_str()).await?; + let gen_lockfile_process = + start_child_process(gen_lockfile_cmd, DOTNET_PATH.as_str(), true).await?; handle_child( job_id, conn, @@ -331,6 +333,7 @@ async fn build_cs_proj( .env("HOME", HOME_ENV.as_str()) .env("DOTNET_CLI_TELEMETRY_OPTOUT", "true") .env("DOTNET_NOLOGO", "true") + .env("MSBUILDDISABLENODEREUSE", "1") .env("DOTNET_ROOT", DOTNET_ROOT.as_str()) .args(vec![ "publish", @@ -368,7 +371,7 @@ async fn build_cs_proj( .unwrap_or_else(|_| format!("{}\\AppData\\Local", HOME_ENV.as_str())), ); - let build_cs_process = start_child_process(build_cs_cmd, DOTNET_PATH.as_str()).await?; + let build_cs_process = start_child_process(build_cs_cmd, DOTNET_PATH.as_str(), true).await?; handle_child( job_id, conn, @@ -573,7 +576,7 @@ pub async fn handle_csharp_job( #[cfg(windows)] nsjail_cmd.env("SystemRoot", SYSTEM_ROOT.as_str()); - start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await? + start_child_process(nsjail_cmd, NSJAIL_PATH.as_str(), true).await? } else { #[cfg(unix)] let compiled_executable_name = "./Main".to_string(); @@ -622,7 +625,7 @@ pub async fn handle_csharp_job( .unwrap_or_else(|_| format!("{}\\AppData\\Local", HOME_ENV.as_str())), ); - start_child_process(run_csharp, &compiled_executable_name).await? + start_child_process(run_csharp, &compiled_executable_name, true).await? }; handle_child( @@ -641,5 +644,5 @@ pub async fn handle_csharp_job( None, ) .await?; - read_result(job_dir).await + read_result(job_dir, None).await } diff --git a/backend/windmill-worker/src/dedicated_worker.rs b/backend/windmill-worker/src/dedicated_worker.rs index 75a80e39d8..56b21f94d6 100644 --- a/backend/windmill-worker/src/dedicated_worker.rs +++ b/backend/windmill-worker/src/dedicated_worker.rs @@ -98,7 +98,7 @@ pub async fn handle_dedicated_process( .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(cmd, command_path).await? + start_child_process(cmd, command_path, false).await? }; let stdout = child @@ -126,7 +126,7 @@ pub async fn handle_dedicated_process( let status = Box::into_pin(child.wait()) .await .expect("child process encountered an error"); - if let Err(e) = process_status(&cmd_name, status) { + if let Err(e) = process_status(&cmd_name, status, vec![]) { tracing::error!("child exit status was not success: {e:#}"); } else { tracing::info!("child exit status was success"); diff --git a/backend/windmill-worker/src/deno_executor.rs b/backend/windmill-worker/src/deno_executor.rs index e2bf579b53..4cc6649525 100644 --- a/backend/windmill-worker/src/deno_executor.rs +++ b/backend/windmill-worker/src/deno_executor.rs @@ -144,7 +144,7 @@ pub async fn generate_deno_lock( .envs(deno_envs) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - let mut child_process = start_child_process(child_cmd, DENO_PATH.as_str()).await?; + let mut child_process = start_child_process(child_cmd, DENO_PATH.as_str(), false).await?; if let Some(db) = db { handle_child( @@ -283,6 +283,10 @@ BigInt.prototype.toJSON = function () {{ return this.toString(); }}; +function isAsyncIterable(obj) {{ + return obj != null && typeof obj[Symbol.asyncIterator] === 'function'; +}} + async function run() {{ {dates} {preprocessor} @@ -291,6 +295,12 @@ async function run() {{ throw new Error("{main_name} function is missing"); }} let res: any = await {main_name}(...argsArr); + if (isAsyncIterable(res)) {{ + for await (const chunk of res) {{ + console.log("WM_STREAM: " + chunk.replace('\n', '\\n')); + }} + res = null; + }} const res_json = JSON.stringify(res ?? null, (key, value) => typeof value === 'undefined' ? null : value); await Deno.writeTextFile("result.json", res_json); Deno.exit(0); @@ -404,11 +414,11 @@ try {{ .args(args) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(deno_cmd, DENO_PATH.as_str()).await? + start_child_process(deno_cmd, DENO_PATH.as_str(), false).await? }; // logs.push_str(format!("prepare: {:?}\n", start.elapsed().as_micros()).as_str()); // start = Instant::now(); - handle_child( + let handle_result = handle_child( &job.id, conn, mem_peak, @@ -445,7 +455,7 @@ try {{ })?; *new_args = Some(args.clone()); } - read_result(job_dir).await + read_result(job_dir, handle_result.result_stream).await } async fn build_import_map( diff --git a/backend/windmill-worker/src/go_executor.rs b/backend/windmill-worker/src/go_executor.rs index 88b8863884..1f09f325de 100644 --- a/backend/windmill-worker/src/go_executor.rs +++ b/backend/windmill-worker/src/go_executor.rs @@ -259,7 +259,7 @@ func Run(req Req) (interface{{}}, error){{ #[cfg(windows)] set_windows_env_vars(&mut build_go_cmd); - let build_go_process = start_child_process(build_go_cmd, GO_PATH.as_str()).await?; + let build_go_process = start_child_process(build_go_cmd, GO_PATH.as_str(), false).await?; handle_child( &job.id, conn, @@ -363,7 +363,7 @@ func Run(req Req) (interface{{}}, error){{ .args(vec!["--config", "run.config.proto", "--", "/tmp/go/main"]) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await? + start_child_process(nsjail_cmd, NSJAIL_PATH.as_str(), false).await? } else { #[cfg(unix)] let compiled_executable_name = "./main"; @@ -406,9 +406,9 @@ func Run(req Req) (interface{{}}, error){{ set_windows_env_vars(&mut run_go); run_go.stdout(Stdio::piped()).stderr(Stdio::piped()); - start_child_process(run_go, &compiled_executable_name).await? + start_child_process(run_go, &compiled_executable_name, false).await? }; - handle_child( + let handle_result = handle_child( &job.id, conn, mem_peak, @@ -425,7 +425,7 @@ func Run(req Req) (interface{{}}, error){{ ) .await?; - read_result(job_dir).await + read_result(job_dir, handle_result.result_stream).await } async fn gen_go_mod( @@ -490,7 +490,7 @@ pub async fn install_go_dependencies( #[cfg(windows)] set_windows_env_vars(&mut child_cmd); - let child_process = start_child_process(child_cmd, GO_PATH.as_str()).await?; + let child_process = start_child_process(child_cmd, GO_PATH.as_str(), false).await?; handle_child( job_id, @@ -582,7 +582,7 @@ pub async fn install_go_dependencies( #[cfg(windows)] set_windows_env_vars(&mut child_cmd); - let child_process = start_child_process(child_cmd, GO_PATH.as_str()).await?; + let child_process = start_child_process(child_cmd, GO_PATH.as_str(), false).await?; handle_child( job_id, diff --git a/backend/windmill-worker/src/handle_child.rs b/backend/windmill-worker/src/handle_child.rs index 82f708be49..11078c7892 100644 --- a/backend/windmill-worker/src/handle_child.rs +++ b/backend/windmill-worker/src/handle_child.rs @@ -7,6 +7,7 @@ use nix::unistd::Pid; use process_wrap::tokio::TokioChildWrapper; use windmill_common::agent_workers::PingJobStatusResponse; use windmill_common::jobs::LARGE_LOG_THRESHOLD_SIZE; +use windmill_common::result_stream::extract_stream_from_logs; #[cfg(windows)] use std::process::Stdio; @@ -52,7 +53,7 @@ use futures::{ }; use crate::common::{resolve_job_timeout, OccupancyMetrics}; -use crate::job_logger::{append_job_logs, append_with_limit}; +use crate::job_logger::{append_job_logs, append_result_stream, append_with_limit}; use crate::job_logger_oss::process_streaming_log_lines; use crate::worker_utils::{ping_job_status, update_worker_ping_from_job}; use crate::{MAX_RESULT_SIZE, MAX_WAIT_FOR_SIGINT, MAX_WAIT_FOR_SIGTERM}; @@ -87,6 +88,10 @@ async fn kill_process_tree(pid: Option) -> Result<(), String> { } } +pub struct HandleChildResult { + pub result_stream: Option, +} + /// - wait until child exits and return with exit status /// - read lines from stdout and stderr and append them to the "queue"."logs" /// quitting early if output exceedes MAX_LOG_SIZE characters (not bytes) @@ -109,7 +114,7 @@ pub async fn handle_child( occupancy_metrics: &mut Option<&mut OccupancyMetrics>, // Do not print logs to output, but instead save to string. pipe_stdout: Option<&mut String>, -) -> error::Result<()> { +) -> error::Result { let start = Instant::now(); let pid = child.id(); @@ -296,6 +301,7 @@ pub async fn handle_child( } }; + let mut stream_result = Vec::new(); /* a future that reads output from the child and appends to the database */ let lines = write_lines( output, @@ -308,6 +314,7 @@ pub async fn handle_child( pipe_stdout, &mut rx2, child_name, + &mut stream_result, ) .instrument(trace_span!("child_lines")); @@ -322,7 +329,7 @@ pub async fn handle_child( _ if *too_many_logs.borrow() => Err(Error::ExecutionErr(format!( "logs or result reached limit. (current max size: {MAX_RESULT_SIZE} characters)" ))), - Ok(Ok(status)) => process_status(&child_name, status), + Ok(Ok(status)) => process_status(&child_name, status, stream_result), Ok(Err(kill_reason)) => match kill_reason { KillReason::AlreadyCompleted => { Err(Error::AlreadyCompleted("Job already completed".to_string())) @@ -346,6 +353,7 @@ pub async fn write_lines( pipe_stdout: Option<&mut String>, rx2: &mut broadcast::Receiver<()>, child_name: &str, + stream_result: &mut Vec, ) { let max_log_size = if *CLOUD_HOSTED { MAX_RESULT_SIZE @@ -401,13 +409,25 @@ pub async fn write_lines( let mut joined = String::new(); let job_id = job_id.clone(); + let mut nstream = String::new(); while let Some(line) = read_lines.next().await { match line { Ok(line) => { if line.is_empty() { continue; } - append_with_limit(&mut joined, &line, &mut log_remaining); + if let Some(stream) = extract_stream_from_logs(&line) { + let len = stream.len(); + if log_remaining >= len { + log_remaining -= len; + nstream.push_str(&stream); + stream_result.push(stream); + } else { + log_remaining = 0; + } + } else { + append_with_limit(&mut joined, &line, &mut log_remaining); + } if log_remaining == 0 { tracing::info!(%job_id, "Too many logs lines for job {job_id}"); let _ = set_too_many_logs.send(true); @@ -460,6 +480,14 @@ pub async fn write_lines( let job_id = job_id.clone(); let pg_log_total_size = pg_log_total_size.clone(); (do_write, write_result) = tokio::spawn(async move { + if !nstream.is_empty() { + if let Err(err) = append_result_stream(&conn, &w_id, &job_id, &nstream).await { + tracing::error!( + "Unable to send result stream for job {job_id}. Error was: {:?}", + err + ); + } + } append_job_logs( &job_id, &w_id, @@ -762,9 +790,19 @@ pub fn lines_to_stream( }) } -pub fn process_status(program: &str, status: ExitStatus) -> error::Result<()> { +pub fn process_status( + program: &str, + status: ExitStatus, + stream_result: Vec, +) -> error::Result { if status.success() { - Ok(()) + Ok(HandleChildResult { + result_stream: if stream_result.is_empty() { + None + } else { + Some(stream_result.join("")) + }, + }) } else if let Some(code) = status.code() { Err(error::Error::ExitStatus(program.to_string(), code)) } else { diff --git a/backend/windmill-worker/src/java_executor.rs b/backend/windmill-worker/src/java_executor.rs index f62de150a9..5a779ad990 100644 --- a/backend/windmill-worker/src/java_executor.rs +++ b/backend/windmill-worker/src/java_executor.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, path::PathBuf, process::Stdio, sync::Arc}; +use std::{collections::HashMap, path::PathBuf, process::Stdio}; use anyhow::{anyhow, bail}; use async_recursion::async_recursion; @@ -21,11 +21,13 @@ use windmill_queue::{append_logs, CanceledBy, MiniPulledJob}; use crate::{ common::{ - create_args_and_out_file, get_reserved_variables, par_install_language_dependencies, - read_result, start_child_process, OccupancyMetrics, RequiredDependency, + create_args_and_out_file, get_reserved_variables, read_result, start_child_process, + OccupancyMetrics, }, - handle_child, COURSIER_CACHE_DIR, DISABLE_NSJAIL, DISABLE_NUSER, JAVA_CACHE_DIR, - JAVA_REPOSITORY_DIR, MAVEN_REPOS, NO_DEFAULT_MAVEN, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, + handle_child, + universal_pkg_installer::{par_install_language_dependencies_all_at_once, RequiredDependency}, + COURSIER_CACHE_DIR, DISABLE_NSJAIL, DISABLE_NUSER, JAVA_CACHE_DIR, JAVA_REPOSITORY_DIR, + MAVEN_REPOS, NO_DEFAULT_MAVEN, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, }; use windmill_common::client::AuthedClient; @@ -88,7 +90,7 @@ pub async fn handle_java_job<'a>(mut args: JobHandlerInput<'a>) -> Result( ); Ok(RequiredDependency { path, - custom_name: Some(format!("{group_id}:{artifact_id}:{version}")), - short_name: Some(format!("{artifact_id}:{version}")), + _s3_handle: format!("{group_id}:{artifact_id}:{version}"), + display_name: format!("{artifact_id}:{version}"), + custom_payload: (), }) } _ => anyhow::bail!("{line} is not parsable"), } }) - .collect::>>()?; + .collect::>>>()?; let classpath = deps .clone() @@ -307,14 +310,14 @@ async fn install<'a>( let job_dir = job_dir.to_owned(); let fetch_dir = format!("{JAVA_CACHE_DIR}/tmp-fetch-{}", Uuid::new_v4()); let fetch_dir2 = fetch_dir.clone(); - par_install_language_dependencies( + par_install_language_dependencies_all_at_once( deps, "java", "java", true, *JAVA_CONCURRENT_DOWNLOADS, true, - crate::common::InstallStrategy::AllAtOnce(Arc::new(move |dependencies| { + move |dependencies| { let mut cmd = Command::new(if cfg!(windows) { "java" } else { @@ -322,12 +325,8 @@ async fn install<'a>( }); let artifacts = dependencies .into_iter() - .map(|e| { - e.custom_name.ok_or(anyhow::anyhow!( - "Internal Error: Artifact name should be Some!" - )) - }) - .collect::>>()?; + .map(|e| e._s3_handle) + .collect::>(); cmd.env_clear() .current_dir(&job_dir) .env("PATH", PATH_ENV.as_str()) @@ -385,7 +384,7 @@ async fn install<'a>( } Ok(cmd) - })), + }, async move |_| { move_to_repository(&fetch_dir2, 0).await?; remove_dir_all(&fetch_dir2).await?; @@ -419,6 +418,7 @@ async fn install<'a>( &job.id, &job.workspace_id, worker_name, + !*DISABLE_NSJAIL, conn, ) .await?; @@ -518,7 +518,7 @@ async fn compile<'a>( std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), ); } - start_child_process(cmd, "javac").await? + start_child_process(cmd, "javac", false).await? }; handle_child::handle_child( &job.id, @@ -642,7 +642,7 @@ async fn run<'a>( cmd.args(vec!["-classpath", &classpath, "net.script.App"]); cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); - start_child_process(cmd, NSJAIL_PATH.as_str()).await? + start_child_process(cmd, NSJAIL_PATH.as_str(), false).await? } else { append_logs( &job.id, @@ -701,7 +701,7 @@ async fn run<'a>( std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), ); } - start_child_process(cmd, "java").await? + start_child_process(cmd, "java", false).await? }; handle_child::handle_child( &job.id, @@ -718,7 +718,8 @@ async fn run<'a>( &mut Some(occupancy_metrics), None, ) - .await + .await?; + Ok(()) } #[derive(Default, Debug)] diff --git a/backend/windmill-worker/src/job_logger.rs b/backend/windmill-worker/src/job_logger.rs index 626dfbce44..263234cee9 100644 --- a/backend/windmill-worker/src/job_logger.rs +++ b/backend/windmill-worker/src/job_logger.rs @@ -1,10 +1,11 @@ use regex::Regex; pub use windmill_common::jobs::LARGE_LOG_THRESHOLD_SIZE; +use windmill_common::result_stream::append_result_stream_db; use windmill_common::utils::WarnAfterExt; use windmill_common::worker::{Connection, CLOUD_HOSTED}; -use windmill_common::DB; +use windmill_common::{error, DB}; use windmill_queue::append_logs; use std::sync::atomic::AtomicU32; @@ -61,6 +62,32 @@ pub async fn append_job_logs( } } +pub async fn append_result_stream( + conn: &Connection, + workspace_id: &str, + job_id: &Uuid, + nstream: &str, +) -> error::Result<()> { + match conn { + Connection::Sql(db) => { + append_result_stream_db(db, workspace_id, job_id, nstream).await?; + } + Connection::Http(client) => { + if let Err(e) = client + .post::<_, String>( + &format!("/api/w/{}/agent_workers/push_logs/{}", workspace_id, job_id), + None, + &nstream, + ) + .await + { + tracing::error!(%job_id, %e, "error sending result stream for job {job_id}: {e}"); + }; + } + } + Ok(()) +} + pub async fn append_logs_with_compaction( job_id: &Uuid, w_id: &str, diff --git a/backend/windmill-worker/src/js_eval.rs b/backend/windmill-worker/src/js_eval.rs index 77ccd9b15f..d5bf2add3b 100644 --- a/backend/windmill-worker/src/js_eval.rs +++ b/backend/windmill-worker/src/js_eval.rs @@ -701,7 +701,7 @@ pub struct MainArgs { #[cfg(feature = "deno_core")] pub struct LogString { - pub s: String, + pub s: mpsc::UnboundedSender, } #[cfg(feature = "deno_core")] @@ -894,6 +894,8 @@ pub async fn eval_fetch_timeout( return y*2; }); + let (log_sender, mut log_receiver) = mpsc::unbounded_channel::(); + { let op_state = js_runtime.op_state(); let mut op_state = op_state.borrow_mut(); @@ -901,7 +903,7 @@ pub async fn eval_fetch_timeout( //reqwest client seems to not be sharable between runtimes unfortunately // op_state.put(HTTP_CLIENT.clone()); op_state.put(MainArgs { args: spread }); - op_state.put(LogString { s: String::new() }); + op_state.put(LogString { s: log_sender }); } sender @@ -913,23 +915,51 @@ pub async fn eval_fetch_timeout( .build()?; let future = async { + use crate::common::merge_result_stream; + + if !extra_logs.is_empty() { + append_logs(&job_id, w_id_.as_str(), format!("{extra_logs}"), &conn_).await; + } + let w_id = w_id_.clone(); + let handle = tokio::spawn(async move { + let mut result_stream = String::new(); + while let Some(log) = log_receiver.recv().await { + use windmill_common::result_stream::extract_stream_from_logs; + + if let Some(stream) = extract_stream_from_logs(&log.trim_end_matches("\n")) { + use crate::job_logger::append_result_stream; + + result_stream.push_str(&stream); + if let Err(e) = append_result_stream(&conn_, &w_id, &job_id, &stream).await + { + tracing::error!("failed to append result stream for job {job_id}: {e}"); + } + } else { + append_logs(&job_id, w_id_.as_str(), log, &conn_).await; + } + } + if !result_stream.is_empty() { + Some(result_stream) + } else { + None + } + }); + let r = tokio::select! { r = eval_fetch(&mut js_runtime, &js_expr, Some(env_code), script_entrypoint_override, load_client, &job_id) => Ok(r), _ = memory_limit_rx.recv() => Err(Error::ExecutionErr("Memory limit reached, killing isolate".to_string())) }; - - append_logs( - &job_id, - w_id_.as_str(), - format!( - "{extra_logs}{}", - js_runtime.op_state().borrow().borrow::().s - ), - &conn_, - ) - .await; - - r + drop(js_runtime); + if let Ok(r) = r { + match handle.await { + Ok(Some(logs)) => Ok(merge_result_stream(r, Some(logs)).await), + Ok(None) => Ok(r), + Err(e) => Err(Error::ExecutionErr(e.to_string())), + } + } else { + r + } + // r }; let r = runtime.block_on(future)?; // tracing::info!("total: {:?}", instant.elapsed()); @@ -1039,8 +1069,46 @@ async fn eval_fetch( "", format!( r#" +function isAsyncIterable(obj) {{ + // return true; // TODO: remove this + return obj != null && typeof obj[Symbol.asyncIterator] === 'function'; +}} + +function processStreamIterative(res) {{ + const iterator = res[Symbol.asyncIterator](); + + function processLoop() {{ + return new Promise(function(resolve) {{ + function step() {{ + iterator.next().then(function(result) {{ + if (!result.done) {{ + const chunk = result.value; + console.log("WM_STREAM: " + chunk.replace('\n', '\\n')); + // Continue the loop + step(); + }} else {{ + resolve("null"); + }} + }}).catch(function(error) {{ + resolve("null"); + }}); + }} + step(); + }}); + }} + + return processLoop(); +}} + let args = Deno.core.ops.op_get_static_args().map(JSON.parse) -import("file:///eval.ts").then((module) => module.{main_override}(...args)).then(JSON.stringify) +import("file:///eval.ts").then((module) => module.{main_override}(...args)) + .then(res => {{ + if (isAsyncIterable(res)) {{ + return processStreamIterative(res) + }} else {{ + return JSON.stringify(res ?? null); + }} + }}) "# ), ) @@ -1120,11 +1188,14 @@ fn op_get_static_args(op_state: Rc>) -> Vec> { #[op2(fast)] fn op_log(op_state: Rc>, #[string] log: &str) { // tracing::error!("log: |{}|", log); - op_state + if let Err(e) = op_state .borrow_mut() .borrow_mut::() .s - .push_str(log); + .send(log.to_string()) + { + tracing::error!("failed to send log: {e}"); + } } #[cfg(feature = "deno_core")] diff --git a/backend/windmill-worker/src/lib.rs b/backend/windmill-worker/src/lib.rs index 3dfeba5c42..e917e32fba 100644 --- a/backend/windmill-worker/src/lib.rs +++ b/backend/windmill-worker/src/lib.rs @@ -13,6 +13,9 @@ mod bash_executor; #[cfg(feature = "java")] mod java_executor; +#[cfg(feature = "ruby")] +mod ruby_executor; + mod bun_executor; pub mod common; mod config; @@ -52,6 +55,7 @@ pub mod result_processor; mod rust_executor; mod sanitized_sql_params; mod schema; +mod universal_pkg_installer; mod worker; mod worker_flow; mod worker_lockfiles; diff --git a/backend/windmill-worker/src/nu_executor.rs b/backend/windmill-worker/src/nu_executor.rs index c31d979c27..58f5e39b1f 100644 --- a/backend/windmill-worker/src/nu_executor.rs +++ b/backend/windmill-worker/src/nu_executor.rs @@ -20,7 +20,6 @@ use crate::{ }; use windmill_common::client::AuthedClient; - const NSJAIL_CONFIG_RUN_NU_CONTENT: &str = include_str!("../nsjail/run.nu.config.proto"); lazy_static::lazy_static! { static ref NU_PATH: String = std::env::var("NU_PATH").unwrap_or_else(|_| "/usr/bin/nu".to_string()); @@ -69,7 +68,7 @@ pub async fn handle_nu_job<'a>(mut args: JobHandlerInput<'a>) -> Result(mut args: JobHandlerInput<'a>) -> Result( .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await? + start_child_process(nsjail_cmd, NSJAIL_PATH.as_str(), false).await? } else { append_logs( &job.id, @@ -324,7 +323,7 @@ async fn run<'a>( std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), ); } - start_child_process(cmd, "nu").await? + start_child_process(cmd, "nu", false).await? }; handle_child::handle_child( &job.id, @@ -341,7 +340,8 @@ async fn run<'a>( &mut Some(occupancy_metrics), None, ) - .await + .await?; + Ok(()) } // #[cfg(test)] // mod test { diff --git a/backend/windmill-worker/src/php_executor.rs b/backend/windmill-worker/src/php_executor.rs index ec8478a9a5..008400daf8 100644 --- a/backend/windmill-worker/src/php_executor.rs +++ b/backend/windmill-worker/src/php_executor.rs @@ -20,8 +20,7 @@ use crate::{ read_result, start_child_process, OccupancyMetrics, }, handle_child::handle_child, - COMPOSER_CACHE_DIR, COMPOSER_PATH, DISABLE_NSJAIL, DISABLE_NUSER, NSJAIL_PATH, - PHP_PATH, + COMPOSER_CACHE_DIR, COMPOSER_PATH, DISABLE_NSJAIL, DISABLE_NUSER, NSJAIL_PATH, PHP_PATH, }; use windmill_common::client::AuthedClient; @@ -91,7 +90,7 @@ pub async fn composer_install( .args(args) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - let child_process = start_child_process(child_cmd, &*COMPOSER_PATH).await?; + let child_process = start_child_process(child_cmd, &*COMPOSER_PATH, false).await?; handle_child( job_id, @@ -307,7 +306,7 @@ try {{ .args(args) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await? + start_child_process(nsjail_cmd, NSJAIL_PATH.as_str(), false).await? } else { let cmd = { let script_path = format!("{job_dir}/wrapper.php"); @@ -326,7 +325,7 @@ try {{ .stderr(Stdio::piped()); php_cmd }; - start_child_process(cmd, &*PHP_PATH).await? + start_child_process(cmd, &*PHP_PATH, false).await? }; handle_child( @@ -345,5 +344,5 @@ try {{ None, ) .await?; - read_result(job_dir).await + read_result(job_dir, None).await } diff --git a/backend/windmill-worker/src/python_executor.rs b/backend/windmill-worker/src/python_executor.rs index fb760fb168..354ff0b8dc 100644 --- a/backend/windmill-worker/src/python_executor.rs +++ b/backend/windmill-worker/src/python_executor.rs @@ -369,7 +369,7 @@ pub async fn uv_pip_compile( ); } - let child_process = start_child_process(child_cmd, uv_cmd).await?; + let child_process = start_child_process(child_cmd, uv_cmd, false).await?; append_logs(&job_id, &w_id, logs, conn).await; handle_child( job_id, @@ -636,7 +636,7 @@ pub async fn handle_python_job( if v == '': del pre_args[k] kwargs = inner_script.preprocessor(**pre_args) - kwrags_json = res_to_json(kwargs) + kwrags_json = res_to_json(kwargs, type(kwargs)) with open("args.json", 'w') as f: f.write(kwrags_json)"# ) @@ -679,8 +679,7 @@ replace_invalid_fields = re.compile(r'(?:\bNaN\b|\\*\\u0000|Infinity|\-Infinity) result_json = os.path.join(os.path.abspath(os.path.dirname(__file__)), "result.json") -def res_to_json(res): - typ = type(res) +def res_to_json(res, typ): if typ.__name__ == 'DataFrame': if typ.__module__ == 'pandas.core.frame': res = res.values.tolist() @@ -704,7 +703,12 @@ try: if inner_script.{main_override} is None or not callable(inner_script.{main_override}): raise ValueError("{main_override} function is missing") res = inner_script.{main_override}(**args) - res_json = res_to_json(res) + typ = type(res) + if hasattr(res, '__iter__') and not isinstance(res, (str, dict, list, bytes, tuple, set, frozenset, range, memoryview, bytearray)) and typ.__name__ != 'DataFrame': + for chunk in res: + print("WM_STREAM: " + chunk.replace('\n', '\\n')) + res = None + res_json = res_to_json(res, typ) with open(result_json, 'w') as f: f.write(res_json) except BaseException as e: @@ -826,7 +830,7 @@ mount {{ ]) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await? + start_child_process(nsjail_cmd, NSJAIL_PATH.as_str(), false).await? } else { let mut python_cmd = Command::new(&python_path); @@ -855,10 +859,10 @@ mount {{ ); } - start_child_process(python_cmd, &python_path).await? + start_child_process(python_cmd, &python_path, false).await? }; - handle_child( + let handle_result = handle_child( &job.id, conn, mem_peak, @@ -892,7 +896,7 @@ mount {{ *new_args = Some(args.clone()); } - read_result(job_dir).await + read_result(job_dir, handle_result.result_stream).await } async fn prepare_wrapper( @@ -1358,7 +1362,7 @@ async fn spawn_uv_install( .args(vec!["--config", &nsjail_proto]) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await + start_child_process(nsjail_cmd, NSJAIL_PATH.as_str(), false).await } else { #[cfg(unix)] let req = req.to_owned(); @@ -1441,7 +1445,7 @@ async fn spawn_uv_install( .args(&command_args[1..]) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(cmd, UV_PATH.as_str()).await + start_child_process(cmd, UV_PATH.as_str(), false).await } #[cfg(windows)] @@ -1492,7 +1496,7 @@ async fn spawn_uv_install( .args(&command_args[1..]) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(cmd, "uv").await + start_child_process(cmd, "uv", false).await } } } diff --git a/backend/windmill-worker/src/python_versions.rs b/backend/windmill-worker/src/python_versions.rs index 99fa67f1fe..3d71e9f9ec 100644 --- a/backend/windmill-worker/src/python_versions.rs +++ b/backend/windmill-worker/src/python_versions.rs @@ -638,7 +638,7 @@ impl PyV { ); } - let child_process = start_child_process(child_cmd, "uv").await?; + let child_process = start_child_process(child_cmd, "uv", false).await?; append_logs(&job_id, &w_id, logs, conn).await; handle_child( @@ -656,7 +656,8 @@ impl PyV { occupancy_metrics, None, ) - .await + .await?; + Ok(()) } async fn find_python(&self) -> error::Result> { #[cfg(windows)] diff --git a/backend/windmill-worker/src/result_processor.rs b/backend/windmill-worker/src/result_processor.rs index a5de05c9e9..cf455e3f35 100644 --- a/backend/windmill-worker/src/result_processor.rs +++ b/backend/windmill-worker/src/result_processor.rs @@ -382,7 +382,7 @@ pub async fn process_result( Err(e) => { let error_value = match e { Error::ExitStatus(program, i) => { - let res = read_result(job_dir).await.ok(); + let res = read_result(job_dir, None).await.ok(); if res.as_ref().is_some_and(|x| !x.get().is_empty()) { res.unwrap() diff --git a/backend/windmill-worker/src/ruby_executor.rs b/backend/windmill-worker/src/ruby_executor.rs new file mode 100644 index 0000000000..944ee5ee9c --- /dev/null +++ b/backend/windmill-worker/src/ruby_executor.rs @@ -0,0 +1,899 @@ +use std::{collections::HashMap, process::Stdio}; + +use anyhow::anyhow; +use const_format::concatcp; +use itertools::Itertools; +use regex::Regex; +use tokio::{ + fs::{self, File}, + io::{AsyncReadExt, AsyncWriteExt}, + process::Command, +}; +use url::Url; +use uuid::Uuid; +use windmill_common::{ + client::AuthedClient, + error::Error, + utils::calculate_hash, + worker::{write_file, Connection, RubyAnnotations}, +}; +use windmill_parser::Arg; +use windmill_parser_ruby::parse_ruby_signature; +use windmill_queue::{append_logs, CanceledBy, MiniPulledJob}; + +use crate::{ + common::{ + create_args_and_out_file, get_reserved_variables, read_result, start_child_process, + OccupancyMetrics, + }, + handle_child::{self}, + universal_pkg_installer::{par_install_language_dependencies_seq, RequiredDependency}, + DISABLE_NSJAIL, DISABLE_NUSER, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, RUBY_CACHE_DIR, RUBY_REPOS, +}; +lazy_static::lazy_static! { + static ref RUBY_CONCURRENT_DOWNLOADS: usize = std::env::var("RUBY_CONCURRENT_DOWNLOADS").ok().map(|flag| flag.parse().unwrap_or(20)).unwrap_or(20); + static ref RUBY_PATH: String = std::env::var("RUBY_PATH").unwrap_or_else(|_| "/usr/bin/ruby".to_string()); + static ref BUNDLE_PATH: String = std::env::var("RUBY_BUNDLE_PATH").unwrap_or_else(|_| "/usr/bin/bundle".to_string()); + static ref GEM_PATH: String = std::env::var("RUBY_GEM_PATH").unwrap_or_else(|_| "/usr/bin/gem".to_string()); + static ref RUBY_PROXY_ENVS: Vec<(String, String)> = { + PROXY_ENVS + .clone() + .into_iter() + .map(|(k, v)| (k.to_lowercase(), v)) + .collect() + }; +} + +const NSJAIL_CONFIG_RUN_RUBY_CONTENT: &str = include_str!("../nsjail/run.ruby.config.proto"); +const NSJAIL_CONFIG_DOWNLOAD_RUBY_CONTENT: &str = + include_str!("../nsjail/download.ruby.config.proto"); +const NSJAIL_CONFIG_LOCK_RUBY_CONTENT: &str = include_str!("../nsjail/lock.ruby.config.proto"); + +#[cfg(debug_assertions)] +const DEV_CONF_NSJAIL: &'static str = r#" +# Mount nix store for nixos to work properly +mount { + src: "/nix/store" + dst: "/nix/store" + is_bind: true + mandatory: false +} +"#; + +#[cfg(not(debug_assertions))] +const DEV_CONF_NSJAIL: &'static str = ""; + +#[allow(dead_code)] +pub(crate) struct JobHandlerInput<'a> { + pub base_internal_url: &'a str, + pub canceled_by: &'a mut Option, + pub client: &'a AuthedClient, + pub parent_runnable_path: Option, + pub conn: &'a Connection, + pub envs: HashMap, + pub inner_content: &'a str, + pub job: &'a MiniPulledJob, + pub job_dir: &'a str, + pub mem_peak: &'a mut i32, + pub occupancy_metrics: &'a mut OccupancyMetrics, + pub requirements_o: Option<&'a String>, + pub shared_mount: &'a str, + pub worker_name: &'a str, +} + +pub async fn handle_ruby_job<'a>( + mut args: JobHandlerInput<'a>, +) -> Result, Error> { + // --- Prepare --- + { + prepare(&args).await?; + } + // --- Gen Lockfile --- + + let lockfile = resolve( + &args.job.id, + args.inner_content, + args.mem_peak, + args.canceled_by, + args.job_dir, + args.conn, + args.worker_name, + &args.job.workspace_id, + ) + .await?; + + // --- Install --- + + let ir = install(&mut args, lockfile).await?; + + // --- Execute --- + { + run(&mut args, ir).await?; + } + // --- Retrieve results --- + { + read_result(&args.job_dir, None).await + } +} +pub async fn prepare<'a>( + JobHandlerInput { + job, + conn, + job_dir, + inner_content, + client, + // + .. + }: &JobHandlerInput<'a>, +) -> Result<(), Error> { + create_args_and_out_file(&client, job, job_dir, conn).await?; + File::create(format!("{}/main.rb", job_dir)) + .await? + .write_all(&wrap(inner_content)?.into_bytes()) + .await?; + + let mini_wm_path = format!("{RUBY_CACHE_DIR}/gems/windmill-internal/windmill"); + if !std::fs::metadata(&mini_wm_path).is_ok() { + fs::create_dir_all(&mini_wm_path).await?; + + // Create inline.rb for dependency management (bundler/inline compatibility) + File::create(format!("{}/inline.rb", &mini_wm_path)) + .await? + .write_all(&wrap( + r#" +class GemfileProxy + def initialize + @gem_calls = [] + end + + def source(arg = nil, &block) + if arg.is_a?(String) && block_given? + # Handle the case where the argument is a string and a block is given + gemfile(&block) + elsif arg.is_a?(String) + # Handle the case where the argument is a string and no block is given + # Do nothing in this case + else + raise ArgumentError, "Invalid argument or block" + end + end + + def group(*args, **kwargs) + # Handle group calls if needed + end + + def ruby(*args, **kwargs) + warn "Custom Ruby runtime specifications (engine, engine_version, patchlevel) are not supported in this environment. Using system ruby" + end + + def gem(name, version = nil, require: nil, **kwargs) + @gem_calls << { name: name, version: version, require: require, kwargs: kwargs } + + case require + when false + # Skip requiring + when nil, true + begin + Kernel.require(name) + rescue LoadError => e + warn "WARN: Gem '#{name}' failed to auto-require. Try either:" + warn " `:require => false` or specify exact require path" + warn "Warn details: #{e.class}: #{e.message}" + end + else + begin + Kernel.require(require.to_s) + rescue LoadError => e + warn "WARN: Gem '#{name}' failed to auto-require. Try either:" + warn " `:require => false` or specify exact require path" + warn "Warn details: #{e.class}: #{e.message}" + end + end + end + + + def call_block(&block) + instance_eval(&block) if block_given? + end +end + +def gemfile(&block) + proxy = GemfileProxy.new + proxy.call_block(&block) +end + +def main +end +"# + )?.into_bytes()) + .await?; + + // Create mini.rb for Windmill client methods + File::create(format!("{}/mini.rb", &mini_wm_path)) + .await? + .write_all( + &wrap( + r##" +require 'net/http' +require 'uri' +require 'json' + +# Windmill mini client methods +def get_variable(path) + base_url = ENV['BASE_INTERNAL_URL'] + workspace = ENV['WM_WORKSPACE'] + token = ENV['WM_TOKEN'] + + uri = URI("#{base_url}/api/w/#{workspace}/variables/get_value/#{path}") + + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = uri.scheme == 'https' + + request = Net::HTTP::Get.new(uri) + request['Authorization'] = "Bearer #{token}" + + response = http.request(request) + + if response.code == '200' + JSON.parse(response.body) + else + raise "Failed to get variable #{path}: #{response.code} #{response.message}" + end +end + +def get_resource(path) + base_url = ENV['BASE_INTERNAL_URL'] + workspace = ENV['WM_WORKSPACE'] + token = ENV['WM_TOKEN'] + + uri = URI("#{base_url}/api/w/#{workspace}/resources/get_value_interpolated/#{path}") + + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = uri.scheme == 'https' + + request = Net::HTTP::Get.new(uri) + request['Authorization'] = "Bearer #{token}" + + response = http.request(request) + + if response.code == '200' + JSON.parse(response.body) + else + raise "Failed to get resource #{path}: #{response.code} #{response.message}" + end +end + +def main +end +"##, + )? + .into_bytes(), + ) + .await?; + } + Ok(()) +} + +pub async fn resolve<'a>( + job_id: &Uuid, + inner_content: &str, + mem_peak: &mut i32, + canceled_by: &mut Option, + job_dir: &str, + conn: &Connection, + worker_name: &str, + w_id: &str, +) -> Result { + lazy_static::lazy_static! { + static ref BUNDLER_RE: Regex = Regex::new(r#"(?m)^\s*require\s*['"]bundler/inline['"]"#).unwrap(); + static ref UNSUPPORTED_SOURCE_RE: Regex = Regex::new(r#"(?m)(?^\s*source\s*['"]\S+://(?\S+):(?\S+)@.*['"]\s*\n)"#).unwrap(); + static ref SOURCES_RE: Regex = Regex::new(r#"\S+://(?\S+:\S+)@(?\S*)"#).unwrap(); + } + + if BUNDLER_RE.find(&inner_content).is_some() { + return Err(anyhow!( + "Detected `require 'bundler/inline'` - please replace with `require 'windmill/inline'`. +Your Gemfile syntax will continue to work as-is." + ) + .into()); + } + + let gemfile: String = windmill_parser_ruby::parse_ruby_requirements(&inner_content)? + .lines() + .map(str::trim) + .filter(|s| !s.is_empty() && !s.starts_with('#')) + .join("\n"); + + if let Some(caps) = UNSUPPORTED_SOURCE_RE.captures(&gemfile) { + if let (Some(src_m), Some(usr_m), Some(passwd_m)) = + (caps.name("src"), caps.name("usr"), caps.name("passwd")) + { + let cause = src_m + .as_str() + .replace(usr_m.as_str(), "REDACTED") + .replace(passwd_m.as_str(), "REDACTED"); + + return Err(anyhow!("!> {cause} - inline source credentials are not supported at the moment. Please set up credentials in instance settings, if you do not have access to it, contact your administrator. If you need this feature please let us know.").into()); + } + } + + let mut file = File::create(job_dir.to_owned() + "/Gemfile").await?; + file.write_all(&gemfile.as_bytes()).await?; + + let req_hash = format!("ruby-{}", calculate_hash(&gemfile)); + if let Some(db) = conn.as_sql() { + if let Some(cached) = sqlx::query_scalar!( + "SELECT lockfile FROM pip_resolution_cache WHERE hash = $1", + req_hash + ) + .fetch_optional(db) + .await? + { + return Ok(cached); + } + } + let lock = { + append_logs( + job_id, + w_id, + format!("\n--- RESOLVING LOCKFILE ---\n"), + conn, + ) + .await; + + let mut cmd = if !cfg!(windows) && !*DISABLE_NSJAIL { + let nsjail_proto = format!("{}.lock.config.proto", Uuid::new_v4()); + let _ = write_file( + &job_dir, + &nsjail_proto, + &NSJAIL_CONFIG_LOCK_RUBY_CONTENT + .replace("{JOB_DIR}", job_dir) + .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) + .replace("{DEV}", DEV_CONF_NSJAIL), // .replace("{BUILD}", &build_dir), + )?; + let mut cmd = Command::new(NSJAIL_PATH.as_str()); + cmd.args(vec!["--config", &nsjail_proto, "--", BUNDLE_PATH.as_str()]); + cmd + } else if cfg!(windows) { + Command::new("bundle.bat") + } else { + Command::new(BUNDLE_PATH.as_str()) + }; + + cmd.env_clear() + .current_dir(job_dir.to_owned()) + .envs(vec![ + ("PATH".to_owned(), PATH_ENV.clone()), + // Make sure there is nothing written to actual home + // This way we keep everything clean, organized and maintable + ("HOME".to_owned(), RUBY_CACHE_DIR.to_owned()), + ]) + .envs(RUBY_PROXY_ENVS.clone()); + + for repo in RUBY_REPOS.read().await.clone().unwrap_or_default() { + if let (Some(url), usr, Some(passwd)) = + (repo.domain(), repo.username(), repo.password()) + { + cmd.env( + format!( + "BUNDLE_{}", + url + // Align with bundlers' format. Example: + // BUNDLE_GEM1__EXAMPLE__COM + .replace(".", "__") + .to_uppercase() + // Remove trailing slashes + .replace("/", "") + ), + // BUNDLE_GEM1__EXAMPLE__COM=admin:123 + format!("{usr}:{passwd}"), + ); + } + } + cmd.args(&["lock", "--retry", "2"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + #[cfg(windows)] + { + cmd.env("SystemRoot", crate::SYSTEM_ROOT.as_str()) + .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ); + } + let child = start_child_process(cmd, "bundle", false).await?; + + handle_child::handle_child( + job_id, + conn, + mem_peak, + canceled_by, + child, + !*DISABLE_NSJAIL, + worker_name, + w_id, + "bundle", + None, + false, + &mut None, + // Some(&mut stdout), + None, + ) + .await?; + + let path_lock = format!("{job_dir}/Gemfile.lock"); + let mut file = File::open(path_lock).await?; + let mut req_content = "".to_string(); + file.read_to_string(&mut req_content).await?; + req_content + }; + + if let Some(db) = conn.as_sql() { + sqlx::query!( + "INSERT INTO pip_resolution_cache (hash, lockfile, expiration) VALUES ($1, $2, now() + ('3 days')::interval) ON CONFLICT (hash) DO UPDATE SET lockfile = $2", + req_hash, + lock.clone(), + ).fetch_optional(db).await?; + } + + append_logs(job_id, w_id, format!("\n{}", &lock), conn).await; + Ok(lock) +} + +struct InstallRes { + /// "/tmp/windmill/cache/ruby/gems/ab..xz-name-1.0.0/gems/name-1.0.0/lib:/tmp/windmill/..." + rubylib: String, + + /// ["/tmp/windmill/cache/ruby/gems/ab..xz-name-1.0.0", "/tmp/windmill/..."] + top_level_paths: Vec, +} + +async fn install<'a>( + JobHandlerInput { + worker_name, + job, + conn, + job_dir, + inner_content, + envs, + client, + parent_runnable_path, + .. + }: &mut JobHandlerInput<'a>, + lockfile: String, +) -> Result { + #[derive(Debug, Clone)] + enum CurrentSource { + GIT { remote: Option, revision: Option }, + GEM { remote: Option }, + INIT, + } + + #[derive(Debug, Clone)] + enum FinalSource { + #[allow(dead_code)] + GIT { + remote: String, + revision: String, + }, + GEM { + remote: String, + }, + } + + #[derive(Clone, Debug)] + struct CustomPayload { + source: FinalSource, + version: String, + pkg: String, + } + + let mut deps = vec![]; + let mut last_source = CurrentSource::INIT; + 'lock_lines: for line in lockfile.lines() { + // Trimed line + let tl = line.trim_start(); + if tl.starts_with("GIT") { + last_source = CurrentSource::GIT { remote: None, revision: None }; + continue 'lock_lines; + } else if tl.starts_with("GEM") { + last_source = CurrentSource::GEM { remote: None }; + continue 'lock_lines; + } + + const REMOTE: &str = "remote: "; + if line.contains(REMOTE) { + match &mut last_source { + CurrentSource::GIT { remote, .. } => remote.replace(tl.replace(REMOTE, "")), + CurrentSource::GEM { remote } => remote.replace(tl.replace(REMOTE, "")), + CurrentSource::INIT => return Err(Error::InternalErr("BUG on installation stage: detected initial enum variant at the stage it was not supposed to be at. Please report this issue.".to_owned())), + }; + continue 'lock_lines; + } + + const REVISION: &str = "revision: "; + if line.contains(REVISION) { + if let CurrentSource::GIT { revision, .. } = &mut last_source { + revision.replace(tl.replace(REVISION, "")); + } + continue 'lock_lines; + } + + // Check on untrimed line + if !line.starts_with(" ") || line.starts_with(" ") { + // "1234" "123456" + // For example: + // + // GIT + // remote: https://github.com/rails/rails.git + // revision: 8cb4f8ceffe375f64052579cd7a580bfdaf36d61 + // specs: + // actioncable (8.1.0.alpha) + // actionpack (= 8.1.0.alpha) + // activesupport (= 8.1.0.alpha) + // nio4r (~> 2.0) + // websocket-driver (>= 0.6.1) + // zeitwerk (~> 2.6) + // actionmailbox (8.1.0.alpha) + // actionpack (= 8.1.0.alpha) + // activejob (= 8.1.0.alpha) + // activerecord (= 8.1.0.alpha) + // activestorage (= 8.1.0.alpha) + // activesupport (= 8.1.0.alpha) + // mail (>= 2.8.0) + // actionmailer (8.1.0.alpha) + // ... + // + // In this snippet we only want actioncable, actionmailbox and actionmailer + // + // If it got the hit than that's not it, just skip + continue 'lock_lines; + } + + let buf = tl.replace(['(', ')'], ""); + let &[pkg, version, ..] = buf.split_whitespace().collect_vec().as_slice() else { + return Err(anyhow!("Cannot determine version and package name for: {}", tl).into()); + }; + + // Strip platform from version. E.g: + // 1.18.9-x86_64-darwin -> 1.18.9 + let version = version.split('-').next().unwrap_or(version); + + let custom_payload = CustomPayload { + version: version.into(), + pkg: pkg.into(), + source: match last_source.clone() { + CurrentSource::GIT { remote, revision } => FinalSource::GIT { + remote: remote.unwrap_or_default(), + revision: revision.unwrap_or_default(), + }, + CurrentSource::GEM { remote } => { + FinalSource::GEM { remote: remote.unwrap_or_default() } + }, + CurrentSource::INIT => return Err(Error::InternalErr("BUG on installation stage: detected initial enum variant at the stage it was not supposed to be at. Please report this issue.".to_owned())), + }, + }; + + // Calculate hash based on source + // Obfuscation is essential because remote can include credentials + let mut hash = calculate_hash(&format!("{:?}", custom_payload.clone())); + + // Use 32 characters which is 256 bits + hash.truncate(32); + + // Final format: + // 123...zx-activesupport-8.0.2 + // ^^^^^^^^ hash based on source and type (GEM or GIT) + let handle = format!("{}-{}-{}", hash, pkg, version); + let path = format!("{RUBY_CACHE_DIR}/gems/{}", &handle); + + deps.push(RequiredDependency { + path, + _s3_handle: handle, + display_name: tl.to_owned(), + custom_payload, + }); + } + + let job_dir = job_dir.to_owned(); + let jailed = !cfg!(windows) && !*DISABLE_NSJAIL; + let RubyAnnotations { verbose } = RubyAnnotations::parse(&inner_content); + let repos = RUBY_REPOS.read().await.clone().unwrap_or_default(); + let (envs, reserved_variables) = ( + envs.clone(), + get_reserved_variables(job, &client.token, conn, parent_runnable_path.clone()).await?, + ); + par_install_language_dependencies_seq( + deps.clone(), + "ruby", + "gem", + false, + *RUBY_CONCURRENT_DOWNLOADS, + // true, + // crate::common::InstallStrategy::Single(Arc::new(move |dependency| { + move |dependency| { + let mut cmd = if jailed { + std::fs::create_dir_all(&dependency.path)?; + let nsjail_proto = format!("{}.download.config.proto", Uuid::new_v4()); + let _ = write_file( + &job_dir, + &nsjail_proto, + &NSJAIL_CONFIG_DOWNLOAD_RUBY_CONTENT + .replace("{TARGET}", &dependency.path) + .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()) + .replace("{DEV}", DEV_CONF_NSJAIL), // .replace("{BUILD}", &build_dir), + )?; + let mut cmd = Command::new(NSJAIL_PATH.as_str()); + cmd.args(vec!["--config", &nsjail_proto, "--", GEM_PATH.as_str()]); + cmd + } else { + Command::new(if cfg!(windows) { + "gem.cmd" + } else { + GEM_PATH.as_str() + }) + }; + cmd.env_clear() + .current_dir(&job_dir) + .envs(vec![ + ("PATH".to_owned(), PATH_ENV.clone()), + // Make sure there is nothing written to actual home + // This way we keep everything clean, organized and maintable + ("HOME".to_owned(), RUBY_CACHE_DIR.to_owned()), + ]) + .envs(RUBY_PROXY_ENVS.clone()); + + // Figure out source + let source = { + let source = match dependency.custom_payload.source { + FinalSource::GIT { .. } => return Err(anyhow!("GIT is not supported").into()), + FinalSource::GEM { remote } => remote, + } + .trim() + .to_owned(); + + let src_url = Url::parse(&source)?; + repos + .iter() + .find(|inst_url| { + inst_url.domain().is_some() + && (inst_url.domain(), inst_url.path()) + == (src_url.domain(), src_url.path()) + }) + .map(Url::to_string) + .unwrap_or(source) + }; + + cmd.args(&[ + "install", + &dependency.custom_payload.pkg, + "--version", + &dependency.custom_payload.version, + "--install-dir", + &dependency.path, + // Disable transitive dependencies! + "--ignore-dependencies", + "--no-document", + // Do not use default sources + "--clear-sources", + // Specify the one from lockfile + "--source", + source.as_str(), + ]) + .envs(envs.clone()) + .envs(reserved_variables.clone()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + if !verbose { + // Make it output minimal info + cmd.args(&["--quiet", "--silent"]); + } + + #[cfg(windows)] + { + cmd.env("SystemRoot", crate::SYSTEM_ROOT.as_str()) + .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ) + .env( + "SYSTEMDRIVE", + std::env::var("SYSTEMDRIVE").unwrap_or_else(|_| String::from("C:")), + ); + } + + Ok(cmd) + }, + // async move |_| Ok(()), + &job.id, + &job.workspace_id, + worker_name, + jailed, + conn, + ) + .await?; + + let mut res = InstallRes { + rubylib: deps + .iter() + .map(|rd| { + format!( + "{}/gems/{}-{}/lib", + rd.path, rd.custom_payload.pkg, rd.custom_payload.version + ) + }) + .join(":"), + top_level_paths: deps.iter().map(|rd| rd.path.clone()).collect_vec(), + }; + // Include builtin windmill client + { + const WM_INTERNAL: &str = concatcp!(RUBY_CACHE_DIR, "/gems/windmill-internal"); + res.top_level_paths.push(WM_INTERNAL.to_owned()); + res.rubylib += format!(":{WM_INTERNAL}").as_str(); + } + Ok(res) +} + +async fn run<'a>( + JobHandlerInput { + occupancy_metrics, + mem_peak, + canceled_by, + worker_name, + job, + conn, + job_dir, + shared_mount, + client, + envs, + base_internal_url, + parent_runnable_path, + .. + }: &mut JobHandlerInput<'a>, + InstallRes { rubylib, top_level_paths }: InstallRes, +) -> Result<(), Error> { + let reserved_variables = + get_reserved_variables(job, &client.token, conn, parent_runnable_path.clone()).await?; + + let child = if !cfg!(windows) && !*DISABLE_NSJAIL { + append_logs( + &job.id, + &job.workspace_id, + format!("\n--- ISOLATED RUBY CODE EXECUTION ---\n"), + conn, + ) + .await; + let shared_deps = top_level_paths + .into_iter() + .map(|pp| { + format!( + r#" +mount {{ + src: "{pp}" + dst: "{pp}" + is_bind: true + rw: false +}} + "# + ) + }) + .join("\n"); + + write_file( + job_dir, + "run.config.proto", + &NSJAIL_CONFIG_RUN_RUBY_CONTENT + .replace("{JOB_DIR}", job_dir) + .replace("{SHARED_MOUNT}", &shared_mount) + .replace("{SHARED_DEPENDENCIES}", &shared_deps) + .replace("{DEV}", DEV_CONF_NSJAIL) + .replace("{CLONE_NEWUSER}", &(!*DISABLE_NUSER).to_string()), + )?; + let mut cmd = Command::new(NSJAIL_PATH.as_str()); + cmd.env_clear() + .current_dir(job_dir) + .env("PATH", PATH_ENV.as_str()) + .env("BASE_INTERNAL_URL", base_internal_url) + .env("RUBYLIB", rubylib.as_str()) + .envs(envs) + .envs(reserved_variables) + .envs(RUBY_PROXY_ENVS.clone()) + .envs(PROXY_ENVS.clone()) + .args(vec![ + "--config", + "run.config.proto", + "--", + RUBY_PATH.as_str(), + "main.rb", + ]); + cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); + + start_child_process(cmd, NSJAIL_PATH.as_str(), false).await? + } else { + append_logs( + &job.id, + &job.workspace_id, + format!("\n--- RUBY CODE EXECUTION ---\n"), + conn, + ) + .await; + + let mut cmd = Command::new(if cfg!(windows) { + "ruby.exe" + } else { + RUBY_PATH.as_str() + }); + + #[cfg(windows)] + let rubylib = rubylib.replace(":", ";"); + + cmd.env_clear() + .current_dir(job_dir.to_owned()) + .env("PATH", PATH_ENV.as_str()) + .env("RUBYLIB", rubylib.as_str()) + .env("BASE_INTERNAL_URL", base_internal_url) + .envs(reserved_variables) + .envs(RUBY_PROXY_ENVS.clone()) + .envs(PROXY_ENVS.clone()) + .envs(envs); + + cmd.args(&["main.rb"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + + #[cfg(windows)] + { + cmd.env("SystemRoot", crate::SYSTEM_ROOT.as_str()) + .env("USERPROFILE", crate::USERPROFILE_ENV.as_str()) + .env( + "TMP", + std::env::var("TMP").unwrap_or_else(|_| String::from("/tmp")), + ); + } + start_child_process(cmd, "ruby", false).await? + }; + handle_child::handle_child( + &job.id, + conn, + mem_peak, + canceled_by, + child, + !*DISABLE_NSJAIL, + worker_name, + &job.workspace_id, + "ruby", + job.timeout, + false, + &mut Some(occupancy_metrics), + None, + ) + .await?; + Ok(()) +} +fn wrap(inner_content: &str) -> Result { + let sig = parse_ruby_signature(inner_content)?; + let spread = sig + .args + .clone() + .into_iter() + .map(|Arg { name, .. }| format!("a[\"{}\"]", name)) + .collect_vec() + .join(","); + Ok( + r#"INNER_CONTENT + +require 'json' +a = JSON.parse(File.read("args.json")) +res = main(SPREAD) +File.open("result.json", "w") do |file| + file.write(JSON.generate(res)) +end + "# + .replace("INNER_CONTENT", inner_content) + .replace("SPREAD", &spread), // .replace("TRANSFORM", transform) + ) +} diff --git a/backend/windmill-worker/src/rust_executor.rs b/backend/windmill-worker/src/rust_executor.rs index 045158511e..b717c7d462 100644 --- a/backend/windmill-worker/src/rust_executor.rs +++ b/backend/windmill-worker/src/rust_executor.rs @@ -175,7 +175,7 @@ pub async fn generate_cargo_lockfile( std::env::var("TMP").unwrap_or_else(|_| "C:\\tmp".to_string()), ); } - let gen_lockfile_process = start_child_process(gen_lockfile_cmd, CARGO_PATH.as_str()).await?; + let gen_lockfile_process = start_child_process(gen_lockfile_cmd, CARGO_PATH.as_str(), false).await?; handle_child( job_id, conn, @@ -282,7 +282,7 @@ async fn get_build_dir( ); tokio::spawn(async move { - if let Err(e) = match start_child_process(sweep_cmd, CARGO_PATH.as_str()).await { + if let Err(e) = match start_child_process(sweep_cmd, CARGO_PATH.as_str(), false).await { Ok(sweep_process) => { handle_child( &job_id, @@ -365,7 +365,7 @@ pub async fn build_rust_crate( if !is_preview { nsjail_cmd.arg("--release"); } - start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await? + start_child_process(nsjail_cmd, NSJAIL_PATH.as_str(), false).await? } else { let mut build_rust_cmd = Command::new(CARGO_PATH.as_str()); build_rust_cmd @@ -394,7 +394,7 @@ pub async fn build_rust_crate( ); build_rust_cmd.env("USERPROFILE", crate::USERPROFILE_ENV.as_str()); } - start_child_process(build_rust_cmd, CARGO_PATH.as_str()).await? + start_child_process(build_rust_cmd, CARGO_PATH.as_str(), false).await? }; handle_child( &job.id, @@ -560,7 +560,7 @@ pub async fn handle_rust_job( .args(vec!["--config", "run.config.proto", "--", "/tmp/main"]) .stdout(Stdio::piped()) .stderr(Stdio::piped()); - start_child_process(nsjail_cmd, NSJAIL_PATH.as_str()).await? + start_child_process(nsjail_cmd, NSJAIL_PATH.as_str(), false).await? } else { let compiled_executable_name = "./main"; let mut run_rust = Command::new(compiled_executable_name); @@ -582,7 +582,7 @@ pub async fn handle_rust_job( run_rust.env("USERPROFILE", crate::USERPROFILE_ENV.as_str()); } - start_child_process(run_rust, compiled_executable_name).await? + start_child_process(run_rust, compiled_executable_name, false).await? }; handle_child( &job.id, @@ -600,5 +600,5 @@ pub async fn handle_rust_job( None, ) .await?; - read_result(job_dir).await + read_result(job_dir, None).await } diff --git a/backend/windmill-worker/src/universal_pkg_installer.rs b/backend/windmill-worker/src/universal_pkg_installer.rs new file mode 100644 index 0000000000..1e4a8c2d39 --- /dev/null +++ b/backend/windmill-worker/src/universal_pkg_installer.rs @@ -0,0 +1,767 @@ +use std::sync::Arc; + +use anyhow::bail; +use itertools::Itertools; +use process_wrap::tokio::TokioChildWrapper; +use tokio::{ + fs::File, + process::Command, + sync::{broadcast::Sender, OwnedSemaphorePermit, RwLock, Semaphore}, + task::JoinHandle, +}; +use uuid::Uuid; +use windmill_common::{error, worker::Connection}; + +use crate::{common::start_child_process, DISABLE_NSJAIL}; + +#[derive(Clone, Debug)] +pub struct RequiredDependency { + /// Expected directory of dependency in cache + /// For example: + /// /tmp/windmill/cache/python_311/rich==0.0.0 + /// IMPORTANT!: path should not end with '/' + pub path: String, + /// Name to use for S3 tars + pub _s3_handle: String, + /// Display name + /// Name that will be used for console output and logging + pub display_name: String, + /// Custom payload can be used for passing information easily. If you wish not to use it just leave '()' + pub custom_payload: T, +} + +#[allow(dead_code)] +pub enum InstallStrategy { + /// Will invoke callback to install single dependency + Single(Arc) -> Result + Send + Sync>), + /// Will try to pull S3 first and will invoke closure to install the rest + AllAtOnce(Arc>>>), +} + +/// # General +/// +/// Languages that compile usually include dependencies in final executable. +/// When dynamic languages do not and runtime dependencies are provided separately. +/// +/// This helper assumes that the language is dynamic. +/// Python, Ruby, Java are dynamic and they can use this helper. +/// +/// # Features +/// +/// This helper will install all specified dependencies in parallel and if it is EE, cache to S3 +/// It has atomic success file, allowing to distinguish failed installations from succesfull. +/// +/// Besides that it provides console output and does logging. +/// +/// # Usage +/// +/// Most important arguments in this helper are `deps` and `install_fn` +/// +/// In `deps` you specify all dependencies that are needed to be on worker in order to execute script. +/// You don't know which are actually installed and which are not. +/// +/// `deps` is a vector of RequiredDependency. Check [RequiredDependency] for more context. +/// +/// After `deps` are provided helper will check each dependency and check if it is in cache, if not it will try to pull from S3 +/// and if it does not work either, it will invoke `install_fn` closure. +/// Closure arguments has dependency name as well as its expected path in cache. +/// Closure should return Command that will install dependency to asked place. +#[allow(dead_code)] +pub async fn par_install_language_dependencies_all_at_once< + 'a, + T: Clone + std::marker::Send + Sync + 'a + 'static, +>( + deps: Vec>, + _language_name: &'a str, + installer_executable_name: &'a str, + _platform_agnostic: bool, + concurrent_downloads: usize, + stdout_on_err: bool, + callback: impl Fn(Vec>) -> Result + + Send + + Sync + + 'static, + postinstall_cb: impl AsyncFn(Vec>) -> Result<(), error::Error>, + job_id: &'a Uuid, + w_id: &'a str, + worker_name: &'a str, + jailed: bool, + conn: &'a Connection, +) -> anyhow::Result<()> { + #[cfg(all(feature = "enterprise", feature = "parquet"))] + let is_not_pro = !matches!( + windmill_common::ee_oss::get_license_plan().await, + windmill_common::ee_oss::LicensePlan::Pro + ); + #[cfg(all(feature = "enterprise", feature = "parquet"))] + if is_not_pro { + windmill_queue::append_logs( + job_id, + w_id, + format!("\nLooking for packages on S3:\n"), + conn, + ) + .await; + } + let total_time = std::time::Instant::now(); + let (missing, name_max_length) = filter_to_missing(deps, job_id, w_id, jailed, conn).await?; + if missing.is_empty() { + return Ok(()); + } + let to_batch_install = Arc::new(RwLock::new(vec![])); + let handles = spawn_wrapped_installation_threads( + missing, + name_max_length, + InstallStrategy::AllAtOnce(to_batch_install.clone()), + installer_executable_name, + concurrent_downloads, + job_id, + w_id, + worker_name, + conn, + _language_name, + _platform_agnostic, + ) + .await?; + let installation_res = process_handles(handles, w_id).await; + if !to_batch_install.read().await.is_empty() { + let for_all_at_once_copy = to_batch_install.read().await.clone(); + windmill_queue::append_logs( + job_id, + w_id, + format!("\n\nFetching {} packages...\n", for_all_at_once_copy.len()), + &conn, + ) + .await; + let cmd = callback(for_all_at_once_copy.clone())?; + let child = start_child_process(cmd, &installer_executable_name, false).await?; + let mut buf = "".to_owned(); + let pipe_stdout = if stdout_on_err { Some(&mut buf) } else { None }; + + if let Err(e) = crate::handle_child::handle_child( + &(if pipe_stdout.is_some() { + Uuid::nil() + } else { + *job_id + }), + conn, + // TODO: Return mem_peak + &mut 0, + // TODO: Return canceld_by_ref + &mut None, + child, + !*DISABLE_NSJAIL, + &worker_name, + &w_id, + &installer_executable_name, + None, + false, + &mut None, + pipe_stdout, + ) + .await + { + bail!(format!( + "\nerror while installing dependencies: {e:?}\n{}", + buf + )); + } + { + postinstall_cb(for_all_at_once_copy.clone()).await?; + } + for RequiredDependency { path, _s3_handle, .. } in for_all_at_once_copy.into_iter() { + mark_success(path.clone(), job_id, w_id).await; + #[cfg(all(feature = "enterprise", feature = "parquet"))] + { + if let Some(os) = windmill_common::s3_helpers::get_object_store().await { + let language_name = _language_name.to_owned(); + tokio::spawn(async move { + if let Err(e) = crate::global_cache::build_tar_and_push( + os, + path, + language_name, + Some(_s3_handle), + _platform_agnostic, + ) + .await + { + tracing::warn!("failed to build tar and push: {e:?}"); + } + }); + } + } + } + } + finish_installation(total_time, job_id, w_id, conn).await; + installation_res +} + +/// # General +/// +/// Languages that compile usually include dependencies in final executable. +/// When dynamic languages do not and runtime dependencies are provided separately. +/// +/// This helper assumes that the language is dynamic. +/// Python, Ruby, Java are dynamic and they can use this helper. +/// +/// # Features +/// +/// This helper will install all specified dependencies in parallel and if it is EE, cache to S3 +/// It has atomic success file, allowing to distinguish failed installations from succesfull. +/// +/// Besides that it provides console output and does logging. +/// +/// # Usage +/// +/// Most important arguments in this helper are `deps` and `install_fn` +/// +/// In `deps` you specify all dependencies that are needed to be on worker in order to execute script. +/// You don't know which are actually installed and which are not. +/// +/// `deps` is a vector of RequiredDependency. Check [RequiredDependency] for more context. +/// +/// After `deps` are provided helper will check each dependency and check if it is in cache, if not it will try to pull from S3 +/// and if it does not work either, it will invoke `install_fn` closure. +/// Closure arguments has dependency name as well as it`s expected path in cache. +/// Closure should return Command that will install dependency to asked place. +#[allow(dead_code)] +pub async fn par_install_language_dependencies_seq< + 'a, + T: Clone + std::marker::Send + Sync + 'a + 'static, +>( + deps: Vec>, + _language_name: &'a str, + installer_executable_name: &'a str, + _platform_agnostic: bool, + concurrent_downloads: usize, + callback: impl Fn(RequiredDependency) -> Result + Send + Sync + 'static, + job_id: &'a Uuid, + w_id: &'a str, + worker_name: &'a str, + jailed: bool, + conn: &'a Connection, +) -> anyhow::Result<()> { + #[cfg(all(feature = "enterprise", feature = "parquet"))] + let is_not_pro = !matches!( + windmill_common::ee_oss::get_license_plan().await, + windmill_common::ee_oss::LicensePlan::Pro + ); + #[cfg(all(feature = "enterprise", feature = "parquet"))] + if is_not_pro { + windmill_queue::append_logs( + job_id, + w_id, + format!("\nLooking for packages on S3:\n"), + conn, + ) + .await; + } + let total_time = std::time::Instant::now(); + let (missing, name_max_length) = filter_to_missing(deps, job_id, w_id, jailed, conn).await?; + if missing.is_empty() { + return Ok(()); + } + let handles = spawn_wrapped_installation_threads( + missing, + name_max_length, + InstallStrategy::Single(Arc::new(callback)), + installer_executable_name, + concurrent_downloads, + job_id, + w_id, + worker_name, + conn, + _language_name, + _platform_agnostic, + ) + .await?; + + let installation_res = process_handles(handles, w_id).await; + finish_installation(total_time, job_id, w_id, conn).await; + installation_res +} + +type NameMaxLength = usize; +async fn filter_to_missing<'a, T: Clone + std::marker::Send + Sync + 'a + 'static>( + mut deps: Vec>, + job_id: &Uuid, + w_id: &str, + jailed: bool, + conn: &Connection, +) -> anyhow::Result<(Vec>, NameMaxLength)> { + // Unique to flatten all same values + deps = deps.into_iter().unique_by(|rd| rd.path.clone()).collect(); + // Total to install + let mut missing = vec![]; + // Name max length + let mut name_ml = 0; + for rd in deps.into_iter() { + let display_name = rd.display_name.clone(); + if rd.path.ends_with("/") { + anyhow::bail!("Internal error: path should not end with '/'") + } + { + // Later will help us align text in log console + if display_name.len() > name_ml { + name_ml = rd.display_name.len(); + } + } + // Will look like: /tmp/windmill/cache/lang/dependency.valid.windmill + if tokio::fs::metadata(rd.path.clone() + ".valid.windmill") + .await + .is_err() + { + missing.push(rd); + } + } + if !missing.is_empty() { + windmill_queue::append_logs( + job_id, + w_id, + if jailed { + format!("\n--- ISOLATED INSTALLATION ---\n\nTo be installed:\n\n") + } else { + format!("\n--- INSTALLATION ---\n\nTo be installed:\n\n") + }, + conn, + ) + .await; + let to_log = missing + .iter() + .map(|rd| format!("- {}", &rd.display_name)) + .join("\n") + + "\n"; + + windmill_queue::append_logs(job_id, w_id, to_log, conn).await; + } + Ok((missing, name_ml)) +} + +enum Action { + Install(Box), + AddToBulk(Arc>>>), +} +struct TaskKiller(tokio::sync::broadcast::Sender); +impl Drop for TaskKiller { + fn drop(&mut self) { + // We don't care if it actually stopped anything or not. + // In some cases it will fire and actually stop + // In other cases do nothing + #[allow(unused_must_use)] + self.0.send("Installation failed".to_owned()); + } +} +// Callback will be invoked only if could not pull from S3 +async fn spawn_wrapped_installation_threads< + 'a, + T: Clone + std::marker::Send + Sync + 'a + 'static, +>( + missing: Vec>, + name_ml: usize, + strategy: InstallStrategy, + installer_executable_name: &str, + concurrent_downloads: usize, + job_id: &Uuid, + w_id: &str, + worker_name: &str, + conn: &Connection, + _language_name: &str, + _platform_agnostic: bool, +) -> anyhow::Result<( + Vec>>, + tokio::sync::broadcast::Sender<()>, +)> { + // Semaphore will panic if value less then 1 + let parallel_limit = concurrent_downloads.clamp(1, 30); + tracing::info!( + workspace_id = %w_id, + "Install parallel limit: {}, job: {}", + parallel_limit, + job_id + ); + + let (mut handles, semaphore, total_to_install, counter_arc) = ( + vec![], + Arc::new(Semaphore::new(parallel_limit)), + missing.len(), + Arc::new(tokio::sync::Mutex::new(0)), + ); + + // Pretty sensitive. Single drop will fail installation + let (kill_tx, ..) = tokio::sync::broadcast::channel::(10); + // ^^^^^^^ Original will go into listen_to_cancel + // + // But before that it will be used to populate t and r for every thread + let trxs: Vec<( + tokio::sync::broadcast::Sender, + tokio::sync::broadcast::Receiver, + )> = (0..missing.len()) + .map(|_| (kill_tx.clone(), kill_tx.subscribe())) + .collect(); + + // Listen to cancel events and stop jobs + // NOTE: + // once closer is dropped, the listener will be closed + let closer = listen_to_cancel(job_id.clone(), w_id.to_owned(), conn.clone(), kill_tx).await; + + for (dep, (kill_tx, mut kill_rx)) in missing.into_iter().zip(trxs) { + let permit = semaphore.clone().acquire_owned().await; // Acquire a permit + + if let Err(_) = permit { + tracing::error!( + workspace_id = %w_id, + "Cannot acquire permit on semaphore, that can only mean that semaphore has been closed." + ); + break; + } + + let permit = permit.unwrap(); + + let action = match strategy { + InstallStrategy::Single(ref callback) => Action::Install( + start_child_process(callback(dep.clone())?, &installer_executable_name, false) + .await?, + ), + InstallStrategy::AllAtOnce(ref rw_lock) => Action::AddToBulk(Arc::clone(rw_lock)), + }; + let task_fut = try_install_one_detached( + dep, + installer_executable_name.to_owned(), + job_id.clone(), + w_id.to_owned(), + worker_name.to_owned(), + conn.to_owned(), + counter_arc.clone(), + total_to_install, + name_ml, + action, + _language_name.to_owned(), + _platform_agnostic, + permit, + TaskKiller(kill_tx), + ); + handles.push(tokio::spawn(async move { + tokio::select! { + reason = kill_rx.recv() => return Err(anyhow::anyhow!("Installation stopped. Reason: {:?}", reason)), + r = task_fut => return r + }; + })); + } + + Ok((handles, closer)) +} +/// Do not drop the returned sender! As soon as you drop it the polling thread will stop +pub async fn listen_to_cancel( + job_id: Uuid, + w_id: String, + conn: Connection, + killpill: tokio::sync::broadcast::Sender, +) -> tokio::sync::broadcast::Sender<()> { + let (close_tx, mut close_rx) = tokio::sync::broadcast::channel::<()>(1); + tokio::spawn(async move { + 'outer: loop { + tokio::select! { + _ = tokio::time::sleep(tokio::time::Duration::from_secs(1)) => { + // TODO: Add memory usage tracking + if crate::worker_utils::ping_job_status(&conn, &job_id, None, None).await.map(|r| r.canceled_by.is_some()).unwrap_or(false){ + tracing::info!( + // If there is listener on other side, + workspace_id = %w_id, + "cancelling installations", + ); + if let Err(ref e) = killpill.send("Job was canceled".to_owned()) { + tracing::error!( + // If there is listener on other side, + workspace_id = %w_id, + "failed to send done: Probably receiving end closed too early or have not opened yet\n{}", + // If there is no listener, it will be dropped safely + e + ); + } + // Stop + break 'outer; + } + + }, + _ = close_rx.recv() => break 'outer, + } + } + }); + + // We will return the sender that can stop created thread. + // It is relatively safe, since if this handle is being dropped from the gecko the thread will stop immediately + // but it will also chainreaction all installation threads, so be carefull + close_tx +} + +async fn try_install_one_detached<'a, T: Clone + std::marker::Send + Sync + 'a + 'static>( + dep: RequiredDependency, + installer_executable_name: String, + job_id: Uuid, + w_id: String, + worker_name: String, + conn: Connection, + counter_arc: Arc>, + total_to_install: usize, + name_ml: usize, + action: Action, + _language_name: String, + _platform_agnostic: bool, + _permit: OwnedSemaphorePermit, + // If dropped the entire installation fails and all installation threads are being stopped + // That's why we just pass it to return so it is not being dropped + kill_all_tasks: TaskKiller, +) -> anyhow::Result { + let start = std::time::Instant::now(); + + #[cfg(all(feature = "enterprise", feature = "parquet"))] + let is_not_pro = !matches!( + windmill_common::ee_oss::get_license_plan().await, + windmill_common::ee_oss::LicensePlan::Pro + ); + + #[cfg(all(feature = "enterprise", feature = "parquet"))] + let s3_pull_future = if is_not_pro { + if let Some(os) = windmill_common::s3_helpers::get_object_store().await { + Some(crate::global_cache::pull_from_tar( + os, + dep.path.clone(), + _language_name.to_owned(), + Some(dep._s3_handle.clone()), + _platform_agnostic, + )) + } else { + None + } + } else { + None + }; + + #[cfg(all(feature = "enterprise", feature = "parquet"))] + if let Some(s3_pull_future) = s3_pull_future { + if let Err(e) = s3_pull_future.await { + tracing::info!( + workspace_id = %w_id, + "No tarball was found for {:?} on S3 or different problem occured {job_id}:\n{e}", + &dep._s3_handle.clone() + ); + } else { + mark_success(dep.path, &job_id, &w_id).await; + print_success( + true, + false, + &job_id, + &w_id, + &dep.display_name, + name_ml, + counter_arc, + total_to_install, + start, + &conn, + ) + .await; + return Ok(kill_all_tasks); + } + } + let child = match action { + Action::Install(child) => child, + Action::AddToBulk(rw_lock) => { + rw_lock.write().await.push(dep.clone()); + return Ok(kill_all_tasks); + } + }; + if let Err(e) = crate::handle_child::handle_child( + &job_id, + &conn, + // TODO: Return mem_peak + &mut 0, + // TODO: Return canceld_by_ref + &mut None, + child, + !*DISABLE_NSJAIL, + &worker_name, + &w_id, + &installer_executable_name, + None, + false, + &mut None, + None, + ) + .await + { + windmill_queue::append_logs( + &job_id, + &w_id, + format!( + "\n[x] - error while installing {}: {e:?}", + &dep.display_name + ), + &conn, + ) + .await; + bail!(format!( + "\n error while installing dependencies: {e:?}\n{}", + &dep.display_name + )); + } else { + mark_success(dep.path.clone(), &job_id, &w_id).await; + print_success( + false, + true, + &job_id, + &w_id, + &dep.display_name, + name_ml, + counter_arc, + total_to_install, + start, + &conn, + ) + .await; + + #[cfg(all(feature = "enterprise", feature = "parquet"))] + { + if let Some(os) = windmill_common::s3_helpers::get_object_store().await { + let language_name = _language_name.to_string(); + let platform_agnostic = _platform_agnostic; + let path = dep.path.clone(); + let handle = dep._s3_handle.clone(); + tokio::spawn(async move { + if let Err(e) = crate::global_cache::build_tar_and_push( + os, + path, + language_name, + Some(handle), + platform_agnostic, + ) + .await + { + tracing::warn!("failed to build tar and push: {e:?}"); + } + }); + } + } + } + + Ok(kill_all_tasks) +} + +// Create a file to indicate that installation was successfull +async fn mark_success(path: String, job_id: &Uuid, w_id: &str) { + let valid_path = path + ".valid.windmill"; + // This is atomic operation, meaning, that it either completes and dependency is valid, + // or it does not and dependency is invalid and will be reinstalled next run + if let Err(e) = File::create(&valid_path).await { + tracing::error!( + workspace_id = %w_id, + job_id = %job_id, + "Failed to create {}!\n{e}\n + This file needed for jobs to function", + valid_path + ); + }; +} +// Append logs with line like this: +// [9/21] + requests==2.32.3 << (S3) | in 57ms +#[allow(unused_assignments)] +async fn print_success( + mut s3_pull: bool, + mut s3_push: bool, + job_id: &Uuid, + w_id: &str, + req: &str, + req_tl: usize, + counter_arc: Arc>, + total_to_install: usize, + instant: std::time::Instant, + conn: &Connection, +) { + #[cfg(not(all(feature = "enterprise", feature = "parquet")))] + { + (s3_pull, s3_push) = (false, false); + } + + #[cfg(all(feature = "enterprise", feature = "parquet"))] + if windmill_common::s3_helpers::OBJECT_STORE_SETTINGS + .read() + .await + .is_none() + { + (s3_pull, s3_push) = (false, false); + } + + let mut counter = counter_arc.lock().await; + *counter += 1; + + windmill_queue::append_logs( + job_id, + w_id, + format!( + "\n{}+ {}{}{}| in {}ms", + windmill_common::worker::pad_string(&format!("[{}/{total_to_install}]", counter), 9), + // Because we want to align to max len [999/999] we take 9 + // 123456789 + windmill_common::worker::pad_string(&req, req_tl + 1), + // Margin to the right ^ + if s3_pull { "<< (S3) " } else { "" }, + if s3_push { " > (S3) " } else { "" }, + instant.elapsed().as_millis(), + ), + conn, + ) + .await; + // Drop lock, so next print success can fire +} + +async fn process_handles( + (handles, closer): (Vec>>, Sender<()>), + w_id: &str, +) -> anyhow::Result<()> { + let mut killers = vec![]; + for handle in handles { + match handle + .await + .unwrap_or(Err(anyhow::anyhow!("Problem by joining handle"))) + { + // __________< Cannot drop just yet. + Ok(kill_tasks) => killers.push(kill_tasks), + Err(e) => { + tracing::warn!( + workspace_id = %w_id, + "Env installation failed: {:?}", + e + ); + // We can safely return, + // it will drop `closer` + // which will drop first kill_tx for all installation threads + // which will trigger chain reaction and kill every installation + return Err(e); + } + } + } + + // Only now we can be sure there is no installation thread running + // we can safely stop poller + drop(closer); + // Drop ONLY after we processed all handles + drop(killers); + Ok(()) +} +async fn finish_installation( + total_time: std::time::Instant, + job_id: &Uuid, + w_id: &str, + conn: &Connection, +) { + let total_time = total_time.elapsed().as_millis(); + windmill_queue::append_logs( + &job_id, + w_id, + format!( + "\nDone. Time spent on installation phase: {}ms\n", + total_time + ), + conn, + ) + .await; +} diff --git a/backend/windmill-worker/src/windmill-client.js b/backend/windmill-worker/src/windmill-client.js index 1ecf28e664..33e51577e6 100644 --- a/backend/windmill-worker/src/windmill-client.js +++ b/backend/windmill-worker/src/windmill-client.js @@ -2999,6 +2999,7 @@ var $RawScript = { "graphql", "nativets", "duckdb", + "ruby", // for related places search: ADD_NEW_LANG ], }, diff --git a/backend/windmill-worker/src/worker.rs b/backend/windmill-worker/src/worker.rs index 33fb3f5213..2ed78f0066 100644 --- a/backend/windmill-worker/src/worker.rs +++ b/backend/windmill-worker/src/worker.rs @@ -134,6 +134,9 @@ use crate::nu_executor::{handle_nu_job, JobHandlerInput as JobHandlerInputNu}; #[cfg(feature = "java")] use crate::java_executor::{handle_java_job, JobHandlerInput as JobHandlerInputJava}; +#[cfg(feature = "ruby")] +use crate::ruby_executor::{handle_ruby_job, JobHandlerInput as JobHandlerInputRuby}; + #[cfg(feature = "php")] use crate::php_executor::handle_php_job; @@ -191,10 +194,14 @@ pub const RUST_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "rust"); pub const NU_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "nu"); pub const CSHARP_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "csharp"); -// JAVA +// Java pub const JAVA_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "java"); pub const COURSIER_CACHE_DIR: &str = concatcp!(JAVA_CACHE_DIR, "/coursier-cache"); pub const JAVA_REPOSITORY_DIR: &str = concatcp!(JAVA_CACHE_DIR, "/repository"); + +// Ruby +pub const RUBY_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "ruby"); + // for related places search: ADD_NEW_LANG pub const BUN_CACHE_DIR: &str = concatcp!(ROOT_CACHE_NOMOUNT_DIR, "bun"); pub const BUN_BUNDLE_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "bun"); @@ -328,12 +335,17 @@ lazy_static::lazy_static! { pub static ref NPM_CONFIG_REGISTRY: Arc>> = Arc::new(RwLock::new(None)); pub static ref BUNFIG_INSTALL_SCOPES: Arc>> = Arc::new(RwLock::new(None)); + pub static ref BUN_NO_CACHE: bool = std::env::var("BUN_NO_CACHE") + .ok() + .and_then(|x| x.parse::().ok()) + .unwrap_or(false); pub static ref NUGET_CONFIG: Arc>> = Arc::new(RwLock::new(None)); pub static ref MAVEN_REPOS: Arc>> = Arc::new(RwLock::new(None)); pub static ref NO_DEFAULT_MAVEN: AtomicBool = AtomicBool::new(std::env::var("NO_DEFAULT_MAVEN") .ok() .and_then(|x| x.parse::().ok()) .unwrap_or(false)); + pub static ref RUBY_REPOS: Arc>>> = Arc::new(RwLock::new(None)); pub static ref PIP_EXTRA_INDEX_URL: Arc>> = Arc::new(RwLock::new(None)); pub static ref PIP_INDEX_URL: Arc>> = Arc::new(RwLock::new(None)); @@ -2401,7 +2413,7 @@ pub async fn handle_queued_job( let flow_data = match preview_data { Some(RawData::Flow(data)) => data, // Not a preview: fetch from the cache or the database. - _ => cache::job::fetch_flow(db, job.kind, job.runnable_id).await?, + _ => cache::job::fetch_flow(db, &job.kind, job.runnable_id).await?, }; handle_flow( job, @@ -3435,6 +3447,33 @@ mount {{ }) .await } + Some(ScriptLang::Ruby) => { + #[cfg(not(feature = "ruby"))] + return Err(anyhow::anyhow!( + "Ruby is not available because the feature is not enabled" + ) + .into()); + + #[cfg(feature = "ruby")] + handle_ruby_job(JobHandlerInputRuby { + mem_peak, + canceled_by, + job, + conn, + client, + parent_runnable_path, + inner_content: &code, + job_dir, + requirements_o: lock.as_ref(), + shared_mount: &shared_mount, + base_internal_url, + worker_name, + envs, + occupancy_metrics, + }) + .await + } + // for related places search: ADD_NEW_LANG _ => panic!("unreachable, language is not supported: {language:#?}"), }; tracing::info!( @@ -3507,6 +3546,10 @@ fn parse_sig_of_lang( ScriptLang::Java => Some(windmill_parser_java::parse_java_signature(code)?), #[cfg(not(feature = "java"))] ScriptLang::Java => None, + #[cfg(feature = "ruby")] + ScriptLang::Ruby => Some(windmill_parser_ruby::parse_ruby_signature(code)?), + #[cfg(not(feature = "ruby"))] + ScriptLang::Ruby => None, // for related places search: ADD_NEW_LANG } } else { diff --git a/backend/windmill-worker/src/worker_flow.rs b/backend/windmill-worker/src/worker_flow.rs index 00477e7510..6abe8e57ad 100644 --- a/backend/windmill-worker/src/worker_flow.rs +++ b/backend/windmill-worker/src/worker_flow.rs @@ -287,7 +287,7 @@ pub async fn update_flow_status_after_job_completion_internal( )) })?; - let flow_data = cache::job::fetch_flow(db, job_kind, script_hash) + let flow_data = cache::job::fetch_flow(db, &job_kind, script_hash) .or_else(|_| cache::job::fetch_preview_flow(db, &flow, raw_flow)) .await?; let flow_value = flow_data.value(); diff --git a/backend/windmill-worker/src/worker_lockfiles.rs b/backend/windmill-worker/src/worker_lockfiles.rs index ba9c6b16c2..575d94c28c 100644 --- a/backend/windmill-worker/src/worker_lockfiles.rs +++ b/backend/windmill-worker/src/worker_lockfiles.rs @@ -42,7 +42,10 @@ use crate::common::OccupancyMetrics; use crate::csharp_executor::generate_nuget_lockfile; #[cfg(feature = "java")] -use crate::java_executor::resolve; +use crate::java_executor; + +#[cfg(feature = "ruby")] +use crate::ruby_executor; #[cfg(feature = "php")] use crate::php_executor::{composer_install, parse_php_imports}; @@ -1892,7 +1895,7 @@ pub async fn handle_app_dependency_job( // .args(args) // .stdout(Stdio::piped()) // .stderr(Stdio::piped()); -// let child = start_child_process(cmd, "esbuild").await?; +// let child = start_child_process(cmd, "esbuild", false).await?; // crate::handle_child::handle_child( // &job.id, @@ -2415,7 +2418,7 @@ async fn capture_dependency_job( )); } - resolve( + java_executor::resolve( job_id, job_raw_code, job_dir, @@ -2424,6 +2427,26 @@ async fn capture_dependency_job( ) .await } + #[cfg(feature = "ruby")] + ScriptLang::Ruby => { + if raw_deps { + return Err(Error::ExecutionErr( + "Raw dependencies not supported for Ruby".to_string(), + )); + } + + ruby_executor::resolve( + job_id, + job_raw_code, + mem_peak, + canceled_by, + job_dir, + &Connection::Sql(db.clone()), + worker_name, + w_id, + ) + .await + } // for related places search: ADD_NEW_LANG _ => Ok("".to_owned()), } diff --git a/benchmarks/lib.ts b/benchmarks/lib.ts index 281b0399c3..1267aa0e52 100644 --- a/benchmarks/lib.ts +++ b/benchmarks/lib.ts @@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts"; import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts"; import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts"; -export const VERSION = "v1.518.2"; +export const VERSION = "v1.527.1"; export async function login(email: string, password: string): Promise { return await windmill.UserService.login({ diff --git a/cli/add-ts-ext.sh b/cli/add-ts-ext.sh deleted file mode 100755 index 07af4db948..0000000000 --- a/cli/add-ts-ext.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash - -# Set script to exit on any error -set -e - -# Default to regular sed -REVERT_MODE=false - -# Function to show usage -show_usage() { - echo "Usage: $0 [-r] [-h]" - echo " -r Revert changes (restore from .orig files)" - echo " -h Show this help message" - exit 1 -} - -# Parse command line arguments -while getopts "rh" opt; do - case ${opt} in - r ) - REVERT_MODE=true - ;; - h ) - show_usage - ;; - \? ) - echo "Invalid option: -$OPTARG" >&2 - show_usage - ;; - esac -done - -# Function to add .ts extensions to relative imports -add_ts_extensions() { - echo "Adding .ts extensions to imports..." - find windmill-utils-internal/src -name "*.ts" -type f | while read -r file; do - # Create backup of original - cp "$file" "$file.orig" - - # Add .ts to relative imports that don't already have extensions - sed -E \ - -e 's/(from[[:space:]]+["'"'"'])(\.[^"'"'"']*[^./][^"'"'"']*)(["'"'"'])/\1\2.ts\3/g' \ - -e 's/(import[[:space:]]+["'"'"'])(\.[^"'"'"']*[^./][^"'"'"']*)(["'"'"'])/\1\2.ts\3/g' \ - "$file.orig" > "$file" - done - echo "✓ Added .ts extensions" -} - -# Function to revert to original files -revert_extensions() { - echo "Removing .ts extensions..." - find windmill-utils-internal/src -name "*.orig" -type f | while read -r backup_file; do - original_file="${backup_file%.orig}" - mv "$backup_file" "$original_file" - done - echo "✓ All files reverted" -} - -# Main execution -if [ "$REVERT_MODE" = true ]; then - revert_extensions -else - add_ts_extensions -fi diff --git a/cli/bootstrap/script_bootstrap.ts b/cli/bootstrap/script_bootstrap.ts index e9e8a12db5..5335e1741c 100644 --- a/cli/bootstrap/script_bootstrap.ts +++ b/cli/bootstrap/script_bootstrap.ts @@ -126,6 +126,11 @@ public class Main { System.out.println("Hello World"); } } +`, + ruby: ` +def main a, b, c + puts a, b, c +end `, // for related places search: ADD_NEW_LANG }; diff --git a/cli/build-mac.sh b/cli/build-mac.sh deleted file mode 100755 index 408ad23252..0000000000 --- a/cli/build-mac.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -# Set script to exit on any error -set -e - -# Generate client files -./gen_wm_client-mac.sh - -# Generate utils client files -./windmill-utils-internal/gen_wm_client-mac.sh - -# Set up trap to ensure cleanup happens on exit, error, or interruption -trap 'echo "Cleaning up..."; ./add-ts-ext.sh -r' EXIT ERR INT TERM - -# Add .ts extensions for Deno -./add-ts-ext.sh - -# Run dnt -echo "Running dnt..." -deno run -A dnt.ts - -echo "Build complete!" \ No newline at end of file diff --git a/cli/build.sh b/cli/build.sh index bffdc4208e..0da1c343a3 100755 --- a/cli/build.sh +++ b/cli/build.sh @@ -9,11 +9,8 @@ set -e # Generate utils client files ./windmill-utils-internal/gen_wm_client.sh -# Set up trap to ensure cleanup happens on exit, error, or interruption -trap 'echo "Cleaning up..."; ./add-ts-ext.sh -r' EXIT ERR INT TERM - -# Add .ts extensions for Deno -./add-ts-ext.sh +# Add .ts extensions to windmill-utils-internal +./windmill-utils-internal/remove-ts-ext.sh -r # Run dnt echo "Running dnt..." diff --git a/cli/dnt.ts b/cli/dnt.ts index ac99fc1c4a..9f576a3a2c 100644 --- a/cli/dnt.ts +++ b/cli/dnt.ts @@ -71,6 +71,8 @@ await build({ "yaml", "csharp", "java", + "ruby", + // for related places search: ADD_NEW_LANG ]; for (const l of dirs) { diff --git a/cli/gen_wm_client-mac.sh b/cli/gen_wm_client-mac.sh deleted file mode 100755 index b25d40a3aa..0000000000 --- a/cli/gen_wm_client-mac.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -set -eou pipefail -script_dirpath="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# only install gnu-sed if not already installed -if ! command -v gsed &> /dev/null; then - brew install gnu-sed -fi - -rm -rf "${script_dirpath}/gen" - -npx --yes @hey-api/openapi-ts@0.53.1 --input "${script_dirpath}/../backend/windmill-api/openapi.yaml" --output "${script_dirpath}/gen" --useOptions --client legacy/fetch --schemas false -cat < temp_file && mv temp_file gen/core/OpenAPI.ts -const getEnv = (key: string) => { - return Deno.env.get(key) -}; - -const baseUrl = getEnv("BASE_INTERNAL_URL") ?? getEnv("BASE_URL") ?? "http://localhost:8000"; -const baseUrlApi = (baseUrl ?? '') + "/api"; - -EOF -gsed -i 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/g' gen/core/OpenAPI.ts -gsed -i 's/TOKEN: undefined/TOKEN: getEnv("WM_TOKEN")/g' gen/core/OpenAPI.ts -gsed -i "s/BASE: '\/api'/BASE: baseUrlApi/g" gen/core/OpenAPI.ts - -find gen/ -name "*.ts" -exec gsed -i -E "s/(import.*from[[:space:]]*['\"][^'\"]+)(['\"])/\1.ts\2/g" {} \; - -find gen/ -name "*.ts" -exec gsed -i -E "s/(export.*from[[:space:]]*['\"][^'\"]+)(['\"])/\1.ts\2/g" {} \; diff --git a/cli/gen_wm_client.sh b/cli/gen_wm_client.sh index 1b36f489d9..f6e5a5e094 100755 --- a/cli/gen_wm_client.sh +++ b/cli/gen_wm_client.sh @@ -14,10 +14,29 @@ const baseUrl = getEnv("BASE_INTERNAL_URL") ?? getEnv("BASE_URL") ?? "http://loc const baseUrlApi = (baseUrl ?? '') + "/api"; EOF -sed -i 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/g' gen/core/OpenAPI.ts -sed -i 's/TOKEN: undefined/TOKEN: getEnv("WM_TOKEN")/g' gen/core/OpenAPI.ts -sed -i "s/BASE: '\/api'/BASE: baseUrlApi/g" gen/core/OpenAPI.ts -find gen/ -name "*.ts" -exec sed -i -E "s/(import.*from[[:space:]]*['\"][^'\"]+)(['\"])/\1.ts\2/g" {} \; +echo "Applying sed transformations..." +if [[ "$OSTYPE" == "darwin"* ]]; then + echo "Detected macOS - using GNU sed (gsed)" + if ! command -v gsed &> /dev/null; then + echo "Error: gsed not found" + echo "Run: brew install gnu-sed" + exit 1 + fi + gsed -i 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/g' gen/core/OpenAPI.ts + gsed -i 's/TOKEN: undefined/TOKEN: getEnv("WM_TOKEN")/g' gen/core/OpenAPI.ts + gsed -i "s/BASE: '\\/api'/BASE: baseUrlApi/g" gen/core/OpenAPI.ts + + find gen/ -name "*.ts" -exec gsed -i -E "s/(import.*from[[:space:]]*['\"][^'\"]+)(['\"])/\1.ts\2/g" {} \; + find gen/ -name "*.ts" -exec gsed -i -E "s/(export.*from[[:space:]]*['\"][^'\"]+)(['\"])/\1.ts\2/g" {} \; +else + echo "Detected Linux - using GNU sed" + sed -i 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/g' gen/core/OpenAPI.ts + sed -i 's/TOKEN: undefined/TOKEN: getEnv("WM_TOKEN")/g' gen/core/OpenAPI.ts + sed -i "s/BASE: '\\/api'/BASE: baseUrlApi/g" gen/core/OpenAPI.ts + + find gen/ -name "*.ts" -exec sed -i -E "s/(import.*from[[:space:]]*['\"][^'\"]+)(['\"])/\1.ts\2/g" {} \; + find gen/ -name "*.ts" -exec sed -i -E "s/(export.*from[[:space:]]*['\"][^'\"]+)(['\"])/\1.ts\2/g" {} \; +fi -find gen/ -name "*.ts" -exec sed -i -E "s/(export.*from[[:space:]]*['\"][^'\"]+)(['\"])/\1.ts\2/g" {} \; +echo "✓ Client generation completed" \ No newline at end of file diff --git a/cli/src/commands/gitsync-settings/converter.ts b/cli/src/commands/gitsync-settings/converter.ts new file mode 100644 index 0000000000..89600fd5d1 --- /dev/null +++ b/cli/src/commands/gitsync-settings/converter.ts @@ -0,0 +1,135 @@ +import { SyncOptions } from "../../core/conf.ts"; +import { BackendGitSyncSettings, GIT_SYNC_FIELDS, INCLUDE_TYPE_MAPPINGS } from "./types.ts"; + +// Helper to compare string arrays (used for includes/excludes/extraIncludes) +function arraysEqual(arr1: string[], arr2: string[]): boolean { + if (arr1.length !== arr2.length) { + return false; + } + const sorted1 = [...arr1].sort(); + const sorted2 = [...arr2].sort(); + return sorted1.every((item, index) => item === sorted2[index]); +} + +export class GitSyncSettingsConverter { + // Convert backend include_type array to SyncOptions boolean flags + static fromBackendFormat(settings: BackendGitSyncSettings): SyncOptions { + const includeTypes = settings.include_type || []; + + const result: SyncOptions = { + includes: settings.include_path || [], + excludes: settings.exclude_path || [], + skipScripts: !includeTypes.includes("script"), + skipFlows: !includeTypes.includes("flow"), + skipApps: !includeTypes.includes("app"), + skipFolders: !includeTypes.includes("folder"), + skipVariables: !includeTypes.includes("variable"), + skipResources: !includeTypes.includes("resource"), + skipResourceTypes: !includeTypes.includes("resourcetype"), + skipSecrets: !includeTypes.includes("secret"), + includeSchedules: includeTypes.includes("schedule"), + includeTriggers: includeTypes.includes("trigger"), + includeUsers: includeTypes.includes("user"), + includeGroups: includeTypes.includes("group"), + includeSettings: includeTypes.includes("settings"), + includeKey: includeTypes.includes("key"), + }; + + // Only include extraIncludes if it has content + if (settings.extra_include_path && settings.extra_include_path.length > 0) { + result.extraIncludes = settings.extra_include_path; + } + + return result; + } + + // Convert SyncOptions boolean flags to backend include_type array + static toBackendFormat(opts: SyncOptions): BackendGitSyncSettings { + const includeTypes: string[] = []; + + if (!opts.skipScripts) includeTypes.push("script"); + if (!opts.skipFlows) includeTypes.push("flow"); + if (!opts.skipApps) includeTypes.push("app"); + if (!opts.skipFolders) includeTypes.push("folder"); + if (!opts.skipVariables) includeTypes.push("variable"); + if (!opts.skipResources) includeTypes.push("resource"); + if (!opts.skipResourceTypes) includeTypes.push("resourcetype"); + if (!opts.skipSecrets) includeTypes.push("secret"); + if (opts.includeSchedules) includeTypes.push("schedule"); + if (opts.includeTriggers) includeTypes.push("trigger"); + if (opts.includeUsers) includeTypes.push("user"); + if (opts.includeGroups) includeTypes.push("group"); + if (opts.includeSettings) includeTypes.push("settings"); + if (opts.includeKey) includeTypes.push("key"); + + const result: BackendGitSyncSettings = { + include_path: opts.includes || [], + include_type: includeTypes, + }; + + // Only include optional arrays if they have content + if (opts.excludes && opts.excludes.length > 0) { + result.exclude_path = opts.excludes; + } + if (opts.extraIncludes && opts.extraIncludes.length > 0) { + result.extra_include_path = opts.extraIncludes; + } + + return result; + } + + // Normalize SyncOptions for semantic comparison - treat undefined values as their defaults + static normalize(opts: SyncOptions): SyncOptions { + return { + ...opts, + includes: opts.includes || [], + excludes: opts.excludes || [], + extraIncludes: opts.extraIncludes || [], + skipVariables: opts.skipVariables ?? false, + skipResources: opts.skipResources ?? false, + skipResourceTypes: opts.skipResourceTypes ?? false, + skipSecrets: opts.skipSecrets ?? true, + skipScripts: opts.skipScripts ?? false, + skipFlows: opts.skipFlows ?? false, + skipApps: opts.skipApps ?? false, + skipFolders: opts.skipFolders ?? false, + includeSchedules: opts.includeSchedules ?? false, + includeTriggers: opts.includeTriggers ?? false, + includeUsers: opts.includeUsers ?? false, + includeGroups: opts.includeGroups ?? false, + includeSettings: opts.includeSettings ?? false, + includeKey: opts.includeKey ?? false, + }; + } + + // Extract only git-sync relevant fields for comparison + static extractGitSyncFields(opts: SyncOptions): Partial { + return { + includes: opts.includes || [], + excludes: opts.excludes || [], + extraIncludes: opts.extraIncludes || [], + skipScripts: opts.skipScripts, + skipFlows: opts.skipFlows, + skipApps: opts.skipApps, + skipFolders: opts.skipFolders, + skipVariables: opts.skipVariables, + skipResources: opts.skipResources, + skipResourceTypes: opts.skipResourceTypes, + skipSecrets: opts.skipSecrets, + includeSchedules: opts.includeSchedules, + includeTriggers: opts.includeTriggers, + includeUsers: opts.includeUsers, + includeGroups: opts.includeGroups, + includeSettings: opts.includeSettings, + includeKey: opts.includeKey, + }; + } + + // Check if two values are different, with special handling for arrays + static isDifferent(value1: any, value2: any): boolean { + if (Array.isArray(value1) && Array.isArray(value2)) { + return !arraysEqual(value1 as string[], value2 as string[]); + } + return value1 !== value2; + } +} diff --git a/cli/src/commands/gitsync-settings/gitsync-settings.ts b/cli/src/commands/gitsync-settings/gitsync-settings.ts index 858bcc3498..5eb3e0dc85 100644 --- a/cli/src/commands/gitsync-settings/gitsync-settings.ts +++ b/cli/src/commands/gitsync-settings/gitsync-settings.ts @@ -1,1353 +1,2 @@ -import { colors, Command, Confirm, log, yamlStringify } from "../../../deps.ts"; -import { GlobalOptions } from "../../types.ts"; -import { requireLogin } from "../../core/auth.ts"; -import { resolveWorkspace } from "../../core/context.ts"; -import * as wmill from "../../../gen/services.gen.ts"; -import { - DEFAULT_SYNC_OPTIONS, - getEffectiveSettings, - readConfigFile, - SyncOptions, -} from "../../core/conf.ts"; -import { deepEqual, Repository, selectRepository } from "../../utils/utils.ts"; - -// Constants for git-sync fields to avoid duplication -const GIT_SYNC_FIELDS = [ - "includes", - "excludes", - "extraIncludes", - "skipScripts", - "skipFlows", - "skipApps", - "skipFolders", - "skipVariables", - "skipResources", - "skipResourceTypes", - "skipSecrets", - "includeSchedules", - "includeTriggers", - "includeUsers", - "includeGroups", - "includeSettings", - "includeKey", -] as const; - -// Helper to normalize repository path by removing $res: prefix -function normalizeRepoPath(path: string): string { - return path.replace(/^\$res:/, ""); -} - -// Helper to get typed field value from SyncOptions -function getFieldValue(opts: SyncOptions, field: string): any { - return (opts as any)[field]; -} - -// Construct override key using the single format: baseUrl:workspaceId:repo -function constructOverrideKey( - baseUrl: string, - workspaceId: string, - repoPath: string, - workspaceLevel = false, -): string { - // Validate that components don't contain colons to avoid key collisions - if (baseUrl.includes(':') && !baseUrl.startsWith('http')) { - throw new Error(`Invalid baseUrl contains colon: ${baseUrl}`); - } - if (workspaceId.includes(':')) { - throw new Error(`Invalid workspaceId contains colon: ${workspaceId}`); - } - if (repoPath.includes(':') && !repoPath.startsWith('$res:')) { - throw new Error(`Invalid repoPath contains colon: ${repoPath}`); - } - - if (workspaceLevel) { - return `${baseUrl}:${workspaceId}:*`; - } - return `${baseUrl}:${workspaceId}:${repoPath}`; -} - -// Helper to compare string arrays (used for includes/excludes/extraIncludes) -function arraysEqual(arr1: string[], arr2: string[]): boolean { - if (arr1.length !== arr2.length) { - return false; - } - const sorted1 = [...arr1].sort(); - const sorted2 = [...arr2].sort(); - return sorted1.every((item, index) => item === sorted2[index]); -} - -// Normalize SyncOptions for semantic comparison - treat undefined arrays as empty arrays -function normalizeSyncOptions(opts: SyncOptions): SyncOptions { - return { - ...opts, - includes: opts.includes || [], - excludes: opts.excludes || [], - extraIncludes: opts.extraIncludes || [], - }; -} - -// Extract only git-sync relevant fields for comparison -function extractGitSyncFields(opts: SyncOptions): Partial { - return { - includes: opts.includes || [], - excludes: opts.excludes || [], - extraIncludes: opts.extraIncludes || [], - skipScripts: opts.skipScripts, - skipFlows: opts.skipFlows, - skipApps: opts.skipApps, - skipFolders: opts.skipFolders, - skipVariables: opts.skipVariables, - skipResources: opts.skipResources, - skipResourceTypes: opts.skipResourceTypes, - skipSecrets: opts.skipSecrets, - includeSchedules: opts.includeSchedules, - includeTriggers: opts.includeTriggers, - includeUsers: opts.includeUsers, - includeGroups: opts.includeGroups, - includeSettings: opts.includeSettings, - includeKey: opts.includeKey, - }; -} - -// Helper function to determine current settings based on write mode and conflicts -function getCurrentSettings( - localConfig: SyncOptions, - writeMode: "override" | "replace", - overrideKey?: string, -): SyncOptions { - if ( - writeMode === "override" && - overrideKey && - localConfig.overrides?.[overrideKey] - ) { - return { - ...DEFAULT_SYNC_OPTIONS, - ...localConfig, - ...localConfig.overrides[overrideKey], - }; - } else { - // For "replace" mode, exclude overrides since they're never accessed - const { overrides, ...configWithoutOverrides } = localConfig; - return { ...DEFAULT_SYNC_OPTIONS, ...configWithoutOverrides }; - } -} - -// Interface for git-sync repository settings from backend -interface GitSyncRepository extends Repository { - settings: { - include_path: string[]; - include_type: string[]; - exclude_path?: string[]; - extra_include_path?: string[]; - }; -} - -// Convert backend include_type array to SyncOptions boolean flags -function includeTypeToSyncOptions( - includeTypes: string[], -): Partial { - return { - skipScripts: !includeTypes.includes("script"), - skipFlows: !includeTypes.includes("flow"), - skipApps: !includeTypes.includes("app"), - skipFolders: !includeTypes.includes("folder"), - skipVariables: !includeTypes.includes("variable"), - skipResources: !includeTypes.includes("resource"), - skipResourceTypes: !includeTypes.includes("resourcetype"), - skipSecrets: !includeTypes.includes("secret"), - includeSchedules: includeTypes.includes("schedule"), - includeTriggers: includeTypes.includes("trigger"), - includeUsers: includeTypes.includes("user"), - includeGroups: includeTypes.includes("group"), - includeSettings: includeTypes.includes("settings"), - includeKey: includeTypes.includes("key"), - }; -} - -// Shared migration function for legacy repositories -async function handleLegacyRepositoryMigration( - selectedRepo: any, - gitSyncSettings: any, - workspace: any, - opts: { yes?: boolean }, - operationName: string = "operation" -): Promise { - if (selectedRepo.settings) { - return selectedRepo; // Already migrated - } - - // This repository is in legacy format - handle migration - if (!gitSyncSettings.include_path || !gitSyncSettings.include_type) { - throw new Error( - `Repository "${selectedRepo.git_repo_resource_path}" has legacy format but workspace-level include_path or include_type is missing. This indicates corrupted git-sync settings.` - ); - } - - const workspaceIncludePath = gitSyncSettings.include_path; - const workspaceIncludeType = gitSyncSettings.include_type; - - if (Deno.stdout.isTerminal() && !opts.yes) { - // Interactive mode - show migration prompt - console.log(colors.yellow('\n⚠️ Legacy git-sync settings detected!')); - console.log(`\nRepository "${selectedRepo.git_repo_resource_path}" has legacy settings format.`); - console.log('The new format allows per-repository filter configuration.'); - if (operationName === "push") { - console.log('This repository must be migrated before pushing settings.\n'); - } else { - console.log('\n'); - } - - console.log(colors.bold('Current workspace-level settings:')); - console.log(` Include paths: ${workspaceIncludePath.join(', ')}`); - console.log(` Include types: ${workspaceIncludeType.join(', ')}\n`); - - // Show what the migration will do - let finalIncludeType = [...workspaceIncludeType]; - if (selectedRepo.exclude_types_override && selectedRepo.exclude_types_override.length > 0) { - const originalCount = finalIncludeType.length; - finalIncludeType = finalIncludeType.filter( - type => !selectedRepo.exclude_types_override.includes(type) - ); - const excludedCount = originalCount - finalIncludeType.length; - console.log(colors.yellow(`Repository excludes ${excludedCount} types: ${selectedRepo.exclude_types_override.join(', ')}`)); - } - - console.log(colors.bold('\nAfter migration, repository will have:')); - console.log(` Include paths: ${workspaceIncludePath.join(', ')}`); - console.log(` Include types: ${finalIncludeType.join(', ')}\n`); - - const confirm = await Confirm.prompt({ - message: operationName === "push" - ? 'Do you want to migrate this repository before pushing?' - : 'Do you want to migrate this repository?', - default: true - }); - - if (!confirm) { - const message = operationName === "push" - ? '\n⚠️ Migration skipped. Cannot push to legacy repository.' - : '\n⚠️ Migration skipped. You can migrate later via the UI.'; - console.log(colors.yellow(message)); - if (operationName === "push") { - return null; // Signal to exit push operation - } - throw new Error('Migration cancelled by user'); - } - - // Perform the migration - let migratedIncludeType = [...workspaceIncludeType]; - if (selectedRepo.exclude_types_override && selectedRepo.exclude_types_override.length > 0) { - migratedIncludeType = migratedIncludeType.filter( - type => !selectedRepo.exclude_types_override.includes(type) - ); - } - - const migratedRepo = { - ...selectedRepo, - settings: { - include_path: [...workspaceIncludePath], - include_type: migratedIncludeType, - exclude_path: [], - extra_include_path: [] - } - }; - - // Remove the old field - delete migratedRepo.exclude_types_override; - - // Update the backend with migrated repository - const updatedRepositories = gitSyncSettings.repositories.map((repo: any) => { - if (repo.git_repo_resource_path === selectedRepo.git_repo_resource_path) { - return migratedRepo; - } - return repo; - }); - - await wmill.editWorkspaceGitSyncConfig({ - workspace: workspace.workspaceId, - requestBody: { - git_sync_settings: { - repositories: updatedRepositories, - // Keep workspace-level settings if other repos are still legacy - ...(gitSyncSettings.repositories.some((r: any) => r.git_repo_resource_path !== selectedRepo.git_repo_resource_path && !r.settings) && { - include_path: workspaceIncludePath, - include_type: workspaceIncludeType - }) - } - } - }); - - console.log(colors.green('\n✓ Repository migration completed successfully!')); - if (operationName === "push") { - console.log('Now proceeding with push operation...\n'); - } - return migratedRepo; - - } else { - // Non-interactive mode - show error - console.error(colors.red('\n❌ Legacy git-sync settings detected!')); - console.error(`\nRepository "${selectedRepo.git_repo_resource_path}" has legacy settings format.`); - if (operationName === "push") { - console.error('This repository must be migrated before pushing settings.'); - } - console.error('Please choose one of the following options:\n'); - console.error('1. Go to the Windmill UI > Workspace Settings > Git Sync'); - console.error(' Review and save this repository to migrate to the new format.\n'); - console.error('2. Run this command in interactive mode (with TTY) to migrate.'); - console.error(` Example: wmill gitsync-settings ${operationName}\n`); - if (operationName === "push") { - console.error('3. Pull settings first to migrate: wmill gitsync-settings pull\n'); - } else { - console.error('3. Push local settings to override backend settings:'); - console.error(' wmill gitsync-settings push\n'); - } - Deno.exit(1); - } -} - -// Convert SyncOptions boolean flags to backend include_type array -function syncOptionsToIncludeType(opts: SyncOptions): string[] { - const includeTypes: string[] = []; - - if (!opts.skipScripts) includeTypes.push("script"); - if (!opts.skipFlows) includeTypes.push("flow"); - if (!opts.skipApps) includeTypes.push("app"); - if (!opts.skipFolders) includeTypes.push("folder"); - if (!opts.skipVariables) includeTypes.push("variable"); - if (!opts.skipResources) includeTypes.push("resource"); - if (!opts.skipResourceTypes) includeTypes.push("resourcetype"); - if (!opts.skipSecrets) includeTypes.push("secret"); - if (opts.includeSchedules) includeTypes.push("schedule"); - if (opts.includeTriggers) includeTypes.push("trigger"); - if (opts.includeUsers) includeTypes.push("user"); - if (opts.includeGroups) includeTypes.push("group"); - if (opts.includeSettings) includeTypes.push("settings"); - if (opts.includeKey) includeTypes.push("key"); - - return includeTypes; -} - -// Convert SyncOptions to backend format used by both Windmill backend and UI -function syncOptionsToBackendFormat(opts: SyncOptions): { - include_path: string[]; - exclude_path: string[]; - extra_include_path: string[]; - include_type: string[]; -} { - return { - include_path: opts.includes || [], - exclude_path: opts.excludes || [], - extra_include_path: opts.extraIncludes || [], - include_type: syncOptionsToIncludeType(opts), - }; -} - -// Select repository interactively if multiple exist -// Generate structured diff showing field changes -function generateStructuredDiff( - current: any, - backend: any, -): { [key: string]: { from: any; to: any } } { - const diff: { [key: string]: { from: any; to: any } } = {}; - - // Get all unique keys from both objects - const allKeys = new Set([...Object.keys(current), ...Object.keys(backend)]); - - for (const key of allKeys) { - const currentValue = current[key]; - const backendValue = backend[key]; - - if (!deepEqual(currentValue, backendValue)) { - diff[key] = { - from: currentValue, - to: backendValue, - }; - } - } - - return diff; -} - -// Helper to generate changes between two normalized SyncOptions objects -function generateChanges( - normalizedCurrent: SyncOptions, - normalizedNew: SyncOptions, -): { [key: string]: { from: any; to: any } } { - const changes: { [key: string]: { from: any; to: any } } = {}; - - for (const field of GIT_SYNC_FIELDS) { - const currentValue = getFieldValue(normalizedCurrent, field); - const newValue = getFieldValue(normalizedNew, field); - if (!deepEqual(currentValue, newValue)) { - changes[field] = { - from: currentValue, - to: newValue, - }; - } - } - - return changes; -} - -// Helper to display changes in human-readable format -function displayChanges( - changes: { [key: string]: { from: any; to: any } }, -): void { - for (const [field, change] of Object.entries(changes)) { - if ( - Array.isArray(change.from) || - Array.isArray(change.to) - ) { - console.log(colors.yellow(` ${field}:`)); - console.log( - colors.red( - ` - ${JSON.stringify(change.from)}`, - ), - ); - console.log( - colors.green( - ` + ${JSON.stringify(change.to)}`, - ), - ); - } else { - console.log( - colors.yellow(` ${field}: `) + - colors.red(`${change.from}`) + - " → " + - colors.green(`${change.to}`), - ); - } - } -} - -async function selectAndLogRepository( - repositories: GitSyncRepository[], - repository?: string, -): Promise { - let selectedRepo: GitSyncRepository; - - if (repository) { - const found = repositories.find( - (r: GitSyncRepository) => - r.git_repo_resource_path === repository || - r.git_repo_resource_path === `$res:${repository}`, - ); - if (!found) { - throw new Error(`Repository ${repository} not found`); - } - selectedRepo = found; - const repoPath = selectedRepo.git_repo_resource_path.replace(/^\$res:/, ""); - log.info(colors.cyan(`Using repository: ${colors.bold(repoPath)}`)); - } else { - selectedRepo = await selectRepository(repositories); - } - - return selectedRepo; -} - -async function pullGitSyncSettings( - opts: GlobalOptions & { - repository?: string; - workspaceLevel?: boolean; - default?: boolean; - diff?: boolean; - jsonOutput?: boolean; - replace?: boolean; - override?: boolean; - withBackendSettings?: string; - yes?: boolean; - }, -) { - const workspace = await resolveWorkspace(opts); - await requireLogin(opts); - - try { - // Parse and validate --with-backend-settings if provided - let settings: any; - if (opts.withBackendSettings) { - try { - const parsedSettings = JSON.parse(opts.withBackendSettings); - - // Validate the structure matches expected test format (raw settings object) - if ( - !parsedSettings.include_path || - !Array.isArray(parsedSettings.include_path) - ) { - throw new Error( - "Invalid settings format. Expected include_path array", - ); - } - if ( - !parsedSettings.include_type || - !Array.isArray(parsedSettings.include_type) - ) { - throw new Error( - "Invalid settings format. Expected include_type array", - ); - } - - // Create mock backend response with single repository using provided settings - const mockRepositoryPath = opts.repository || "u/mock/repo"; - settings = { - git_sync: { - repositories: [{ - git_repo_resource_path: mockRepositoryPath, - settings: { - include_path: parsedSettings.include_path, - include_type: parsedSettings.include_type, - exclude_path: parsedSettings.exclude_path || [], - extra_include_path: parsedSettings.extra_include_path || [], - }, - }], - }, - }; - } catch (parseError) { - const errorMessage = parseError instanceof Error ? parseError.message : String(parseError); - if (opts.jsonOutput) { - console.log( - JSON.stringify({ - success: false, - error: - `Failed to parse --with-backend-settings JSON: ${errorMessage}`, - }), - ); - } else { - log.error( - colors.red( - `Failed to parse --with-backend-settings JSON: ${errorMessage}`, - ), - ); - } - return; - } - } else { - // Fetch workspace settings to get git-sync configuration - try { - settings = await wmill.getSettings({ - workspace: workspace.workspaceId, - }); - } catch (apiError) { - const errorMessage = apiError instanceof Error ? apiError.message : String(apiError); - if (opts.jsonOutput) { - console.log( - JSON.stringify({ - success: false, - error: `Failed to fetch workspace settings: ${errorMessage}`, - }), - ); - } else { - log.error( - colors.red( - `Failed to fetch workspace settings: ${errorMessage}`, - ), - ); - } - return; - } - } - - if ( - !settings.git_sync?.repositories || - settings.git_sync.repositories.length === 0 - ) { - if (opts.jsonOutput) { - console.log( - JSON.stringify({ - success: false, - error: "No git-sync repositories configured", - }), - ); - } else { - log.error( - colors.red( - "No git-sync repositories configured in workspace", - ), - ); - } - return; - } - - // Find the repository to work with - let selectedRepo = await selectAndLogRepository( - settings.git_sync.repositories, - opts.repository, - ); - - // Check if the selected repository needs migration and handle it - selectedRepo = await handleLegacyRepositoryMigration( - selectedRepo, - settings.git_sync, - workspace, - opts, - "pull" - ); - - // Convert backend settings to SyncOptions format - const backendSyncOptions: SyncOptions = { - includes: selectedRepo.settings.include_path || [], - excludes: selectedRepo.settings.exclude_path || [], - extraIncludes: selectedRepo.settings.extra_include_path || [], - ...includeTypeToSyncOptions( - selectedRepo.settings.include_type || [], - ), - }; - - // Check if wmill.yaml exists - require it for git-sync settings commands - try { - await Deno.stat("wmill.yaml"); - } catch (error) { - log.error( - colors.red( - "No wmill.yaml file found. Please run 'wmill init' first to create the configuration file.", - ), - ); - Deno.exit(1); - } - - // Read current local configuration - const localConfig = await readConfigFile(); - - // Determine where to write the settings for diff display - let overrideKey: string | undefined; - let writeMode: "override" | "replace" = "replace"; - - // For diff mode, determine what the write mode would be without interactive prompts - if (opts.default) { - writeMode = "replace"; - } else if (opts.replace) { - writeMode = "replace"; - } else if (opts.override || opts.workspaceLevel) { - writeMode = "override"; - if (opts.workspaceLevel) { - overrideKey = constructOverrideKey( - workspace.remote, - workspace.workspaceId, - "", - true, - ); - } else { - const repoPath = normalizeRepoPath(selectedRepo.git_repo_resource_path); - overrideKey = constructOverrideKey( - workspace.remote, - workspace.workspaceId, - repoPath, - ); - } - } else { - // Default behavior for existing files with no explicit flags - // Use same logic as diff to determine if there's a real conflict - const currentSettings = getCurrentSettings( - localConfig, - "replace", // Check against replace mode - undefined, - ); - - const gitSyncBackend = extractGitSyncFields( - normalizeSyncOptions(backendSyncOptions), - ); - const gitSyncCurrent = extractGitSyncFields( - normalizeSyncOptions(currentSettings), - ); - const hasConflict = !deepEqual(gitSyncBackend, gitSyncCurrent); - - if (hasConflict) { - // For diff mode, show what override would look like - writeMode = "override"; - const repoPath = normalizeRepoPath(selectedRepo.git_repo_resource_path); - overrideKey = constructOverrideKey( - workspace.remote, - workspace.workspaceId, - repoPath, - ); - } else { - writeMode = "replace"; - } - } - - if (opts.diff) { - // Show differences between local and backend - const currentSettings = getCurrentSettings( - localConfig, - writeMode, - overrideKey, - ); - - const normalizedCurrent = normalizeSyncOptions(currentSettings); - const normalizedBackend = normalizeSyncOptions(backendSyncOptions); - const gitSyncCurrent = extractGitSyncFields(normalizedCurrent); - const gitSyncBackend = extractGitSyncFields(normalizedBackend); - const hasChanges = !deepEqual(gitSyncBackend, gitSyncCurrent); - - if (opts.jsonOutput) { - const repoPath = normalizeRepoPath(selectedRepo.git_repo_resource_path); - - // Generate structured diff using the same normalized objects - const structuredDiff = hasChanges - ? generateStructuredDiff(gitSyncCurrent, gitSyncBackend) - : {}; - - console.log( - JSON.stringify({ - success: true, - hasChanges, - local: syncOptionsToBackendFormat(normalizedCurrent), - backend: syncOptionsToBackendFormat(normalizedBackend), - repository: selectedRepo.git_repo_resource_path, - diff: structuredDiff, - }), - ); - } else { - if (hasChanges) { - log.info("Changes that would be applied locally:"); - const changes = generateChanges(normalizedCurrent, normalizedBackend); - - if (Object.keys(changes).length === 0) { - log.info(colors.green("No differences found")); - } else { - displayChanges(changes); - } - } else { - log.info(colors.green("No differences found")); - } - } - return; - } - - // For non-diff mode, handle interactive logic if needed - // Only show interactive prompts for existing files with conflicts - if ( - !opts.diff && - !opts.default && - !opts.replace && - !opts.override && - !opts.workspaceLevel - ) { - // Use the same logic as diff to determine current settings - const currentSettings = getCurrentSettings( - localConfig, - writeMode, - overrideKey, - ); - - const gitSyncBackend = extractGitSyncFields( - normalizeSyncOptions(backendSyncOptions), - ); - const gitSyncCurrent = extractGitSyncFields( - normalizeSyncOptions(currentSettings), - ); - const hasConflict = !deepEqual(gitSyncBackend, gitSyncCurrent); - - if (hasConflict && !opts.yes && Deno.stdin.isTerminal()) { - // Show the diff first - log.info("Changes that would be applied locally:"); - const changes = generateChanges(currentSettings, backendSyncOptions); - if (Object.keys(changes).length > 0) { - displayChanges(changes); - } - - // Interactive mode - ask user - const { Select } = await import("../../../deps.ts"); - const choice = await Select.prompt({ - message: "Settings conflict detected. How would you like to proceed?", - options: [ - { - name: "Replace existing settings", - value: "replace", - }, - { - name: "Add repository-specific override", - value: "override", - }, - { name: "Cancel", value: "cancel" }, - ], - }); - - if (choice === "cancel") { - log.info("Operation cancelled"); - return; - } - - writeMode = choice as "replace" | "override"; - if (writeMode === "override") { - const repoPath = normalizeRepoPath( - selectedRepo.git_repo_resource_path, - ); - overrideKey = constructOverrideKey( - workspace.remote, - workspace.workspaceId, - repoPath, - ); - } - } else if (hasConflict && opts.yes) { - // --yes flag: default to override behavior for conflicts - writeMode = "override"; - const repoPath = normalizeRepoPath(selectedRepo.git_repo_resource_path); - overrideKey = constructOverrideKey( - workspace.remote, - workspace.workspaceId, - repoPath, - ); - log.info(colors.yellow("Settings conflict detected. Using --override behavior (default for --yes).")); - } else if (hasConflict) { - // Non-interactive mode with conflicts - show message and exit - if (opts.jsonOutput) { - console.log( - JSON.stringify({ - success: false, - error: - "Settings conflict detected. Use --replace or --override flags to resolve.", - hasConflict: true, - }), - ); - } else { - log.error(colors.red("Settings conflict detected.")); - log.info( - "Use --replace to overwrite existing settings or --override to add repository-specific override.", - ); - } - return; - } - } - - // Check if there are actually any changes before writing - const currentSettingsForCheck = getCurrentSettings( - localConfig, - writeMode, - overrideKey, - ); - const gitSyncBackend = extractGitSyncFields( - normalizeSyncOptions(backendSyncOptions), - ); - const gitSyncCurrent = extractGitSyncFields( - normalizeSyncOptions(currentSettingsForCheck), - ); - const hasActualChanges = !deepEqual(gitSyncBackend, gitSyncCurrent); - - if (!hasActualChanges) { - if (opts.jsonOutput) { - console.log( - JSON.stringify({ - success: true, - message: "No changes needed", - repository: selectedRepo.git_repo_resource_path, - }), - ); - } else { - log.info( - colors.green( - "No changes needed - settings are already up to date", - ), - ); - } - return; - } - - // Apply the settings based on write mode - let updatedConfig: SyncOptions; - - // Log which settings mode is being used - const repoPath = selectedRepo.git_repo_resource_path.replace(/^\$res:/, ""); - if (writeMode === "override") { - log.info(`Applied repository-specific overrides for repository "${repoPath}"`); - } else { - log.info(`Applied settings for repository "${repoPath}"`); - } - - if (writeMode === "replace") { - // Preserve existing local config and update only git-sync fields - updatedConfig = { ...localConfig }; - // Clear overrides since we're in replace mode, but keep empty object for consistency - updatedConfig.overrides = {}; - // Update with backend git-sync settings - Object.assign(updatedConfig, backendSyncOptions); - } else if (writeMode === "override" && overrideKey) { - // Add repository-specific override - updatedConfig = { ...localConfig }; - if (!updatedConfig.overrides) { - updatedConfig.overrides = {}; - } - - // Only store the delta - settings that differ from current effective settings - const currentEffective = getCurrentSettings(localConfig, "replace"); - const deltaSettings: Partial = {}; - - // Compare each setting and only include differences - for (const [key, value] of Object.entries(backendSyncOptions)) { - if (key === "overrides") continue; // Skip overrides field - - const currentValue = (currentEffective as any)[key]; - const newValue = value; - - // Compare arrays by content, primitives by value - const isDifferent = - Array.isArray(currentValue) && Array.isArray(newValue) - ? !arraysEqual(currentValue, newValue) - : currentValue !== newValue; - - if (isDifferent) { - (deltaSettings as any)[key] = newValue; - } - } - - updatedConfig.overrides[overrideKey] = deltaSettings; - } else { - // Replace top-level settings - updatedConfig = { ...localConfig }; - // Copy all backend settings to top level, excluding overrides - const { overrides, ...topLevelSettings } = backendSyncOptions; - Object.assign(updatedConfig, topLevelSettings); - } - - // Write updated configuration - await Deno.writeTextFile("wmill.yaml", yamlStringify(updatedConfig)); - - if (opts.jsonOutput) { - console.log( - JSON.stringify({ - success: true, - message: `Git-sync settings pulled successfully`, - repository: selectedRepo.git_repo_resource_path, - overrideKey, - }), - ); - } else { - log.info( - colors.green( - `Git-sync settings pulled successfully from ${selectedRepo.git_repo_resource_path}`, - ), - ); - if (writeMode === "override" && overrideKey) { - log.info( - colors.gray( - `Settings written to override key: ${overrideKey}`, - ), - ); - } else if (writeMode === "replace") { - log.info(colors.gray(`Settings written as simple configuration`)); - } else { - log.info(colors.gray(`Settings written to top-level defaults`)); - } - } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - if (opts.jsonOutput) { - console.log( - JSON.stringify({ success: false, error: errorMessage }), - ); - } else { - log.error( - colors.red( - `Failed to pull git-sync settings: ${errorMessage}`, - ), - ); - } - } -} - -async function pushGitSyncSettings( - opts: GlobalOptions & { - repository?: string; - diff?: boolean; - jsonOutput?: boolean; - withBackendSettings?: string; - yes?: boolean; - }, -) { - const workspace = await resolveWorkspace(opts); - await requireLogin(opts); - - try { - // Check if wmill.yaml exists - require it for git-sync settings commands - try { - await Deno.stat("wmill.yaml"); - } catch (error) { - log.error( - colors.red( - "No wmill.yaml file found. Please run 'wmill init' first to create the configuration file.", - ), - ); - Deno.exit(1); - } - - // Read local configuration - const localConfig = await readConfigFile(); - - // Parse and validate --with-backend-settings if provided, otherwise fetch from backend - let settings: any; - if (opts.withBackendSettings) { - try { - const parsedSettings = JSON.parse(opts.withBackendSettings); - - // Validate the structure matches expected test format (raw settings object) - if ( - !parsedSettings.include_path || - !Array.isArray(parsedSettings.include_path) - ) { - throw new Error( - "Invalid settings format. Expected include_path array", - ); - } - if ( - !parsedSettings.include_type || - !Array.isArray(parsedSettings.include_type) - ) { - throw new Error( - "Invalid settings format. Expected include_type array", - ); - } - - // Create mock backend response with single repository using provided settings - const mockRepositoryPath = opts.repository || "u/mock/repo"; - settings = { - git_sync: { - repositories: [{ - git_repo_resource_path: mockRepositoryPath, - settings: { - include_path: parsedSettings.include_path, - include_type: parsedSettings.include_type, - exclude_path: parsedSettings.exclude_path || [], - extra_include_path: parsedSettings.extra_include_path || [], - }, - }], - }, - }; - } catch (parseError) { - const errorMessage = parseError instanceof Error ? parseError.message : String(parseError); - if (opts.jsonOutput) { - console.log( - JSON.stringify({ - success: false, - error: - `Failed to parse --with-backend-settings JSON: ${errorMessage}`, - }), - ); - } else { - log.error( - colors.red( - `Failed to parse --with-backend-settings JSON: ${errorMessage}`, - ), - ); - } - return; - } - } else { - // Fetch current backend settings - try { - settings = await wmill.getSettings({ - workspace: workspace.workspaceId, - }); - } catch (apiError) { - const errorMessage = apiError instanceof Error ? apiError.message : String(apiError); - if (opts.jsonOutput) { - console.log( - JSON.stringify({ - success: false, - error: `Failed to fetch workspace settings: ${errorMessage}`, - }), - ); - } else { - log.error( - colors.red( - `Failed to fetch workspace settings: ${errorMessage}`, - ), - ); - } - return; - } - } - - if ( - !settings.git_sync?.repositories || - settings.git_sync.repositories.length === 0 - ) { - if (opts.jsonOutput) { - console.log( - JSON.stringify({ - success: false, - error: "No git-sync repositories configured", - }), - ); - } else { - log.error( - colors.red( - "No git-sync repositories configured in workspace", - ), - ); - } - return; - } - - // Find the repository to work with - let selectedRepo = await selectAndLogRepository( - settings.git_sync.repositories, - opts.repository, - ); - - // Check if the selected repository needs migration and handle it - selectedRepo = await handleLegacyRepositoryMigration( - selectedRepo, - settings.git_sync, - workspace, - opts, - "push" - ); - - // If migration was cancelled, exit - if (selectedRepo === null) { - return; - } - - // Get effective settings for this workspace/repo - const repoPath = normalizeRepoPath(selectedRepo.git_repo_resource_path); - const effectiveSettings = getEffectiveSettings( - localConfig, - workspace.remote, - workspace.workspaceId, - repoPath, - ); - - // Convert to backend format - const backendFormat = { - include_path: effectiveSettings.includes || [], - include_type: syncOptionsToIncludeType(effectiveSettings), - exclude_path: effectiveSettings.excludes || [], - extra_include_path: effectiveSettings.extraIncludes || [], - }; - - // Calculate diff for all modes - const currentBackend = selectedRepo.settings; - - // Convert current backend settings to SyncOptions for user-friendly display - const currentSyncOptions: SyncOptions = { - includes: currentBackend.include_path || [], - excludes: currentBackend.exclude_path || [], - extraIncludes: currentBackend.extra_include_path || [], - ...includeTypeToSyncOptions(currentBackend.include_type || []), - }; - - const normalizedCurrent = normalizeSyncOptions(currentSyncOptions); - const normalizedEffective = normalizeSyncOptions(effectiveSettings); - const gitSyncCurrent = extractGitSyncFields(normalizedCurrent); - const gitSyncEffective = extractGitSyncFields(normalizedEffective); - const hasChanges = !deepEqual(gitSyncEffective, gitSyncCurrent); - - if (opts.diff) { - // --diff flag: show differences and exit - if (opts.jsonOutput) { - // Generate structured diff using the same normalized objects - const structuredDiff = hasChanges - ? generateStructuredDiff(gitSyncCurrent, gitSyncEffective) - : {}; - - console.log( - JSON.stringify({ - success: true, - hasChanges, - local: syncOptionsToBackendFormat(normalizedEffective), - backend: syncOptionsToBackendFormat(normalizedCurrent), - repository: selectedRepo.git_repo_resource_path, - diff: structuredDiff, - }), - ); - } else { - if (hasChanges) { - log.info("Changes that would be pushed to Windmill:"); - const changes = generateChanges( - normalizedCurrent, - normalizedEffective, - ); - - if (Object.keys(changes).length === 0) { - log.info(colors.green("No changes to push")); - } else { - displayChanges(changes); - } - } else { - log.info(colors.green("No changes to push")); - } - } - return; - } - - // Default behavior: show changes and ask for confirmation (unless --yes is passed) - if (hasChanges) { - if (!opts.jsonOutput) { - const changes = generateChanges( - normalizedCurrent, - normalizedEffective, - ); - - if (Object.keys(changes).length === 0) { - log.info(colors.green("No changes to push")); - return; - } else { - log.info("Changes that would be pushed to Windmill:"); - displayChanges(changes); - } - } - - // Ask for confirmation unless --yes is passed or not in TTY - if (!opts.yes && Deno.stdin.isTerminal()) { - const confirmed = await Confirm.prompt({ - message: `Do you want to apply these changes to the remote?`, - default: true, - }); - - if (!confirmed) { - return; - } - } - } else { - log.info(colors.green("No changes to push")); - return; - } - - if (opts.withBackendSettings) { - // Skip backend update when using simulated settings - if (opts.jsonOutput) { - console.log( - JSON.stringify({ - success: true, - message: - `Git-sync settings push simulated (--with-backend-settings used)`, - repository: selectedRepo.git_repo_resource_path, - simulated: true, - }), - ); - } else { - log.info( - colors.green( - `Git-sync settings push simulated for ${selectedRepo.git_repo_resource_path} (--with-backend-settings used)`, - ), - ); - } - } else { - // Update the specific repository settings - const updatedRepos = settings.git_sync.repositories.map( - (repo: GitSyncRepository) => { - if ( - repo.git_repo_resource_path === - selectedRepo.git_repo_resource_path - ) { - return { - ...repo, - settings: backendFormat, - }; - } - return repo; - }, - ); - - // Push updated settings to backend - try { - await wmill.editWorkspaceGitSyncConfig({ - workspace: workspace.workspaceId, - requestBody: { - git_sync_settings: { - repositories: updatedRepos, - }, - }, - }); - } catch (apiError) { - const errorMessage = apiError instanceof Error ? apiError.message : String(apiError); - if (opts.jsonOutput) { - console.log( - JSON.stringify({ - success: false, - error: `Failed to update workspace git-sync config: ${errorMessage}`, - }), - ); - } else { - log.error( - colors.red( - `Failed to update workspace git-sync config: ${errorMessage}`, - ), - ); - } - return; - } - - if (opts.jsonOutput) { - console.log( - JSON.stringify({ - success: true, - message: `Git-sync settings pushed successfully`, - repository: selectedRepo.git_repo_resource_path, - }), - ); - } else { - log.info( - colors.green( - `Git-sync settings pushed successfully to ${selectedRepo.git_repo_resource_path}`, - ), - ); - } - } - } catch (error) { - const errorMessage = error instanceof Error ? error.message : String(error); - if (opts.jsonOutput) { - console.log( - JSON.stringify({ success: false, error: errorMessage }), - ); - } else { - log.error( - colors.red( - `Failed to push git-sync settings: ${errorMessage}`, - ), - ); - } - } -} - -const command = new Command() - .description( - "Manage git-sync settings between local wmill.yaml and Windmill backend", - ) - .command("pull") - .description( - "Pull git-sync settings from Windmill backend to local wmill.yaml", - ) - .option( - "--repository ", - "Specify repository path (e.g., u/user/repo)", - ) - .option( - "--workspace-level", - "Write settings to workspace:* override instead of workspace:repo", - ) - .option( - "--default", - "Write settings to top-level defaults instead of overrides", - ) - .option("--replace", "Replace existing settings (non-interactive mode)") - .option( - "--override", - "Add repository-specific override (non-interactive mode)", - ) - .option("--diff", "Show differences without applying changes") - .option("--json-output", "Output in JSON format") - .option( - "--with-backend-settings ", - "Use provided JSON settings instead of querying backend (for testing)", - ) - .option("--yes", "Skip interactive prompts and use default behavior") - .action(pullGitSyncSettings as any) - .command("push") - .description( - "Push git-sync settings from local wmill.yaml to Windmill backend", - ) - .option( - "--repository ", - "Specify repository path (e.g., u/user/repo)", - ) - .option("--diff", "Show what would be pushed without applying changes") - .option("--json-output", "Output in JSON format") - .option( - "--with-backend-settings ", - "Use provided JSON settings instead of querying backend (for testing)", - ) - .option("--yes", "Skip interactive prompts and use default behavior") - .action(pushGitSyncSettings as any); - -export { pullGitSyncSettings, pushGitSyncSettings }; -export default command; +export { pullGitSyncSettings, pushGitSyncSettings } from "./index.ts"; +export { default } from "./index.ts"; \ No newline at end of file diff --git a/cli/src/commands/gitsync-settings/index.ts b/cli/src/commands/gitsync-settings/index.ts new file mode 100644 index 0000000000..567bc50d1a --- /dev/null +++ b/cli/src/commands/gitsync-settings/index.ts @@ -0,0 +1,60 @@ +import { Command } from "../../../deps.ts"; +import { pullGitSyncSettings } from "./pull.ts"; +import { pushGitSyncSettings } from "./push.ts"; + +const command = new Command() + .description( + "Manage git-sync settings between local wmill.yaml and Windmill backend", + ) + .command("pull") + .description( + "Pull git-sync settings from Windmill backend to local wmill.yaml", + ) + .option( + "--repository ", + "Specify repository path (e.g., u/user/repo)", + ) + .option( + "--default", + "Write settings to top-level defaults instead of overrides", + ) + .option("--replace", "Replace existing settings (non-interactive mode)") + .option( + "--override", + "Add branch-specific override (non-interactive mode)", + ) + .option("--diff", "Show differences without applying changes") + .option("--json-output", "Output in JSON format") + .option( + "--with-backend-settings ", + "Use provided JSON settings instead of querying backend (for testing)", + ) + .option("--yes", "Skip interactive prompts and use default behavior") + .option( + "--promotion ", + "Use promotionOverrides from the specified branch instead of regular overrides" + ) + .action(pullGitSyncSettings as any) + .command("push") + .description( + "Push git-sync settings from local wmill.yaml to Windmill backend", + ) + .option( + "--repository ", + "Specify repository path (e.g., u/user/repo)", + ) + .option("--diff", "Show what would be pushed without applying changes") + .option("--json-output", "Output in JSON format") + .option( + "--with-backend-settings ", + "Use provided JSON settings instead of querying backend (for testing)", + ) + .option("--yes", "Skip interactive prompts and use default behavior") + .option( + "--promotion ", + "Use promotionOverrides from the specified branch instead of regular overrides" + ) + .action(pushGitSyncSettings as any); + +export { pullGitSyncSettings, pushGitSyncSettings }; +export default command; \ No newline at end of file diff --git a/cli/src/commands/gitsync-settings/legacySettings.ts b/cli/src/commands/gitsync-settings/legacySettings.ts new file mode 100644 index 0000000000..fe6f29faa5 --- /dev/null +++ b/cli/src/commands/gitsync-settings/legacySettings.ts @@ -0,0 +1,144 @@ +import { colors, Confirm } from "../../../deps.ts"; +import * as wmill from "../../../gen/services.gen.ts"; +import { GitSyncRepository } from "./types.ts"; + +// Shared migration function for legacy repositories +export async function handleLegacyRepositoryMigration( + selectedRepo: any, + gitSyncSettings: any, + workspace: any, + opts: { yes?: boolean }, + operationName: string = "operation" +): Promise { + if (selectedRepo.settings) { + return selectedRepo; // Already migrated + } + + // This repository is in legacy format - handle migration + if (!gitSyncSettings.include_path || !gitSyncSettings.include_type) { + throw new Error( + `Repository "${selectedRepo.git_repo_resource_path}" has legacy format but workspace-level include_path or include_type is missing. This indicates corrupted git-sync settings.` + ); + } + + const workspaceIncludePath = gitSyncSettings.include_path; + const workspaceIncludeType = gitSyncSettings.include_type; + + if (Deno.stdout.isTerminal() && !opts.yes) { + // Interactive mode - show migration prompt + console.log(colors.yellow('\n⚠️ Legacy git-sync settings detected!')); + console.log(`\nRepository "${selectedRepo.git_repo_resource_path}" has legacy settings format.`); + console.log('The new format allows per-repository filter configuration.'); + if (operationName === "push") { + console.log('This repository must be migrated before pushing settings.\n'); + } else { + console.log('\n'); + } + + console.log(colors.bold('Current workspace-level settings:')); + console.log(` Include paths: ${workspaceIncludePath.join(', ')}`); + console.log(` Include types: ${workspaceIncludeType.join(', ')}\n`); + + // Show what the migration will do + let finalIncludeType = [...workspaceIncludeType]; + if (selectedRepo.exclude_types_override && selectedRepo.exclude_types_override.length > 0) { + const originalCount = finalIncludeType.length; + finalIncludeType = finalIncludeType.filter( + type => !selectedRepo.exclude_types_override.includes(type) + ); + const excludedCount = originalCount - finalIncludeType.length; + console.log(colors.yellow(`Repository excludes ${excludedCount} types: ${selectedRepo.exclude_types_override.join(', ')}`)); + } + + console.log(colors.bold('\nAfter migration, repository will have:')); + console.log(` Include paths: ${workspaceIncludePath.join(', ')}`); + console.log(` Include types: ${finalIncludeType.join(', ')}\n`); + + const confirm = await Confirm.prompt({ + message: operationName === "push" + ? 'Do you want to migrate this repository before pushing?' + : 'Do you want to migrate this repository?', + default: true + }); + + if (!confirm) { + const message = operationName === "push" + ? '\n⚠️ Migration skipped. Cannot push to legacy repository.' + : '\n⚠️ Migration skipped. You can migrate later via the UI.'; + console.log(colors.yellow(message)); + if (operationName === "push") { + return null; // Signal to exit push operation + } + throw new Error('Migration cancelled by user'); + } + + // Perform the migration + let migratedIncludeType = [...workspaceIncludeType]; + if (selectedRepo.exclude_types_override && selectedRepo.exclude_types_override.length > 0) { + migratedIncludeType = migratedIncludeType.filter( + type => !selectedRepo.exclude_types_override.includes(type) + ); + } + + const migratedRepo = { + ...selectedRepo, + settings: { + include_path: [...workspaceIncludePath], + include_type: migratedIncludeType, + exclude_path: [], + extra_include_path: [] + } + }; + + // Remove the old field + delete migratedRepo.exclude_types_override; + + // Update the backend with migrated repository + const updatedRepositories = gitSyncSettings.repositories.map((repo: any) => { + if (repo.git_repo_resource_path === selectedRepo.git_repo_resource_path) { + return migratedRepo; + } + return repo; + }); + + await wmill.editWorkspaceGitSyncConfig({ + workspace: workspace.workspaceId, + requestBody: { + git_sync_settings: { + repositories: updatedRepositories, + // Keep workspace-level settings if other repos are still legacy + ...(gitSyncSettings.repositories.some((r: any) => r.git_repo_resource_path !== selectedRepo.git_repo_resource_path && !r.settings) && { + include_path: workspaceIncludePath, + include_type: workspaceIncludeType + }) + } + } + }); + + console.log(colors.green('\n✓ Repository migration completed successfully!')); + if (operationName === "push") { + console.log('Now proceeding with push operation...\n'); + } + return migratedRepo; + + } else { + // Non-interactive mode - show error + console.error(colors.red('\n❌ Legacy git-sync settings detected!')); + console.error(`\nRepository "${selectedRepo.git_repo_resource_path}" has legacy settings format.`); + if (operationName === "push") { + console.error('This repository must be migrated before pushing settings.'); + } + console.error('Please choose one of the following options:\n'); + console.error('1. Go to the Windmill UI > Workspace Settings > Git Sync'); + console.error(' Review and save this repository to migrate to the new format.\n'); + console.error('2. Run this command in interactive mode (with TTY) to migrate.'); + console.error(` Example: wmill gitsync-settings ${operationName}\n`); + if (operationName === "push") { + console.error('3. Pull settings first to migrate: wmill gitsync-settings pull\n'); + } else { + console.error('3. Push local settings to override backend settings:'); + console.error(' wmill gitsync-settings push\n'); + } + Deno.exit(1); + } +} \ No newline at end of file diff --git a/cli/src/commands/gitsync-settings/pull.ts b/cli/src/commands/gitsync-settings/pull.ts new file mode 100644 index 0000000000..b02017af55 --- /dev/null +++ b/cli/src/commands/gitsync-settings/pull.ts @@ -0,0 +1,492 @@ +import { colors, log, yamlStringify } from "../../../deps.ts"; +import { GlobalOptions } from "../../types.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { resolveWorkspace } from "../../core/context.ts"; +import * as wmill from "../../../gen/services.gen.ts"; +import { SyncOptions, readConfigFile, getEffectiveSettings, DEFAULT_SYNC_OPTIONS } from "../../core/conf.ts"; +import { deepEqual } from "../../utils/utils.ts"; +import { getCurrentGitBranch, isGitRepository } from "../../utils/git.ts"; + +import { GitSyncRepository, WriteMode } from "./types.ts"; +import { GitSyncSettingsConverter } from "./converter.ts"; +import { handleLegacyRepositoryMigration } from "./legacySettings.ts"; +import { + selectAndLogRepository, + generateStructuredDiff, + generateChanges, + displayChanges, + applyBackendSettingsToBranch, + normalizeRepoPath, + outputResult +} from "./utils.ts"; + +export async function pullGitSyncSettings( + opts: GlobalOptions & { + repository?: string; + workspaceLevel?: boolean; + default?: boolean; + diff?: boolean; + jsonOutput?: boolean; + replace?: boolean; + override?: boolean; + withBackendSettings?: string; + yes?: boolean; + promotion?: string; + }, +) { + const workspace = await resolveWorkspace(opts); + await requireLogin(opts); + + try { + // Parse and validate --with-backend-settings if provided + let settings: any; + if (opts.withBackendSettings) { + try { + const parsedSettings = JSON.parse(opts.withBackendSettings); + + // Validate the structure matches expected format (raw settings object) + if ( + !parsedSettings.include_path || + !Array.isArray(parsedSettings.include_path) + ) { + throw new Error( + "Invalid settings format. Expected include_path array", + ); + } + if ( + !parsedSettings.include_type || + !Array.isArray(parsedSettings.include_type) + ) { + throw new Error( + "Invalid settings format. Expected include_type array", + ); + } + + // Create mock backend response with single repository using provided settings + const mockRepositoryPath = opts.repository || "u/mock/repo"; + settings = { + git_sync: { + repositories: [{ + git_repo_resource_path: mockRepositoryPath, + settings: { + include_path: parsedSettings.include_path, + include_type: parsedSettings.include_type, + exclude_path: parsedSettings.exclude_path || [], + extra_include_path: parsedSettings.extra_include_path || [], + }, + }], + }, + }; + } catch (parseError) { + const errorMessage = parseError instanceof Error ? parseError.message : String(parseError); + outputResult(opts, { + success: false, + error: `Failed to parse --with-backend-settings JSON: ${errorMessage}`, + }); + return; + } + } else { + // Fetch workspace settings to get git-sync configuration + try { + settings = await wmill.getSettings({ + workspace: workspace.workspaceId, + }); + } catch (apiError) { + const errorMessage = apiError instanceof Error ? apiError.message : String(apiError); + outputResult(opts, { + success: false, + error: `Failed to fetch workspace settings: ${errorMessage}`, + }); + return; + } + } + + if ( + !settings.git_sync?.repositories || + settings.git_sync.repositories.length === 0 + ) { + outputResult(opts, { + success: false, + error: "No git-sync repositories configured", + }); + return; + } + + // Find the repository to work with + let selectedRepo = await selectAndLogRepository( + settings.git_sync.repositories, + opts.repository, + opts.jsonOutput, + ); + + // Check if the selected repository needs migration and handle it + selectedRepo = await handleLegacyRepositoryMigration( + selectedRepo, + settings.git_sync, + workspace, + opts, + "pull" + ); + + // Convert backend settings to SyncOptions format + const backendSyncOptions: SyncOptions = GitSyncSettingsConverter.fromBackendFormat(selectedRepo.settings); + + // Check if wmill.yaml exists - create a default one if it doesn't exist + let wmillYamlExists = true; + try { + await Deno.stat("wmill.yaml"); + } catch (error) { + wmillYamlExists = false; + if (!opts.jsonOutput) { + log.info( + colors.yellow( + "No wmill.yaml file found. Will create one with backend git-sync settings.", + ), + ); + } + } + + let localConfig: SyncOptions; + if (wmillYamlExists) { + localConfig = await readConfigFile(); + } else { + localConfig = { ...DEFAULT_SYNC_OPTIONS }; + } + + // Handle the case where wmill.yaml doesn't exist - just create it with backend settings + if (!wmillYamlExists) { + // When creating from scratch, just apply backend settings directly + let updatedConfig = { ...localConfig }; + + // Apply backend settings to the default config + Object.assign(updatedConfig, backendSyncOptions); + + // Add git branch structure if in a git repository + if (isGitRepository()) { + const currentBranch = getCurrentGitBranch(); + if (currentBranch) { + if (!updatedConfig.git_branches) { + updatedConfig.git_branches = {}; + } + if (!updatedConfig.git_branches[currentBranch]) { + updatedConfig.git_branches[currentBranch] = { overrides: {} }; + } + } + } + + // Write the new configuration + await Deno.writeTextFile("wmill.yaml", yamlStringify(updatedConfig)); + + if (opts.jsonOutput) { + console.log( + JSON.stringify({ + success: true, + message: "wmill.yaml created with backend git-sync settings", + repository: selectedRepo.git_repo_resource_path, + }), + ); + } else { + log.info( + colors.green( + `wmill.yaml created with git-sync settings from ${selectedRepo.git_repo_resource_path}`, + ), + ); + } + return; + } + + // For existing wmill.yaml files, continue with the normal flow + // Calculate current settings once and reuse throughout + const currentSettings = await getEffectiveSettings(localConfig, opts.promotion, true, opts.jsonOutput); + + // Determine write mode for branch-based configuration + let writeMode: WriteMode = "replace"; + + // Determine write mode based on flags + if (opts.default || opts.replace) { + writeMode = "replace"; + } else if (opts.override) { + writeMode = "override"; + } else { + // Default behavior for existing files with no explicit flags + // Use same logic as diff to determine if there's a real conflict + const gitSyncBackend = GitSyncSettingsConverter.extractGitSyncFields( + GitSyncSettingsConverter.normalize(backendSyncOptions), + ); + const gitSyncCurrent = GitSyncSettingsConverter.extractGitSyncFields( + GitSyncSettingsConverter.normalize(currentSettings), + ); + const hasConflict = !deepEqual(gitSyncBackend, gitSyncCurrent); + + if (hasConflict) { + // For diff mode, show what override would look like + writeMode = "override"; + } else { + writeMode = "replace"; + } + } + + if (opts.diff) { + // Show differences between local and backend + const normalizedCurrent = GitSyncSettingsConverter.normalize(currentSettings); + const normalizedBackend = GitSyncSettingsConverter.normalize(backendSyncOptions); + const gitSyncCurrent = GitSyncSettingsConverter.extractGitSyncFields(normalizedCurrent); + const gitSyncBackend = GitSyncSettingsConverter.extractGitSyncFields(normalizedBackend); + const hasChanges = !deepEqual(gitSyncBackend, gitSyncCurrent); + + if (opts.jsonOutput) { + const repoPath = normalizeRepoPath(selectedRepo.git_repo_resource_path); + + // Generate structured diff using the same normalized objects + const structuredDiff = hasChanges + ? generateStructuredDiff(gitSyncCurrent, gitSyncBackend) + : {}; + + console.log( + JSON.stringify({ + success: true, + hasChanges, + local: GitSyncSettingsConverter.toBackendFormat(normalizedCurrent), + backend: GitSyncSettingsConverter.toBackendFormat(normalizedBackend), + repository: selectedRepo.git_repo_resource_path, + diff: structuredDiff, + }), + ); + } else { + if (hasChanges) { + log.info("Changes that would be applied locally:"); + const changes = generateChanges(currentSettings, backendSyncOptions); + + if (Object.keys(changes).length === 0) { + log.info(colors.green("No differences found")); + } else { + displayChanges(changes); + } + } else { + log.info(colors.green("No differences found")); + } + } + return; + } + + // For non-diff mode, handle interactive logic if needed + // Only show interactive prompts for existing files with conflicts + if ( + !opts.diff && + !opts.default && + !opts.replace && + !opts.override + ) { + // Recalculate current settings with promotion if needed + const effectiveCurrentSettings = await getEffectiveSettings(localConfig, opts.promotion, true, opts.jsonOutput); + // Use the same logic as diff to determine current settings + const gitSyncBackend = GitSyncSettingsConverter.extractGitSyncFields( + GitSyncSettingsConverter.normalize(backendSyncOptions), + ); + const gitSyncCurrent = GitSyncSettingsConverter.extractGitSyncFields( + GitSyncSettingsConverter.normalize(effectiveCurrentSettings), + ); + const hasConflict = !deepEqual(gitSyncBackend, gitSyncCurrent); + + if (hasConflict && !opts.yes && Deno.stdin.isTerminal()) { + // Show the diff first + log.info("Changes that would be applied locally:"); + const changes = generateChanges(effectiveCurrentSettings, backendSyncOptions); + if (Object.keys(changes).length > 0) { + displayChanges(changes); + } + + // Interactive mode - ask user + const { Select } = await import("../../../deps.ts"); + const choice = await Select.prompt({ + message: "Settings conflict detected. How would you like to proceed?", + options: [ + { + name: "Replace existing settings", + value: "replace", + }, + { + name: "Add branch-specific override", + value: "override", + }, + { name: "Cancel", value: "cancel" }, + ], + }); + + if (choice === "cancel") { + log.info("Operation cancelled"); + return; + } + + writeMode = choice as WriteMode; + } else if (hasConflict && opts.yes) { + // --yes flag: default to override behavior for conflicts + writeMode = "override"; + log.info(colors.yellow("Settings conflict detected. Using --override behavior (default for --yes).")); + } else if (hasConflict) { + // Non-interactive mode with conflicts - show message and exit + if (opts.jsonOutput) { + console.log( + JSON.stringify({ + success: false, + error: + "Settings conflict detected. Use --replace or --override flags to resolve.", + hasConflict: true, + }), + ); + } else { + log.error(colors.red("Settings conflict detected.")); + log.info( + "Use --replace to overwrite existing settings or --override to add branch-specific override.", + ); + } + return; + } + } + + // Check if there are actually any changes before writing + const gitSyncBackend = GitSyncSettingsConverter.extractGitSyncFields( + GitSyncSettingsConverter.normalize(backendSyncOptions), + ); + const gitSyncCurrent = GitSyncSettingsConverter.extractGitSyncFields( + GitSyncSettingsConverter.normalize(currentSettings), + ); + const hasActualChanges = !deepEqual(gitSyncBackend, gitSyncCurrent); + + if (!hasActualChanges && wmillYamlExists) { + // Even if no changes, check if we need to create empty branch structure + let needsBranchStructure = false; + if (isGitRepository()) { + const currentBranch = getCurrentGitBranch(); + if (currentBranch && (!localConfig.git_branches || !localConfig.git_branches[currentBranch])) { + needsBranchStructure = true; + + // Create empty branch structure + const updatedConfig = { ...localConfig }; + if (!updatedConfig.git_branches) { + updatedConfig.git_branches = {}; + } + if (!updatedConfig.git_branches[currentBranch]) { + updatedConfig.git_branches[currentBranch] = { overrides: {} }; + } + + // Write updated configuration + await Deno.writeTextFile("wmill.yaml", yamlStringify(updatedConfig)); + + if (opts.jsonOutput) { + console.log( + JSON.stringify({ + success: true, + message: `Created empty branch structure for: ${currentBranch}`, + repository: selectedRepo.git_repo_resource_path, + }), + ); + } else { + log.info( + colors.green( + `Created empty branch structure for: ${currentBranch}`, + ), + ); + } + return; + } + } + + if (opts.jsonOutput) { + console.log( + JSON.stringify({ + success: true, + message: "No changes needed", + repository: selectedRepo.git_repo_resource_path, + }), + ); + } else { + log.info( + colors.green( + "No changes needed - settings are already up to date", + ), + ); + } + return; + } + + // Apply the settings based on write mode + let updatedConfig: SyncOptions; + + + if (writeMode === "replace") { + // Replace top-level settings + updatedConfig = { ...localConfig }; + // Update with backend git-sync settings + Object.assign(updatedConfig, backendSyncOptions); + } else { + // Override mode - will be handled by branch logic below + updatedConfig = { ...localConfig }; + } + + // For replace mode, add empty branch structure if in Git repo + if (writeMode === "replace" && isGitRepository()) { + const currentBranch = getCurrentGitBranch(); + if (currentBranch) { + log.info(`Detected Git repository, adding empty branch structure for: ${currentBranch}`); + if (!updatedConfig.git_branches) { + updatedConfig.git_branches = {}; + } + if (!updatedConfig.git_branches[currentBranch]) { + updatedConfig.git_branches[currentBranch] = { overrides: {} }; + } + } + } + + // For override mode, write to branch overrides + if (writeMode === "override" && isGitRepository()) { + const currentBranch = getCurrentGitBranch(); + if (currentBranch) { + log.info(`Detected Git repository, writing settings to branch: ${currentBranch}`); + updatedConfig = applyBackendSettingsToBranch(localConfig, currentBranch, backendSyncOptions); + } + } + + // Write updated configuration + await Deno.writeTextFile("wmill.yaml", yamlStringify(updatedConfig)); + + if (opts.jsonOutput) { + console.log( + JSON.stringify({ + success: true, + message: `Git-sync settings pulled successfully`, + repository: selectedRepo.git_repo_resource_path, + }), + ); + } else { + log.info( + colors.green( + `Git-sync settings pulled successfully from ${selectedRepo.git_repo_resource_path}`, + ), + ); + if (writeMode === "override") { + const currentBranch = getCurrentGitBranch(); + if (currentBranch) { + log.info( + colors.gray(`Settings written to branch '${currentBranch}' overrides`), + ); + } + } else if (writeMode === "replace") { + log.info(colors.gray(`Settings written to top-level configuration`)); + } + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + if (opts.jsonOutput) { + console.log( + JSON.stringify({ success: false, error: errorMessage }), + ); + } else { + log.error( + colors.red( + `Failed to pull git-sync settings: ${errorMessage}`, + ), + ); + } + } +} diff --git a/cli/src/commands/gitsync-settings/push.ts b/cli/src/commands/gitsync-settings/push.ts new file mode 100644 index 0000000000..14f8a52d98 --- /dev/null +++ b/cli/src/commands/gitsync-settings/push.ts @@ -0,0 +1,361 @@ +import { colors, log, Confirm } from "../../../deps.ts"; +import { GlobalOptions } from "../../types.ts"; +import { requireLogin } from "../../core/auth.ts"; +import { resolveWorkspace } from "../../core/context.ts"; +import * as wmill from "../../../gen/services.gen.ts"; +import { SyncOptions, readConfigFile, validateBranchConfiguration, getEffectiveSettings } from "../../core/conf.ts"; +import { deepEqual } from "../../utils/utils.ts"; + +import { GitSyncRepository } from "./types.ts"; +import { GitSyncSettingsConverter } from "./converter.ts"; +import { handleLegacyRepositoryMigration } from "./legacySettings.ts"; +import { + selectAndLogRepository, + generateStructuredDiff, + generateChanges, + displayChanges, + normalizeRepoPath, + outputResult +} from "./utils.ts"; + +export async function pushGitSyncSettings( + opts: GlobalOptions & { + repository?: string; + diff?: boolean; + jsonOutput?: boolean; + withBackendSettings?: string; + yes?: boolean; + promotion?: string; + }, +) { + // Validate branch configuration like sync commands + try { + await validateBranchConfiguration(); + } catch (error) { + if (error instanceof Error && error.message.includes("overrides")) { + log.error(error.message); + Deno.exit(1); + } + throw error; + } + + const workspace = await resolveWorkspace(opts); + await requireLogin(opts); + + try { + // Check if wmill.yaml exists - require it for git-sync settings commands + try { + await Deno.stat("wmill.yaml"); + } catch (error) { + log.error( + colors.red( + "No wmill.yaml file found. Please run 'wmill init' first to create the configuration file.", + ), + ); + Deno.exit(1); + } + + // Read local configuration + const localConfig = await readConfigFile(); + + // Parse and validate --with-backend-settings if provided, otherwise fetch from backend + let settings: any; + if (opts.withBackendSettings) { + try { + const parsedSettings = JSON.parse(opts.withBackendSettings); + + // Validate the structure matches expected test format (raw settings object) + if ( + !parsedSettings.include_path || + !Array.isArray(parsedSettings.include_path) + ) { + throw new Error( + "Invalid settings format. Expected include_path array", + ); + } + if ( + !parsedSettings.include_type || + !Array.isArray(parsedSettings.include_type) + ) { + throw new Error( + "Invalid settings format. Expected include_type array", + ); + } + + // Create mock backend response with single repository using provided settings + const mockRepositoryPath = opts.repository || "u/mock/repo"; + settings = { + git_sync: { + repositories: [{ + git_repo_resource_path: mockRepositoryPath, + settings: { + include_path: parsedSettings.include_path, + include_type: parsedSettings.include_type, + exclude_path: parsedSettings.exclude_path || [], + extra_include_path: parsedSettings.extra_include_path || [], + }, + }], + }, + }; + } catch (parseError) { + const errorMessage = parseError instanceof Error ? parseError.message : String(parseError); + outputResult(opts, { + success: false, + error: `Failed to parse --with-backend-settings JSON: ${errorMessage}`, + }); + return; + } + } else { + // Fetch current backend settings + try { + settings = await wmill.getSettings({ + workspace: workspace.workspaceId, + }); + } catch (apiError) { + const errorMessage = apiError instanceof Error ? apiError.message : String(apiError); + if (opts.jsonOutput) { + console.log( + JSON.stringify({ + success: false, + error: `Failed to fetch workspace settings: ${errorMessage}`, + }), + ); + } else { + log.error( + colors.red( + `Failed to fetch workspace settings: ${errorMessage}`, + ), + ); + } + return; + } + } + + if ( + !settings.git_sync?.repositories || + settings.git_sync.repositories.length === 0 + ) { + if (opts.jsonOutput) { + console.log( + JSON.stringify({ + success: false, + error: "No git-sync repositories configured", + }), + ); + } else { + log.error( + colors.red( + "No git-sync repositories configured in workspace", + ), + ); + } + return; + } + + // Find the repository to work with + let selectedRepo = await selectAndLogRepository( + settings.git_sync.repositories, + opts.repository, + opts.jsonOutput, + ); + + // Check if the selected repository needs migration and handle it + selectedRepo = await handleLegacyRepositoryMigration( + selectedRepo, + settings.git_sync, + workspace, + opts, + "push" + ); + + // If migration was cancelled, exit + if (selectedRepo === null) { + return; + } + + // Get effective settings for this workspace/repo + const repoPath = normalizeRepoPath(selectedRepo.git_repo_resource_path); + const effectiveSettings = await getEffectiveSettings(localConfig, opts.promotion, true, opts.jsonOutput); + + // Convert to backend format + const backendFormat = GitSyncSettingsConverter.toBackendFormat(effectiveSettings); + + // Calculate diff for all modes + const currentBackend = selectedRepo.settings; + + // Convert current backend settings to SyncOptions for user-friendly display + const currentSyncOptions: SyncOptions = GitSyncSettingsConverter.fromBackendFormat(currentBackend); + + const normalizedCurrent = GitSyncSettingsConverter.normalize(currentSyncOptions); + const normalizedEffective = GitSyncSettingsConverter.normalize(effectiveSettings); + const gitSyncCurrent = GitSyncSettingsConverter.extractGitSyncFields(normalizedCurrent); + const gitSyncEffective = GitSyncSettingsConverter.extractGitSyncFields(normalizedEffective); + const hasChanges = !deepEqual(gitSyncEffective, gitSyncCurrent); + + if (opts.diff) { + // --diff flag: show differences and exit + if (opts.jsonOutput) { + // Generate structured diff using the same normalized objects + const structuredDiff = hasChanges + ? generateStructuredDiff(gitSyncCurrent, gitSyncEffective) + : {}; + + console.log( + JSON.stringify({ + success: true, + hasChanges, + local: GitSyncSettingsConverter.toBackendFormat(normalizedEffective), + backend: GitSyncSettingsConverter.toBackendFormat(normalizedCurrent), + repository: selectedRepo.git_repo_resource_path, + diff: structuredDiff, + }), + ); + } else { + if (hasChanges) { + log.info("Changes that would be pushed to Windmill:"); + const changes = generateChanges( + currentSyncOptions, + effectiveSettings, + ); + + if (Object.keys(changes).length === 0) { + log.info(colors.green("No changes to push")); + } else { + displayChanges(changes); + } + } else { + log.info(colors.green("No changes to push")); + } + } + return; + } + + // Default behavior: show changes and ask for confirmation (unless --yes is passed) + if (hasChanges) { + if (!opts.jsonOutput) { + const changes = generateChanges( + currentSyncOptions, + effectiveSettings, + ); + + if (Object.keys(changes).length === 0) { + log.info(colors.green("No changes to push")); + return; + } else { + log.info("Changes that would be pushed to Windmill:"); + displayChanges(changes); + } + } + + // Ask for confirmation unless --yes is passed or not in TTY + if (!opts.yes && Deno.stdin.isTerminal()) { + const confirmed = await Confirm.prompt({ + message: `Do you want to apply these changes to the remote?`, + default: true, + }); + + if (!confirmed) { + return; + } + } + } else { + log.info(colors.green("No changes to push")); + return; + } + + if (opts.withBackendSettings) { + // Skip backend update when using simulated settings + if (opts.jsonOutput) { + console.log( + JSON.stringify({ + success: true, + message: + `Git-sync settings push simulated (--with-backend-settings used)`, + repository: selectedRepo.git_repo_resource_path, + simulated: true, + }), + ); + } else { + log.info( + colors.green( + `Git-sync settings push simulated for ${selectedRepo.git_repo_resource_path} (--with-backend-settings used)`, + ), + ); + } + } else { + // Update the specific repository settings + const updatedRepos = settings.git_sync.repositories.map( + (repo: GitSyncRepository) => { + if ( + repo.git_repo_resource_path === + selectedRepo.git_repo_resource_path + ) { + return { + ...repo, + settings: backendFormat, + }; + } + return repo; + }, + ); + + // Push updated settings to backend + try { + await wmill.editWorkspaceGitSyncConfig({ + workspace: workspace.workspaceId, + requestBody: { + git_sync_settings: { + repositories: updatedRepos, + }, + }, + }); + } catch (apiError) { + const errorMessage = apiError instanceof Error ? apiError.message : String(apiError); + if (opts.jsonOutput) { + console.log( + JSON.stringify({ + success: false, + error: `Failed to update workspace git-sync config: ${errorMessage}`, + }), + ); + } else { + log.error( + colors.red( + `Failed to update workspace git-sync config: ${errorMessage}`, + ), + ); + } + return; + } + + if (opts.jsonOutput) { + console.log( + JSON.stringify({ + success: true, + message: `Git-sync settings pushed successfully`, + repository: selectedRepo.git_repo_resource_path, + }), + ); + } else { + log.info( + colors.green( + `Git-sync settings pushed successfully to ${selectedRepo.git_repo_resource_path}`, + ), + ); + } + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + if (opts.jsonOutput) { + console.log( + JSON.stringify({ success: false, error: errorMessage }), + ); + } else { + log.error( + colors.red( + `Failed to push git-sync settings: ${errorMessage}`, + ), + ); + } + } +} diff --git a/cli/src/commands/gitsync-settings/types.ts b/cli/src/commands/gitsync-settings/types.ts new file mode 100644 index 0000000000..36b51aedbc --- /dev/null +++ b/cli/src/commands/gitsync-settings/types.ts @@ -0,0 +1,63 @@ +import { Repository } from "../../utils/utils.ts"; + +// Interface for git-sync repository settings from backend +export interface GitSyncRepository extends Repository { + settings: { + include_path: string[]; + include_type: string[]; + exclude_path?: string[]; + extra_include_path?: string[]; + }; +} + +// Backend format for git-sync settings +export interface BackendGitSyncSettings { + include_path: string[]; + include_type: string[]; + exclude_path?: string[]; + extra_include_path?: string[]; +} + +// Constants for git-sync fields +export const GIT_SYNC_FIELDS = [ + "includes", + "excludes", + "extraIncludes", + "skipScripts", + "skipFlows", + "skipApps", + "skipFolders", + "skipVariables", + "skipResources", + "skipResourceTypes", + "skipSecrets", + "includeSchedules", + "includeTriggers", + "includeUsers", + "includeGroups", + "includeSettings", + "includeKey", +] as const; + +export type GitSyncField = typeof GIT_SYNC_FIELDS[number]; + +// Type mappings for backend include_type values +export const INCLUDE_TYPE_MAPPINGS = { + script: "skipScripts", + flow: "skipFlows", + app: "skipApps", + folder: "skipFolders", + variable: "skipVariables", + resource: "skipResources", + resourcetype: "skipResourceTypes", + secret: "skipSecrets", + schedule: "includeSchedules", + trigger: "includeTriggers", + user: "includeUsers", + group: "includeGroups", + settings: "includeSettings", + key: "includeKey", +} as const; + +// Write mode for branch-based configuration +export type WriteMode = "override" | "replace"; \ No newline at end of file diff --git a/cli/src/commands/gitsync-settings/utils.ts b/cli/src/commands/gitsync-settings/utils.ts new file mode 100644 index 0000000000..47db2c8dfb --- /dev/null +++ b/cli/src/commands/gitsync-settings/utils.ts @@ -0,0 +1,188 @@ +import { colors, log } from "../../../deps.ts"; +import { deepEqual, selectRepository } from "../../utils/utils.ts"; +import { SyncOptions, getEffectiveSettings, DEFAULT_SYNC_OPTIONS } from "../../core/conf.ts"; +import { GitSyncRepository, GIT_SYNC_FIELDS } from "./types.ts"; +import { GitSyncSettingsConverter } from "./converter.ts"; + +// Helper for consistent output handling between JSON and regular modes +export function outputResult(opts: { jsonOutput?: boolean }, result: { + success: boolean; + message?: string; + error?: string; + [key: string]: any; +}): void { + if (opts.jsonOutput) { + console.log(JSON.stringify(result)); + } else if (result.success && result.message) { + log.info(colors.green(result.message)); + } else if (!result.success && result.error) { + log.error(colors.red(result.error)); + } +} + +// Helper to normalize repository path by removing $res: prefix +export function normalizeRepoPath(path: string): string { + return path.replace(/^\$res:/, ""); +} + +// Helper to get or create branch configuration +export function getOrCreateBranchConfig(config: SyncOptions, branchName: string): { + config: SyncOptions; + branchKey: string; +} { + if (!config.git_branches) { + config.git_branches = {}; + } + + if (!config.git_branches[branchName]) { + config.git_branches[branchName] = {}; + } + + return { + config, + branchKey: branchName + }; +} + +// Helper to apply backend settings to branch configuration +export function applyBackendSettingsToBranch( + config: SyncOptions, + branchName: string, + backendSettings: SyncOptions +): SyncOptions { + const { config: updatedConfig } = getOrCreateBranchConfig(config, branchName); + + // Get the base settings (top-level + defaults) to compare against + const { git_branches, ...topLevelSettings } = config; + const baseSettings: Partial = { ...DEFAULT_SYNC_OPTIONS, ...topLevelSettings }; + + // Only store fields that differ from the base settings + Object.keys(backendSettings).forEach(key => { + if (key !== 'git_branches' && backendSettings[key as keyof SyncOptions] !== undefined) { + const backendValue = backendSettings[key as keyof SyncOptions]; + const baseValue = baseSettings[key as keyof SyncOptions]; + + // Only store if different from base + const isDifferent = GitSyncSettingsConverter.isDifferent(backendValue, baseValue); + + if (isDifferent) { + if (!updatedConfig.git_branches![branchName].overrides) { + updatedConfig.git_branches![branchName].overrides = {}; + } + (updatedConfig.git_branches![branchName].overrides as any)[key] = backendValue; + } + } + }); + + return updatedConfig; +} + +// Select repository interactively if multiple exist +export async function selectAndLogRepository( + repositories: GitSyncRepository[], + repository?: string, + suppressLogs?: boolean, +): Promise { + let selectedRepo: GitSyncRepository; + + if (repository) { + const found = repositories.find( + (r: GitSyncRepository) => + r.git_repo_resource_path === repository || + r.git_repo_resource_path === `$res:${repository}`, + ); + if (!found) { + throw new Error(`Repository ${repository} not found`); + } + selectedRepo = found; + const repoPath = selectedRepo.git_repo_resource_path.replace(/^\$res:/, ""); + if (!suppressLogs) { + log.info(colors.cyan(`Using repository: ${colors.bold(repoPath)}`)); + } + } else { + selectedRepo = await selectRepository(repositories); + } + + return selectedRepo; +} + +// Generate structured diff showing field changes +export function generateStructuredDiff( + current: any, + backend: any, +): { [key: string]: { from: any; to: any } } { + const diff: { [key: string]: { from: any; to: any } } = {}; + + // Get all unique keys from both objects + const allKeys = new Set([...Object.keys(current), ...Object.keys(backend)]); + + for (const key of allKeys) { + const currentValue = current[key]; + const backendValue = backend[key]; + + if (!deepEqual(currentValue, backendValue)) { + diff[key] = { + from: currentValue, + to: backendValue, + }; + } + } + + return diff; +} + +// Helper to generate changes between two SyncOptions objects (normalizes automatically) +export function generateChanges( + current: SyncOptions, + new_: SyncOptions, +): { [key: string]: { from: any; to: any } } { + const changes: { [key: string]: { from: any; to: any } } = {}; + + // Normalize both inputs for consistent comparison + const normalizedCurrent = GitSyncSettingsConverter.normalize(current); + const normalizedNew = GitSyncSettingsConverter.normalize(new_); + + for (const field of GIT_SYNC_FIELDS) { + const currentValue = (normalizedCurrent as any)[field]; + const newValue = (normalizedNew as any)[field]; + if (!deepEqual(currentValue, newValue)) { + changes[field] = { + from: currentValue, + to: newValue, + }; + } + } + + return changes; +} + +// Helper to display changes in human-readable format +export function displayChanges( + changes: { [key: string]: { from: any; to: any } }, +): void { + for (const [field, change] of Object.entries(changes)) { + if ( + Array.isArray(change.from) || + Array.isArray(change.to) + ) { + console.log(colors.yellow(` ${field}:`)); + console.log( + colors.red( + ` - ${JSON.stringify(change.from)}`, + ), + ); + console.log( + colors.green( + ` + ${JSON.stringify(change.to)}`, + ), + ); + } else { + console.log( + colors.yellow(` ${field}: `) + + colors.red(`${change.from}`) + + " → " + + colors.green(`${change.to}`), + ); + } + } +} diff --git a/cli/src/commands/init/init.ts b/cli/src/commands/init/init.ts index fc612e7922..c150c4f4fc 100644 --- a/cli/src/commands/init/init.ts +++ b/cli/src/commands/init/init.ts @@ -3,6 +3,7 @@ import { Command, log, yamlStringify, + Confirm, } from "../../../deps.ts"; import { GlobalOptions } from "../../types.ts"; import { readLockfile } from "../../utils/metadata.ts"; @@ -19,6 +20,7 @@ export interface InitOptions { token?: string; baseUrl?: string; configDir?: string; + bindProfile?: boolean; } /** @@ -32,22 +34,24 @@ async function initAction(opts: InitOptions) { const { DEFAULT_SYNC_OPTIONS } = await import("../../core/conf.ts"); // Create initial config with defaults - const initialConfig = { - defaultTs: DEFAULT_SYNC_OPTIONS.defaultTs, - includes: DEFAULT_SYNC_OPTIONS.includes, - excludes: DEFAULT_SYNC_OPTIONS.excludes, - codebases: DEFAULT_SYNC_OPTIONS.codebases, - skipVariables: DEFAULT_SYNC_OPTIONS.skipVariables, - skipResources: DEFAULT_SYNC_OPTIONS.skipResources, - skipSecrets: DEFAULT_SYNC_OPTIONS.skipSecrets, - skipScripts: DEFAULT_SYNC_OPTIONS.skipScripts, - skipFlows: DEFAULT_SYNC_OPTIONS.skipFlows, - skipApps: DEFAULT_SYNC_OPTIONS.skipApps, - skipFolders: DEFAULT_SYNC_OPTIONS.skipFolders, - includeSchedules: DEFAULT_SYNC_OPTIONS.includeSchedules, - includeTriggers: DEFAULT_SYNC_OPTIONS.includeTriggers, - overrides: {}, - }; + const initialConfig = { ...DEFAULT_SYNC_OPTIONS } as any; + + // Add branch structure + const { isGitRepository, getCurrentGitBranch } = await import( + "../../utils/git.ts" + ); + if (isGitRepository()) { + const currentBranch = getCurrentGitBranch(); + if (currentBranch) { + initialConfig.git_branches = { + [currentBranch]: { overrides: {} }, + }; + } else { + initialConfig.git_branches = {}; + } + } else { + initialConfig.git_branches = {}; + } await Deno.writeTextFile("wmill.yaml", yamlStringify(initialConfig)); log.info(colors.green("wmill.yaml created with default settings")); @@ -55,6 +59,89 @@ async function initAction(opts: InitOptions) { // Create lock file await readLockfile(); + // Offer to bind workspace profile to current branch + if (isGitRepository()) { + const { getActiveWorkspace } = await import("../workspace/workspace.ts"); + const activeWorkspace = await getActiveWorkspace( + opts as GlobalOptions + ); + const currentBranch = getCurrentGitBranch(); + + if (activeWorkspace && currentBranch) { + // Determine binding behavior based on flags + const shouldBind = opts.bindProfile === true; + const shouldPrompt = + opts.bindProfile === undefined && + Deno.stdin.isTerminal() && + !opts.useDefault; + const shouldSkip = + opts.bindProfile === false || + opts.useDefault || + (!Deno.stdin.isTerminal() && opts.bindProfile === undefined); + + if (shouldSkip) { + return; + } + + // Show workspace info if we're binding or prompting + if (shouldBind || shouldPrompt) { + log.info( + colors.yellow( + `\nCurrent Git branch: ${colors.bold(currentBranch)}` + ) + ); + log.info( + colors.yellow( + `Active workspace profile: ${colors.bold( + activeWorkspace.name + )}` + ) + ); + log.info( + colors.yellow( + ` ${activeWorkspace.workspaceId} on ${activeWorkspace.remote}` + ) + ); + } + + if ( + shouldBind || + (shouldPrompt && + (await Confirm.prompt({ + message: "Bind workspace profile to current Git branch?", + default: true, + }))) + ) { + // Update the config with workspace binding + const currentConfig = await import("../../core/conf.ts").then((m) => + m.readConfigFile() + ); + if (!currentConfig.git_branches) { + currentConfig.git_branches = {}; + } + if (!currentConfig.git_branches[currentBranch]) { + currentConfig.git_branches[currentBranch] = { overrides: {} }; + } + + currentConfig.git_branches[currentBranch].baseUrl = + activeWorkspace.remote; + currentConfig.git_branches[currentBranch].workspaceId = + activeWorkspace.workspaceId; + + await Deno.writeTextFile( + "wmill.yaml", + yamlStringify(currentConfig) + ); + + log.info( + colors.green( + `✓ Bound branch '${currentBranch}' to workspace '${activeWorkspace.name}'` + ) + ); + } + } + } + // Check for backend git-sync settings unless --use-default is specified if (!opts.useDefault) { try { @@ -153,10 +240,8 @@ async function initAction(opts: InitOptions) { } } catch (error) { // If there's an error checking backend settings, just continue with defaults - const errorMessage = - error instanceof Error ? error.message : String(error); log.warn( - `Could not check backend for git-sync settings: ${errorMessage}` + `Could not check backend for git-sync settings: ${error.message}` ); log.info("Continuing with default settings"); } @@ -223,6 +308,11 @@ const command = new Command() "--repository ", "Specify repository path (e.g., u/user/repo) when using backend settings" ) + .option( + "--bind-profile", + "Automatically bind active workspace profile to current Git branch" + ) + .option("--no-bind-profile", "Skip workspace profile binding prompt") .action(initAction as any); -export default command; \ No newline at end of file +export default command; diff --git a/cli/src/commands/script/script.ts b/cli/src/commands/script/script.ts index 2becb662e4..a2240498ab 100644 --- a/cli/src/commands/script/script.ts +++ b/cli/src/commands/script/script.ts @@ -578,7 +578,9 @@ export function filePathExtensionFromContentType( return ".nu"; } else if (language === "java") { return ".java"; - // for related places search: ADD_NEW_LANG + } else if (language === "ruby") { + return ".rb"; + // for related places search: ADD_NEW_LANG } else { throw new Error("Invalid language: " + language); } @@ -608,7 +610,8 @@ export const exts = [ ".nu", ".playbook.yml", ".java", - // for related places search: ADD_NEW_LANG + ".rb" + // for related places search: ADD_NEW_LANG ]; export function removeExtensionToPath(path: string): string { diff --git a/cli/src/commands/sync/sync.ts b/cli/src/commands/sync/sync.ts index 32d387cf76..c17861a497 100644 --- a/cli/src/commands/sync/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -37,7 +37,12 @@ import { import { handleFile } from "../script/script.ts"; import { deepEqual, isFileResource } from "../../utils/utils.ts"; -import { SyncOptions, readConfigFile, getEffectiveSettings } from "../../core/conf.ts"; +import { + SyncOptions, + readConfigFile, + getEffectiveSettings, + validateBranchConfiguration, +} from "../../core/conf.ts"; import { Workspace } from "../workspace/workspace.ts"; import { removePathPrefix } from "../../types.ts"; import { SyncCodebase, listSyncCodebases } from "../../utils/codebase.ts"; @@ -59,98 +64,13 @@ function mergeCliWithEffectiveOptions< return Object.assign({}, effectiveOpts, cliOpts) as T; } -// Resolve effective sync options with smart repository detection +// Resolve effective sync options using branch-based configuration async function resolveEffectiveSyncOptions( workspace: Workspace, - repositoryPath?: string + promotion?: string ): Promise { const localConfig = await readConfigFile(); - - // If repository path is already specified, use it directly - if (repositoryPath) { - return getEffectiveSettings( - localConfig, - workspace.remote, - workspace.workspaceId, - repositoryPath - ); - } - - // Auto-detect repository from overrides if not specified - if (localConfig.overrides) { - const prefix = `${workspace.remote}:${workspace.workspaceId}:`; - const applicableRepos: string[] = []; - - // Find all repository-specific overrides for this workspace - for (const key of Object.keys(localConfig.overrides)) { - if (key.startsWith(prefix) && !key.endsWith(":*")) { - const repo = key.substring(prefix.length); - if (repo) { - applicableRepos.push(repo); - } - } - } - - if (applicableRepos.length === 1) { - // Single repository found - auto-select it - log.info(`Auto-selected repository: ${applicableRepos[0]}`); - return getEffectiveSettings( - localConfig, - workspace.remote, - workspace.workspaceId, - applicableRepos[0] - ); - } else if (applicableRepos.length > 1) { - // Multiple repositories found - prompt for selection - const isInteractive = Deno.stdin.isTerminal() && Deno.stdout.isTerminal(); - - if (isInteractive) { - const choices = [ - { - name: "Use top-level settings (no repository-specific override)", - value: "", - }, - ...applicableRepos.map((repo) => ({ name: repo, value: repo })), - ]; - - const selectedRepo = await Select.prompt({ - message: "Multiple repository overrides found. Select which to use:", - options: choices, - }); - - if (selectedRepo) { - log.info(`Selected repository: ${selectedRepo}`); - } - - return getEffectiveSettings( - localConfig, - workspace.remote, - workspace.workspaceId, - selectedRepo - ); - } else { - // Non-interactive mode - list options and use top-level - log.warn( - `Multiple repository overrides found: ${applicableRepos.join(", ")}` - ); - log.warn( - `Running in non-interactive mode. Use --repository flag to specify which one to use.` - ); - log.info( - `Falling back to top-level settings (no repository-specific overrides applied)` - ); - } - } - } - - // No repository overrides found or selected - use top-level settings - log.info(`No repository overrides found, using top-level settings`); - return getEffectiveSettings( - localConfig, - workspace.remote, - workspace.workspaceId, - "" - ); + return await getEffectiveSettings(localConfig, promotion); } type DynFSElement = { @@ -222,7 +142,13 @@ async function addCodebaseDigestIfRelevant( if (isTs) { const c = findCodebase(replacedPath, codebases); if (c) { - const parsed: any = yamlParseContent(path, content); + let parsed: any; + try { + parsed = yamlParseContent(path, content); + } catch (error) { + log.error(`Failed to parse YAML content for codebase digest at path: ${path}`); + throw error; + } if (parsed && typeof parsed == "object") { if (ignoreCodebaseChanges) { parsed["codebase"] = undefined; @@ -398,13 +324,25 @@ function ZipFSElement( path: finalPath, async *getChildren(): AsyncIterable { if (kind == "flow") { - const flow: OpenFlow = JSON.parse(await f.async("text")); - const inlineScripts = extractInlineScriptsForFlows( - flow.value.modules, - {}, - SEP, - defaultTs, - ); + let flow: OpenFlow; + try { + flow = JSON.parse(await f.async("text")); + } catch (error) { + log.error(`Failed to parse flow.yaml at path: ${p}`); + throw error; + } + let inlineScripts; + try { + inlineScripts = extractInlineScriptsForFlows( + flow.value.modules, + {}, + SEP, + defaultTs, + ); + } catch (error) { + log.error(`Failed to extract inline scripts for flow at path: ${p}`); + throw error; + } for (const s of inlineScripts) { yield { isDirectory: false, @@ -427,8 +365,20 @@ function ZipFSElement( }, }; } else if (kind == "app") { - const app = JSON.parse(await f.async("text")); - const inlineScripts = extractInlineScriptsForApps(app?.["value"], newPathAssigner(defaultTs)); + let app; + try { + app = JSON.parse(await f.async("text")); + } catch (error) { + log.error(`Failed to parse app.yaml at path: ${p}`); + throw error; + } + let inlineScripts; + try { + inlineScripts = extractInlineScriptsForApps(app?.["value"], newPathAssigner(defaultTs)); + } catch (error) { + log.error(`Failed to extract inline scripts for app at path: ${p}`); + throw error; + } for (const s of inlineScripts) { yield { isDirectory: false, @@ -457,7 +407,13 @@ function ZipFSElement( const content = await f.async("text"); if (kind == "script") { - const parsed = JSON.parse(content); + let parsed; + try { + parsed = JSON.parse(content); + } catch (error) { + log.error(`Failed to parse script.yaml at path: ${p}`); + throw error; + } if ( parsed["lock"] && parsed["lock"] != "" && @@ -482,7 +438,13 @@ function ZipFSElement( if (kind == "resource") { const content = await f.async("text"); - const parsed = JSON.parse(content); + let parsed; + try { + parsed = JSON.parse(content); + } catch (error) { + log.error(`Failed to parse resource.yaml at path: ${p}`); + throw error; + } const formatExtension = resourceTypeToFormatExtension[parsed["resource_type"]]; @@ -499,14 +461,27 @@ function ZipFSElement( } return useYaml && isJson - ? yamlStringify(JSON.parse(content), yamlOptions) + ? (() => { + try { + return yamlStringify(JSON.parse(content), yamlOptions); + } catch (error) { + log.error(`Failed to parse JSON content at path: ${p}`); + throw error; + } + })() : content; }, }, ]; if (kind == "script") { const content = await f.async("text"); - const parsed = JSON.parse(content); + let parsed; + try { + parsed = JSON.parse(content); + } catch (error) { + log.error(`Failed to parse script lock content at path: ${p}`); + throw error; + } const lock = parsed["lock"]; if (lock && lock != "") { r.push({ @@ -522,7 +497,13 @@ function ZipFSElement( } if (kind == "resource") { const content = await f.async("text"); - const parsed = JSON.parse(content); + let parsed; + try { + parsed = JSON.parse(content); + } catch (error) { + log.error(`Failed to parse resource file content at path: ${p}`); + throw error; + } const formatExtension = resourceTypeToFormatExtension[parsed["resource_type"]]; @@ -713,7 +694,8 @@ export async function elementsToMap( "yml", "nu", "java", - // for related places search: ADD_NEW_LANG + "rb", + // for related places search: ADD_NEW_LANG ].includes(path.split(".").pop() ?? "") && !isFileResource(path) ) @@ -724,9 +706,19 @@ export async function elementsToMap( try { let o; if (json) { - o = JSON.parse(content); + try { + o = JSON.parse(content); + } catch (error) { + log.error(`Failed to parse JSON variable content at path: ${path}`); + throw error; + } } else { - o = yamlParseContent(path, content); + try { + o = yamlParseContent(path, content); + } catch (error) { + log.error(`Failed to parse YAML variable content at path: ${path}`); + throw error; + } } if (o["is_secret"]) { continue; @@ -779,7 +771,13 @@ async function compareDynFSElement( function parseYaml(k: string, v: string) { if (k.endsWith(".script.yaml")) { - const o: any = yamlParseContent(k, v); + let o: any; + try { + o = yamlParseContent(k, v); + } catch (error) { + log.error(`Failed to parse script YAML content at path: ${k}`); + throw error; + } if (typeof o == "object") { if (Array.isArray(o?.["lock"])) { o["lock"] = o["lock"].join("\n"); @@ -790,7 +788,13 @@ async function compareDynFSElement( } return o; } else if (k.endsWith(".app.yaml")) { - const o: any = yamlParseContent(k, v); + let o: any; + try { + o = yamlParseContent(k, v); + } catch (error) { + log.error(`Failed to parse app YAML content at path: ${k}`); + throw error; + } const o2 = o["policy"]; if (typeof o2 == "object") { @@ -803,7 +807,12 @@ async function compareDynFSElement( } return o; } else { - return yamlParseContent(k, v); + try { + return yamlParseContent(k, v); + } catch (error) { + log.error(`Failed to parse YAML content at path: ${k}`); + throw error; + } } } @@ -823,7 +832,20 @@ async function compareDynFSElement( if (m2[k] == v) { continue; } else if (k.endsWith(".json")) { - if (deepEqual(JSON.parse(v), JSON.parse(m2[k]))) { + let parsedV, parsedM2; + try { + parsedV = JSON.parse(v); + } catch (error) { + log.error(`Failed to parse new JSON content for comparison at path: ${k}`); + throw error; + } + try { + parsedM2 = JSON.parse(m2[k]); + } catch (error) { + log.error(`Failed to parse existing JSON content for comparison at path: ${k}`); + throw error; + } + if (deepEqual(parsedV, parsedM2)) { continue; } } else if (k.endsWith(".yaml")) { @@ -1138,8 +1160,20 @@ async function buildTracker(changes: Change[]) { } export async function pull( - opts: GlobalOptions & SyncOptions & { repository?: string } + opts: GlobalOptions & + SyncOptions & { repository?: string; promotion?: string } ) { + // Validate branch configuration early + try { + await validateBranchConfiguration(false, opts.yes); + } catch (error) { + if (error instanceof Error && error.message.includes("overrides")) { + log.error(error.message); + Deno.exit(1); + } + throw error; + } + if (opts.stateful) { await ensureDir(path.join(Deno.cwd(), ".wmill")); } @@ -1147,10 +1181,10 @@ export async function pull( const workspace = await resolveWorkspace(opts); await requireLogin(opts); - // Resolve effective sync options with repository awareness + // Resolve effective sync options with branch awareness const effectiveOpts = await resolveEffectiveSyncOptions( workspace, - opts.repository + opts.promotion ); // Merge CLI flags with resolved settings (CLI flags take precedence only for explicit overrides) @@ -1465,13 +1499,24 @@ function removeSuffix(str: string, suffix: string) { export async function push( opts: GlobalOptions & SyncOptions & { repository?: string } ) { + // Validate branch configuration early + try { + await validateBranchConfiguration(false, opts.yes); + } catch (error) { + if (error instanceof Error && error.message.includes("overrides")) { + log.error(error.message); + Deno.exit(1); + } + throw error; + } + const workspace = await resolveWorkspace(opts); await requireLogin(opts); - // Resolve effective sync options with repository awareness + // Resolve effective sync options with branch awareness const effectiveOpts = await resolveEffectiveSyncOptions( workspace, - opts.repository + opts.promotion ); // Merge CLI flags with resolved settings (CLI flags take precedence only for explicit overrides) @@ -2074,6 +2119,10 @@ const command = new Command() "--repository ", "Specify repository path (e.g., u/user/repo) when multiple repositories exist" ) + .option( + "--promotion ", + "Use promotionOverrides from the specified branch instead of regular overrides" + ) // deno-lint-ignore no-explicit-any .action(pull as any) .command("push") diff --git a/cli/src/commands/workspace/workspace.ts b/cli/src/commands/workspace/workspace.ts index b4bab0866c..fbf93aed76 100644 --- a/cli/src/commands/workspace/workspace.ts +++ b/cli/src/commands/workspace/workspace.ts @@ -298,42 +298,6 @@ export async function addWorkspace(workspace: Workspace, opts: any) { } } - // Check 2: Same (remote, workspaceId) tuple already exists under different name - const tupleConflict = existingWorkspaces.find(w => - w.remote === workspace.remote && - w.workspaceId === workspace.workspaceId && - w.name !== workspace.name - ); - - if (tupleConflict) { - log.info(colors.red.bold(`❌ Workspace ${workspace.workspaceId} on ${workspace.remote} already exists!`)); - log.info(` Existing name: "${tupleConflict.name}"`); - log.info(` New name: "${workspace.name}"`); - log.info(colors.yellow(`\nNote: Backend constraint prevents duplicate (remote, workspaceId) combinations.`)); - - if (!isInteractive) { - // In non-interactive mode (tests, scripts), auto-overwrite with force flag - if (opts.force) { - log.info(colors.yellow(`Force flag enabled, overwriting existing workspace "${tupleConflict.name}".`)); - } else { - throw new Error(`Backend constraint violation: (${workspace.remote}, ${workspace.workspaceId}) already exists as "${tupleConflict.name}". Use --force to overwrite.`); - } - } else { - const overwrite = await Confirm.prompt({ - message: `Do you want to overwrite the existing workspace "${tupleConflict.name}"?`, - default: false, - }); - - if (!overwrite) { - log.info(colors.yellow("Operation cancelled.")); - return; - } - } - - // Remove the conflicting workspace - await removeWorkspace(tupleConflict.name, true, opts); - } - // Remove existing workspace with same name (if updating) await removeWorkspace(workspace.name, true, opts); @@ -395,7 +359,74 @@ async function whoami(_opts: GlobalOptions) { log.info("Active: " + colors.green.bold(activeName || "none")); } +async function bind( + opts: GlobalOptions & { branch?: string }, + bindWorkspace?: boolean +) { + const { isGitRepository, getCurrentGitBranch } = await import("../../utils/git.ts"); + + if (!isGitRepository()) { + log.error(colors.red("Not in a Git repository")); + return; + } + + const branch = opts.branch || getCurrentGitBranch(); + if (!branch) { + log.error(colors.red("Could not determine current Git branch")); + return; + } + + const { readConfigFile } = await import("../../core/conf.ts"); + const config = await readConfigFile(); + + const activeWorkspace = await getActiveWorkspace(opts); + if (!activeWorkspace && bindWorkspace) { + log.error(colors.red("No active workspace. Use 'wmill workspace add' or 'wmill workspace switch' first")); + return; + } + + // For unbind, check if branch exists + if (!bindWorkspace && (!config.git_branches || !config.git_branches[branch])) { + log.error(colors.red(`Branch '${branch}' not found in wmill.yaml git_branches`)); + return; + } + + // Update the branch configuration with workspace binding + if (!config.git_branches) { + config.git_branches = {}; + } + if (!config.git_branches[branch]) { + config.git_branches[branch] = { overrides: {} }; + } + + if (bindWorkspace && activeWorkspace) { + config.git_branches[branch].baseUrl = activeWorkspace.remote; + config.git_branches[branch].workspaceId = activeWorkspace.workspaceId; + + log.info(colors.green( + `✓ Bound branch '${branch}' to workspace '${activeWorkspace.name}'\n` + + ` ${activeWorkspace.workspaceId} on ${activeWorkspace.remote}` + )); + } else { + // Unbind + delete config.git_branches[branch].baseUrl; + delete config.git_branches[branch].workspaceId; + + log.info(colors.green(`✓ Removed workspace binding from branch '${branch}'`)); + } + + // Write back the updated config + const { yamlStringify } = await import("../../../deps.ts"); + try { + await Deno.writeTextFile("wmill.yaml", yamlStringify(config)); + } catch (error) { + log.error(colors.red(`Failed to save configuration: ${error.message}`)); + return; + } +} + const command = new Command() + .alias("profile") .description("workspace related commands") .action(list as any) .command("switch") @@ -425,6 +456,14 @@ const command = new Command() .action(remove as any) .command("whoami") .description("Show the currently active user") - .action(whoami as any); + .action(whoami as any) + .command("bind") + .description("Bind the current Git branch to the active workspace") + .option("--branch ", "Specify branch (defaults to current)") + .action((opts) => bind(opts as any, true)) + .command("unbind") + .description("Remove workspace binding from the current Git branch") + .option("--branch ", "Specify branch (defaults to current)") + .action((opts) => bind(opts as any, false)); export default command; diff --git a/cli/src/core/branch-profiles.ts b/cli/src/core/branch-profiles.ts new file mode 100644 index 0000000000..006b7fed18 --- /dev/null +++ b/cli/src/core/branch-profiles.ts @@ -0,0 +1,74 @@ +import { log } from "../../deps.ts"; +import { getStore } from "./store.ts"; + +export interface BranchProfileMapping { + lastUsed: { + [key: string]: string; // key format: "branch|baseUrl|workspaceId" -> profile name + }; +} + +const BRANCH_PROFILES_FILE = "branch-profiles.json"; + +export async function getBranchProfilesPath(configDirOverride?: string): Promise { + return (await getStore("", configDirOverride)) + BRANCH_PROFILES_FILE; +} + +export async function loadBranchProfiles(configDirOverride?: string): Promise { + try { + const path = await getBranchProfilesPath(configDirOverride); + const content = await Deno.readTextFile(path); + return JSON.parse(content); + } catch { + // File doesn't exist or invalid JSON - return empty mapping + return { lastUsed: {} }; + } +} + +export async function saveBranchProfiles( + mapping: BranchProfileMapping, + configDirOverride?: string +): Promise { + const path = await getBranchProfilesPath(configDirOverride); + await Deno.writeTextFile(path, JSON.stringify(mapping, null, 2)); +} + +export function getBranchProfileKey( + branch: string, + baseUrl: string, + workspaceId: string +): string { + // Normalize baseUrl to ensure consistency + let normalizedUrl: string; + try { + normalizedUrl = new URL(baseUrl).toString(); + } catch { + // Fallback to non-normalized URL if parsing fails + normalizedUrl = baseUrl; + } + return `${branch}|${normalizedUrl}|${workspaceId}`; +} + +export async function getLastUsedProfile( + branch: string, + baseUrl: string, + workspaceId: string, + configDirOverride?: string +): Promise { + const mapping = await loadBranchProfiles(configDirOverride); + const key = getBranchProfileKey(branch, baseUrl, workspaceId); + return mapping.lastUsed[key]; +} + +export async function setLastUsedProfile( + branch: string, + baseUrl: string, + workspaceId: string, + profileName: string, + configDirOverride?: string +): Promise { + const mapping = await loadBranchProfiles(configDirOverride); + const key = getBranchProfileKey(branch, baseUrl, workspaceId); + mapping.lastUsed[key] = profileName; + await saveBranchProfiles(mapping, configDirOverride); + log.debug(`Saved last used profile for ${key}: ${profileName}`); +} diff --git a/cli/src/core/conf.ts b/cli/src/core/conf.ts index efbbb56f17..23f2fd2820 100644 --- a/cli/src/core/conf.ts +++ b/cli/src/core/conf.ts @@ -1,4 +1,5 @@ -import { log, yamlParseFile } from "../../deps.ts"; +import { log, yamlParseFile, Confirm, yamlStringify } from "../../deps.ts"; +import { getCurrentGitBranch, isGitRepository } from "../utils/git.ts"; export let showDiffs = false; export function setShowDiffs(value: boolean) { @@ -36,7 +37,15 @@ export interface SyncOptions { codebases?: Codebase[]; parallel?: number; jsonOutput?: boolean; - overrides?: { [key: string]: Partial }; + git_branches?: { + [branchName: string]: SyncOptions & { + overrides?: Partial; + promotionOverrides?: Partial; + baseUrl?: string; + workspaceId?: string; + } + }; + promotion?: string; } export interface Codebase { @@ -56,6 +65,31 @@ export interface Codebase { export async function readConfigFile(): Promise { try { const conf = (await yamlParseFile("wmill.yaml")) as SyncOptions; + + // Handle obsolete overrides format + if (conf && 'overrides' in conf) { + const overrides = conf.overrides as any; + const hasSettings = overrides && typeof overrides === 'object' && Object.keys(overrides).length > 0; + + if (hasSettings) { + throw new Error( + "❌ The 'overrides' field is no longer supported.\n" + + " The configuration system now uses Git branch-based configuration only.\n" + + " Please delete your wmill.yaml and run 'wmill init' to recreate it with the new format." + ); + } else { + // Remove empty overrides with a note + log.info("ℹ️ Removing empty 'overrides: {}' from wmill.yaml (migrated to git_branches format)"); + delete conf.overrides; + // Write the updated config back to file + try { + await Deno.writeTextFile("wmill.yaml", yamlStringify(conf)); + } catch (error) { + log.warn(`Could not update wmill.yaml to remove empty overrides: ${error instanceof Error ? error.message : error}`); + } + } + } + if (conf?.defaultTs == undefined) { log.warn( "No defaultTs defined in your wmill.yaml. Using 'bun' as default." @@ -63,6 +97,9 @@ export async function readConfigFile(): Promise { } return typeof conf == "object" ? conf : ({} as SyncOptions); } catch (e) { + if (e instanceof Error && (e.message.includes("overrides") || e.message.includes("Obsolete configuration format"))) { + throw e; // Re-throw the specific obsolete format error + } log.warn( "No wmill.yaml found. Use 'wmill init' to bootstrap it. Using 'bun' as default typescript runtime." ); @@ -104,53 +141,107 @@ export async function mergeConfigWithConfigFile( return Object.assign(configFile ?? {}, opts); } -// Get effective settings by merging top-level settings and overrides -export function getEffectiveSettings( - config: SyncOptions, - baseUrl: string, - workspaceId: string, - repo: string -): SyncOptions { - // Start with empty object - no defaults - let effective = {} as SyncOptions; +// Validate branch configuration early in the process +export async function validateBranchConfiguration(skipValidation?: boolean, autoAccept?: boolean): Promise { + if (skipValidation || !isGitRepository()) { + return; + } - // Merge top-level settings from config (which contains user's chosen defaults) - Object.keys(config).forEach(key => { - if (key !== 'overrides' && config[key as keyof SyncOptions] !== undefined) { - (effective as any)[key] = config[key as keyof SyncOptions]; + const config = await readConfigFile(); + const { git_branches } = config; + const currentBranch = getCurrentGitBranch(); + + // In a git repository, git_branches section is recommended + if (!git_branches || Object.keys(git_branches).length === 0) { + log.warn( + "⚠️ WARNING: In a Git repository, the 'git_branches' section is recommended in wmill.yaml.\n" + + " Consider adding a git_branches section with configuration for your Git branches.\n" + + " Run 'wmill init' to recreate the configuration file with proper branch setup." + ); + return; + } + + // Current branch must be defined in git_branches config + if (currentBranch && !git_branches[currentBranch]) { + // In interactive mode, offer to create the branch + if (Deno.stdin.isTerminal()) { + const availableBranches = Object.keys(git_branches).join(', '); + log.info( + `Current Git branch '${currentBranch}' is not defined in the git_branches configuration.\n` + + `Available branches: ${availableBranches}` + ); + + const shouldCreate = autoAccept || await Confirm.prompt({ + message: `Create empty branch configuration for '${currentBranch}'?`, + default: true, + }); + + if (shouldCreate) { + // Read current config, add branch, and write it back + const currentConfig = await readConfigFile(); + + if (!currentConfig.git_branches) { + currentConfig.git_branches = {}; + } + currentConfig.git_branches[currentBranch] = { overrides: {} }; + + await Deno.writeTextFile("wmill.yaml", yamlStringify(currentConfig)); + + log.info(`✅ Created empty branch configuration for '${currentBranch}'`); + } else { + log.warn("⚠️ WARNING: Branch creation cancelled. You can manually add the branch to wmill.yaml or use 'wmill gitsync-settings pull' to pull configuration from an existing windmill workspace git-sync configuration."); + return; + } + } else { + log.warn( + `⚠️ WARNING: Current Git branch '${currentBranch}' is not defined in the git_branches configuration.\n` + + ` Consider adding configuration for branch '${currentBranch}' in the git_branches section of wmill.yaml.\n` + + ` Available branches: ${Object.keys(git_branches).join(', ')}` + ); + return; } - }); + } +} - if (!config.overrides) { - if (repo) { - log.info(`No overrides found in wmill.yaml, using top-level settings (repository flag ignored)`); +// Get effective settings by merging top-level settings with branch-specific overrides +export async function getEffectiveSettings(config: SyncOptions, promotion?: string, skipBranchValidation?: boolean, suppressLogs?: boolean): Promise { + // Start with top-level settings from config + const { git_branches, ...topLevelSettings } = config; + let effective = { ...topLevelSettings }; + + if (isGitRepository()) { + const currentBranch = getCurrentGitBranch(); + + // If promotion is specified, use that branch's promotionOverrides or overrides + if (promotion && git_branches && git_branches[promotion]) { + const targetBranch = git_branches[promotion]; + + // First try promotionOverrides, then fall back to overrides + if (targetBranch.promotionOverrides) { + Object.assign(effective, targetBranch.promotionOverrides); + if (!suppressLogs) { + log.info(`Applied promotion settings from branch: ${promotion}`); + } + } else if (targetBranch.overrides) { + Object.assign(effective, targetBranch.overrides); + if (!suppressLogs) { + log.info(`Applied settings from branch: ${promotion} (no promotionOverrides found)`); + } + } else { + log.debug(`No promotion or regular overrides found for branch '${promotion}', using top-level settings`); + } } - return effective; - } - - // Construct override keys using the single format - const workspaceKey = `${baseUrl}:${workspaceId}:*`; - const repoKey = `${baseUrl}:${workspaceId}:${repo}`; - - let appliedOverrides: string[] = []; - - // Apply workspace-level overrides - if (config.overrides[workspaceKey]) { - Object.assign(effective, config.overrides[workspaceKey]); - appliedOverrides.push("workspace-level"); - } - - // Apply repository-specific overrides (overrides workspace-level) - if (config.overrides[repoKey]) { - Object.assign(effective, config.overrides[repoKey]); - appliedOverrides.push("repository-specific"); - } else if (repo) { - // Repository was specified but no override found - log.info(`Repository override not found for "${repo}", using ${appliedOverrides.length > 0 ? appliedOverrides.join(" + ") : "top-level"} settings`); - } - - if (appliedOverrides.length > 0) { - log.info(`Applied ${appliedOverrides.join(" + ")} overrides${repo ? ` for repository "${repo}"` : ""}`); + // Otherwise use current branch overrides (existing behavior) + else if (currentBranch && git_branches && git_branches[currentBranch] && git_branches[currentBranch].overrides) { + Object.assign(effective, git_branches[currentBranch].overrides); + if (!suppressLogs) { + log.info(`Applied settings for Git branch: ${currentBranch}`); + } + } else if (currentBranch) { + log.debug(`No branch-specific overrides found for '${currentBranch}', using top-level settings`); + } + } else { + log.debug("Not in a Git repository, using top-level settings"); } return effective; diff --git a/cli/src/core/context.ts b/cli/src/core/context.ts index a305f597ff..628471dcce 100644 --- a/cli/src/core/context.ts +++ b/cli/src/core/context.ts @@ -1,13 +1,86 @@ // deno-lint-ignore-file no-explicit-any -import { colors, log } from "../../deps.ts"; +import { colors, log, Select, Confirm, Input } from "../../deps.ts"; +import { loginInteractive } from "./login.ts"; import { GlobalOptions } from "../types.ts"; import { getHeaders } from "../utils/utils.ts"; + import { getActiveWorkspace, getWorkspaceByName, Workspace, + allWorkspaces, + addWorkspace, } from "../commands/workspace/workspace.ts"; +import { + getLastUsedProfile, + setLastUsedProfile +} from "./branch-profiles.ts"; +import { readConfigFile } from "./conf.ts"; +import { getCurrentGitBranch, isGitRepository } from "../utils/git.ts"; + +// Helper function to select from multiple matching profiles +async function selectFromMultipleProfiles( + profiles: Workspace[], + baseUrl: string, + workspaceId: string, + context: string, + configDir?: string +): Promise { + if (profiles.length === 1) { + return profiles[0]; + } + + // Check for last used profile + const lastUsedProfileName = await getLastUsedProfile("", baseUrl, workspaceId, configDir); + if (lastUsedProfileName) { + const lastUsedProfile = profiles.find(p => p.name === lastUsedProfileName); + if (lastUsedProfile) { + log.info(colors.green(`Using last used profile '${lastUsedProfile.name}' for ${context}`)); + return lastUsedProfile; + } + } + + // No last used or it no longer exists - prompt for selection + if (!Deno.stdin.isTerminal() || !Deno.stdout.isTerminal()) { + const selectedProfile = profiles[0]; + log.info(colors.yellow(`Multiple profiles found for ${context}. Using first available profile: '${selectedProfile.name}'`)); + + // Save selection for next time + await setLastUsedProfile( + "", // No branch context for general workspace selection + baseUrl, + workspaceId, + selectedProfile.name, + configDir + ); + + return selectedProfile; + } + + log.info(colors.yellow(`\nMultiple workspace profiles found for ${context}:`)); + + const selectedName = await Select.prompt({ + message: "Select profile", + options: profiles.map(p => ({ + name: `${p.name} (${p.workspaceId} on ${p.remote})`, + value: p.name, + })), + }); + + const selectedProfile = profiles.find(p => p.name === selectedName)!; + + // Save selection for next time + await setLastUsedProfile( + "", // No branch context for general workspace selection + baseUrl, + workspaceId, + selectedProfile.name, + configDir + ); + + return selectedProfile; +} export type Context = { workspace: string; @@ -47,12 +120,154 @@ async function tryResolveWorkspace( return { isError: false, value: defaultWorkspace }; } +async function tryResolveBranchWorkspace( + opts: GlobalOptions +): Promise { + // Only try branch-based resolution if in a Git repository + if (!isGitRepository()) { + return undefined; + } + + const currentBranch = getCurrentGitBranch(); + if (!currentBranch) { + return undefined; + } + + // Read wmill.yaml to check for branch workspace configuration + const config = await readConfigFile(); + const branchConfig = config.git_branches?.[currentBranch]; + + // Check if branch has workspace configuration + if (!branchConfig?.baseUrl || !branchConfig?.workspaceId) { + return undefined; + } + + const { baseUrl, workspaceId } = branchConfig; + let normalizedBaseUrl: string; + try { + normalizedBaseUrl = new URL(baseUrl).toString(); + } catch (error) { + log.error(colors.red(`Invalid baseUrl in branch configuration: ${baseUrl}`)); + return undefined; + } + + // Find all profiles matching this baseUrl and workspaceId + const allProfiles = await allWorkspaces(opts.configDir); + const matchingProfiles = allProfiles.filter( + w => w.remote === normalizedBaseUrl && w.workspaceId === workspaceId + ); + + if (matchingProfiles.length === 0) { + // No matching profile exists - prompt to create one + log.info(colors.yellow( + `\nNo workspace profile found for branch '${currentBranch}'\n` + + `(${normalizedBaseUrl}, ${workspaceId})` + )); + + if (!Deno.stdin.isTerminal() || !Deno.stdout.isTerminal()) { + log.info("Not a TTY, cannot create profile interactively. Use 'wmill workspace add' first."); + return undefined; + } + + const shouldCreate = await Confirm.prompt({ + message: "Would you like to create a new profile?", + default: true, + }); + + if (!shouldCreate) { + return undefined; + } + + // Prompt for profile details + const profileName = await Input.prompt({ + message: "Profile name", + default: workspaceId, + }); + + const token = await loginInteractive(normalizedBaseUrl); + if (!token) { + log.error("Failed to obtain token"); + return undefined; + } + + // Create the new profile + const newWorkspace: Workspace = { + name: profileName, + remote: normalizedBaseUrl, + workspaceId: workspaceId, + token: token, + }; + + await addWorkspace(newWorkspace, opts); + + // Set as last used for this branch + await setLastUsedProfile(currentBranch, normalizedBaseUrl, workspaceId, profileName, opts.configDir); + + log.info(colors.green(`✓ Created profile '${profileName}' for ${workspaceId} on ${normalizedBaseUrl}`)); + log.info(colors.green(`✓ Profile '${profileName}' is now active`)); + + return newWorkspace; + } + + // Handle multiple profiles - use special branch-aware logic + let selectedProfile: Workspace; + + if (matchingProfiles.length === 1) { + selectedProfile = matchingProfiles[0]; + log.info(colors.green(`Using workspace profile '${selectedProfile.name}' for branch '${currentBranch}'`)); + } else { + // For multiple profiles, check branch-specific last used first + const lastUsedName = await getLastUsedProfile( + currentBranch, + normalizedBaseUrl, + workspaceId, + opts.configDir + ); + + if (lastUsedName) { + const lastUsedProfile = matchingProfiles.find(p => p.name === lastUsedName); + if (lastUsedProfile) { + log.info(colors.green(`Using workspace profile '${lastUsedProfile.name}' for branch '${currentBranch}' (last used)`)); + return lastUsedProfile; + } + } + + // Fall back to general selection logic + selectedProfile = await selectFromMultipleProfiles( + matchingProfiles, + normalizedBaseUrl, + workspaceId, + `branch '${currentBranch}'`, + opts.configDir + ); + + // Save branch-specific selection + await setLastUsedProfile( + currentBranch, + normalizedBaseUrl, + workspaceId, + selectedProfile.name, + opts.configDir + ); + + log.info(colors.green(`Using workspace profile '${selectedProfile.name}' for branch '${currentBranch}'`)); + } + + return selectedProfile; +} + export async function resolveWorkspace( opts: GlobalOptions ): Promise { if (opts.baseUrl) { if (opts.workspace && opts.token) { - const normalizedBaseUrl = new URL(opts.baseUrl).toString(); // add trailing slash if not present + let normalizedBaseUrl: string; + try { + normalizedBaseUrl = new URL(opts.baseUrl).toString(); // add trailing slash if not present + } catch (error) { + log.info(colors.red(`Invalid base URL: ${opts.baseUrl}`)); + return Deno.exit(-1); + } // Try to find existing workspace profile by name, then by workspaceId + remote if (opts.workspace) { @@ -67,9 +282,14 @@ export async function resolveWorkspace( w => w.workspaceId === opts.workspace && w.remote === normalizedBaseUrl ); - // Due to uniqueness constraint, there can only be 0 or 1 match - if (matchingWorkspaces.length === 1) { - existingWorkspace = matchingWorkspaces[0]; + if (matchingWorkspaces.length >= 1) { + existingWorkspace = await selectFromMultipleProfiles( + matchingWorkspaces, + normalizedBaseUrl, + opts.workspace!, + `workspace "${opts.workspace}" on ${normalizedBaseUrl}`, + opts.configDir + ); } } @@ -107,13 +327,22 @@ export async function resolveWorkspace( return Deno.exit(-1); } } + + // Try explicit workspace flag first (should override branch-based resolution) const res = await tryResolveWorkspace(opts); - if (res.isError) { - log.info(colors.red.bold(res.error)); - return Deno.exit(-1); - } else { + if (!res.isError) { return res.value; } + + // Fall back to branch-based resolution if no explicit workspace + const branchWorkspace = await tryResolveBranchWorkspace(opts); + if (branchWorkspace) { + return branchWorkspace; + } + + // If both failed, show the original error from explicit workspace resolution + log.info(colors.red.bold(res.error)); + return Deno.exit(-1); } diff --git a/cli/src/main.ts b/cli/src/main.ts index 894fc32d25..baf95987e6 100644 --- a/cli/src/main.ts +++ b/cli/src/main.ts @@ -8,7 +8,9 @@ import { import flow from "./commands/flow/flow.ts"; import app from "./commands/app/apps.ts"; import script from "./commands/script/script.ts"; -import workspace, { getActiveWorkspace } from "./commands/workspace/workspace.ts"; +import workspace, { + getActiveWorkspace, +} from "./commands/workspace/workspace.ts"; import resource from "./commands/resource/resource.ts"; import resourceType from "./commands/resource-type/resource-type.ts"; import user from "./commands/user/user.ts"; @@ -66,7 +68,7 @@ export { // } // }); -export const VERSION = "1.518.2"; +export const VERSION = "1.527.1"; const command = new Command() .name("wmill") @@ -167,8 +169,6 @@ const command = new Command() ) .command("completions", new CompletionsCommand()); - - async function main() { try { if (Deno.args.length === 0) { @@ -205,7 +205,9 @@ async function main() { await command.parse(Deno.args); } catch (e) { if (e && typeof e === "object" && "name" in e && e.name === "ApiError") { - console.log("Server failed. " + (e as any).statusText + ": " + (e as any).body); + console.log( + "Server failed. " + (e as any).statusText + ": " + (e as any).body + ); } throw e; } diff --git a/cli/src/types.ts b/cli/src/types.ts index e74d302a42..57c1c9b8e7 100644 --- a/cli/src/types.ts +++ b/cli/src/types.ts @@ -232,6 +232,7 @@ export function getTypeStrFromPath( parsed.ext == ".cs" || parsed.ext == ".nu" || parsed.ext == ".java" || + parsed.ext == ".rb" || // for related places search: ADD_NEW_LANG (parsed.ext == ".yml" && parsed.name.split(".").pop() == "playbook") ) { diff --git a/cli/src/utils/git.ts b/cli/src/utils/git.ts new file mode 100644 index 0000000000..c1032a8218 --- /dev/null +++ b/cli/src/utils/git.ts @@ -0,0 +1,29 @@ +import { log } from "../../deps.ts"; +import { execSync } from "node:child_process"; + +export function getCurrentGitBranch(): string | null { + try { + const result = execSync("git rev-parse --abbrev-ref HEAD", { + encoding: "utf8", + stdio: "pipe" + }); + const branch = result.trim(); + return branch || null; + } catch (error) { + log.debug(`Failed to get Git branch: ${error}`); + return null; + } +} + +export function isGitRepository(): boolean { + try { + execSync("git rev-parse --git-dir", { + encoding: "utf8", + stdio: "pipe" + }); + return true; + } catch (error) { + log.debug(`Failed to check Git repository: ${error}`); + return false; + } +} diff --git a/cli/src/utils/metadata.ts b/cli/src/utils/metadata.ts index cb5de7391a..25c9e2a219 100644 --- a/cli/src/utils/metadata.ts +++ b/cli/src/utils/metadata.ts @@ -643,7 +643,10 @@ export async function inferSchema( } else if (language === "java") { const { parse_java } = await import("../../wasm/java/windmill_parser_wasm.js"); inferedSchema = JSON.parse(parse_java(content)); - // for related places search: ADD_NEW_LANG + } else if (language === "ruby") { + const { parse_ruby } = await import("../../wasm/ruby/windmill_parser_wasm.js"); + inferedSchema = JSON.parse(parse_ruby(content)); + // for related places search: ADD_NEW_LANG } else { throw new Error("Invalid language: " + language); } @@ -909,4 +912,4 @@ export async function updateMetadataGlobalLock( WMILL_LOCKFILE, yamlStringify(conf as Record, yamlOptions) ); -} \ No newline at end of file +} diff --git a/cli/src/utils/script_common.ts b/cli/src/utils/script_common.ts index 85efea9786..df661d25fe 100644 --- a/cli/src/utils/script_common.ts +++ b/cli/src/utils/script_common.ts @@ -19,6 +19,7 @@ export type ScriptLanguage = | "csharp" | "nu" | "ansible" + | "ruby" | "java"; // for related places search: ADD_NEW_LANG @@ -87,7 +88,9 @@ export function inferContentTypeFromFilePath( return "nu"; } else if (contentPath.endsWith(".java")) { return "java"; - // for related places search: ADD_NEW_LANG + } else if (contentPath.endsWith(".rb")) { + return "ruby"; + // for related places search: ADD_NEW_LANG } else { throw new Error( "Invalid language: " + contentPath.substring(contentPath.lastIndexOf(".")) diff --git a/cli/wasm/ruby/windmill_parser_wasm.d.ts b/cli/wasm/ruby/windmill_parser_wasm.d.ts new file mode 100644 index 0000000000..1d3229c2ec --- /dev/null +++ b/cli/wasm/ruby/windmill_parser_wasm.d.ts @@ -0,0 +1,3 @@ +/* tslint:disable */ +/* eslint-disable */ +export function parse_ruby(code: string): string; diff --git a/cli/wasm/ruby/windmill_parser_wasm.js b/cli/wasm/ruby/windmill_parser_wasm.js new file mode 100644 index 0000000000..2c44b756b6 --- /dev/null +++ b/cli/wasm/ruby/windmill_parser_wasm.js @@ -0,0 +1,121 @@ + + +let WASM_VECTOR_LEN = 0; + +let cachedUint8ArrayMemory0 = null; + +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); + +const encodeString = function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +}; + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); + +if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} +/** + * @param {string} code + * @returns {string} + */ +export function parse_ruby(code) { + let deferred2_0; + let deferred2_1; + try { + const ptr0 = passStringToWasm0(code, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.parse_ruby(ptr0, len0); + deferred2_0 = ret[0]; + deferred2_1 = ret[1]; + return getStringFromWasm0(ret[0], ret[1]); + } finally { + wasm.__wbindgen_free(deferred2_0, deferred2_1, 1); + } +} + +const imports = { + __wbindgen_placeholder__: { + __wbindgen_init_externref_table: function() { + const table = wasm.__wbindgen_export_0; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + ; + }, + }, + +}; + +const wasm_url = new URL('windmill_parser_wasm_bg.wasm', import.meta.url); +let wasmCode = ''; +switch (wasm_url.protocol) { + case 'file:': + wasmCode = await Deno.readFile(wasm_url); + break + case 'https:': + case 'http:': + wasmCode = await (await fetch(wasm_url)).arrayBuffer(); + break + default: + throw new Error(`Unsupported protocol: ${wasm_url.protocol}`); +} + +const wasmInstance = (await WebAssembly.instantiate(wasmCode, imports)).instance; +const wasm = wasmInstance.exports; +export const __wasm = wasm; + +wasm.__wbindgen_start(); + diff --git a/cli/wasm/ruby/windmill_parser_wasm_bg.wasm b/cli/wasm/ruby/windmill_parser_wasm_bg.wasm new file mode 100644 index 0000000000..e71dce2e5c Binary files /dev/null and b/cli/wasm/ruby/windmill_parser_wasm_bg.wasm differ diff --git a/cli/wasm/ruby/windmill_parser_wasm_bg.wasm.d.ts b/cli/wasm/ruby/windmill_parser_wasm_bg.wasm.d.ts new file mode 100644 index 0000000000..ec299d2fdc --- /dev/null +++ b/cli/wasm/ruby/windmill_parser_wasm_bg.wasm.d.ts @@ -0,0 +1,35 @@ +/* tslint:disable */ +/* eslint-disable */ +export const memory: WebAssembly.Memory; +export const parse_ruby: (a: number, b: number) => [number, number]; +export const free: (a: number) => void; +export const abort: () => void; +export const malloc: (a: number) => number; +export const calloc: (a: number, b: number) => number; +export const realloc: (a: number, b: number) => number; +export const strncmp: (a: number, b: number, c: number) => number; +export const memchr: (a: number, b: number, c: number) => number; +export const strchr: (a: number, b: number) => number; +export const iswspace: (a: number) => number; +export const iswalnum: (a: number) => number; +export const iswdigit: (a: number) => number; +export const iswupper: (a: number) => number; +export const iswalpha: (a: number) => number; +export const iswlower: (a: number) => number; +export const clock: () => bigint; +export const isprint: (a: number) => number; +export const fprintf: (a: number, b: number, c: number) => number; +export const fputs: (a: number, b: number) => number; +export const fputc: (a: number, b: number) => number; +export const fdopen: (a: number, b: number) => number; +export const fclose: (a: number) => number; +export const fwrite: (a: number, b: number, c: number, d: number) => number; +export const vsnprintf: (a: number, b: number, c: number, d: number) => number; +export const clock_gettime: (a: number, b: number) => void; +export const snprintf: () => void; +export const __assert_fail: (a: number, b: number, c: number, d: number) => void; +export const __wbindgen_export_0: WebAssembly.Table; +export const __wbindgen_malloc: (a: number, b: number) => number; +export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; +export const __wbindgen_free: (a: number, b: number, c: number) => void; +export const __wbindgen_start: () => void; diff --git a/cli/windmill-utils-internal/README.md b/cli/windmill-utils-internal/README.md new file mode 100644 index 0000000000..2d60566621 --- /dev/null +++ b/cli/windmill-utils-internal/README.md @@ -0,0 +1,39 @@ +# Windmill Utils Internal + +Internal TypeScript utility package for Windmill development tools and scripts. + +## What this package contains + +This package provides internal utilities and tools used by the Windmill CLI, the VS CODE extension, and the frontend. + +## Development + +To work on this package we need to generate the windmill client, and remove .ts extensions to the imports and exports (added by default for deno compatibility, so that the CLI can use this package). + +You just need to run this before working on the package: + +```bash +npm run dev +``` + +After you are done with your modifications, add back the .ts extensions: + +```bash +./remove-ts-ext.sh -r +``` + +### Building + +To build the package for production: + +```bash +npm run build +``` + +### Publishing + +To publish the package: + +```bash +./publish.sh +``` \ No newline at end of file diff --git a/cli/windmill-utils-internal/gen_wm_client-mac.sh b/cli/windmill-utils-internal/gen_wm_client-mac.sh deleted file mode 100755 index ba0e90c151..0000000000 --- a/cli/windmill-utils-internal/gen_wm_client-mac.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash - -# only install gnu-sed if not already installed -if ! command -v gsed &> /dev/null; then - brew install gnu-sed -fi - -script_dirpath="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -output_dirpath="${script_dirpath}/src/gen" - -rm -rf "${output_dirpath}" - -npx --yes @hey-api/openapi-ts@0.53.1 --input "${script_dirpath}/../../backend/windmill-api/openapi.yaml" --output "${output_dirpath}" --useOptions --client legacy/fetch --schemas false -cat < temp_file && mv temp_file "${output_dirpath}/core/OpenAPI.ts" -const getEnv = (key: string) => { - return process.env[key] -}; - -const baseUrl = getEnv("BASE_INTERNAL_URL") ?? getEnv("BASE_URL") ?? "http://localhost:8000"; -const baseUrlApi = (baseUrl ?? '') + "/api"; - -EOF -gsed -i 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/g' "${output_dirpath}/core/OpenAPI.ts" -gsed -i 's/TOKEN: undefined/TOKEN: getEnv("WM_TOKEN")/g' "${output_dirpath}/core/OpenAPI.ts" -gsed -i "s/BASE: '\/api'/BASE: baseUrlApi/g" "${output_dirpath}/core/OpenAPI.ts" diff --git a/cli/windmill-utils-internal/gen_wm_client.sh b/cli/windmill-utils-internal/gen_wm_client.sh index e504c2da96..63ff42fefd 100755 --- a/cli/windmill-utils-internal/gen_wm_client.sh +++ b/cli/windmill-utils-internal/gen_wm_client.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash +# Set script to exit on any error +set -e + script_dirpath="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" output_dirpath="${script_dirpath}/src/gen" @@ -15,6 +18,23 @@ const baseUrl = getEnv("BASE_INTERNAL_URL") ?? getEnv("BASE_URL") ?? "http://loc const baseUrlApi = (baseUrl ?? '') + "/api"; EOF -sed -i 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/g' "${output_dirpath}/core/OpenAPI.ts" -sed -i 's/TOKEN: undefined/TOKEN: getEnv("WM_TOKEN")/g' "${output_dirpath}/core/OpenAPI.ts" -sed -i "s/BASE: '\/api'/BASE: baseUrlApi/g" "${output_dirpath}/core/OpenAPI.ts" + +echo "Applying sed transformations..." +if [[ "$OSTYPE" == "darwin"* ]]; then + echo "Detected macOS - using GNU sed (gsed)" + if ! command -v gsed &> /dev/null; then + echo "Error: gsed not found" + echo "Run: brew install gnu-sed" + exit 1 + fi + gsed -i 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/g' "${output_dirpath}/core/OpenAPI.ts" + gsed -i 's/TOKEN: undefined/TOKEN: getEnv("WM_TOKEN")/g' "${output_dirpath}/core/OpenAPI.ts" + gsed -i "s/BASE: '\\/api'/BASE: baseUrlApi/g" "${output_dirpath}/core/OpenAPI.ts" +else + echo "Detected Linux - using GNU sed" + sed -i 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/g' "${output_dirpath}/core/OpenAPI.ts" + sed -i 's/TOKEN: undefined/TOKEN: getEnv("WM_TOKEN")/g' "${output_dirpath}/core/OpenAPI.ts" + sed -i "s/BASE: '\\/api'/BASE: baseUrlApi/g" "${output_dirpath}/core/OpenAPI.ts" +fi + +echo "✓ Client generation completed" \ No newline at end of file diff --git a/cli/windmill-utils-internal/package-lock.json b/cli/windmill-utils-internal/package-lock.json index 19a2425d76..72bb67e2e7 100644 --- a/cli/windmill-utils-internal/package-lock.json +++ b/cli/windmill-utils-internal/package-lock.json @@ -9,18 +9,18 @@ "version": "1.0.0", "license": "Apache 2.0", "devDependencies": { - "@types/node": "^24.1.0", + "@types/node": "^24.2.0", "typescript": "^5.0.0" } }, "node_modules/@types/node": { - "version": "24.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", - "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.0.tgz", + "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.8.0" + "undici-types": "~7.10.0" } }, "node_modules/typescript": { @@ -38,9 +38,9 @@ } }, "node_modules/undici-types": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", "dev": true, "license": "MIT" } diff --git a/cli/windmill-utils-internal/package.json b/cli/windmill-utils-internal/package.json index eaa9823d6b..68a7f40104 100644 --- a/cli/windmill-utils-internal/package.json +++ b/cli/windmill-utils-internal/package.json @@ -5,8 +5,8 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { - "build": "./gen_wm_client.sh && tsc", - "build-mac": "./gen_wm_client-mac.sh && tsc", + "dev": "./gen_wm_client.sh && ./remove-ts-ext.sh", + "build": "./gen_wm_client.sh && ./remove-ts-ext.sh && tsc", "prepublishOnly": "npm run build" }, "keywords": [ @@ -15,10 +15,10 @@ "author": "Ruben Fiszel", "license": "Apache 2.0", "devDependencies": { - "@types/node": "^24.1.0", + "@types/node": "^24.2.0", "typescript": "^5.0.0" }, "files": [ "dist/**/*" ] -} \ No newline at end of file +} diff --git a/cli/windmill-utils-internal/remove-ts-ext.sh b/cli/windmill-utils-internal/remove-ts-ext.sh new file mode 100755 index 0000000000..8b5390b73e --- /dev/null +++ b/cli/windmill-utils-internal/remove-ts-ext.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +# Set script to exit on any error +set -e +script_dirpath="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Parse command line arguments +RESTORE_MODE=false +while [[ $# -gt 0 ]]; do + case $1 in + -r) + RESTORE_MODE=true + shift + ;; + *) + echo "Unknown option: $1" + echo "Usage: $0 [-r]" + echo " -r Restore .ts extensions to imports/exports" + exit 1 + ;; + esac +done + +if [[ "$RESTORE_MODE" == true ]]; then + echo "Adding .ts extensions to imports..." + # Only add .ts if the path doesn't already end with .ts or / + REGEX='/\.ts["'\'']/! s/(from|import)[[:space:]]+["'\'']([^"'\'']*[^/])(["'\''])/\1 "\2.ts\3/g' + SUCCESS_MSG="✓ All .ts extensions added to import/export statements" +else + echo "Removing .ts extensions from imports..." + REGEX='s/(from|import)[[:space:]]+["'\'']([^"'\'']*?)\.ts(["'\''])/\1 "\2\3/g' + SUCCESS_MSG="✓ All .ts extensions removed from import/export statements" +fi + +if [[ "$OSTYPE" == "darwin"* ]]; then + echo "Detected macOS - using GNU sed (gsed)" + if ! command -v gsed &> /dev/null; then + echo "Error: gsed not found" + echo "Run: brew install gnu-sed" + exit 1 + fi +else + echo "Detected Linux - using GNU sed" +fi + +find "$script_dirpath"/src -name "*.ts" -type f | while read -r file; do + if [[ "$OSTYPE" == "darwin"* ]]; then + gsed -E -i "$REGEX" "$file" + else + sed -E -i "$REGEX" "$file" + fi +done + +echo "$SUCCESS_MSG" \ No newline at end of file diff --git a/cli/windmill-utils-internal/src/config/index.ts b/cli/windmill-utils-internal/src/config/index.ts index e23ba6ca86..f3ae42b3c8 100644 --- a/cli/windmill-utils-internal/src/config/index.ts +++ b/cli/windmill-utils-internal/src/config/index.ts @@ -1 +1 @@ -export * from "./config"; \ No newline at end of file +export * from "./config.ts"; \ No newline at end of file diff --git a/cli/windmill-utils-internal/src/index.ts b/cli/windmill-utils-internal/src/index.ts index da314a7c5a..635893e2d1 100644 --- a/cli/windmill-utils-internal/src/index.ts +++ b/cli/windmill-utils-internal/src/index.ts @@ -8,8 +8,8 @@ * - Cross-platform path constants */ -export * from "./inline-scripts"; -export * from "./path-utils"; -export * from "./parse"; -export * from "./config"; -export { SEP, DELIMITER } from "./constants"; \ No newline at end of file +export * from "./inline-scripts.ts"; +export * from "./path-utils.ts"; +export * from "./parse.ts"; +export * from "./config.ts"; +export { SEP, DELIMITER } from "./constants.ts"; \ No newline at end of file diff --git a/cli/windmill-utils-internal/src/inline-scripts/extractor.ts b/cli/windmill-utils-internal/src/inline-scripts/extractor.ts index b64e7ca789..be2d33c0de 100644 --- a/cli/windmill-utils-internal/src/inline-scripts/extractor.ts +++ b/cli/windmill-utils-internal/src/inline-scripts/extractor.ts @@ -1,5 +1,5 @@ -import { newPathAssigner } from "../path-utils/path-assigner"; -import { FlowModule } from "../gen/types.gen"; +import { newPathAssigner } from "../path-utils/path-assigner.ts"; +import { FlowModule } from "../gen/types.gen.ts"; /** * Represents an inline script extracted from a flow module diff --git a/cli/windmill-utils-internal/src/inline-scripts/index.ts b/cli/windmill-utils-internal/src/inline-scripts/index.ts index eace8d3e4f..bb3c917dbb 100644 --- a/cli/windmill-utils-internal/src/inline-scripts/index.ts +++ b/cli/windmill-utils-internal/src/inline-scripts/index.ts @@ -1,2 +1,2 @@ -export * from "./replacer"; -export * from "./extractor"; \ No newline at end of file +export * from "./replacer.ts"; +export * from "./extractor.ts"; \ No newline at end of file diff --git a/cli/windmill-utils-internal/src/inline-scripts/replacer.ts b/cli/windmill-utils-internal/src/inline-scripts/replacer.ts index be349785c7..9ec25e9f65 100644 --- a/cli/windmill-utils-internal/src/inline-scripts/replacer.ts +++ b/cli/windmill-utils-internal/src/inline-scripts/replacer.ts @@ -1,4 +1,4 @@ -import { FlowModule } from "../gen/types.gen"; +import { FlowModule } from "../gen/types.gen.ts"; /** * Replaces inline script references with actual file content from the filesystem. diff --git a/cli/windmill-utils-internal/src/parse/index.ts b/cli/windmill-utils-internal/src/parse/index.ts index fc26ce611a..41d09ed00d 100644 --- a/cli/windmill-utils-internal/src/parse/index.ts +++ b/cli/windmill-utils-internal/src/parse/index.ts @@ -1 +1 @@ -export * from "./parse-schema"; \ No newline at end of file +export * from "./parse-schema.ts"; \ No newline at end of file diff --git a/cli/windmill-utils-internal/src/path-utils/index.ts b/cli/windmill-utils-internal/src/path-utils/index.ts index ef23185664..6f5c8d68be 100644 --- a/cli/windmill-utils-internal/src/path-utils/index.ts +++ b/cli/windmill-utils-internal/src/path-utils/index.ts @@ -1 +1 @@ -export * from "./path-assigner"; \ No newline at end of file +export * from "./path-assigner.ts"; \ No newline at end of file diff --git a/cli/windmill-utils-internal/src/path-utils/path-assigner.ts b/cli/windmill-utils-internal/src/path-utils/path-assigner.ts index 9020974214..d1a79a3a32 100644 --- a/cli/windmill-utils-internal/src/path-utils/path-assigner.ts +++ b/cli/windmill-utils-internal/src/path-utils/path-assigner.ts @@ -1,4 +1,4 @@ -import { RawScript } from "../gen/types.gen"; +import { RawScript } from "../gen/types.gen.ts"; const INLINE_SCRIPT_PREFIX = "inline_script"; diff --git a/docker/DockerfileFull b/docker/DockerfileFull index 6efb16c7bd..1d5f639113 100644 --- a/docker/DockerfileFull +++ b/docker/DockerfileFull @@ -22,3 +22,6 @@ RUN curl -fLo coursier https://github.com/coursier/coursier/releases/download/v2 && mv ./coursier /usr/bin/coursier \ && chmod +x /usr/bin/coursier RUN /usr/bin/java -jar /usr/bin/coursier about + +# Ruby +RUN apt-get install -y ruby ruby-bundler diff --git a/docker/DockerfileFullEe b/docker/DockerfileFullEe index e93aaaf9a0..376f994f89 100644 --- a/docker/DockerfileFullEe +++ b/docker/DockerfileFullEe @@ -45,3 +45,6 @@ RUN curl -fLo coursier https://github.com/coursier/coursier/releases/download/v2 && mv ./coursier /usr/bin/coursier \ && chmod +x /usr/bin/coursier RUN /usr/bin/java -jar /usr/bin/coursier about + +# Ruby +RUN apt-get install -y ruby ruby-bundler diff --git a/docker/DockerfileNsjail b/docker/DockerfileNsjail index 2e9b0b0580..a995c76fac 100644 --- a/docker/DockerfileNsjail +++ b/docker/DockerfileNsjail @@ -70,6 +70,9 @@ RUN curl -fLo coursier https://github.com/coursier/coursier/releases/download/v2 && chmod +x /usr/bin/coursier RUN /usr/bin/java -jar /usr/bin/coursier about +# Ruby +RUN apt-get install -y ruby ruby-bundler + # iptables RUN apt-get install -y iptables diff --git a/docker/dev.nu b/docker/dev.nu index a1fd303b0b..77d2641204 100755 --- a/docker/dev.nu +++ b/docker/dev.nu @@ -4,6 +4,8 @@ # run: # # docker/dev.nu --help # # ------------------------------------ # +# NOTE: +# Does not work if built from git worktree branch let branch = git branch --show-current; # cp -f Caddyfile .dev-docker/Caddyfile @@ -57,6 +59,7 @@ def patch_custom_dockerfile [ ] { open $custom_dockerfile | str replace 'ghcr.io/windmill-labs/windmill-ee:dev' (get_ident -f $features) + | str replace 'ghcr.io/windmill-labs/windmill:dev' (get_ident -f $features) | return $in } diff --git a/flake.nix b/flake.nix index 9aac3a3664..be958fd547 100644 --- a/flake.nix +++ b/flake.nix @@ -143,6 +143,7 @@ dotnet-sdk_9 oracle-instantclient ansible + ruby_3_4 # LSP/Local dev svelte-language-server @@ -214,7 +215,10 @@ GIT_PATH = "${pkgs.git}/bin/git"; NODE_ENV = "development"; NODE_OPTIONS = "--max-old-space-size=16384"; - DATABASE_URL = "postgres://postgres:changeme@127.0.0.1:5432/"; + # DATABASE_URL = "postgres://postgres:changeme@127.0.0.1:5432/"; + DATABASE_URL = + "postgres://postgres:changeme@127.0.0.1:5432/windmill?sslmode=disable"; + REMOTE = "http://127.0.0.1:8000"; REMOTE_LSP = "http://127.0.0.1:3001"; RUSTC_WRAPPER = "${pkgs.sccache}/bin/sccache"; @@ -228,7 +232,10 @@ JAVA_PATH = "${pkgs.jdk21}/bin/java"; JAVAC_PATH = "${pkgs.jdk21}/bin/javac"; COURSIER_PATH = "${coursier}/coursier"; - # for related places search: ADD_NEW_LANG + RUBY_PATH = "${pkgs.ruby}/bin/ruby"; + RUBY_BUNDLE_PATH = "${pkgs.ruby}/bin/bundle"; + RUBY_GEM_PATH = "${pkgs.ruby}/bin/gem"; + # for related places search: ADD_NEW_LANG FLOCK_PATH = "${pkgs.flock}/bin/flock"; CARGO_PATH = "${rust}/bin/cargo"; CARGO_SWEEP_PATH = "${pkgs.cargo-sweep}/bin/cargo-sweep"; diff --git a/frontend/CLAUDE.md b/frontend/CLAUDE.md index 2caad3c0cf..6aa8ef3c48 100644 --- a/frontend/CLAUDE.md +++ b/frontend/CLAUDE.md @@ -9,8 +9,48 @@ ## UI Guidelines -- Follow existing design system -- Use consistent spacing and colors +### Styling Guidelines + +- **Use Tailwind CSS** for all styling instead of custom CSS +- **Use Windmill's theming classes** for consistent colors and surfaces +- **Avoid custom styles** - prefer Tailwind utility classes +- **Follow existing patterns** - look at other components for reference + +### Windmill Theme Classes + +Use these semantic color classes that automatically handle light/dark modes: + +#### Backgrounds +- `bg-surface` - Main surface background +- `bg-surface-secondary` - Secondary/elevated surfaces +- `bg-surface-hover` - Hover states for interactive elements + +#### Text Colors +- `text-primary` - Primary text color +- `text-secondary` - Secondary text (less prominent) +- `text-tertiary` - Tertiary text (subtle/muted) + +#### Borders +- `border-gray-200 dark:border-gray-700` - Standard borders that adapt to theme + +#### Status Colors +Use standard Tailwind color classes with dark mode variants: +- Success: `text-green-500`, `bg-green-100 dark:bg-green-900/30` +- Error: `text-red-500`, `bg-red-50 dark:bg-red-900/20` +- Warning: `text-yellow-500`, `bg-yellow-100 dark:bg-yellow-900/30` +- Info: `text-blue-500`, `bg-blue-100 dark:bg-blue-900/30` + +#### Typography +- `font-mono` - For code/technical content +- `text-xs`, `text-sm`, `text-2xs` - Standard text sizes +- Use `font-medium`, `font-semibold` for emphasis + +### Layout Guidelines + +- Use Tailwind spacing utilities (`p-3`, `m-2`, `gap-2`, etc.) +- Use flexbox/grid utilities for layouts +- Use `transition-colors` for smooth hover effects +- Use `overflow-hidden`, `rounded-md` for consistent card styles ## Backend API diff --git a/frontend/bundle_graphql_worker.sh b/frontend/bundle_graphql_worker.sh index d64dea37db..3d562f50ce 100755 --- a/frontend/bundle_graphql_worker.sh +++ b/frontend/bundle_graphql_worker.sh @@ -1 +1 @@ -npx esbuild node_modules/monaco-graphql/esm/graphql.worker --bundle --minify --outfile=src/lib/monaco_workers/graphql.worker.bundle.js \ No newline at end of file +npx esbuild ./node_modules/monaco-graphql/esm/graphql.worker --bundle --minify --outfile=src/lib/monaco_workers/graphql.worker.bundle.js \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a5268d9b79..19e959f53b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,23 +1,23 @@ { "name": "windmill-components", - "version": "1.518.2", + "version": "1.527.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "windmill-components", - "version": "1.518.2", + "version": "1.527.1", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { "@aws-crypto/sha256-js": "^4.0.0", - "@codingame/monaco-vscode-configuration-service-override": "~16.1.1", - "@codingame/monaco-vscode-editor-api": "~16.1.1", - "@codingame/monaco-vscode-standalone-css-language-features": "~16.1.1", - "@codingame/monaco-vscode-standalone-html-language-features": "^16.1.1", - "@codingame/monaco-vscode-standalone-json-language-features": "~16.1.1", - "@codingame/monaco-vscode-standalone-languages": "~16.1.1", - "@codingame/monaco-vscode-standalone-typescript-language-features": "~16.1.1", + "@codingame/monaco-vscode-configuration-service-override": "~19.1.4", + "@codingame/monaco-vscode-editor-api": "~19.1.4", + "@codingame/monaco-vscode-standalone-css-language-features": "~19.1.4", + "@codingame/monaco-vscode-standalone-html-language-features": "~19.1.4", + "@codingame/monaco-vscode-standalone-json-language-features": "~19.1.4", + "@codingame/monaco-vscode-standalone-languages": "~19.1.4", + "@codingame/monaco-vscode-standalone-typescript-language-features": "~19.1.4", "@json2csv/plainjs": "^7.0.6", "@leeoniya/ufuzzy": "^1.0.8", "@popperjs/core": "^2.11.6", @@ -50,10 +50,10 @@ "lru-cache": "^11.1.0", "lucide-svelte": "^0.399.0", "minimatch": "^10.0.1", - "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@~16.1.1", - "monaco-editor-wrapper": "6.7.0", - "monaco-graphql": "^1.6.0", - "monaco-languageclient": "9.6.0", + "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@~19.1.4", + "monaco-editor-wrapper": "6.10.0", + "monaco-graphql": "=1.6.0", + "monaco-languageclient": "9.9.0", "monaco-vim": "^0.4.1", "ol": "^7.4.0", "openai": "^4.87.1", @@ -70,7 +70,7 @@ "svelte-infinite-loading": "^1.4.0", "svelte-tiny-virtual-list": "^2.0.5", "tailwind-merge": "^1.13.2", - "vscode": "npm:@codingame/monaco-vscode-extension-api@~16.1.1", + "vscode": "npm:@codingame/monaco-vscode-extension-api@~19.1.4", "vscode-languageclient": "~9.0.1", "vscode-uri": "~3.1.0", "vscode-ws-jsonrpc": "~3.4.0", @@ -81,6 +81,7 @@ "windmill-parser-wasm-php": "1.510.1", "windmill-parser-wasm-py": "1.510.1", "windmill-parser-wasm-regex": "1.512.0", + "windmill-parser-wasm-ruby": "1.526.1", "windmill-parser-wasm-rust": "1.510.1", "windmill-parser-wasm-ts": "1.514.1", "windmill-parser-wasm-yaml": "1.510.1", @@ -102,9 +103,9 @@ "@melt-ui/svelte": "^0.86.2", "@playwright/test": "^1.34.3", "@sveltejs/adapter-static": "^3.0.6", - "@sveltejs/kit": "^2.16.0", + "@sveltejs/kit": "^2.28.0", "@sveltejs/package": "^2.3.7", - "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@sveltejs/vite-plugin-svelte": "^6.1.1", "@tailwindcss/forms": "^0.5.3", "@tailwindcss/typography": "^0.5.8", "@types/d3": "^7.4.0", @@ -130,7 +131,7 @@ "prettier-plugin-svelte": "^3.3.3", "style-to-object": "^0.4.1", "stylelint-config-recommended": "^13.0.0", - "svelte": "^5.0.0", + "svelte": "^5.38.0", "svelte-awesome-color-picker": "^3.0.4", "svelte-check": "^4.0.0", "svelte-floating-ui": "^1.5.8", @@ -144,7 +145,7 @@ "tar": "^7.4.3", "tslib": "^2.6.1", "typescript": "^5.5.0", - "vite": "^6.3.2", + "vite": "^7.1.2", "vite-plugin-mkcert": "^1.17.5", "yootils": "^0.3.1" }, @@ -156,6 +157,11 @@ "svelte": "^5.0.0" } }, + "../backend/parsers/windmill-parser-wasm/pkg-ruby": { + "name": "windmill-parser-wasm-ruby", + "version": "1.510.0", + "extraneous": true + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -280,1505 +286,1584 @@ "node": ">=6.9.0" } }, - "node_modules/@c4312/eventsource-umd": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@c4312/eventsource-umd/-/eventsource-umd-3.0.5.tgz", - "integrity": "sha512-0QhLg51eFB+SS/a4Pv5tHaRSnjJBpdFsjT3WN/Vfh6qzeFXqvaE+evVIIToYvr2lRBLg1NIB635ip8ML+/84Sg==", - "dependencies": { - "eventsource-parser": "^3.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@codingame/monaco-vscode-039b5553-0838-562a-97c2-30d6e54a7b42-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-039b5553-0838-562a-97c2-30d6e54a7b42-common/-/monaco-vscode-039b5553-0838-562a-97c2-30d6e54a7b42-common-16.1.1.tgz", - "integrity": "sha512-agR9tyi9MXmguqEdWAY7wVcGjYB385FlobsvKH6Um1+kElcWwwigjKAw5xdVI/m63wnovr6wI6VvYXBVe1g/ug==" - }, "node_modules/@codingame/monaco-vscode-0b087f42-a5a3-5eb9-9bfd-1eebc1bba163-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-0b087f42-a5a3-5eb9-9bfd-1eebc1bba163-common/-/monaco-vscode-0b087f42-a5a3-5eb9-9bfd-1eebc1bba163-common-16.1.1.tgz", - "integrity": "sha512-It8Daw7LNg3fU7YMu/5UXsTlTOnUfd8zlgIwAISaziQWmY6nPFDHXqgD/OVtTLsMIKCF20q/xv/EpGf2ZksGDg==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-0b087f42-a5a3-5eb9-9bfd-1eebc1bba163-common/-/monaco-vscode-0b087f42-a5a3-5eb9-9bfd-1eebc1bba163-common-19.1.4.tgz", + "integrity": "sha512-zXrqQmP9KJMDMuBx6BYra2+W1pZ745yyA3mcxy16QgOsgZ/nhLdkkd80L8G/+DW0vnvwkHDGCaZVllwGwOOryw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-7bbc9e7d-eeae-55fc-8bf9-dc2f66e0dc73-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-7bbc9e7d-eeae-55fc-8bf9-dc2f66e0dc73-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-0c06bfba-d24d-5c4d-90cd-b40cefb7f811-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-0c06bfba-d24d-5c4d-90cd-b40cefb7f811-common/-/monaco-vscode-0c06bfba-d24d-5c4d-90cd-b40cefb7f811-common-16.1.1.tgz", - "integrity": "sha512-bPV+7zRuX7C2+rmrY9X8uLWBhm3lS/D+mYJIEJwtHn+mhFmxUe/9v19h66qdInvsc+9UY2wEGyKH+2RkLbkFrA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-0c06bfba-d24d-5c4d-90cd-b40cefb7f811-common/-/monaco-vscode-0c06bfba-d24d-5c4d-90cd-b40cefb7f811-common-19.1.4.tgz", + "integrity": "sha512-GYB3uDAvaOtTkYLfLs7qXfeBD3WJ9J/jXhkrvKGrzlGIKA3IB7jjvWq0xNLCa8EpgCFO7SnukraGJlbk9h3h5w==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-2e67e044-0db9-5fa6-8bd1-3737a7d586d4-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-0cc5da60-f921-59b9-bd8c-a018e93c0a6f-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-0cc5da60-f921-59b9-bd8c-a018e93c0a6f-common/-/monaco-vscode-0cc5da60-f921-59b9-bd8c-a018e93c0a6f-common-16.1.1.tgz", - "integrity": "sha512-NYgJF6QtukfElCQQ+8b9ouQpEA6ig+FX9xKJkWYmPx/0cLOsWOU1Hz4aaxMBBte0lED6jsjp2UDksAcoUQ+EyA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-0cc5da60-f921-59b9-bd8c-a018e93c0a6f-common/-/monaco-vscode-0cc5da60-f921-59b9-bd8c-a018e93c0a6f-common-19.1.4.tgz", + "integrity": "sha512-fHVD3rTtV9ehISbI0G+vaGwMyf/5JI57N2aPxReqvWQCs31JGWzAcsqjJ56+/tut3LN9Xk7k27gBgBUjDNYAlw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-f22e7e55-aee8-5b52-a6bc-950efd9f5890-common": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-0f5ced28-abde-558b-8652-db8e7d4d64aa-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-0f5ced28-abde-558b-8652-db8e7d4d64aa-common/-/monaco-vscode-0f5ced28-abde-558b-8652-db8e7d4d64aa-common-16.1.1.tgz", - "integrity": "sha512-EZZpjcGDVMrTUMG8BIjYqInVIWmohhmrChPPFcRZuxC/ZXFM44MCnUSY6cOqvghfXNQY/r9Cx6XWZD4SclCPJw==", - "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-f22e7e55-aee8-5b52-a6bc-950efd9f5890-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-1021b67c-93e5-5c78-a270-cbdb2574d980-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-1021b67c-93e5-5c78-a270-cbdb2574d980-common/-/monaco-vscode-1021b67c-93e5-5c78-a270-cbdb2574d980-common-16.1.1.tgz", - "integrity": "sha512-ISgM3PK2yo5Tr8BPn054ljnTKPLqMWj9P5cvvvebGS9OdG27Tq9i3rc90BSnlakG748xRy6xoOOg0WAEUAprbw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-1021b67c-93e5-5c78-a270-cbdb2574d980-common/-/monaco-vscode-1021b67c-93e5-5c78-a270-cbdb2574d980-common-19.1.4.tgz", + "integrity": "sha512-hIaVLDphqi1nVwicw3upDeRjVVSN27E8Fmb1csZGz+49KxiAXlCiLgdBkn+jzU9FsTc9l4BS0hHfNz5pKIYppQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common/-/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common-16.1.1.tgz", - "integrity": "sha512-cjxFkHB9VDzHMLvgWgLPh8eF+/g5mNDX25O+x04jHDOG4Kyn+xTpnECyQyZoDG5xHBAQecqMbOtaiY4wQSf90Q==", - "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-15626ec7-b165-51e1-8caf-7bcc2ae9b95a-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-15626ec7-b165-51e1-8caf-7bcc2ae9b95a-common/-/monaco-vscode-15626ec7-b165-51e1-8caf-7bcc2ae9b95a-common-16.1.1.tgz", - "integrity": "sha512-E9K7ES4UP8v+nW/hAkWTPo00BQz5c9qTdH/RyGrmAwSPOFaPc5xtXtjqlSno4uklEYFFVr3qZX/FAjOuR4VsBQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-15626ec7-b165-51e1-8caf-7bcc2ae9b95a-common/-/monaco-vscode-15626ec7-b165-51e1-8caf-7bcc2ae9b95a-common-19.1.4.tgz", + "integrity": "sha512-naRJG1IzwX+PAFejDOwyZAVYtNfg7shWrPEMfXfW4zPrQ1+ICRGkPDZhBZzjaB9RqfX6FG59Qn+IMD6qcNyx1w==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-2e67e044-0db9-5fa6-8bd1-3737a7d586d4-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-158b9837-fc78-5d9c-86f5-9134e4358643-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-158b9837-fc78-5d9c-86f5-9134e4358643-common/-/monaco-vscode-158b9837-fc78-5d9c-86f5-9134e4358643-common-19.1.4.tgz", + "integrity": "sha512-TIT4Ve7u32zbZOx9vmB/LX4fUC93kbAl3vxkuT+uTQskkxeZwHmIVuVxzRNJLSws16mFDsHB8EGo8o7vSeqaUQ==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-1b4486de-4fe4-59c4-9e6d-34f265ff6625-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-1b4486de-4fe4-59c4-9e6d-34f265ff6625-common/-/monaco-vscode-1b4486de-4fe4-59c4-9e6d-34f265ff6625-common-16.1.1.tgz", - "integrity": "sha512-DIuGlP6akqbFsqb1w+3CQCtqTiASZ5hfQ85rpTJOMDBW8+NmFwTfUBynDlyliv8+7UHs475SEc1pWQQyrMs7yQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-1b4486de-4fe4-59c4-9e6d-34f265ff6625-common/-/monaco-vscode-1b4486de-4fe4-59c4-9e6d-34f265ff6625-common-19.1.4.tgz", + "integrity": "sha512-5RPD9EEIwmkYYx9F2J1ETGpulED07834yJmskx2dgKrlQvrSyAIg6fbwyxC6TWySQGax96l+3Rqof9Em98H4ZA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-2a94c04a-b85b-5669-b06b-89c1bfa11cb9-common": "16.1.1", - "@codingame/monaco-vscode-422642f2-7e3a-5c1c-9e1e-1d3ef1817346-common": "16.1.1", - "@codingame/monaco-vscode-65619f8f-0eab-5d8b-855a-43b6353fe527-common": "16.1.1", - "@codingame/monaco-vscode-9a1a5840-af83-5d07-a156-ba32a36c5c4b-common": "16.1.1", - "@codingame/monaco-vscode-a7c9ae3c-16d2-5d17-86b2-981be7094566-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-e67a0dae-5b2c-54e6-8d61-90102c78362d-common": "16.1.1" + "@codingame/monaco-vscode-2a94c04a-b85b-5669-b06b-89c1bfa11cb9-common": "19.1.4", + "@codingame/monaco-vscode-422642f2-7e3a-5c1c-9e1e-1d3ef1817346-common": "19.1.4", + "@codingame/monaco-vscode-501b06ab-3f58-516b-8a1a-c29d375d3da4-common": "19.1.4", + "@codingame/monaco-vscode-937ecbdf-94c7-5b16-aefa-ad78ae557a93-common": "19.1.4", + "@codingame/monaco-vscode-9a1a5840-af83-5d07-a156-ba32a36c5c4b-common": "19.1.4", + "@codingame/monaco-vscode-a8d3bd74-e63e-5327-96e8-4f931661e329-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-1ba786a5-b7d7-5d26-8a85-ae48ee2a74a4-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-1ba786a5-b7d7-5d26-8a85-ae48ee2a74a4-common/-/monaco-vscode-1ba786a5-b7d7-5d26-8a85-ae48ee2a74a4-common-19.1.4.tgz", + "integrity": "sha512-5+/Yo9gTV9GJNz3SSTB8SgtXOcStIulQv4f5a5EF1xOUpOaJr+gYKVXvndCAznIekOa1qa7DiJyzHTety8E7Yg==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-9c1add21-d437-5ee3-ae2b-ad115a9ef171-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-1cc4ea0a-c5b6-54ed-bb60-078a99119b55-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-1cc4ea0a-c5b6-54ed-bb60-078a99119b55-common/-/monaco-vscode-1cc4ea0a-c5b6-54ed-bb60-078a99119b55-common-16.1.1.tgz", - "integrity": "sha512-YqOJP4tG4d1jxa8TGHEXWZF2pCKiNKkBb6kQDb9RGs37ER0SR1NDRQmuKLUslX6m3grQ3L73uQGOriZ8zhdsfQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-1cc4ea0a-c5b6-54ed-bb60-078a99119b55-common/-/monaco-vscode-1cc4ea0a-c5b6-54ed-bb60-078a99119b55-common-19.1.4.tgz", + "integrity": "sha512-xLwdVl/9iGOuLvt6eSQtb9x+P9Wh/kMO1FZcWx58YstsGdJRWV13WMlMn9hEGb4XhcKYFDmiL2t6aGEWT68Z9A==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-2bd6777a-3bff-5b83-ad25-0e029e81ffe9-common": "16.1.1", - "@codingame/monaco-vscode-ae8a8ca1-f243-508b-9c37-c88ebbd295fa-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-210e86a9-a91b-5273-b05d-390c776dde1f-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-210e86a9-a91b-5273-b05d-390c776dde1f-common/-/monaco-vscode-210e86a9-a91b-5273-b05d-390c776dde1f-common-16.1.1.tgz", - "integrity": "sha512-eQEUpx1RkaMHiEJvx8sStyZfybYsrrP6U+vQtdg6YiibRjeWFpfu8HtjdkIHEfsdGpHfOwbJjeryqFudSJkwfw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-210e86a9-a91b-5273-b05d-390c776dde1f-common/-/monaco-vscode-210e86a9-a91b-5273-b05d-390c776dde1f-common-19.1.4.tgz", + "integrity": "sha512-FL7QK6qLDfomC7QxvsunI0wbXpbRdfV3ZqEsAHjeETPael95AbfsDJz2P3VfMfYI0RSc1OFHbtIDgeuyjZQUQw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-23aade48-f094-5c08-9555-97fc9cca96c9-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-23aade48-f094-5c08-9555-97fc9cca96c9-common/-/monaco-vscode-23aade48-f094-5c08-9555-97fc9cca96c9-common-19.1.4.tgz", + "integrity": "sha512-+KhtN75iz97znRni2iohp8F1IPPXQSR0d9ppQzjElz+ik4RMucYJX3xotM1e6TViJS5EuG810TXZQVe0onShQw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-249dc928-1da3-51c1-82d0-45e0ba9d08a1-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-249dc928-1da3-51c1-82d0-45e0ba9d08a1-common/-/monaco-vscode-249dc928-1da3-51c1-82d0-45e0ba9d08a1-common-19.1.4.tgz", + "integrity": "sha512-g2Je7T43LYA5vowIsj3rGcL4+Zpsvt/WhnRzviYWa9ymj1hz9uSNEvzhoaG5PDTV17zd+iSAcYL7ilH9HkpiKg==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-256d5b78-0649-50e9-8354-2807f95f68f4-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-256d5b78-0649-50e9-8354-2807f95f68f4-common/-/monaco-vscode-256d5b78-0649-50e9-8354-2807f95f68f4-common-16.1.1.tgz", - "integrity": "sha512-j4O4K366VuYubOfe0QZlWA91TpU6iz6UCwf4eEa6dh8keo/ehfWGSq2QMCnP7sH3ZZti1DWtCzUhgx9SOweZGA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-256d5b78-0649-50e9-8354-2807f95f68f4-common/-/monaco-vscode-256d5b78-0649-50e9-8354-2807f95f68f4-common-19.1.4.tgz", + "integrity": "sha512-DDPpUuASJv06CejaIlZ8DzLX+oJ5z1G9MvpEzXPkboEuL+rTZpQqGYa2yY4ye6DEldSPjV0YWyK5X/3JvXP94w==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-29bc1406-2925-5b8f-b25e-d04a7772d896-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-29bc1406-2925-5b8f-b25e-d04a7772d896-common/-/monaco-vscode-29bc1406-2925-5b8f-b25e-d04a7772d896-common-16.1.1.tgz", - "integrity": "sha512-A8oBqfu+GHFTnH4tMAC+0wVzqq3S9KuAZmhmGnleaihla4p8AasjdrtGOvmFPOORJ18CCHz+qX8ZYVvV2p/OLQ==", + "node_modules/@codingame/monaco-vscode-262ed59d-4f76-57cd-9e9f-1877f26ae049-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-262ed59d-4f76-57cd-9e9f-1877f26ae049-common/-/monaco-vscode-262ed59d-4f76-57cd-9e9f-1877f26ae049-common-19.1.4.tgz", + "integrity": "sha512-J1lI68EQ1aQzeTTfpTbeXYq+/CQeuz2l1WL51V8FGNlATUWff1uoB3nnkkLkH3ib+z1ol+rRyTfA7AuR3X0thg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-210e86a9-a91b-5273-b05d-390c776dde1f-common": "16.1.1", - "@codingame/monaco-vscode-9a6d8b6c-ad4c-5ea3-9558-f43d6dc4c0ad-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-c4e2825a-b5b1-5f0e-b547-068c32e06d50-common": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-271a23cd-c7d7-5761-ae35-a923a42987b8-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-271a23cd-c7d7-5761-ae35-a923a42987b8-common/-/monaco-vscode-271a23cd-c7d7-5761-ae35-a923a42987b8-common-19.1.4.tgz", + "integrity": "sha512-yhnOF3NawoNEjQG3IG2CuKNM0BHCNtC+VOCdSoR8l/ViWP9IvxcU2v7iVRpNK1CpKFJTxu/UbBZgX/4M/qgZig==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common/-/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common-19.1.4.tgz", + "integrity": "sha512-heYpYKLywC51rXhm6xFtCbaLAJa+zN32UsPrS+0o8XnxVpJ+5s4wuguiiTV6YTgmQCMEFVHeS8nrBkhMg5F/zg==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-273022ab-b991-5d6a-9f04-decc266cb61b-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-273022ab-b991-5d6a-9f04-decc266cb61b-common/-/monaco-vscode-273022ab-b991-5d6a-9f04-decc266cb61b-common-19.1.4.tgz", + "integrity": "sha512-dOUG+16qtkcVYsk7SChzhmrL0ufSa+Kylnzov9jJYSm8AN5I2ldPr7ahobc8wQ1Z7momJhPB7+Do00vsWXqSBg==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-2a22c7b4-b906-5914-8cd1-3ed912fb738f-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-2a22c7b4-b906-5914-8cd1-3ed912fb738f-common/-/monaco-vscode-2a22c7b4-b906-5914-8cd1-3ed912fb738f-common-16.1.1.tgz", - "integrity": "sha512-9T6RXPoa0aNRMVRuxLFmfJ9NI+w8vbJ1k/+ssdHDC9Ev0PtIYmdcLTOl6Anq6i6mgeiK3BlkN6xKgj3inoC1hQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-2a22c7b4-b906-5914-8cd1-3ed912fb738f-common/-/monaco-vscode-2a22c7b4-b906-5914-8cd1-3ed912fb738f-common-19.1.4.tgz", + "integrity": "sha512-zgNLlXYGynFPptcaAGqtgU8XD6yzJDklznrVyDut111MIs7QOTddjGkmGbeW9hYjkBVINhrYGz66dR028vTs9w==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-2a94c04a-b85b-5669-b06b-89c1bfa11cb9-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-2a94c04a-b85b-5669-b06b-89c1bfa11cb9-common/-/monaco-vscode-2a94c04a-b85b-5669-b06b-89c1bfa11cb9-common-16.1.1.tgz", - "integrity": "sha512-lDbX0Z/Q6s6WN/LFvv9P6ak0iMHXmZbsyLqPoDSyzVOHyrejSpIzuG+8azreDMYFdstroRoU5sC8mOzBIsupow==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-2a94c04a-b85b-5669-b06b-89c1bfa11cb9-common/-/monaco-vscode-2a94c04a-b85b-5669-b06b-89c1bfa11cb9-common-19.1.4.tgz", + "integrity": "sha512-BR+Jv7TBfgcP3WJn5hbsSuuudGegQ8Yb66GvrTdV348Spns2HdknKzcKIPbGf8AhQdFTz+AeTcliqzaXkZUvSw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-4a3ac544-9a61-534c-88df-756262793ef7-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-e67a0dae-5b2c-54e6-8d61-90102c78362d-common": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-2b1a9082-790f-527f-b013-d1b29d6265a3-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-2b1a9082-790f-527f-b013-d1b29d6265a3-common/-/monaco-vscode-2b1a9082-790f-527f-b013-d1b29d6265a3-common-16.1.1.tgz", - "integrity": "sha512-wI/GwfpQSuJivSttrR7/zZC10VfnYJvho2MKYN+P50YJPwW6WOHilc4Dh0tvtEIZFnEliU3WsutY9rbHp/L/NA==", - "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-2bd6777a-3bff-5b83-ad25-0e029e81ffe9-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-2bd6777a-3bff-5b83-ad25-0e029e81ffe9-common/-/monaco-vscode-2bd6777a-3bff-5b83-ad25-0e029e81ffe9-common-16.1.1.tgz", - "integrity": "sha512-8F0XLlQgqsUQX4EaUcNIC8J5o8jKc3XBZQrV9qztaaZSvEMlUW85mcV7+RRxymwkbjuo5pExVv3EVEiuI9LHrQ==", - "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-2e67e044-0db9-5fa6-8bd1-3737a7d586d4-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-2e67e044-0db9-5fa6-8bd1-3737a7d586d4-common/-/monaco-vscode-2e67e044-0db9-5fa6-8bd1-3737a7d586d4-common-16.1.1.tgz", - "integrity": "sha512-nOGfG1JVaoGWWmpylaqV4F6zJ8HTyechymMMJNJpasIwp07AErdV2e4yCPxKbfTwMsxgOeROZK7ut7lSyaguNQ==", - "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1", - "@vscode/iconv-lite-umd": "0.7.0", - "jschardet": "3.1.4" + "@codingame/monaco-vscode-0b087f42-a5a3-5eb9-9bfd-1eebc1bba163-common": "19.1.4", + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-4a3ac544-9a61-534c-88df-756262793ef7-common": "19.1.4", + "@codingame/monaco-vscode-a8d3bd74-e63e-5327-96e8-4f931661e329-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-2f06fe84-148e-5e6b-a7ca-c7989c5f128a-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-2f06fe84-148e-5e6b-a7ca-c7989c5f128a-common/-/monaco-vscode-2f06fe84-148e-5e6b-a7ca-c7989c5f128a-common-16.1.1.tgz", - "integrity": "sha512-6wtBRaxJW1QILFOkyQzq8b7U60YZy4VnmjGmR+PuxvXqbtN5KWPkFygmJWtIg4GP9O6dSVEL7U7ynt8F9KaxTQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-2f06fe84-148e-5e6b-a7ca-c7989c5f128a-common/-/monaco-vscode-2f06fe84-148e-5e6b-a7ca-c7989c5f128a-common-19.1.4.tgz", + "integrity": "sha512-FJjDYJIMYBSDg24PdiuDWbA3AxYJiII9jsIOPVJk8+HasR0zvRE4sQ64SxTD5jaPlrmqcRzEBCH6HiufLdiC/w==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-3109a756-1f83-5d09-945b-9f0fcad928f0-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-3109a756-1f83-5d09-945b-9f0fcad928f0-common/-/monaco-vscode-3109a756-1f83-5d09-945b-9f0fcad928f0-common-16.1.1.tgz", - "integrity": "sha512-Owb9Y0AHyWAzWMODiwZl3A9kuPWQd/zo1OWvoRpA4mJ08ZbCfkOm5M9H53XAlQ0hqxg4lQSeACreMEJVAj0r5w==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-3109a756-1f83-5d09-945b-9f0fcad928f0-common/-/monaco-vscode-3109a756-1f83-5d09-945b-9f0fcad928f0-common-19.1.4.tgz", + "integrity": "sha512-V8pMcE9OibvywfwjNmiIoyajI2FxNeC9teA8FcVliqxF4+xBMcVWTQGyiGk9ZzxjptZ4F24d4yuWblv6Yimr1g==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-2e67e044-0db9-5fa6-8bd1-3737a7d586d4-common": "16.1.1", - "@codingame/monaco-vscode-336c86fe-5135-5343-af5f-c6d13bc09c07-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-b1e8558d-1726-5299-bc75-e43ee6d1a124-common": "16.1.1", - "@codingame/monaco-vscode-c4e2825a-b5b1-5f0e-b547-068c32e06d50-common": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-336c86fe-5135-5343-af5f-c6d13bc09c07-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-336c86fe-5135-5343-af5f-c6d13bc09c07-common/-/monaco-vscode-336c86fe-5135-5343-af5f-c6d13bc09c07-common-16.1.1.tgz", - "integrity": "sha512-5uAJyhYIdpK2QxmcvBkF5/ao2TbaaeLlL8LLim1u9xHdhbANvtlwT9S+G+as9eZ18UqwAfEWD7FfVXW7envPRg==", - "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1" + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-bc6d9a89-1625-5010-b57e-ff44151144fe-common": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4", + "@codingame/monaco-vscode-ddbd4d51-94af-51e5-96b5-f2166ed67ad0-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-33833ac7-3af3-5e9d-8fb9-11838d852c59-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-33833ac7-3af3-5e9d-8fb9-11838d852c59-common/-/monaco-vscode-33833ac7-3af3-5e9d-8fb9-11838d852c59-common-16.1.1.tgz", - "integrity": "sha512-TUUrDIykZzDRDATjqzi7HblxDdXyO5pfRIGVbF0vT5HVF4odrsV8Er7KPpUeODgSaFwXUXNl+VayZUvnMQkntQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-33833ac7-3af3-5e9d-8fb9-11838d852c59-common/-/monaco-vscode-33833ac7-3af3-5e9d-8fb9-11838d852c59-common-19.1.4.tgz", + "integrity": "sha512-VDPSjUu6LOmIh9kR8Zf1rRxZsHbCcLPqUXc6s1O3aCypICMj4kq3fmdr0S2q1+FkXlIT77N466gzOFEjBGaMYw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-38f25ab8-ea30-5ba9-8a08-ae3308c297c0-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-38f25ab8-ea30-5ba9-8a08-ae3308c297c0-common/-/monaco-vscode-38f25ab8-ea30-5ba9-8a08-ae3308c297c0-common-16.1.1.tgz", - "integrity": "sha512-YWdVvdNKb0q4ab2aiEBEkmS/o7EBY5QDSt/QpHv3P/6pcS23JUqkF/4s6h6oBPHJeDzLuWottpFw44ghkNPpTg==", + "node_modules/@codingame/monaco-vscode-33de3d75-8b1a-53f0-9e27-0c71c327e1a1-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-33de3d75-8b1a-53f0-9e27-0c71c327e1a1-common/-/monaco-vscode-33de3d75-8b1a-53f0-9e27-0c71c327e1a1-common-19.1.4.tgz", + "integrity": "sha512-TR1HgMPvM1qzLx+whiN0x195iGMGgA8WBAXS8OCLuNd896P/odfSkykGlzse3Vq1A1SfIxGZbnGXd89FPDncow==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-336c86fe-5135-5343-af5f-c6d13bc09c07-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1" + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-501b06ab-3f58-516b-8a1a-c29d375d3da4-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4", + "@codingame/monaco-vscode-da4efea3-7112-5351-9c1d-60017cbd9532-common": "19.1.4", + "@codingame/monaco-vscode-dbfe5f85-b426-55ed-a79b-5f811b395762-common": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-3e369455-cc41-54c6-86fb-8b7cd999a5da-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-3e369455-cc41-54c6-86fb-8b7cd999a5da-common/-/monaco-vscode-3e369455-cc41-54c6-86fb-8b7cd999a5da-common-16.1.1.tgz", - "integrity": "sha512-WAc4sfnzaITnHTmXyUZF1q6PWjN3JkAA9FUioQfMUu+sTuaULbXikCqY2sWSO0f38dPydfBuI2Ohf05Rlq4jEw==", + "node_modules/@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common/-/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common-19.1.4.tgz", + "integrity": "sha512-8FNy0ozAjxRALMMN9Z3iMp4omhdvYDH6qfCbbNBAdIsyMcrgoz58PmWYds6dpPjDCCKorClvbSs2ATCETPFzdg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-3927dd53-f845-534c-9baa-1528ff3956c8-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-3927dd53-f845-534c-9baa-1528ff3956c8-common/-/monaco-vscode-3927dd53-f845-534c-9baa-1528ff3956c8-common-19.1.4.tgz", + "integrity": "sha512-7o90xqRpPcool4amFY/BNERAafNfuYIQPVYf3vE4fvIubsWWNW6qYMqb0GgjlBcLIm8PM2pvS11QpBIsGKRQGw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-210e86a9-a91b-5273-b05d-390c776dde1f-common": "19.1.4", + "@codingame/monaco-vscode-9911aec2-e4dd-5483-8369-9d695cc70d8a-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-c465110a-57c0-59d7-a6b2-be0a4db7e517-common": "19.1.4", + "@codingame/monaco-vscode-ddbd4d51-94af-51e5-96b5-f2166ed67ad0-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common/-/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common-16.1.1.tgz", - "integrity": "sha512-UCAcIJ0Qd7KCjH/fs/pbm3TlL+0Zm8mQwmmR/RDQiZXg7LcdSP+92anqslkb+yAq3quJWGbhlnjhChzkQvB00w==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common/-/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common-19.1.4.tgz", + "integrity": "sha512-qd7w4VGLFMoQ8/ySnY/XvJx+VAi+IpldEvbjSdJ4mGR2QPY/uvqYaWfg0PbYBsSJjEeyx6P1wopnwawx3znmFQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-7559b0be-bfa5-5fe6-b731-1973fe9fffa1-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common/-/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common-16.1.1.tgz", - "integrity": "sha512-IJj1N67jVIayeR94HFvYfwoveNVRhFXe6N2fRphbo5Gun69F+cwLFpT8c79NKIshP80txh8iHRjqraaVSM1p7A==", - "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-158b9837-fc78-5d9c-86f5-9134e4358643-common": "19.1.4", + "@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-422642f2-7e3a-5c1c-9e1e-1d3ef1817346-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-422642f2-7e3a-5c1c-9e1e-1d3ef1817346-common/-/monaco-vscode-422642f2-7e3a-5c1c-9e1e-1d3ef1817346-common-16.1.1.tgz", - "integrity": "sha512-P/l/oHqF4f4sWOjGdYXEzv6mB2Fu5wdXHcVe1IycPbAkDa+wgBA57hhEY5SmTNAPZPXVIc9JKwmViy2iKPcbhQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-422642f2-7e3a-5c1c-9e1e-1d3ef1817346-common/-/monaco-vscode-422642f2-7e3a-5c1c-9e1e-1d3ef1817346-common-19.1.4.tgz", + "integrity": "sha512-WmWWhRs9WrMXrF128+CWxVbaisRk36UEzn4RX4beGhGBG4SnTsYe7/zIQdajLCdELh6MTbrI52MqbRUFei4JLg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-42931eb9-e564-530c-bafc-fa23ab43a070-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-42931eb9-e564-530c-bafc-fa23ab43a070-common/-/monaco-vscode-42931eb9-e564-530c-bafc-fa23ab43a070-common-16.1.1.tgz", - "integrity": "sha512-2Dcv0WMdgMJkb+twQTEDea5347r3stLNhYrjnMd1p6rhi+mZH7N70F+EmhC7oug7u0DMYpABn7bAtNiioEsh8Q==", - "dependencies": { - "@codingame/monaco-vscode-0b087f42-a5a3-5eb9-9bfd-1eebc1bba163-common": "16.1.1", - "@codingame/monaco-vscode-29bc1406-2925-5b8f-b25e-d04a7772d896-common": "16.1.1", - "@codingame/monaco-vscode-2bd6777a-3bff-5b83-ad25-0e029e81ffe9-common": "16.1.1", - "@codingame/monaco-vscode-9a6d8b6c-ad4c-5ea3-9558-f43d6dc4c0ad-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common/-/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common-16.1.1.tgz", - "integrity": "sha512-UEE4ZWJhkWj6zX3u60UPSWIqENLacpJ3YYTUdGYF19c/4+sR4NsNdk0coLOzNdtIjQ2MIvVYfSXHQKfzRpSHMw==", - "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-494be54c-bd37-5b3c-af70-02f086e28768-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-494be54c-bd37-5b3c-af70-02f086e28768-common/-/monaco-vscode-494be54c-bd37-5b3c-af70-02f086e28768-common-16.1.1.tgz", - "integrity": "sha512-a5U8XEaM7GVQMtchjCw4+utFycCc2fJvnt9P3vxdDZZatyqKrekGDK5vERzSRa9y0QlHrX3fgPDIi3bsfI6Duw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-494be54c-bd37-5b3c-af70-02f086e28768-common/-/monaco-vscode-494be54c-bd37-5b3c-af70-02f086e28768-common-19.1.4.tgz", + "integrity": "sha512-+6y3v27chjQRdBBhnUiDfDZozd7wSXJMOnvi374huyvPvroyVAM7Zat9dx8A7pldYjVtF552Zu9Z/fQ9qqtW/w==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-4a316137-39d1-5d77-8b53-112db3547c1e-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-4a316137-39d1-5d77-8b53-112db3547c1e-common/-/monaco-vscode-4a316137-39d1-5d77-8b53-112db3547c1e-common-16.1.1.tgz", - "integrity": "sha512-R2qy4sPlhmZOBlz41OgymJVCTKjhYx0VHcF6h7O5FTvTLIsaFHIxx/p0D0Ws0SlqonNd7+aziXSN1tcmclYTLQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-4a316137-39d1-5d77-8b53-112db3547c1e-common/-/monaco-vscode-4a316137-39d1-5d77-8b53-112db3547c1e-common-19.1.4.tgz", + "integrity": "sha512-c8g/G1QV9Wu/KpUNRE5gEaUC8Eee/lUL1atGAUZzbsYQOBS7DPT9SUOzzSJmvsPIfmaxDvd5Y6sQkWDRz+AR3Q==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-38f25ab8-ea30-5ba9-8a08-ae3308c297c0-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-65619f8f-0eab-5d8b-855a-43b6353fe527-common": "16.1.1", - "@codingame/monaco-vscode-a7c9ae3c-16d2-5d17-86b2-981be7094566-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-501b06ab-3f58-516b-8a1a-c29d375d3da4-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-937ecbdf-94c7-5b16-aefa-ad78ae557a93-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-dbfe5f85-b426-55ed-a79b-5f811b395762-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-4a3ac544-9a61-534c-88df-756262793ef7-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-4a3ac544-9a61-534c-88df-756262793ef7-common/-/monaco-vscode-4a3ac544-9a61-534c-88df-756262793ef7-common-16.1.1.tgz", - "integrity": "sha512-ktskOhaBisQE9OuktmiW4OhFYgBJvG0pttdAVFfHAKWgBnwWcy6FETIHMwymUWWb+P7PW15Vr/Bdyhi2phb4kg==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-4a3ac544-9a61-534c-88df-756262793ef7-common/-/monaco-vscode-4a3ac544-9a61-534c-88df-756262793ef7-common-19.1.4.tgz", + "integrity": "sha512-14t2Jav1nsc+WyyAcjNIuGpVOzIpjyr1FNmqxZZCC77kOLCuqffRDacl88kKZBXp/ozj2LSid4WfSgIqrdugWQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-501b06ab-3f58-516b-8a1a-c29d375d3da4-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-501b06ab-3f58-516b-8a1a-c29d375d3da4-common/-/monaco-vscode-501b06ab-3f58-516b-8a1a-c29d375d3da4-common-19.1.4.tgz", + "integrity": "sha512-jrJxIBlzhuOM8dQX7jzHv7Mc4QiDs5VPCutG0sdEq6Gh+aKU9pwnAmeBwTOuEgfVA+WfrTLfYX2tlzNDkonSFw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-516ccb00-d5b9-5317-baf0-fcc96e99669a-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-516ccb00-d5b9-5317-baf0-fcc96e99669a-common/-/monaco-vscode-516ccb00-d5b9-5317-baf0-fcc96e99669a-common-19.1.4.tgz", + "integrity": "sha512-Os2XUaX6jvXAKtYCf5EyXhrloQDTATp72ri3KsqwAK8lVzG0ZV5uIt+/a3GhXgizqmW5BPPqwK7gv1c3a3UcZQ==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-523730aa-81e6-55d7-9916-87ad537fe087-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-523730aa-81e6-55d7-9916-87ad537fe087-common/-/monaco-vscode-523730aa-81e6-55d7-9916-87ad537fe087-common-16.1.1.tgz", - "integrity": "sha512-aq5I4O1Z3hqxG8V15Xa2V0UMFVIFp8dyfyUxWvxu2xux6IxpUn/zwpvLAHhKE9NvGJtIcAfdJA/5QRWkYh/pxA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-523730aa-81e6-55d7-9916-87ad537fe087-common/-/monaco-vscode-523730aa-81e6-55d7-9916-87ad537fe087-common-19.1.4.tgz", + "integrity": "sha512-JQljvSNph3Hf/yAgQvydDuE/YcpISi+tnV6Vnnuaj7nclBiKpSUPT4VIj41nmGrltjgITDvg91yPKnd/9cLClw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-c4e2825a-b5b1-5f0e-b547-068c32e06d50-common": "16.1.1" + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4", + "@codingame/monaco-vscode-ddbd4d51-94af-51e5-96b5-f2166ed67ad0-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-5452e2b7-9081-5f95-839b-4ab3544ce28f-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-5452e2b7-9081-5f95-839b-4ab3544ce28f-common/-/monaco-vscode-5452e2b7-9081-5f95-839b-4ab3544ce28f-common-16.1.1.tgz", - "integrity": "sha512-fd0ZNX29deKMuAtzpqRGwq4GdVMOn8ZoAWrgiKh1qJ+xi5HOaXvgwN7fZ8IFI/btbuWFNfCaVHrV97d2uCmE5Q==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-5452e2b7-9081-5f95-839b-4ab3544ce28f-common/-/monaco-vscode-5452e2b7-9081-5f95-839b-4ab3544ce28f-common-19.1.4.tgz", + "integrity": "sha512-BuW+ukQupZ8FA62KbqYpXtDUJbW2ue70PIOyE6iO+GaRaLWKKxb570uYTGFMpNhaEquRdob9Zu3wnucC/mQH8g==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-1cc4ea0a-c5b6-54ed-bb60-078a99119b55-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-1cc4ea0a-c5b6-54ed-bb60-078a99119b55-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-65619f8f-0eab-5d8b-855a-43b6353fe527-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-65619f8f-0eab-5d8b-855a-43b6353fe527-common/-/monaco-vscode-65619f8f-0eab-5d8b-855a-43b6353fe527-common-16.1.1.tgz", - "integrity": "sha512-9esf2JI7cyATZ8OjcL7ZedAiHMbg9aUoUe/17js5w3LXuzxbXLTER5Icy0HopFOWlyH7F7ixwap09QRW0UjWiA==", + "node_modules/@codingame/monaco-vscode-55041ed9-a1ea-544a-883d-08b0b26934f3-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-55041ed9-a1ea-544a-883d-08b0b26934f3-common/-/monaco-vscode-55041ed9-a1ea-544a-883d-08b0b26934f3-common-19.1.4.tgz", + "integrity": "sha512-nfRpPcqmwELPWanx44F2jfn7i2AGADchD3Z08M5sdi/y+7whFqlddF16mNIAKv+a59y1fYQdQVIRC2cW8MRI+g==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-33de3d75-8b1a-53f0-9e27-0c71c327e1a1-common": "19.1.4", + "@codingame/monaco-vscode-501b06ab-3f58-516b-8a1a-c29d375d3da4-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-72a1b7d3-3f58-5545-9b7e-f579bd003081-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-dbfe5f85-b426-55ed-a79b-5f811b395762-common": "19.1.4", + "@codingame/monaco-vscode-f24e325c-2ce0-5bba-8236-bfc4f53180ab-common": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-571c8352-7953-5038-9f09-e03bb6219a0e-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-571c8352-7953-5038-9f09-e03bb6219a0e-common/-/monaco-vscode-571c8352-7953-5038-9f09-e03bb6219a0e-common-19.1.4.tgz", + "integrity": "sha512-PXWcAfk40MZ1DUB++c+1f/BYXa2wTk934T8rvLC5HgN/oM4dWfVWUsGlr3n0NE/8C8UZHdEu4BSW8OEMQziXIQ==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-249dc928-1da3-51c1-82d0-45e0ba9d08a1-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common/-/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common-19.1.4.tgz", + "integrity": "sha512-/kV1r4OrA4mhUil4ulni7Knbcw1F76OHcPO2iMRMmWWdgUU8389vLSUXN/+32zpb3/pIDUTabSJzSJVrugiw5w==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-6845754f-e617-5ed9-8aaa-6ca3653a9532-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-6845754f-e617-5ed9-8aaa-6ca3653a9532-common/-/monaco-vscode-6845754f-e617-5ed9-8aaa-6ca3653a9532-common-16.1.1.tgz", - "integrity": "sha512-XLdVybsir3d0YtX2NbGXhwOok+QPptcodHu5dDeN6JXbSXTTK9ZcCLh8zl25Zjqw/v45yZm6md6SUh/li5zN2A==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-6845754f-e617-5ed9-8aaa-6ca3653a9532-common/-/monaco-vscode-6845754f-e617-5ed9-8aaa-6ca3653a9532-common-19.1.4.tgz", + "integrity": "sha512-azBjvwdUsKqkZe+D6AOsbp4sK0Kin0jsim9VYx9C0Q7Jb4Q6e35uriqXF7Au3wkCIEwoOklzEYbfqRohMmyCJA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common/-/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common-16.1.1.tgz", - "integrity": "sha512-sj7MQLU88YG43VZi64yuRafIXSdH0XRH9f7Hx4iL6eTwu5UFMwP1PusOoVox+uNBZ5ArrV+BbdNRsXbP3PUTug==", - "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-6980eeab-47bb-5a48-8e15-32caf0785565-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-6980eeab-47bb-5a48-8e15-32caf0785565-common/-/monaco-vscode-6980eeab-47bb-5a48-8e15-32caf0785565-common-16.1.1.tgz", - "integrity": "sha512-fctirpqR8NvDLFf1aE2XL2YTBn8OVBSAfUymWV98huYm5LOuwaUAQmSLnFtg5nD6wAZKA/GC0A6gGUl0CADIUg==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-6980eeab-47bb-5a48-8e15-32caf0785565-common/-/monaco-vscode-6980eeab-47bb-5a48-8e15-32caf0785565-common-19.1.4.tgz", + "integrity": "sha512-Dj5mojheMBnXJZLyiGYQTbWWzcDpwwNHNZD0MQxeEKA8WOyFCC8OCm0jqco5dGRYKqfMwWYzYbl2LDWJRtGMdw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-0cc5da60-f921-59b9-bd8c-a018e93c0a6f-common": "16.1.1", - "@codingame/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-65619f8f-0eab-5d8b-855a-43b6353fe527-common": "16.1.1", - "@codingame/monaco-vscode-6bf85d7b-e6e3-54e9-9bc1-7e08d663f0f6-common": "16.1.1", - "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "16.1.1", - "@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common": "16.1.1", - "@codingame/monaco-vscode-ad89fae6-94f0-5ac2-a185-22dea4b68ee0-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-e67a0dae-5b2c-54e6-8d61-90102c78362d-common": "16.1.1" + "@codingame/monaco-vscode-0cc5da60-f921-59b9-bd8c-a018e93c0a6f-common": "19.1.4", + "@codingame/monaco-vscode-33de3d75-8b1a-53f0-9e27-0c71c327e1a1-common": "19.1.4", + "@codingame/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common": "19.1.4", + "@codingame/monaco-vscode-501b06ab-3f58-516b-8a1a-c29d375d3da4-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-6bf85d7b-e6e3-54e9-9bc1-7e08d663f0f6-common": "19.1.4", + "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "19.1.4", + "@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common": "19.1.4", + "@codingame/monaco-vscode-a8d3bd74-e63e-5327-96e8-4f931661e329-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-6bf85d7b-e6e3-54e9-9bc1-7e08d663f0f6-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-6bf85d7b-e6e3-54e9-9bc1-7e08d663f0f6-common/-/monaco-vscode-6bf85d7b-e6e3-54e9-9bc1-7e08d663f0f6-common-16.1.1.tgz", - "integrity": "sha512-o/uZVRvXdQq2aNi+oua41fhd+0T/VL/KG/y8+cK9HUCjyqN+oBxn0J99fWNN1S3P1CyHg0CsPuuK47znVPawug==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-6bf85d7b-e6e3-54e9-9bc1-7e08d663f0f6-common/-/monaco-vscode-6bf85d7b-e6e3-54e9-9bc1-7e08d663f0f6-common-19.1.4.tgz", + "integrity": "sha512-fb/vitFdW+KMl8PeOdutjbQ36AJI9cD2U+ppPHGTPRdy/zApa7BSjWhOvmsNZSPzWnl8Zqy6NfMZr7hN0yDjdg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-0cc5da60-f921-59b9-bd8c-a018e93c0a6f-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "16.1.1", - "@codingame/monaco-vscode-96e83782-7f38-572e-8787-02e981f1c54f-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-f22e7e55-aee8-5b52-a6bc-950efd9f5890-common": "16.1.1" + "@codingame/monaco-vscode-0cc5da60-f921-59b9-bd8c-a018e93c0a6f-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "19.1.4", + "@codingame/monaco-vscode-96e83782-7f38-572e-8787-02e981f1c54f-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-f22e7e55-aee8-5b52-a6bc-950efd9f5890-common": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-6ec8cd23-5be5-5c15-93ce-01afcf65acd3-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-6ec8cd23-5be5-5c15-93ce-01afcf65acd3-common/-/monaco-vscode-6ec8cd23-5be5-5c15-93ce-01afcf65acd3-common-16.1.1.tgz", - "integrity": "sha512-8Sz0jYQb5Y2uJX/OhMt+CqdhK4KfdDYyCh+KWA4Z8/3HG/dPMrlSEIFAAQIS9WwBuJVX2OdTJdDQ9UD80AgmPw==", + "node_modules/@codingame/monaco-vscode-6f931a91-88ea-5232-897f-a17ec3929ba5-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-6f931a91-88ea-5232-897f-a17ec3929ba5-common/-/monaco-vscode-6f931a91-88ea-5232-897f-a17ec3929ba5-common-19.1.4.tgz", + "integrity": "sha512-7+Zv28j+oGT8Lfj6ZTYn2zGfRZ/f/RxgTByRyOYDPN/GwtdBo7NiPR2dRDoMkjRKZKDohom1ITDQ/7EMekxhnA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-271a23cd-c7d7-5761-ae35-a923a42987b8-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-7559b0be-bfa5-5fe6-b731-1973fe9fffa1-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-7559b0be-bfa5-5fe6-b731-1973fe9fffa1-common/-/monaco-vscode-7559b0be-bfa5-5fe6-b731-1973fe9fffa1-common-16.1.1.tgz", - "integrity": "sha512-/hZQbB+cY/sSvumJ38riiMYrcjFU2OruM/QejgGFtSMXFoXQ1qjZRmR4NrEPvHjUHeibW8t5Z3NSi5UIYKPDEw==", + "node_modules/@codingame/monaco-vscode-72a1b7d3-3f58-5545-9b7e-f579bd003081-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-72a1b7d3-3f58-5545-9b7e-f579bd003081-common/-/monaco-vscode-72a1b7d3-3f58-5545-9b7e-f579bd003081-common-19.1.4.tgz", + "integrity": "sha512-MuAJjta2rNgwUgVi5eR9JrpBvKb98/gTFyUCgOZeOWJS3qn0bB4SaE4tgwy0fGzfMrhTs7KzEnSJBzXp+I+J1A==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4", + "@codingame/monaco-vscode-f24e325c-2ce0-5bba-8236-bfc4f53180ab-common": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-78982ab1-8dc8-5de0-9139-e8865a161f5a-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-78982ab1-8dc8-5de0-9139-e8865a161f5a-common/-/monaco-vscode-78982ab1-8dc8-5de0-9139-e8865a161f5a-common-16.1.1.tgz", - "integrity": "sha512-J9cK32E9vsLiR4g2MIWtIgc9kbtRFselUKWJDJdc/zIVXPq7uYQ5PReDyNqYn0W9RMtxehKLZvK/eXl7x+GbbQ==", + "node_modules/@codingame/monaco-vscode-7869cfe8-f42c-5721-9f2b-7d04a6a41f16-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-7869cfe8-f42c-5721-9f2b-7d04a6a41f16-common/-/monaco-vscode-7869cfe8-f42c-5721-9f2b-7d04a6a41f16-common-19.1.4.tgz", + "integrity": "sha512-pZMcSoXWbBSysm5Z5l4yOm7oJNrtkyO5+1ZWuSQUgA6H6CGO45/zZmv43YGhiV+H2OT1CguUoDR/lI+NVYVk5w==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-38f25ab8-ea30-5ba9-8a08-ae3308c297c0-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-7ba0af96-90c2-5e11-ad7f-befdbbf246c8-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-7ba0af96-90c2-5e11-ad7f-befdbbf246c8-common/-/monaco-vscode-7ba0af96-90c2-5e11-ad7f-befdbbf246c8-common-16.1.1.tgz", - "integrity": "sha512-alboDbhalDQxDnLVLWoFEjWUEidjzs0o4BVDaDDcxO5HsKnxoVZXaoxdlrUQa21a78CwBm6/63zpFCF78OsK/g==", - "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-7bbc9e7d-eeae-55fc-8bf9-dc2f66e0dc73-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-7bbc9e7d-eeae-55fc-8bf9-dc2f66e0dc73-common/-/monaco-vscode-7bbc9e7d-eeae-55fc-8bf9-dc2f66e0dc73-common-16.1.1.tgz", - "integrity": "sha512-iRDD9i+FFD+IUJx2zB+sPgJ9j2Gy8HsZd+LiQxP34sJD6o2rq4lx3IXA+TWfAoOGRmyQ+fNvINBxRBrzaZ3PrQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-7bbc9e7d-eeae-55fc-8bf9-dc2f66e0dc73-common/-/monaco-vscode-7bbc9e7d-eeae-55fc-8bf9-dc2f66e0dc73-common-19.1.4.tgz", + "integrity": "sha512-QY+1z4GnSMMrfxUNTMS1Px02n7Lrfg/7r89LX/vQQRybSClBEBcpMFr/B/G/0to4JJw2ZgFoqDkPaYvYPtYfrQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-7f39b6f1-3542-5430-8760-0f404d7a7cee-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-7f39b6f1-3542-5430-8760-0f404d7a7cee-common/-/monaco-vscode-7f39b6f1-3542-5430-8760-0f404d7a7cee-common-16.1.1.tgz", - "integrity": "sha512-RT1uQmhMr23Hfr7LEfgI42m+F9fn5srv1xqYSibX4/4BdcA7oqQQZAPO1z2n9Bo6TnEflZFf3w6BJadHErTynw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-7f39b6f1-3542-5430-8760-0f404d7a7cee-common/-/monaco-vscode-7f39b6f1-3542-5430-8760-0f404d7a7cee-common-19.1.4.tgz", + "integrity": "sha512-cwSF5t4epEggihsVua23pTlNXnkNbT8z6jfFIPy7hcBOoTbZd9w/E2rvbmO5JG5CRh55IpgAqMdEJttaY8eGaA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-9b5a5e82-d649-5455-b4bf-ef90d6afd294-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-805e9c2f-56b6-5a43-8b5b-d2dc2d3805fc-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-805e9c2f-56b6-5a43-8b5b-d2dc2d3805fc-common/-/monaco-vscode-805e9c2f-56b6-5a43-8b5b-d2dc2d3805fc-common-16.1.1.tgz", - "integrity": "sha512-ZcSAw15rOgbyQJN5ZuxyVLht8wfsajRIAelBu4MZVy1jzZsNSExJuxiftAhJQ2vj71nbc7z56QO0RcIV2uY57g==", - "dependencies": { - "@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common": "16.1.1", - "@codingame/monaco-vscode-7559b0be-bfa5-5fe6-b731-1973fe9fffa1-common": "16.1.1", - "@codingame/monaco-vscode-7ba0af96-90c2-5e11-ad7f-befdbbf246c8-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-271a23cd-c7d7-5761-ae35-a923a42987b8-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common/-/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common-16.1.1.tgz", - "integrity": "sha512-DuhmEu/XOTvWDRbpoJ0Q0a6UwllpFyIF0lJHw4OWquOrnrRlMLH8xZHOToup7UxSwYey1UC73QAvBBRoEILj7w==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common/-/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common-19.1.4.tgz", + "integrity": "sha512-8cvOSb7DU6TOcLnOfQQ9HfyqE+QpXA23iSgWdiW/7wPEWDwOmBU06ONxuPTULqzRVVnAgqdWUyr80a0+Oz1DSw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common/-/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common-16.1.1.tgz", - "integrity": "sha512-KQohEm8yRSwEGAb+h0565NLjnDL9AoluaueOBiwp1qfwDJwtt2+V0RJ9GKB4G88zdCYZyzKKmcFC/4WaDaG7nw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common/-/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common-19.1.4.tgz", + "integrity": "sha512-ccLyaE2qMzECIFtkShOrpdRbkvo451fgFUHHCYZJ1YUiGLhLjjigso9HJZY4lsvekPqp7Z/sUnwghGmCIfMYXQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-897bebad-39df-57cb-8a57-36a271d038be-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-897bebad-39df-57cb-8a57-36a271d038be-common/-/monaco-vscode-897bebad-39df-57cb-8a57-36a271d038be-common-16.1.1.tgz", - "integrity": "sha512-e8iFOJ7Wg8KxZF2jhvSWJB6YFhVSSlqFs7n+AZi00M0eLYDvEINAXYOBHrtifciXMmXAn3qVEP2hB2GksGi9xA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-897bebad-39df-57cb-8a57-36a271d038be-common/-/monaco-vscode-897bebad-39df-57cb-8a57-36a271d038be-common-19.1.4.tgz", + "integrity": "sha512-wDHIukvBCbCy3dlM6uRrNdmOocO6qRWYzZk3qCM3S2n3n4bkgbWvoiR1hGejXKdfwmGEEWZ9DzS0lRvntwfWgQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-9b5a5e82-d649-5455-b4bf-ef90d6afd294-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-bba55be6-41a2-50cd-a3cc-8bafa35bfa89-common": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-89a82baf-8ded-5b2f-b8af-e5fbd72dc5ad-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-89a82baf-8ded-5b2f-b8af-e5fbd72dc5ad-common/-/monaco-vscode-89a82baf-8ded-5b2f-b8af-e5fbd72dc5ad-common-16.1.1.tgz", - "integrity": "sha512-xPgOnfxY3ocGMhEO/3IImt7F7tCGegNBmIbdkJjkIhGnNwNgRn6YXWecblhN+5K4n1SkbMLzmye0D3QR4aB5oA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-89a82baf-8ded-5b2f-b8af-e5fbd72dc5ad-common/-/monaco-vscode-89a82baf-8ded-5b2f-b8af-e5fbd72dc5ad-common-19.1.4.tgz", + "integrity": "sha512-gSraX0+VILng5O4krMvOsPIUs7pcBnjPHvrNPx2OZ3m1kxZqh066zJNsqA8OXJxL19OUWWUtqVY7MXCqVJX0CA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-256d5b78-0649-50e9-8354-2807f95f68f4-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-256d5b78-0649-50e9-8354-2807f95f68f4-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-8ccb7637-50ea-5359-97bf-00015d7fe567-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-8ccb7637-50ea-5359-97bf-00015d7fe567-common/-/monaco-vscode-8ccb7637-50ea-5359-97bf-00015d7fe567-common-16.1.1.tgz", - "integrity": "sha512-BxX5OFrJju3tU+jfuhnqYAJArUIrm9glMKhKpZkbKaCugjUzh+ICh+HewdMjmjwXz13RLAGc12QowIUG0wyPKA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-8ccb7637-50ea-5359-97bf-00015d7fe567-common/-/monaco-vscode-8ccb7637-50ea-5359-97bf-00015d7fe567-common-19.1.4.tgz", + "integrity": "sha512-v6nKKPFRdL4/WX6wYytqWuxDdf0rgRMCQHYP51Ni0e55Pe6KDqcKvdheeo0NcxNBz74aOc73Z1HuQ4a48kd9Ng==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-210e86a9-a91b-5273-b05d-390c776dde1f-common": "16.1.1", - "@codingame/monaco-vscode-2bd6777a-3bff-5b83-ad25-0e029e81ffe9-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-897bebad-39df-57cb-8a57-36a271d038be-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-210e86a9-a91b-5273-b05d-390c776dde1f-common": "19.1.4", + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-897bebad-39df-57cb-8a57-36a271d038be-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-937ecbdf-94c7-5b16-aefa-ad78ae557a93-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-937ecbdf-94c7-5b16-aefa-ad78ae557a93-common/-/monaco-vscode-937ecbdf-94c7-5b16-aefa-ad78ae557a93-common-19.1.4.tgz", + "integrity": "sha512-W80UQUbTpUU78IfmklSHW2K3KoJNyl1vXycsjzRIvT7/kJlQ85xFpJzp9SlnUOMdbZb5CEqhrUof5MQ0w3sfpg==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-96e83782-7f38-572e-8787-02e981f1c54f-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-96e83782-7f38-572e-8787-02e981f1c54f-common/-/monaco-vscode-96e83782-7f38-572e-8787-02e981f1c54f-common-16.1.1.tgz", - "integrity": "sha512-EDEE1jc5nl8HTo58Kywvu0++sDx7R0fQFNfwWlhjbiKw8+yFGO9lVO1q+VY+cVhHP6IoPXdrTSlkMVN/rAybOw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-96e83782-7f38-572e-8787-02e981f1c54f-common/-/monaco-vscode-96e83782-7f38-572e-8787-02e981f1c54f-common-19.1.4.tgz", + "integrity": "sha512-MJ0af1uvXwpTFtQmdgkA5P71RW5JKzyF4yZBfemLsturcTUBkzhVVEH8+qpsszeVQZT82TSuSG32F/cmwFxhnQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-6ec8cd23-5be5-5c15-93ce-01afcf65acd3-common": "16.1.1", - "@codingame/monaco-vscode-7559b0be-bfa5-5fe6-b731-1973fe9fffa1-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-f22e7e55-aee8-5b52-a6bc-950efd9f5890-common": "16.1.1" + "@codingame/monaco-vscode-158b9837-fc78-5d9c-86f5-9134e4358643-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-e7671ac2-ad6e-5d29-8b2e-8bc090939746-common": "19.1.4", + "@codingame/monaco-vscode-f22e7e55-aee8-5b52-a6bc-950efd9f5890-common": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-9911aec2-e4dd-5483-8369-9d695cc70d8a-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9911aec2-e4dd-5483-8369-9d695cc70d8a-common/-/monaco-vscode-9911aec2-e4dd-5483-8369-9d695cc70d8a-common-19.1.4.tgz", + "integrity": "sha512-zPEcqSaR+9idJQlm77JvgbXDK3M0fOIKpaYiRqN+gnC0yEqRHhAEL/so6BeMvjlZVyU3wwwQ9yW4lJSlec+aFw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-262ed59d-4f76-57cd-9e9f-1877f26ae049-common": "19.1.4", + "@codingame/monaco-vscode-9ebd6daf-8734-5a28-ad24-b238f5e5819d-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-e7671ac2-ad6e-5d29-8b2e-8bc090939746-common": "19.1.4", + "marked": "14.0.0" } }, "node_modules/@codingame/monaco-vscode-9a1a5840-af83-5d07-a156-ba32a36c5c4b-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9a1a5840-af83-5d07-a156-ba32a36c5c4b-common/-/monaco-vscode-9a1a5840-af83-5d07-a156-ba32a36c5c4b-common-16.1.1.tgz", - "integrity": "sha512-09YsvgMmjTCsdmtrtnA2GVTh78rkmf1UOOou7bxRlBmJmL6iue/YqOBfeaFxAuSgLnRauoAzRCtGeWRQ8R1DUA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9a1a5840-af83-5d07-a156-ba32a36c5c4b-common/-/monaco-vscode-9a1a5840-af83-5d07-a156-ba32a36c5c4b-common-19.1.4.tgz", + "integrity": "sha512-S896GGxMwhhrG6+MCZ6cVTLgzLwRuQ67kvx5l8d4fRRRX+UX1p1io0y4dbrthE8PO9spQci9zpbdaMqjqUt5Sw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-9a6d8b6c-ad4c-5ea3-9558-f43d6dc4c0ad-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9a6d8b6c-ad4c-5ea3-9558-f43d6dc4c0ad-common/-/monaco-vscode-9a6d8b6c-ad4c-5ea3-9558-f43d6dc4c0ad-common-16.1.1.tgz", - "integrity": "sha512-6GpbLvuW6n/V1fBjyZMjL5vjwrevfpoZc+/7JCdm3aQphFvw6EAhkVZsPL2L/ZhMy8oVbrtOp+h7bwOW565ehA==", + "node_modules/@codingame/monaco-vscode-9c1add21-d437-5ee3-ae2b-ad115a9ef171-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9c1add21-d437-5ee3-ae2b-ad115a9ef171-common/-/monaco-vscode-9c1add21-d437-5ee3-ae2b-ad115a9ef171-common-19.1.4.tgz", + "integrity": "sha512-sI2eFeDQDoLMcpm3js9e0W7a8wjjiZgK5Fyk/A/zsy96DO+n2bIFCDcPJhIv9tPQtgOoe3RIJWAJHonVLm8MXw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-2b1a9082-790f-527f-b013-d1b29d6265a3-common": "16.1.1", - "@codingame/monaco-vscode-2bd6777a-3bff-5b83-ad25-0e029e81ffe9-common": "16.1.1", - "@codingame/monaco-vscode-3e369455-cc41-54c6-86fb-8b7cd999a5da-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-65619f8f-0eab-5d8b-855a-43b6353fe527-common": "16.1.1", - "@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common": "16.1.1", - "@codingame/monaco-vscode-6ec8cd23-5be5-5c15-93ce-01afcf65acd3-common": "16.1.1", - "@codingame/monaco-vscode-7ba0af96-90c2-5e11-ad7f-befdbbf246c8-common": "16.1.1", - "@codingame/monaco-vscode-805e9c2f-56b6-5a43-8b5b-d2dc2d3805fc-common": "16.1.1", - "@codingame/monaco-vscode-9b5a5e82-d649-5455-b4bf-ef90d6afd294-common": "16.1.1", - "@codingame/monaco-vscode-a1eb43d5-da63-5f76-8ad4-acf0d6c3b749-common": "16.1.1", - "@codingame/monaco-vscode-a3f28a41-ba19-5a7e-8f5a-d6c1403b507d-common": "16.1.1", - "@codingame/monaco-vscode-ae8a8ca1-f243-508b-9c37-c88ebbd295fa-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-b73d4be0-4f5e-582d-a326-d3b800f9844a-common": "16.1.1", - "@codingame/monaco-vscode-c4e2825a-b5b1-5f0e-b547-068c32e06d50-common": "16.1.1", - "@codingame/monaco-vscode-d56fc266-2991-5e70-8f69-134ad70e1700-common": "16.1.1", - "@codingame/monaco-vscode-ec21d4de-4d65-574c-8715-bb1a6bbad376-common": "16.1.1", - "marked": "14.0.0" + "@codingame/monaco-vscode-api": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-9b5a5e82-d649-5455-b4bf-ef90d6afd294-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9b5a5e82-d649-5455-b4bf-ef90d6afd294-common/-/monaco-vscode-9b5a5e82-d649-5455-b4bf-ef90d6afd294-common-16.1.1.tgz", - "integrity": "sha512-f7ogqQICGnNCTNddyAu38WKVe+Vuv4eBPFmNFj1Pap4KGNEERVtN7v3Vv9fX+sGbzJOTA5FqrHCTSHA9ZhXl0g==", + "node_modules/@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common/-/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common-19.1.4.tgz", + "integrity": "sha512-hS7DRU6I56ncOSJxr5BHL/QA7udyMMIy+WJNoNCAejPlarvdJWKRyQE9eP6xnDDuA5t6O7Xic/3TKg1XngjSSA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-38f25ab8-ea30-5ba9-8a08-ae3308c297c0-common": "16.1.1", - "@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-c4e2825a-b5b1-5f0e-b547-068c32e06d50-common": "16.1.1", - "@codingame/monaco-vscode-d56fc266-2991-5e70-8f69-134ad70e1700-common": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1" + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-9c84f943-bcb5-5bcf-92a6-91f66a732f26-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9c84f943-bcb5-5bcf-92a6-91f66a732f26-common/-/monaco-vscode-9c84f943-bcb5-5bcf-92a6-91f66a732f26-common-16.1.1.tgz", - "integrity": "sha512-BXDVJUlpCFOo44UsejAilElzPvbqsLePKtLN19KY1PaMaFaBnT59cIsSRw0cMvKUY3DfDMz8M0eIyVk0/Uoe+Q==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9c84f943-bcb5-5bcf-92a6-91f66a732f26-common/-/monaco-vscode-9c84f943-bcb5-5bcf-92a6-91f66a732f26-common-19.1.4.tgz", + "integrity": "sha512-vXz2Vvp0bOQjytNQ2tP/ux1OJMJGbTxP93HsKy0dUvvz9M4s3Jswv0kiXv4nEdRNGDMwjIWs4ouNE+dWMHx12Q==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-9d0168a3-519b-57f3-9bcc-89efc41f951a-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9d0168a3-519b-57f3-9bcc-89efc41f951a-common/-/monaco-vscode-9d0168a3-519b-57f3-9bcc-89efc41f951a-common-16.1.1.tgz", - "integrity": "sha512-Ar9tzKr1PHe8NC3WCjKlMSNzA0QG80UzYEQgR98f1pxPL6SpqMATxvR624qgzL9ukPEPeNhQTmHQ1zm93otdrQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9d0168a3-519b-57f3-9bcc-89efc41f951a-common/-/monaco-vscode-9d0168a3-519b-57f3-9bcc-89efc41f951a-common-19.1.4.tgz", + "integrity": "sha512-VcDXKpCdiOgaXrNBatpCET/Nz2K8s6opdBv2ze9ALOI0To9mDJixnuedAjzSSQ6oigzTOGa4xQSN4yzaMytNNA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-7ba0af96-90c2-5e11-ad7f-befdbbf246c8-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-9c1add21-d437-5ee3-ae2b-ad115a9ef171-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-9ebd6daf-8734-5a28-ad24-b238f5e5819d-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9ebd6daf-8734-5a28-ad24-b238f5e5819d-common/-/monaco-vscode-9ebd6daf-8734-5a28-ad24-b238f5e5819d-common-19.1.4.tgz", + "integrity": "sha512-dvVk86rhFy+ndcKW606dnzGA/md2BT9LoaiMGBJKZwxtlgobLtoe/SU9RtLRmdVevx33QiDUq7M18eXgrjRlCw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-9ed6fe06-a052-57c2-a234-5d9b94d2e7e0-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9ed6fe06-a052-57c2-a234-5d9b94d2e7e0-common/-/monaco-vscode-9ed6fe06-a052-57c2-a234-5d9b94d2e7e0-common-19.1.4.tgz", + "integrity": "sha512-nd3eiJ/U1f1pRRZZg5bYfOLtOMdNrXRLPb2h6DlZSl291JVqMQr4i1hQwYqtrU3LsHB2GxEiJI42LFboGLWDWw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-501b06ab-3f58-516b-8a1a-c29d375d3da4-common": "19.1.4", + "@codingame/monaco-vscode-516ccb00-d5b9-5317-baf0-fcc96e99669a-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-f24e325c-2ce0-5bba-8236-bfc4f53180ab-common": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-9ee79c1a-3f03-568b-8eac-b02513a98b68-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9ee79c1a-3f03-568b-8eac-b02513a98b68-common/-/monaco-vscode-9ee79c1a-3f03-568b-8eac-b02513a98b68-common-19.1.4.tgz", + "integrity": "sha512-l9hnZ56h1N/tvUKfwA3hIRxJGss8PdQlYdn7h/Deqs+POAI7aizsMJ33FY1TWm73qFcbcNqyWKc4jAGy/CN/dw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-262ed59d-4f76-57cd-9e9f-1877f26ae049-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common/-/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common-16.1.1.tgz", - "integrity": "sha512-ttguYBv3IjT/KKS7bRi2aI9SfC/2cTcHpouQSMJrqkIllmA/IjEARgXKDz6LFh78CcvYEbDNNeQNlMOkyO5EYQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common/-/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common-19.1.4.tgz", + "integrity": "sha512-NbvhSZQ+TXOKC7x2/CzJNywIa66Qi8d8kZ/TM8JQ0iTHfye/VvUftGh1Xom6Nj0W1dI7UDsvq9hJaxOWNxW/LQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-2bd6777a-3bff-5b83-ad25-0e029e81ffe9-common": "16.1.1", - "@codingame/monaco-vscode-336c86fe-5135-5343-af5f-c6d13bc09c07-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-65619f8f-0eab-5d8b-855a-43b6353fe527-common": "16.1.1", - "@codingame/monaco-vscode-6ec8cd23-5be5-5c15-93ce-01afcf65acd3-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-e67a0dae-5b2c-54e6-8d61-90102c78362d-common": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-a1eb43d5-da63-5f76-8ad4-acf0d6c3b749-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-a1eb43d5-da63-5f76-8ad4-acf0d6c3b749-common/-/monaco-vscode-a1eb43d5-da63-5f76-8ad4-acf0d6c3b749-common-16.1.1.tgz", - "integrity": "sha512-99sNgYVTz8BOFnq9FbbTu3Tvdzp78V+b1ciUwmC4i9UdodOGWQGneXPGnIiNkHqJgxfFza3D+CT7m47OrIDiqw==", - "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-a2719803-af40-5ae9-a29f-8a2231c33056-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-a2719803-af40-5ae9-a29f-8a2231c33056-common/-/monaco-vscode-a2719803-af40-5ae9-a29f-8a2231c33056-common-16.1.1.tgz", - "integrity": "sha512-hSk8j1e2sScJNPhTraYMFujwVre06SzDsakz9yCPnnu1UQy/s17M4CuLKwtevxxgsZYXUEsOmuzKUM6Apn1JZg==", - "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-501b06ab-3f58-516b-8a1a-c29d375d3da4-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-a8d3bd74-e63e-5327-96e8-4f931661e329-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-bc6d9a89-1625-5010-b57e-ff44151144fe-common": "19.1.4", + "@codingame/monaco-vscode-e7671ac2-ad6e-5d29-8b2e-8bc090939746-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-a3eaa464-944c-5b8f-8886-213068ba4897-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-a3eaa464-944c-5b8f-8886-213068ba4897-common/-/monaco-vscode-a3eaa464-944c-5b8f-8886-213068ba4897-common-16.1.1.tgz", - "integrity": "sha512-tkmk6HMoYEVmRSYxBmHH8NmJiHuGt0eragxeZG6XqkTTHBF7U1kaRZ82LAPT61q3gYAsbMtqyAd/31Qbp8H+WQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-a3eaa464-944c-5b8f-8886-213068ba4897-common/-/monaco-vscode-a3eaa464-944c-5b8f-8886-213068ba4897-common-19.1.4.tgz", + "integrity": "sha512-0Q50lzhyo/Cj9HHE2TXGXdwqZwSm9LpnRyH14/AHNdBH24o1ePYpl3mrutfetswfsEVU/2jk0YwGAZDOZHI9uA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-a3f28a41-ba19-5a7e-8f5a-d6c1403b507d-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-a3f28a41-ba19-5a7e-8f5a-d6c1403b507d-common/-/monaco-vscode-a3f28a41-ba19-5a7e-8f5a-d6c1403b507d-common-16.1.1.tgz", - "integrity": "sha512-DplU+k/VidBWfUlMQZpjwxzVeg1rUoNrTZUVaH+H0qDg9eUzGlIX6YMhNJIYu9LD271uIOVpLwYrA2lOC6913w==", + "node_modules/@codingame/monaco-vscode-a8d3bd74-e63e-5327-96e8-4f931661e329-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-a8d3bd74-e63e-5327-96e8-4f931661e329-common/-/monaco-vscode-a8d3bd74-e63e-5327-96e8-4f931661e329-common-19.1.4.tgz", + "integrity": "sha512-dEquFEWW6E/FT/Gq7m6iTq0XDtrh9DyOhcs5wktNVZ1Hu7nuCatrIMQeKWxtcW8EhozO/F5bBBi9a9JJT+s7xw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-3e369455-cc41-54c6-86fb-8b7cd999a5da-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-501b06ab-3f58-516b-8a1a-c29d375d3da4-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-a7c9ae3c-16d2-5d17-86b2-981be7094566-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-a7c9ae3c-16d2-5d17-86b2-981be7094566-common/-/monaco-vscode-a7c9ae3c-16d2-5d17-86b2-981be7094566-common-16.1.1.tgz", - "integrity": "sha512-h2/cbABuNq57zt5/Fg+avdn5xPireMgLVM4g9FiFGA4tW+9ArOX7yNhTzgyaqSK7ZIqACrQH6uVJrChcfI5sZA==", - "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" - } + "node_modules/@codingame/monaco-vscode-a9da9abe-278d-5ce6-9418-99c7c07c5c37-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-a9da9abe-278d-5ce6-9418-99c7c07c5c37-common/-/monaco-vscode-a9da9abe-278d-5ce6-9418-99c7c07c5c37-common-19.1.4.tgz", + "integrity": "sha512-RE8koz3fYlVrMNal4KPlbcAJ+kQRNSdWvgnsoZ8O9/t7VpDiDIqhfpEBJmWYlfULj8gEyNtyP4r4aGs3RjFMGA==", + "license": "MIT" }, "node_modules/@codingame/monaco-vscode-abed5a84-8a82-5f84-9412-88a736235bae-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-abed5a84-8a82-5f84-9412-88a736235bae-common/-/monaco-vscode-abed5a84-8a82-5f84-9412-88a736235bae-common-16.1.1.tgz", - "integrity": "sha512-jJiMIwn/wfJ3iY2+SdtXJzD2mN0u4cYc/dJVC8pZsdIBsI4CBIRxrQTh+Pd0XnolAZZfTxwJUSwbfADhGrsFtg==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-abed5a84-8a82-5f84-9412-88a736235bae-common/-/monaco-vscode-abed5a84-8a82-5f84-9412-88a736235bae-common-19.1.4.tgz", + "integrity": "sha512-esA8qk4KbkXNxt+pDdm8rfN6HmuniSHCIZt8XbzKDSocaRux0V2mqy8610HKugrWGdcNXKhD2QFWcyQcXhxhvg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-ad89fae6-94f0-5ac2-a185-22dea4b68ee0-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-ad89fae6-94f0-5ac2-a185-22dea4b68ee0-common/-/monaco-vscode-ad89fae6-94f0-5ac2-a185-22dea4b68ee0-common-16.1.1.tgz", - "integrity": "sha512-/cCe43mDX+pT7eRsH8nO43j7hAFjxWq3gE7qQ0M17Ok+WryL3I9Zk5/LNJk5SGeZYWwAukCkSMLM0rpPZMHfLQ==", - "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-2bd6777a-3bff-5b83-ad25-0e029e81ffe9-common": "16.1.1", - "@codingame/monaco-vscode-38f25ab8-ea30-5ba9-8a08-ae3308c297c0-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-65619f8f-0eab-5d8b-855a-43b6353fe527-common": "16.1.1", - "@codingame/monaco-vscode-78982ab1-8dc8-5de0-9139-e8865a161f5a-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-ae8a8ca1-f243-508b-9c37-c88ebbd295fa-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-ae8a8ca1-f243-508b-9c37-c88ebbd295fa-common/-/monaco-vscode-ae8a8ca1-f243-508b-9c37-c88ebbd295fa-common-16.1.1.tgz", - "integrity": "sha512-PniQixP9sBaQwbv43na2jAAOHJKjkMbHn2/7vVKfPDm59KbgM8/cIPKV3XdbMTeDWDMOBNUBWnBOvRMSmjowKw==", - "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-api": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-api/-/monaco-vscode-api-16.1.1.tgz", - "integrity": "sha512-MGnl7bDV+AMCS5VdYs87sVNEK7cV2TRxQiMpNxnKGkEYCN8A9yRiRykoq3c0T1p9sLZZDMuHeRDzRUeEKh/8fw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-api/-/monaco-vscode-api-19.1.4.tgz", + "integrity": "sha512-/FzA0wyovmJIufc/3KH67Pv0hv34JHa7GgqdWXfs3uSdGilkwexYyGkubXOq45Bn0NanD6IkYvcBRUgspsNR+A==", + "license": "MIT", "dependencies": { - "@c4312/eventsource-umd": "3.0.5", - "@codingame/monaco-vscode-base-service-override": "16.1.1", - "@codingame/monaco-vscode-environment-service-override": "16.1.1", - "@codingame/monaco-vscode-extensions-service-override": "16.1.1", - "@codingame/monaco-vscode-files-service-override": "16.1.1", - "@codingame/monaco-vscode-host-service-override": "16.1.1", - "@codingame/monaco-vscode-layout-service-override": "16.1.1", - "@codingame/monaco-vscode-quickaccess-service-override": "16.1.1", + "@codingame/monaco-vscode-base-service-override": "19.1.4", + "@codingame/monaco-vscode-environment-service-override": "19.1.4", + "@codingame/monaco-vscode-extensions-service-override": "19.1.4", + "@codingame/monaco-vscode-files-service-override": "19.1.4", + "@codingame/monaco-vscode-host-service-override": "19.1.4", + "@codingame/monaco-vscode-layout-service-override": "19.1.4", + "@codingame/monaco-vscode-quickaccess-service-override": "19.1.4", + "@vscode/iconv-lite-umd": "0.7.0", + "dompurify": "3.2.6", + "jschardet": "3.1.4", "marked": "14.0.0" } }, - "node_modules/@codingame/monaco-vscode-b1e8558d-1726-5299-bc75-e43ee6d1a124-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-b1e8558d-1726-5299-bc75-e43ee6d1a124-common/-/monaco-vscode-b1e8558d-1726-5299-bc75-e43ee6d1a124-common-16.1.1.tgz", - "integrity": "sha512-oebWVGw0HIQVM7mSsZq58juucgyQ2P9h706YpqUY9yDq9XIBciJ2kXiTXRMTQfFyLJG4e718jWt+Rs3DP6aPiQ==", - "dependencies": { - "@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-ad89fae6-94f0-5ac2-a185-22dea4b68ee0-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-c4e2825a-b5b1-5f0e-b547-068c32e06d50-common": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-b73d4be0-4f5e-582d-a326-d3b800f9844a-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-b73d4be0-4f5e-582d-a326-d3b800f9844a-common/-/monaco-vscode-b73d4be0-4f5e-582d-a326-d3b800f9844a-common-16.1.1.tgz", - "integrity": "sha512-Wb0j7QS/o8idjWTHIEVGuXkj6oE2WvRvN+FMSa/eGN3YTZr7c02nAZfAC+cnBMHnfcpm2sptDEkha9TBhyVGyw==", - "dependencies": { - "@codingame/monaco-vscode-7ba0af96-90c2-5e11-ad7f-befdbbf246c8-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" - } - }, "node_modules/@codingame/monaco-vscode-base-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-base-service-override/-/monaco-vscode-base-service-override-16.1.1.tgz", - "integrity": "sha512-0z2bFMvkMAwYdlQgGFAoOVVTXzYKU5gjlk5WLVcneuFTmf5B3E+X3nbq3nszWuXQQ1iKDJDRU+XYyWuX6QLr+A==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-base-service-override/-/monaco-vscode-base-service-override-19.1.4.tgz", + "integrity": "sha512-X9fsfGmHzIQosBBxqiFLLrDwLfDqJopdJdQvyuoTzqWQksNKk+VCgnzqbULZ9gjSCCYKOAFTqe0rNBhdc3fwTA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-039b5553-0838-562a-97c2-30d6e54a7b42-common": "16.1.1", - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": "16.1.1", - "@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common": "16.1.1", - "@codingame/monaco-vscode-7559b0be-bfa5-5fe6-b731-1973fe9fffa1-common": "16.1.1", - "@codingame/monaco-vscode-9a1a5840-af83-5d07-a156-ba32a36c5c4b-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-d987325e-3e05-53aa-b9ff-6f97476f64db-common": "16.1.1" + "@codingame/monaco-vscode-158b9837-fc78-5d9c-86f5-9134e4358643-common": "19.1.4", + "@codingame/monaco-vscode-23aade48-f094-5c08-9555-97fc9cca96c9-common": "19.1.4", + "@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-9a1a5840-af83-5d07-a156-ba32a36c5c4b-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-d987325e-3e05-53aa-b9ff-6f97476f64db-common": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-bba55be6-41a2-50cd-a3cc-8bafa35bfa89-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-bba55be6-41a2-50cd-a3cc-8bafa35bfa89-common/-/monaco-vscode-bba55be6-41a2-50cd-a3cc-8bafa35bfa89-common-16.1.1.tgz", - "integrity": "sha512-ZaeuaT95QaSPecSl0WqLc9Q+W7bYnZZ0siwkGSzo8JWDdpYHTmDz5qqjKYT+8r9d+wtqu4TxUwAHUujMWbSnpA==", + "node_modules/@codingame/monaco-vscode-bc6d9a89-1625-5010-b57e-ff44151144fe-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-bc6d9a89-1625-5010-b57e-ff44151144fe-common/-/monaco-vscode-bc6d9a89-1625-5010-b57e-ff44151144fe-common-19.1.4.tgz", + "integrity": "sha512-m3Fa5zOxPKA9ryUUtJum5tzOH/EC1lNrBvujV42pn/6X5SY6ij0AuJBmwVMgyh4L5hFy+cZ6Y0sEYlQafRIZ5Q==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-1cc4ea0a-c5b6-54ed-bb60-078a99119b55-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-5452e2b7-9081-5f95-839b-4ab3544ce28f-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-9a6d8b6c-ad4c-5ea3-9558-f43d6dc4c0ad-common": "16.1.1", - "@codingame/monaco-vscode-9b5a5e82-d649-5455-b4bf-ef90d6afd294-common": "16.1.1", - "@codingame/monaco-vscode-a1eb43d5-da63-5f76-8ad4-acf0d6c3b749-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-be8ddbb5-094a-5657-b1cc-fe106c94c632-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-be8ddbb5-094a-5657-b1cc-fe106c94c632-common/-/monaco-vscode-be8ddbb5-094a-5657-b1cc-fe106c94c632-common-16.1.1.tgz", - "integrity": "sha512-Z4EuIV0ceXKPhoCkO1yvIFcQIkm8kw4H4XBiXzs2IaclNv0oIg/meDPpQQlhSZWqf7yzFwcjPBrKVCkFcdN+Xg==", - "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-bf94ddb5-e436-506a-9763-5ab86b642508-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-bf94ddb5-e436-506a-9763-5ab86b642508-common/-/monaco-vscode-bf94ddb5-e436-506a-9763-5ab86b642508-common-16.1.1.tgz", - "integrity": "sha512-nX94i6HPT/gdNjqWDCGhrpDgvsY0oXk49jDqyzITjrB5efSLW14wkkFxV62njVJUyCIGdk7IqQd/6hyj/3fWpw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-bf94ddb5-e436-506a-9763-5ab86b642508-common/-/monaco-vscode-bf94ddb5-e436-506a-9763-5ab86b642508-common-19.1.4.tgz", + "integrity": "sha512-/0b6AhoxiZpXqyGJ+lAfui8BHoTp5Z8X94BB+rMW6VrcEWDCjOhiwiE9Bwe7OxkwS+jrOXX3o07Eiak6YCDHLw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-bulk-edit-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-bulk-edit-service-override/-/monaco-vscode-bulk-edit-service-override-16.1.1.tgz", - "integrity": "sha512-Vqrqo/bCQruuqALJc37IDbT3mnIG0/L5RAdWCBavts4/820wVTstexC5EIfWJjzaEXaIAVHhQKdoU/D+pu+oBA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-bulk-edit-service-override/-/monaco-vscode-bulk-edit-service-override-19.1.4.tgz", + "integrity": "sha512-2WLWUVtMilXzewpFl+kSkM49YItgaNbLmRZwucLe5W4YvTriOivVa+DmobSr7nyvcoojWX9Es6piCuiMKKWyIA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-65619f8f-0eab-5d8b-855a-43b6353fe527-common": "16.1.1", - "@codingame/monaco-vscode-9a6d8b6c-ad4c-5ea3-9558-f43d6dc4c0ad-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-e67a0dae-5b2c-54e6-8d61-90102c78362d-common": "16.1.1" + "@codingame/monaco-vscode-271a23cd-c7d7-5761-ae35-a923a42987b8-common": "19.1.4", + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-501b06ab-3f58-516b-8a1a-c29d375d3da4-common": "19.1.4", + "@codingame/monaco-vscode-a8d3bd74-e63e-5327-96e8-4f931661e329-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-c4e2825a-b5b1-5f0e-b547-068c32e06d50-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-c4e2825a-b5b1-5f0e-b547-068c32e06d50-common/-/monaco-vscode-c4e2825a-b5b1-5f0e-b547-068c32e06d50-common-16.1.1.tgz", - "integrity": "sha512-yp/pst9DCf9dxkmX3Q9fIW3XLSs5ERq/SiBAaBmG49XtTKsaS7CY710GVksHKquT7Xm5JjO7i+gQNTwYlJkrYg==", + "node_modules/@codingame/monaco-vscode-c465110a-57c0-59d7-a6b2-be0a4db7e517-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-c465110a-57c0-59d7-a6b2-be0a4db7e517-common/-/monaco-vscode-c465110a-57c0-59d7-a6b2-be0a4db7e517-common-19.1.4.tgz", + "integrity": "sha512-LEEZsfT1qj4X3UVMyQTTMzaDeGdH8LW3mF01y6RZlHg6JeVKdJoLUoa7Gc2JQ/2/49sjrhRis7Vps0Wb/kBCDQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1" + "@codingame/monaco-vscode-1ba786a5-b7d7-5d26-8a85-ae48ee2a74a4-common": "19.1.4", + "@codingame/monaco-vscode-262ed59d-4f76-57cd-9e9f-1877f26ae049-common": "19.1.4", + "@codingame/monaco-vscode-271a23cd-c7d7-5761-ae35-a923a42987b8-common": "19.1.4", + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-273022ab-b991-5d6a-9f04-decc266cb61b-common": "19.1.4", + "@codingame/monaco-vscode-3927dd53-f845-534c-9baa-1528ff3956c8-common": "19.1.4", + "@codingame/monaco-vscode-516ccb00-d5b9-5317-baf0-fcc96e99669a-common": "19.1.4", + "@codingame/monaco-vscode-55041ed9-a1ea-544a-883d-08b0b26934f3-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-9911aec2-e4dd-5483-8369-9d695cc70d8a-common": "19.1.4", + "@codingame/monaco-vscode-9ebd6daf-8734-5a28-ad24-b238f5e5819d-common": "19.1.4", + "@codingame/monaco-vscode-9ed6fe06-a052-57c2-a234-5d9b94d2e7e0-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-f24e325c-2ce0-5bba-8236-bfc4f53180ab-common": "19.1.4", + "marked": "14.0.0" + } + }, + "node_modules/@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common/-/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common-19.1.4.tgz", + "integrity": "sha512-30pBQGjs3hfXu5jSZkiL7tcwIWLHbsulZs3WI+PzQ7GbR16wV8A2K9Xh5eybF2kCYWypT+DXYfp1/7DDElLDdw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-caf773dd-ab1d-5936-941d-a2fe9e896ffc-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-caf773dd-ab1d-5936-941d-a2fe9e896ffc-common/-/monaco-vscode-caf773dd-ab1d-5936-941d-a2fe9e896ffc-common-19.1.4.tgz", + "integrity": "sha512-385inAyYTbwaLpMRdSDUQfBRCVkq/G73x5MUejSsWTkhr3FYHxIx6YTA4ba7VaBhl8GfNZGDleKFQPQWZOMTSA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-ce7c734f-7712-563c-9335-d7acb43306af-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-ce7c734f-7712-563c-9335-d7acb43306af-common/-/monaco-vscode-ce7c734f-7712-563c-9335-d7acb43306af-common-19.1.4.tgz", + "integrity": "sha512-SIAr7U8I0gyLj4cKjpnfrjpGJEmkQBeFfzqknWpBu5jTGDNIvCfg8UGEOtsQI2WQ148t9pZyDdQYDU5SpBIp3g==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-cea4d01f-6526-5c2f-8b09-b168fead499f-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-cea4d01f-6526-5c2f-8b09-b168fead499f-common/-/monaco-vscode-cea4d01f-6526-5c2f-8b09-b168fead499f-common-16.1.1.tgz", - "integrity": "sha512-LiePgtMNQcQm0I/xmf+xUSrwL5uyqrUykI/B+5CDB7+pv0Ml7P+KUoqrZHM6V0Ii5GovO8N+i5EkJubWwVHNXQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-cea4d01f-6526-5c2f-8b09-b168fead499f-common/-/monaco-vscode-cea4d01f-6526-5c2f-8b09-b168fead499f-common-19.1.4.tgz", + "integrity": "sha512-Q/BH6eJviTOzh/I2h5jEY81033z4xpg1HGN+aSvjFy2XaRRwG6LlHTD/XiEaJg1Ju0128EXl8FklDGXtUkFGSw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-configuration-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-configuration-service-override/-/monaco-vscode-configuration-service-override-16.1.1.tgz", - "integrity": "sha512-fdXjkVfCJVOqvGcBWJE9HNUzsZLC1/jKvPPe2P1eGKz0LEua9/tH8+aYSrqULlQonFPnHQvj0xoVDhG7h6hMbg==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-configuration-service-override/-/monaco-vscode-configuration-service-override-19.1.4.tgz", + "integrity": "sha512-2JMyURUjiE0o1TaCCp4jPchuo9LYU93MfQrqo2Kk5oj2jD3i1d3xLlTzdfHss+rTiymLisAGYR+KF53YQYx8Kw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-2b1a9082-790f-527f-b013-d1b29d6265a3-common": "16.1.1", - "@codingame/monaco-vscode-422642f2-7e3a-5c1c-9e1e-1d3ef1817346-common": "16.1.1", - "@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common": "16.1.1", - "@codingame/monaco-vscode-7559b0be-bfa5-5fe6-b731-1973fe9fffa1-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-d987325e-3e05-53aa-b9ff-6f97476f64db-common": "16.1.1", - "@codingame/monaco-vscode-files-service-override": "16.1.1" + "@codingame/monaco-vscode-158b9837-fc78-5d9c-86f5-9134e4358643-common": "19.1.4", + "@codingame/monaco-vscode-422642f2-7e3a-5c1c-9e1e-1d3ef1817346-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ce7c734f-7712-563c-9335-d7acb43306af-common": "19.1.4", + "@codingame/monaco-vscode-d987325e-3e05-53aa-b9ff-6f97476f64db-common": "19.1.4", + "@codingame/monaco-vscode-f24e325c-2ce0-5bba-8236-bfc4f53180ab-common": "19.1.4", + "@codingame/monaco-vscode-files-service-override": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-d26a96d3-122c-5a3d-a04d-deb5ff0f19c0-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-d26a96d3-122c-5a3d-a04d-deb5ff0f19c0-common/-/monaco-vscode-d26a96d3-122c-5a3d-a04d-deb5ff0f19c0-common-16.1.1.tgz", - "integrity": "sha512-MhL8gTG326pcNFL1K8z5AFw1RGztgq8pVBK1O3PG5IM5L2OTNDJtBXwQiJ9BsG6rykGQMrvvYfjVj18lgWBVsA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-d26a96d3-122c-5a3d-a04d-deb5ff0f19c0-common/-/monaco-vscode-d26a96d3-122c-5a3d-a04d-deb5ff0f19c0-common-19.1.4.tgz", + "integrity": "sha512-S77th4ZmNGfPvlZbK6SbdxK0CmeEmTOBOH3EAytYSq2dGucApaCrxcRz9Z+/ctsG+iQ3t/N6KoQg4kI9teRnpQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-2a94c04a-b85b-5669-b06b-89c1bfa11cb9-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-2a94c04a-b85b-5669-b06b-89c1bfa11cb9-common": "19.1.4", + "@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-d481a59e-259c-524e-bee1-76483d75d3a1-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-d481a59e-259c-524e-bee1-76483d75d3a1-common/-/monaco-vscode-d481a59e-259c-524e-bee1-76483d75d3a1-common-16.1.1.tgz", - "integrity": "sha512-fqiZ5OWGLj5oD57sbvR5ArdJPmcnEUk3/XJLKbFGw/nz3O4p1S7crKlUzTUr1ITPTAxkX5U7XplwV1R4xqMzeQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-d481a59e-259c-524e-bee1-76483d75d3a1-common/-/monaco-vscode-d481a59e-259c-524e-bee1-76483d75d3a1-common-19.1.4.tgz", + "integrity": "sha512-EtAmfeH0xvKXeZqucMjjmT/j31ruGL9aVnZYw5vhxWFCjAaT72fWibW/VLw2rExr0qGI4DmZo98wb2vRgWmejA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-523730aa-81e6-55d7-9916-87ad537fe087-common": "16.1.1", - "@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-9b5a5e82-d649-5455-b4bf-ef90d6afd294-common": "16.1.1", - "@codingame/monaco-vscode-a7c9ae3c-16d2-5d17-86b2-981be7094566-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-b1e8558d-1726-5299-bc75-e43ee6d1a124-common": "16.1.1", - "@codingame/monaco-vscode-c4e2825a-b5b1-5f0e-b547-068c32e06d50-common": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-d56fc266-2991-5e70-8f69-134ad70e1700-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-d56fc266-2991-5e70-8f69-134ad70e1700-common/-/monaco-vscode-d56fc266-2991-5e70-8f69-134ad70e1700-common-16.1.1.tgz", - "integrity": "sha512-ec+L2z0O1tKqWbHOGYAPGbwbipPFxXKP+vBYaQ/yt3we94i82vA1a+4E75nUodWOoAC9YfsEtmR2cNeNDpneMg==", - "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-2bd6777a-3bff-5b83-ad25-0e029e81ffe9-common": "16.1.1", - "@codingame/monaco-vscode-38f25ab8-ea30-5ba9-8a08-ae3308c297c0-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-65619f8f-0eab-5d8b-855a-43b6353fe527-common": "16.1.1", - "@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-ad89fae6-94f0-5ac2-a185-22dea4b68ee0-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-c4e2825a-b5b1-5f0e-b547-068c32e06d50-common": "16.1.1" + "@codingame/monaco-vscode-523730aa-81e6-55d7-9916-87ad537fe087-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-72a1b7d3-3f58-5545-9b7e-f579bd003081-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-937ecbdf-94c7-5b16-aefa-ad78ae557a93-common": "19.1.4", + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ddbd4d51-94af-51e5-96b5-f2166ed67ad0-common": "19.1.4", + "@codingame/monaco-vscode-f24e325c-2ce0-5bba-8236-bfc4f53180ab-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-d609a7d3-bf87-551a-884f-550a8b327ec5-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-d609a7d3-bf87-551a-884f-550a8b327ec5-common/-/monaco-vscode-d609a7d3-bf87-551a-884f-550a8b327ec5-common-16.1.1.tgz", - "integrity": "sha512-fVcGP0yCE08Wv+DEuni/kq1slHboltBGTdClRdbk0SISvlaUd3FgtsXdCXitSH9yDPVa/urkbo5gyrPrULoOsw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-d609a7d3-bf87-551a-884f-550a8b327ec5-common/-/monaco-vscode-d609a7d3-bf87-551a-884f-550a8b327ec5-common-19.1.4.tgz", + "integrity": "sha512-mzSCA6NPZ+mgVclaf5NChYCjDSt3Ec1Xy4SCFQKReXKDbabyIfyMoGH14sVZaJRtHFMbi3TI8PXAp/+xnllcKQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-d7f659f5-da33-5ea8-a3b8-9b94f2cf5f33-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-d7f659f5-da33-5ea8-a3b8-9b94f2cf5f33-common/-/monaco-vscode-d7f659f5-da33-5ea8-a3b8-9b94f2cf5f33-common-16.1.1.tgz", - "integrity": "sha512-sUdxunDgT/51nik7bcrEe0eFNWOecK+ikyMooMZxUrlB6NSoTZKs12YZG/oy1l1lN0ak08CqliFEL3lsjefAwQ==", - "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-7559b0be-bfa5-5fe6-b731-1973fe9fffa1-common": "16.1.1", - "@codingame/monaco-vscode-a2719803-af40-5ae9-a29f-8a2231c33056-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-d987325e-3e05-53aa-b9ff-6f97476f64db-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-d987325e-3e05-53aa-b9ff-6f97476f64db-common/-/monaco-vscode-d987325e-3e05-53aa-b9ff-6f97476f64db-common-16.1.1.tgz", - "integrity": "sha512-BHm6t29QN6JPgqHdP/tN6CvYw/oNg/+yUM+Agtxnd5H3BGxrRnsxB61pM+ct/1/hsvP73FzyuZtGKPCuHZcCNQ==" + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-d987325e-3e05-53aa-b9ff-6f97476f64db-common/-/monaco-vscode-d987325e-3e05-53aa-b9ff-6f97476f64db-common-19.1.4.tgz", + "integrity": "sha512-zYMdVBWSdWk22KBC6kexEM5KrMd/g0cR87wGFQWiwBYNQwf0n9oOiKngFB4KrhGMtzMBo0ON3ct4sDbq9Tj2HA==", + "license": "MIT" }, - "node_modules/@codingame/monaco-vscode-e59ecb8c-db32-5324-8fe4-cf9921fd92b8-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-e59ecb8c-db32-5324-8fe4-cf9921fd92b8-common/-/monaco-vscode-e59ecb8c-db32-5324-8fe4-cf9921fd92b8-common-16.1.1.tgz", - "integrity": "sha512-pVd5itpQTjIttr3RAmK3swnyvibo/uUpaO9UpguN/KN8JVeSqUoGpm9VyJlcuNL4AYo5KYlKW4SJ0lTGksQ/sA==" - }, - "node_modules/@codingame/monaco-vscode-e67a0dae-5b2c-54e6-8d61-90102c78362d-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-e67a0dae-5b2c-54e6-8d61-90102c78362d-common/-/monaco-vscode-e67a0dae-5b2c-54e6-8d61-90102c78362d-common-16.1.1.tgz", - "integrity": "sha512-0FVHtSCYXE4Zig990HlVs+z/tG1qJO/xBjBxdlst7/pAZWdoMaWOVMtLPPaVqln4ffYq/mmAWXTa4FZBV26lGQ==", + "node_modules/@codingame/monaco-vscode-da4efea3-7112-5351-9c1d-60017cbd9532-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-da4efea3-7112-5351-9c1d-60017cbd9532-common/-/monaco-vscode-da4efea3-7112-5351-9c1d-60017cbd9532-common-19.1.4.tgz", + "integrity": "sha512-th2LVsA0GoqNG+Qgg0nr326i6a/MIdU6jygK6oPBR7Y82r85u/OPQiRl/Pxbeg+ETN3EWNFIvQvjqRa08EgdtA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-65619f8f-0eab-5d8b-855a-43b6353fe527-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-dbfe5f85-b426-55ed-a79b-5f811b395762-common": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-e72c94ca-257a-5b75-8b68-5a5fa3c18255-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-e72c94ca-257a-5b75-8b68-5a5fa3c18255-common/-/monaco-vscode-e72c94ca-257a-5b75-8b68-5a5fa3c18255-common-16.1.1.tgz", - "integrity": "sha512-gb9A8gtzmnZzJ18E5idXQgQywSGeGM5ZqsrO1qe/zlB9NvDz/q2UcI+t7L/pyCOn1nkl6twHMPX7YwY06z4qaA==", + "node_modules/@codingame/monaco-vscode-dbfe5f85-b426-55ed-a79b-5f811b395762-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-dbfe5f85-b426-55ed-a79b-5f811b395762-common/-/monaco-vscode-dbfe5f85-b426-55ed-a79b-5f811b395762-common-19.1.4.tgz", + "integrity": "sha512-UHpqNaC2lmWb/wZrGi4c9ucBKo/E70lSG63cno3UG4LI9gMIaxnUAUH/mHBhEsnR9MC/H0L/itr4uKT8frolKw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-d609a7d3-bf87-551a-884f-550a8b327ec5-common": "16.1.1" + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-bc6d9a89-1625-5010-b57e-ff44151144fe-common": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-ddbd4d51-94af-51e5-96b5-f2166ed67ad0-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-ddbd4d51-94af-51e5-96b5-f2166ed67ad0-common/-/monaco-vscode-ddbd4d51-94af-51e5-96b5-f2166ed67ad0-common-19.1.4.tgz", + "integrity": "sha512-RdjlFf9VQxq+RmsM+HmaQRgS3qEaao4sNRoRCaVyJedfkw7PQw15bxhQGHETJ2lxMk+07sj9aX0q3C4A3nezrQ==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-33de3d75-8b1a-53f0-9e27-0c71c327e1a1-common": "19.1.4", + "@codingame/monaco-vscode-72a1b7d3-3f58-5545-9b7e-f579bd003081-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-f24e325c-2ce0-5bba-8236-bfc4f53180ab-common": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-e59ecb8c-db32-5324-8fe4-cf9921fd92b8-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-e59ecb8c-db32-5324-8fe4-cf9921fd92b8-common/-/monaco-vscode-e59ecb8c-db32-5324-8fe4-cf9921fd92b8-common-19.1.4.tgz", + "integrity": "sha512-q6So750nCrwGMMzZ5EOJND7xwWQTVF4moXPt0KmUEGBBdfZdqvEwx47XQMF+M4BqkY/NWj+S5iiI+vPkyCteMQ==", + "license": "MIT" + }, + "node_modules/@codingame/monaco-vscode-e72c94ca-257a-5b75-8b68-5a5fa3c18255-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-e72c94ca-257a-5b75-8b68-5a5fa3c18255-common/-/monaco-vscode-e72c94ca-257a-5b75-8b68-5a5fa3c18255-common-19.1.4.tgz", + "integrity": "sha512-hznl7zCMHb3lIDYXp5up+8PFINYa8oQr6z9+RCgXepsOr87Z/Czq9os88RBJDS8qsQ2z4yMZPelUWd+z72qLXw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-158b9837-fc78-5d9c-86f5-9134e4358643-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-d609a7d3-bf87-551a-884f-550a8b327ec5-common": "19.1.4" + } + }, + "node_modules/@codingame/monaco-vscode-e7671ac2-ad6e-5d29-8b2e-8bc090939746-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-e7671ac2-ad6e-5d29-8b2e-8bc090939746-common/-/monaco-vscode-e7671ac2-ad6e-5d29-8b2e-8bc090939746-common-19.1.4.tgz", + "integrity": "sha512-M0oFX5H77oSjeDY9XlzIIwQkXHf2MmPZIF1Ym8lYobtIng3ADw+VcX9is2LrR1cYsTgZ6ASUN6h8qWpbVtYIgA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-ea14e352-8f1c-5569-b79a-8a96a53e8abe-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-ea14e352-8f1c-5569-b79a-8a96a53e8abe-common/-/monaco-vscode-ea14e352-8f1c-5569-b79a-8a96a53e8abe-common-16.1.1.tgz", - "integrity": "sha512-ES88S4bDWPWBEtgurYRUPSmJmH5xnQZ135xDZgwMuibVUUVEJs5cOIUEdy1EhnF2/bfYn6fbEre99+FUXrPZSw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-ea14e352-8f1c-5569-b79a-8a96a53e8abe-common/-/monaco-vscode-ea14e352-8f1c-5569-b79a-8a96a53e8abe-common-19.1.4.tgz", + "integrity": "sha512-uMoKkQa/g+I5uX8XEB6vZ5sf5je9Y+qJBy8+6iIEjJHh8xD0WqRDrcvsatI954bykp94nks9siXPx+7UVDl6Rg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-2bd6777a-3bff-5b83-ad25-0e029e81ffe9-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-eb7d5efd-2e60-59f8-9ba4-9a8ae8cb2957-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-eb7d5efd-2e60-59f8-9ba4-9a8ae8cb2957-common/-/monaco-vscode-eb7d5efd-2e60-59f8-9ba4-9a8ae8cb2957-common-16.1.1.tgz", - "integrity": "sha512-lCLHGScD1yQZJ1LzMyTyPAijf2vJpFSIKj0U9OWAkV8iqQGPlmrxrtmzdvQY/uxtOWTSYkD6pbe2Nd9dD/+uzQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-eb7d5efd-2e60-59f8-9ba4-9a8ae8cb2957-common/-/monaco-vscode-eb7d5efd-2e60-59f8-9ba4-9a8ae8cb2957-common-19.1.4.tgz", + "integrity": "sha512-r8Ee01CU3O473OCVqCLrFr2xZsCHiIvEwnHgLm0CyStK9ornoa0c72RQQvY2k+cDO120ly022PgpUw+H3OotLA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-3e369455-cc41-54c6-86fb-8b7cd999a5da-common": "16.1.1", - "@codingame/monaco-vscode-65619f8f-0eab-5d8b-855a-43b6353fe527-common": "16.1.1", - "@codingame/monaco-vscode-a3f28a41-ba19-5a7e-8f5a-d6c1403b507d-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-262ed59d-4f76-57cd-9e9f-1877f26ae049-common": "19.1.4", + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-501b06ab-3f58-516b-8a1a-c29d375d3da4-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-9ee79c1a-3f03-568b-8eac-b02513a98b68-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-eba0b9b3-174c-5dae-9867-a37810ca1808-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-eba0b9b3-174c-5dae-9867-a37810ca1808-common/-/monaco-vscode-eba0b9b3-174c-5dae-9867-a37810ca1808-common-16.1.1.tgz", - "integrity": "sha512-yFyKYBrWElATsDxW0U+FWptO6iBbklzMjEstC0HaSAwIDJYgNlGm9FZydyEgreDC0shVeJvgujeZ50lkTx5KOg==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-eba0b9b3-174c-5dae-9867-a37810ca1808-common/-/monaco-vscode-eba0b9b3-174c-5dae-9867-a37810ca1808-common-19.1.4.tgz", + "integrity": "sha512-upBAvROPEz1IpHpi9pfzsLNr1h1al+8ZqpoEoZ+6cQHa63HXu1HuzURVikH6XcDNwuAOFWvaojde2LqdkLp0gQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-9b5a5e82-d649-5455-b4bf-ef90d6afd294-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" - } - }, - "node_modules/@codingame/monaco-vscode-ec21d4de-4d65-574c-8715-bb1a6bbad376-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-ec21d4de-4d65-574c-8715-bb1a6bbad376-common/-/monaco-vscode-ec21d4de-4d65-574c-8715-bb1a6bbad376-common-16.1.1.tgz", - "integrity": "sha512-V0tzm/ydKB8yeWwGtBflql7t9uqcUWOi72OyJrj6MdqEOL6Pb1B2FAp6JyiSh0u/XQRApzMKyvXmFguEDL5ygA==", - "dependencies": { - "@codingame/monaco-vscode-2b1a9082-790f-527f-b013-d1b29d6265a3-common": "16.1.1", - "@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common": "16.1.1", - "@codingame/monaco-vscode-ae8a8ca1-f243-508b-9c37-c88ebbd295fa-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-271a23cd-c7d7-5761-ae35-a923a42987b8-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-editor-api": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-editor-api/-/monaco-vscode-editor-api-16.1.1.tgz", - "integrity": "sha512-w3IB7oe1NVi//bfuORHjxuSsK2CKkQatSUcByaKUGrdctFRatcfoRxpj/fuIKQ2nsIGdk+5Q/jaLgxx+foqK7Q==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-editor-api/-/monaco-vscode-editor-api-19.1.4.tgz", + "integrity": "sha512-BNjAi7+gW4Eue63AXPf3CmJRIeB4x7vrXTlfJSA71NQwIrKVm4guuBOYEqQl2FG+rPC/XWuLrP3BcwHqQHd4CA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-5452e2b7-9081-5f95-839b-4ab3544ce28f-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-5452e2b7-9081-5f95-839b-4ab3544ce28f-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-editor-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-editor-service-override/-/monaco-vscode-editor-service-override-16.1.1.tgz", - "integrity": "sha512-tbTsAuT/s0PWinYBgVR/IAnuv3nStwMlo9I3hPSltflFTNbeoWyuDSbLdkXLRB8nqbpzXM+Y89AaWbsEJX/TMQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-editor-service-override/-/monaco-vscode-editor-service-override-19.1.4.tgz", + "integrity": "sha512-MfMP3e+j3XH/Hg7EExKMkKh1LVwgO1S8MPXfZT0FFavgCCL9Q/qP5aJwc/j+h8v/lMtgTjMajYiZNI5nWXJjkw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-b1e8558d-1726-5299-bc75-e43ee6d1a124-common": "16.1.1", - "@codingame/monaco-vscode-c4e2825a-b5b1-5f0e-b547-068c32e06d50-common": "16.1.1" + "@codingame/monaco-vscode-72a1b7d3-3f58-5545-9b7e-f579bd003081-common": "19.1.4", + "@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ddbd4d51-94af-51e5-96b5-f2166ed67ad0-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-environment-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-environment-service-override/-/monaco-vscode-environment-service-override-16.1.1.tgz", - "integrity": "sha512-DzzNef+v7otp75E75Dl/Duk3DZ+49Y8RE5jbamS8oEq5QnAandO2XtHx+xrp3SOGoubYKr1a+mA3GfuiG4sJgA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-environment-service-override/-/monaco-vscode-environment-service-override-19.1.4.tgz", + "integrity": "sha512-0LkhqDxGnl5UiXX1vkNbxM+8J9wz1vlTJr/y7x1AgpJCRkEIE3bJS7EZ9wrR5zvaIcJrrHGceCo993Kfgg6qdA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-abed5a84-8a82-5f84-9412-88a736235bae-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-abed5a84-8a82-5f84-9412-88a736235bae-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-extension-api": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-extension-api/-/monaco-vscode-extension-api-16.1.1.tgz", - "integrity": "sha512-DG0QMk51h6pHBgpvD2iZc/Loj24PZQPnJU1IyKxpv9ZvE7zczYtTuvaNR5NHwSmjYju5kcszDyVlh3XICNH1/g==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-extension-api/-/monaco-vscode-extension-api-19.1.4.tgz", + "integrity": "sha512-qWRWkpS2s+iIrLZLwXkOWZXnnNb7GsWwXiaNLq9O3gJtO6fUdBOOWnT521w3d0Et9ax0HAQHkDJHOcg1ywgAAg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": "16.1.1", - "@codingame/monaco-vscode-4a3ac544-9a61-534c-88df-756262793ef7-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-extensions-service-override": "16.1.1" + "@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": "19.1.4", + "@codingame/monaco-vscode-4a3ac544-9a61-534c-88df-756262793ef7-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-extensions-service-override": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-extensions-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-extensions-service-override/-/monaco-vscode-extensions-service-override-16.1.1.tgz", - "integrity": "sha512-8oYq3+MDDMNHCZrjq3qrphAFtopd2MA5oiYnYyT2/2r+K3TPcfYdjJbAS+HhcH8Lx1WwBO0faNB1E3hb/HWmIg==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-extensions-service-override/-/monaco-vscode-extensions-service-override-19.1.4.tgz", + "integrity": "sha512-hc9eGHgfWG2ahbZDHRGD1q29eYZAi/d9rKGPi6ZTtaYgILbpTwy9U3VMans43qukmtn4H62E+bA3x3Wz4HNsMw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-0b087f42-a5a3-5eb9-9bfd-1eebc1bba163-common": "16.1.1", - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-1cc4ea0a-c5b6-54ed-bb60-078a99119b55-common": "16.1.1", - "@codingame/monaco-vscode-256d5b78-0649-50e9-8354-2807f95f68f4-common": "16.1.1", - "@codingame/monaco-vscode-2a94c04a-b85b-5669-b06b-89c1bfa11cb9-common": "16.1.1", - "@codingame/monaco-vscode-38f25ab8-ea30-5ba9-8a08-ae3308c297c0-common": "16.1.1", - "@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": "16.1.1", - "@codingame/monaco-vscode-42931eb9-e564-530c-bafc-fa23ab43a070-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-4a3ac544-9a61-534c-88df-756262793ef7-common": "16.1.1", - "@codingame/monaco-vscode-6845754f-e617-5ed9-8aaa-6ca3653a9532-common": "16.1.1", - "@codingame/monaco-vscode-7ba0af96-90c2-5e11-ad7f-befdbbf246c8-common": "16.1.1", - "@codingame/monaco-vscode-7f39b6f1-3542-5430-8760-0f404d7a7cee-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-897bebad-39df-57cb-8a57-36a271d038be-common": "16.1.1", - "@codingame/monaco-vscode-8ccb7637-50ea-5359-97bf-00015d7fe567-common": "16.1.1", - "@codingame/monaco-vscode-9a6d8b6c-ad4c-5ea3-9558-f43d6dc4c0ad-common": "16.1.1", - "@codingame/monaco-vscode-9b5a5e82-d649-5455-b4bf-ef90d6afd294-common": "16.1.1", - "@codingame/monaco-vscode-ad89fae6-94f0-5ac2-a185-22dea4b68ee0-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-bba55be6-41a2-50cd-a3cc-8bafa35bfa89-common": "16.1.1", - "@codingame/monaco-vscode-be8ddbb5-094a-5657-b1cc-fe106c94c632-common": "16.1.1", - "@codingame/monaco-vscode-bf94ddb5-e436-506a-9763-5ab86b642508-common": "16.1.1", - "@codingame/monaco-vscode-d7f659f5-da33-5ea8-a3b8-9b94f2cf5f33-common": "16.1.1", - "@codingame/monaco-vscode-e67a0dae-5b2c-54e6-8d61-90102c78362d-common": "16.1.1", - "@codingame/monaco-vscode-eb7d5efd-2e60-59f8-9ba4-9a8ae8cb2957-common": "16.1.1", - "@codingame/monaco-vscode-eba0b9b3-174c-5dae-9867-a37810ca1808-common": "16.1.1", - "@codingame/monaco-vscode-ec21d4de-4d65-574c-8715-bb1a6bbad376-common": "16.1.1", - "@codingame/monaco-vscode-f22e7e55-aee8-5b52-a6bc-950efd9f5890-common": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1", - "@codingame/monaco-vscode-files-service-override": "16.1.1" + "@codingame/monaco-vscode-1cc4ea0a-c5b6-54ed-bb60-078a99119b55-common": "19.1.4", + "@codingame/monaco-vscode-256d5b78-0649-50e9-8354-2807f95f68f4-common": "19.1.4", + "@codingame/monaco-vscode-271a23cd-c7d7-5761-ae35-a923a42987b8-common": "19.1.4", + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-2a94c04a-b85b-5669-b06b-89c1bfa11cb9-common": "19.1.4", + "@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": "19.1.4", + "@codingame/monaco-vscode-4a3ac544-9a61-534c-88df-756262793ef7-common": "19.1.4", + "@codingame/monaco-vscode-571c8352-7953-5038-9f09-e03bb6219a0e-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-6845754f-e617-5ed9-8aaa-6ca3653a9532-common": "19.1.4", + "@codingame/monaco-vscode-6f931a91-88ea-5232-897f-a17ec3929ba5-common": "19.1.4", + "@codingame/monaco-vscode-7f39b6f1-3542-5430-8760-0f404d7a7cee-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-8ccb7637-50ea-5359-97bf-00015d7fe567-common": "19.1.4", + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-9ebd6daf-8734-5a28-ad24-b238f5e5819d-common": "19.1.4", + "@codingame/monaco-vscode-a8d3bd74-e63e-5327-96e8-4f931661e329-common": "19.1.4", + "@codingame/monaco-vscode-a9da9abe-278d-5ce6-9418-99c7c07c5c37-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-bf94ddb5-e436-506a-9763-5ab86b642508-common": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4", + "@codingame/monaco-vscode-caf773dd-ab1d-5936-941d-a2fe9e896ffc-common": "19.1.4", + "@codingame/monaco-vscode-eb7d5efd-2e60-59f8-9ba4-9a8ae8cb2957-common": "19.1.4", + "@codingame/monaco-vscode-eba0b9b3-174c-5dae-9867-a37810ca1808-common": "19.1.4", + "@codingame/monaco-vscode-f22e7e55-aee8-5b52-a6bc-950efd9f5890-common": "19.1.4", + "@codingame/monaco-vscode-files-service-override": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-f1bbc6d3-6129-583c-a2ba-c80b832993d2-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-f1bbc6d3-6129-583c-a2ba-c80b832993d2-common/-/monaco-vscode-f1bbc6d3-6129-583c-a2ba-c80b832993d2-common-16.1.1.tgz", - "integrity": "sha512-L88lm+ggybCTOOx2zUoVlAL8kIbMH7BawSPyY7lONiB9/rMWp9EW3H0jsc4KGS++uSYQd35ThxBKDWNWCEQhCA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-f1bbc6d3-6129-583c-a2ba-c80b832993d2-common/-/monaco-vscode-f1bbc6d3-6129-583c-a2ba-c80b832993d2-common-19.1.4.tgz", + "integrity": "sha512-53kOJAzWNC04KeOk9AXhaA6gJMCpAHjYLHIVuVzpPjBXUr6T7rIibY/QdbwPcyOaeFeCLqi5P4CC2NDOijRK6w==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-f22e7e55-aee8-5b52-a6bc-950efd9f5890-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-f22e7e55-aee8-5b52-a6bc-950efd9f5890-common/-/monaco-vscode-f22e7e55-aee8-5b52-a6bc-950efd9f5890-common-16.1.1.tgz", - "integrity": "sha512-aiMAyd/y8jPB14dIJWN69R93IEqDYuZTfH3ltpuzfq0dawODo8HH7CKHtVkZQwhOmRfFOEHFa+bEW1exv0VJ1A==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-f22e7e55-aee8-5b52-a6bc-950efd9f5890-common/-/monaco-vscode-f22e7e55-aee8-5b52-a6bc-950efd9f5890-common-19.1.4.tgz", + "integrity": "sha512-GofIDcoCHs5urtVMvXqdzpjlYRBelSlCd5yA9ukGNIV33HJjNAihrrMnNHmhjohaUwkODZJc5vltxYnY0nl+YA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-a7c9ae3c-16d2-5d17-86b2-981be7094566-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-937ecbdf-94c7-5b16-aefa-ad78ae557a93-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, - "node_modules/@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common/-/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common-16.1.1.tgz", - "integrity": "sha512-JF3ZQGtt0rMk0ozoP53mSP21rqgtyV0wJnJFbwmA9N4M2PKa7KepnYxgc6c3d1KozFC1vtDZdk/qQWHwaRsCZg==", + "node_modules/@codingame/monaco-vscode-f24e325c-2ce0-5bba-8236-bfc4f53180ab-common": { + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-f24e325c-2ce0-5bba-8236-bfc4f53180ab-common/-/monaco-vscode-f24e325c-2ce0-5bba-8236-bfc4f53180ab-common-19.1.4.tgz", + "integrity": "sha512-LIEpyEkbdELikw5C3bY9SU2SgrspfoNZEaAH4Yo595raBSFvHBeLHdN9NauofptRhUeS9hRN9au+byJRbDCjUg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-55041ed9-a1ea-544a-883d-08b0b26934f3-common": "19.1.4", + "@codingame/monaco-vscode-72a1b7d3-3f58-5545-9b7e-f579bd003081-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-dbfe5f85-b426-55ed-a79b-5f811b395762-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-ff9fa663-eae3-5274-8573-c2b918871e4b-common": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-ff9fa663-eae3-5274-8573-c2b918871e4b-common/-/monaco-vscode-ff9fa663-eae3-5274-8573-c2b918871e4b-common-16.1.1.tgz", - "integrity": "sha512-886VvA6VCR5nbe4ClOf3eG6GyByL0a51tUnCnhAgQqjl4UI/p3Id1gdPlvr2BzmlBvcluzS4CuySZ5txW4VSgg==" + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-ff9fa663-eae3-5274-8573-c2b918871e4b-common/-/monaco-vscode-ff9fa663-eae3-5274-8573-c2b918871e4b-common-19.1.4.tgz", + "integrity": "sha512-Xm3BwhJP/XgdNWS1NWnyta62SNtgL2clVc4CczzGv7GAeSa8kG91aZnvpfExi1w7gergvOzYTRAulxcWYQy4iQ==", + "license": "MIT" }, "node_modules/@codingame/monaco-vscode-files-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-files-service-override/-/monaco-vscode-files-service-override-16.1.1.tgz", - "integrity": "sha512-h2mwAUCdbj7+ui0dbjcJtw4KRM97+PY55Z2OKYphy8qUdaIFpLkNw4pZoO9V112SO45pCWuh/nexutsIj576bw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-files-service-override/-/monaco-vscode-files-service-override-19.1.4.tgz", + "integrity": "sha512-8A6/lChU8yuQ0iuSZVR8Whd2uZM8iXPsBunSCnLPgG2+xrm9hmbFYaG0wwDbymjB44FwYR88aN3ZrNFmaJYmig==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-0c06bfba-d24d-5c4d-90cd-b40cefb7f811-common": "16.1.1", - "@codingame/monaco-vscode-15626ec7-b165-51e1-8caf-7bcc2ae9b95a-common": "16.1.1", - "@codingame/monaco-vscode-2e67e044-0db9-5fa6-8bd1-3737a7d586d4-common": "16.1.1", - "@codingame/monaco-vscode-2f06fe84-148e-5e6b-a7ca-c7989c5f128a-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-ae8a8ca1-f243-508b-9c37-c88ebbd295fa-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-cea4d01f-6526-5c2f-8b09-b168fead499f-common": "16.1.1" + "@codingame/monaco-vscode-0c06bfba-d24d-5c4d-90cd-b40cefb7f811-common": "19.1.4", + "@codingame/monaco-vscode-15626ec7-b165-51e1-8caf-7bcc2ae9b95a-common": "19.1.4", + "@codingame/monaco-vscode-2f06fe84-148e-5e6b-a7ca-c7989c5f128a-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4", + "@codingame/monaco-vscode-cea4d01f-6526-5c2f-8b09-b168fead499f-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-host-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-host-service-override/-/monaco-vscode-host-service-override-16.1.1.tgz", - "integrity": "sha512-9JGDbm8MUO51Mrtk/Tey6LzOPVp8wZvqXEKPwKpq7EmyRY+HTfNTIhv4cd/wI8Ks4fhYXe+cA602VX+zopCrhA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-host-service-override/-/monaco-vscode-host-service-override-19.1.4.tgz", + "integrity": "sha512-ItCorUFZiwbCXlHv0i1MymA23koN1/LXxMUe86B1XXIeL9z/zb+z3kX+p+8q1TByXC7X2yxJjdaoVf2JVoHieA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": "16.1.1", - "@codingame/monaco-vscode-7559b0be-bfa5-5fe6-b731-1973fe9fffa1-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-158b9837-fc78-5d9c-86f5-9134e4358643-common": "19.1.4", + "@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-keybindings-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-keybindings-service-override/-/monaco-vscode-keybindings-service-override-16.1.1.tgz", - "integrity": "sha512-MJGB+p9vR/7DFypQT2l8pZZE8uFREoVwQohgNESNbSiR/TKfNirUg+KGzTKbsP87pfUZpHtbx78l9ZHPqpIFVg==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-keybindings-service-override/-/monaco-vscode-keybindings-service-override-19.1.4.tgz", + "integrity": "sha512-QzAsK5wuyDLyqMuPR0NaSjC0gBub2WJsNdhuCRDD2jq3qoOFHIvIfRW4gz/DKtRNDBPBVKx3BWR6LktWekN8kQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-2a22c7b4-b906-5914-8cd1-3ed912fb738f-common": "16.1.1", - "@codingame/monaco-vscode-2b1a9082-790f-527f-b013-d1b29d6265a3-common": "16.1.1", - "@codingame/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-7559b0be-bfa5-5fe6-b731-1973fe9fffa1-common": "16.1.1", - "@codingame/monaco-vscode-a3eaa464-944c-5b8f-8886-213068ba4897-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-d609a7d3-bf87-551a-884f-550a8b327ec5-common": "16.1.1", - "@codingame/monaco-vscode-files-service-override": "16.1.1" + "@codingame/monaco-vscode-273022ab-b991-5d6a-9f04-decc266cb61b-common": "19.1.4", + "@codingame/monaco-vscode-2a22c7b4-b906-5914-8cd1-3ed912fb738f-common": "19.1.4", + "@codingame/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common": "19.1.4", + "@codingame/monaco-vscode-a3eaa464-944c-5b8f-8886-213068ba4897-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-d609a7d3-bf87-551a-884f-550a8b327ec5-common": "19.1.4", + "@codingame/monaco-vscode-files-service-override": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-language-pack-cs": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-cs/-/monaco-vscode-language-pack-cs-16.1.1.tgz", - "integrity": "sha512-OmSCVNIUWbjNvybhImQivJn6s/stcJQ5nxFiDdTb9iVo7nz7ftz5MzQJFEvBHadalV93QYYecC3waNlK06cTzA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-cs/-/monaco-vscode-language-pack-cs-19.1.4.tgz", + "integrity": "sha512-Ml6QAXtZ6QwQt/6jYpWtAZh1d89RdY2Dk6YRa/jTF8AeqN5LO3Dom9wrcV5RQOLJIPvlRESM9gHtinpIyJvwSA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-language-pack-de": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-de/-/monaco-vscode-language-pack-de-16.1.1.tgz", - "integrity": "sha512-o7f4D27ThoWKLXR7beE4kVuGyKj8g2cDlTS5BsfdVjGDopYxpka7WBaY2gWTdejspvFuNmGjL6JDizey6dtY2Q==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-de/-/monaco-vscode-language-pack-de-19.1.4.tgz", + "integrity": "sha512-F6U8KDUvT4jgF53xI/04pjZ+sV2sZngIdIc4M9W8vnLbkKm3tYyz4Y9yMua+R/GzbNB5Q26tRCWB3U7tcAHPXA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-language-pack-es": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-es/-/monaco-vscode-language-pack-es-16.1.1.tgz", - "integrity": "sha512-Lh23et+3F/P20ePMvHyVqoe7hJ0DEm9DmFc2aOLqseUTXkp3CbhHFgGcZQYt67D+wmMFeGAOeev4VCMBTIkwxQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-es/-/monaco-vscode-language-pack-es-19.1.4.tgz", + "integrity": "sha512-W4I+6Cvfk4EWRT0W6t0Hdrz0uP81Kytew5k09ptsVeTNOAUJQ05RlO/J2UIeEGXTqzktqFo4n6QpFJ6XRoe5hg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-language-pack-fr": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-fr/-/monaco-vscode-language-pack-fr-16.1.1.tgz", - "integrity": "sha512-dYMpEBDMiVKHuz5P55BCwNbz+YfFA74RxYzKfzy6fXA8Ku9FW12gB45cyOGcBzFQbnXj+6fk+1trOMuX0QCVlw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-fr/-/monaco-vscode-language-pack-fr-19.1.4.tgz", + "integrity": "sha512-ecP6m9CqyiX5Us11hAw0gJnUwAoN0pWM3fcSDKzMbr/iBwnhgqcyu7TCF270zJrt7iRQitDYoBuPl2vLfNfsPw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-language-pack-it": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-it/-/monaco-vscode-language-pack-it-16.1.1.tgz", - "integrity": "sha512-LnFtvhQqEdc5oukD2ScxBsEKhFPq2AXf6flJKySbhKc82Gp2jiw79KIJp8sA+eT+x3nf/yIUfcqcmY4nc4Ritg==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-it/-/monaco-vscode-language-pack-it-19.1.4.tgz", + "integrity": "sha512-2XUTwuPr9xXufT22F8vTDba+Z8dCvkKCVYob56Qj4oB78u9A+JbI9xO/EBdpkYCQ4ttjUdEuk791SbCez1aDsg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-language-pack-ja": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-ja/-/monaco-vscode-language-pack-ja-16.1.1.tgz", - "integrity": "sha512-fsXQRzE8Y90BEAjOkq9hDago256L38nr0gEnalkQz5jTsZbSijjRl8SmRVknT8p6zytdlelGl13KoKKS35U2Hw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-ja/-/monaco-vscode-language-pack-ja-19.1.4.tgz", + "integrity": "sha512-8uIxtEyTJaH840DVq3XkquCBkEGk/IBIdqzJFC/J991bPl01luYsJpjK57eGdzYq2LdpSsBRtymeLs8FbMKpcQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-language-pack-ko": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-ko/-/monaco-vscode-language-pack-ko-16.1.1.tgz", - "integrity": "sha512-lHAjB+961+WtuTWrcpMT7X6gHCIKjCHPW72mdP6cLFolr2xnFN+ypXl6krlybwQTJITD63kEnXqI/i4kzj0AAA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-ko/-/monaco-vscode-language-pack-ko-19.1.4.tgz", + "integrity": "sha512-hlfx/9lSsVN/OYcDDWK6AbGN+DJXK2zaoXZHpf32+wLFgxenFvUW8BcZeajntHCr5DtXoDBBsLR3eednysUe0g==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-language-pack-pl": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-pl/-/monaco-vscode-language-pack-pl-16.1.1.tgz", - "integrity": "sha512-AxmMnD6veoIkUn0rx0jt0IIPpfdeDBpWl14LV7HDRnqFZCWmWMpoMwHd4wrV4A4lz1G6mxknalC67JGwtqAtBw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-pl/-/monaco-vscode-language-pack-pl-19.1.4.tgz", + "integrity": "sha512-X180GKwDDsprF8bqUxL7sndNO43NadHn1wcdZwvR4fibI3uMO6MTpq2mxaBIxvbaLALQ5Wyyp60GGavICtIHIQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-language-pack-pt-br": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-pt-br/-/monaco-vscode-language-pack-pt-br-16.1.1.tgz", - "integrity": "sha512-CGe1VSphBUmp+hFS27qYhjdDmHz0nejdTt1Eg5k0FbibPSG+SnVjshFP0K43IQsRrdigsPVL1I+CnvXlnhJhQA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-pt-br/-/monaco-vscode-language-pack-pt-br-19.1.4.tgz", + "integrity": "sha512-apbhLWEMoX1RGNgz9QOMG+O3DUqbH0QVjLCClzJ+oLqLogjnQQ8JFHt8cIwAfNQa/0iVHjAwM0jBMpGx6lK/1g==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-language-pack-qps-ploc": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-qps-ploc/-/monaco-vscode-language-pack-qps-ploc-16.1.1.tgz", - "integrity": "sha512-gh5IozzmpixbumT9znmlvinex1b7zSELqtzsb/CBy82lXW6Vi6HfAQI2crOUrzu080g3yWUo6AuxzzjF6F820w==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-qps-ploc/-/monaco-vscode-language-pack-qps-ploc-19.1.4.tgz", + "integrity": "sha512-gYRznsEiSqmZN6zojkWFIJ92Nlk/vgLiZYezeDVRHz3pTbAxPFCU9J0stDnPhtaln33IvgeI15BJL2r8RJ/q4w==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-language-pack-ru": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-ru/-/monaco-vscode-language-pack-ru-16.1.1.tgz", - "integrity": "sha512-CROyXppUkBPvCPY+SJ/nsNrqYi/Porh8M0GvLrsDj3lD/94bdrmkk9OcrZdZAzvq/8yMNTeS2YOEHekwyQPwwg==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-ru/-/monaco-vscode-language-pack-ru-19.1.4.tgz", + "integrity": "sha512-Hq8rONSz3ibH+VQoVuU9sobL4yli5exnc6M07Fa5Ol0oIaO/ILksLKXTiYjvh9RbhXEbYNxukbL6wW0GAqOOLg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-language-pack-tr": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-tr/-/monaco-vscode-language-pack-tr-16.1.1.tgz", - "integrity": "sha512-1JU1vNBmxMPXVZhVEzN0ac0lOL/6srmm16FDp2lbIteakiAhBajuHrr5OC/256s3hxnMhADI3Oiknvg3gho9NQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-tr/-/monaco-vscode-language-pack-tr-19.1.4.tgz", + "integrity": "sha512-Jx5TRaxGLJYZwUYHPAGFiOGTqGG/e9OmAeXzYkuFTaRQ9/P/boLYa3j3PiBkqZaL+r3//Lr/91BxAhLXu15w2g==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-language-pack-zh-hans": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-zh-hans/-/monaco-vscode-language-pack-zh-hans-16.1.1.tgz", - "integrity": "sha512-/YdeXEG2ipaq7GlW1dtaZI5x5gMVM+hBvt/gZ36ARyqH7ZdvKIDtp2K2VqEE2V1ouvTtc4xWGFZnsk0Cmj8/YQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-zh-hans/-/monaco-vscode-language-pack-zh-hans-19.1.4.tgz", + "integrity": "sha512-YbdY3BLSLPDnn+J6+96X2GDerdo3Py4odLD5pF5+8n4pV0YW3Wtvy6oPy0U7eArt5fUkBM0kDKACc4Gzvx70vQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-language-pack-zh-hant": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-zh-hant/-/monaco-vscode-language-pack-zh-hant-16.1.1.tgz", - "integrity": "sha512-i9paxpOZAPCGngICNTdOGyGKcYND+VSNO6GA+69vTSnCfcCCJAEomSWRm73Y6dhkYPeLkpwrukO2ZhAJFYfmIg==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-zh-hant/-/monaco-vscode-language-pack-zh-hant-19.1.4.tgz", + "integrity": "sha512-FpAxGU/IZA3M04H5scBtIHib/+qOUvGLiMlW9n8cR6esC4r0Kg+TIr9y0HaolFPybeLCjUSQQjmJ0gLf/2g2tg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-languages-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-languages-service-override/-/monaco-vscode-languages-service-override-16.1.1.tgz", - "integrity": "sha512-ji9H2bOAdJvIpp7w5oh61RWWCoQYmZ04Xjlpi041W+JK2JPHkvldlSTf6foEcLhoKka0GRDzteUX7gtz/C90AQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-languages-service-override/-/monaco-vscode-languages-service-override-19.1.4.tgz", + "integrity": "sha512-bIXNUxTB4H1ScEhkCJgcM91Z8heX/otHFvzzvqn79PMRIRY8ZdjBOCtOBZVpqEOCY8pR9X9svq3LQSFHZJporA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-files-service-override": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-files-service-override": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-layout-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-layout-service-override/-/monaco-vscode-layout-service-override-16.1.1.tgz", - "integrity": "sha512-cowmA85gMHrsgLeS00mjmj6aAKOqRkuAuceIvNXkeivyp4GgnLDMf0AOaqJLGPbICxBsHxLqUFiyaVOOLDJqIQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-layout-service-override/-/monaco-vscode-layout-service-override-19.1.4.tgz", + "integrity": "sha512-U26dLMtX8jc5JTgrdmCacdVcCI6WTr5re/THuRYVyMWBci0ghO3BcixehRCqSi6Mcu164zqxXFYnBrDDG6DwqA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-6bf85d7b-e6e3-54e9-9bc1-7e08d663f0f6-common": "16.1.1", - "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-6bf85d7b-e6e3-54e9-9bc1-7e08d663f0f6-common": "19.1.4", + "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-localization-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-localization-service-override/-/monaco-vscode-localization-service-override-16.1.1.tgz", - "integrity": "sha512-FGfQCXYFUzzO1PjFnlMUehtzbPI5Fa+L1R7Ex2mVZZtkIUd2YX4F427kFRS+bNJyPNzvvQGk+NQ/qUydznAFWg==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-localization-service-override/-/monaco-vscode-localization-service-override-19.1.4.tgz", + "integrity": "sha512-9dy6aeX9bjoGEgHmjrg988ytfxvWHmwG6XmbAYCuvkLGQ7hqcy0pDryKJDetH6vG/syUwtqqJwudA43i0CBVWA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-7ba0af96-90c2-5e11-ad7f-befdbbf246c8-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-516ccb00-d5b9-5317-baf0-fcc96e99669a-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-log-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-log-service-override/-/monaco-vscode-log-service-override-16.1.1.tgz", - "integrity": "sha512-LlI3nUsj07nqZ1b1wLCVCvV+ZH1C5sKSDBbvYjqJEwjVqyW0rd3Ohh5GO7k9MXvou1i3VnEN526m75+Qk3MqnQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-log-service-override/-/monaco-vscode-log-service-override-19.1.4.tgz", + "integrity": "sha512-/ugYvUtJaGNAll9rPgyjIA28E2kHQBDtDaJKoXfD0NZZ0wE9Qu5jHbqDS/ushbNFuVe6vAXlH8nB3ddbLNnBFw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-abed5a84-8a82-5f84-9412-88a736235bae-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-cea4d01f-6526-5c2f-8b09-b168fead499f-common": "16.1.1", - "@codingame/monaco-vscode-environment-service-override": "16.1.1" + "@codingame/monaco-vscode-abed5a84-8a82-5f84-9412-88a736235bae-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-cea4d01f-6526-5c2f-8b09-b168fead499f-common": "19.1.4", + "@codingame/monaco-vscode-environment-service-override": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-model-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-model-service-override/-/monaco-vscode-model-service-override-16.1.1.tgz", - "integrity": "sha512-PVRMUvFYpCzA5B2FWwnkrGWHHCMyXHoVSRRVELglRtn1UEerX95WiuZu6oDFfz868jwEvXJ5UzEJC+hXZs3WDw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-model-service-override/-/monaco-vscode-model-service-override-19.1.4.tgz", + "integrity": "sha512-0GJLf3z+yWuu5woMPw9Ca1vRk5jFRy8hbzPzm19Ogz3/T2w9RFKU313h4Ro8w7FcOXldQ11BliYfmr8veZ+1EQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-0c06bfba-d24d-5c4d-90cd-b40cefb7f811-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-0c06bfba-d24d-5c4d-90cd-b40cefb7f811-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-monarch-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-monarch-service-override/-/monaco-vscode-monarch-service-override-16.1.1.tgz", - "integrity": "sha512-qIzUpsbYHx7F1BYhQEKodbYLO03kLR1mWa0PKXtMuBuqtQcqjIcx+0NypUZ5dQEy/kE0UuuZpgz+/TrVyqVvGw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-monarch-service-override/-/monaco-vscode-monarch-service-override-19.1.4.tgz", + "integrity": "sha512-lymP90vXYAR6uOrwmPHK6TlwVTcDZthDgpWOY0IdbCBXuxJr80FhoJSKLnrRfdrYVzQPEOXloo27/rtw/Gd3Ww==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-quickaccess-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-quickaccess-service-override/-/monaco-vscode-quickaccess-service-override-16.1.1.tgz", - "integrity": "sha512-AGvwtzIPaqMZXv1cYNOvkqrewmLmpA5dkgz1EmfB+HQNVXwWGv0x6Gde06AhGDlK9T0AyfoYc/vEh5Hd/9ddWw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-quickaccess-service-override/-/monaco-vscode-quickaccess-service-override-19.1.4.tgz", + "integrity": "sha512-bcG35wYAjFgFKBDSXzqNvHTp3EyKIHtLtVL8nrMgnNSCJXP7hKUtrfrcWQk+WS/I4KDJYKE1p+zEIeqotMsfew==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common": "16.1.1", - "@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": "16.1.1", - "@codingame/monaco-vscode-7559b0be-bfa5-5fe6-b731-1973fe9fffa1-common": "16.1.1", - "@codingame/monaco-vscode-9a1a5840-af83-5d07-a156-ba32a36c5c4b-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-d609a7d3-bf87-551a-884f-550a8b327ec5-common": "16.1.1", - "@codingame/monaco-vscode-ea14e352-8f1c-5569-b79a-8a96a53e8abe-common": "16.1.1" + "@codingame/monaco-vscode-158b9837-fc78-5d9c-86f5-9134e4358643-common": "19.1.4", + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": "19.1.4", + "@codingame/monaco-vscode-3927dd53-f845-534c-9baa-1528ff3956c8-common": "19.1.4", + "@codingame/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common": "19.1.4", + "@codingame/monaco-vscode-9a1a5840-af83-5d07-a156-ba32a36c5c4b-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-d609a7d3-bf87-551a-884f-550a8b327ec5-common": "19.1.4", + "@codingame/monaco-vscode-ea14e352-8f1c-5569-b79a-8a96a53e8abe-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-standalone-css-language-features": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-standalone-css-language-features/-/monaco-vscode-standalone-css-language-features-16.1.1.tgz", - "integrity": "sha512-jnB72TVPsDPrGvxnRhvZCx7/hP/+UkxvDTCdaOcq99Uk/GTukAliXa2c7uLTMC3Oi7x4wXxLIlMpgNtjeiCvPw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-standalone-css-language-features/-/monaco-vscode-standalone-css-language-features-19.1.4.tgz", + "integrity": "sha512-GGp74SVHAMt6udkTaIsglbeHBUg1AkjGIuyl4xlLV8GN7T2XM6jd3A6s4R8WBOjJ/NHEen0APjt+AoEFb5xhLA==", + "license": "MIT", "dependencies": { - "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@16.1.1" + "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@19.1.4" } }, "node_modules/@codingame/monaco-vscode-standalone-html-language-features": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-standalone-html-language-features/-/monaco-vscode-standalone-html-language-features-16.1.1.tgz", - "integrity": "sha512-K7FivVWb/MCGfnUfG+S4uHpsViig5a3Lgtet8hjK+diiwm2TOu0Dcci03FPuF+tmtTBXAekrWbR7uX7QDaTqTw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-standalone-html-language-features/-/monaco-vscode-standalone-html-language-features-19.1.4.tgz", + "integrity": "sha512-vSrkv0O5sdgDTSuzwKppxOFH3zDklx0fUDLvmp9swL0RAsgdcRBxtp9+BJormQRPSXNra3DOZPScY3t2Ir5rnA==", + "license": "MIT", "dependencies": { - "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@16.1.1" + "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@19.1.4" } }, "node_modules/@codingame/monaco-vscode-standalone-json-language-features": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-standalone-json-language-features/-/monaco-vscode-standalone-json-language-features-16.1.1.tgz", - "integrity": "sha512-oEAtKl27ktz0otrXaqEGsvbVWYYrRmiZnMrnXPN+ZAAxlfR6W5czxxZ6NhZQ6u0UV79zbgs2pAp5ACbNBmYUjw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-standalone-json-language-features/-/monaco-vscode-standalone-json-language-features-19.1.4.tgz", + "integrity": "sha512-eoDF61cMIAzR0lV0XhD/zUImCcMX855Jf6qUhvXOZT1/1X0+d882hy97WniJkl1hIsQwEoC15RwDP3mkXjW4vg==", + "license": "MIT", "dependencies": { - "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@16.1.1" + "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@19.1.4" } }, "node_modules/@codingame/monaco-vscode-standalone-languages": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-standalone-languages/-/monaco-vscode-standalone-languages-16.1.1.tgz", - "integrity": "sha512-wJ2RpHbJHfLo2FzZ80wStBc6rmTTTS9AdEdDi0BObMIgF/x4vLX3v+tT1MTrZnd30TWgk3w078zuhRKhEVF9dA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-standalone-languages/-/monaco-vscode-standalone-languages-19.1.4.tgz", + "integrity": "sha512-3oKeP89KFo7698CKO/RAuOebzeuk7yVDtYhPY+NInR7TUKQ5Mkbiaq8GAgCyL6kijbMvDbx0NXf/38sZQHhSlA==", + "license": "MIT", "dependencies": { - "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@16.1.1" + "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@19.1.4" } }, "node_modules/@codingame/monaco-vscode-standalone-typescript-language-features": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-standalone-typescript-language-features/-/monaco-vscode-standalone-typescript-language-features-16.1.1.tgz", - "integrity": "sha512-/8oKPnX3eJGTRnKFwVd6mrGGca+8/upVSEOrtvfdCTrfJCH8mhZp41OWhDmDUx0PmB5qhlKXZPnTEXTux2hRjw==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-standalone-typescript-language-features/-/monaco-vscode-standalone-typescript-language-features-19.1.4.tgz", + "integrity": "sha512-2nSTjkEee1PQURuQx0N/UZVsXknYxiYbqV2aBVXyMycHnqm0DAcZqIylLLzDC3SCk7O4QeTBxFvAxs54Sa69bA==", + "license": "MIT", "dependencies": { - "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@16.1.1" + "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@19.1.4" } }, "node_modules/@codingame/monaco-vscode-textmate-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-textmate-service-override/-/monaco-vscode-textmate-service-override-16.1.1.tgz", - "integrity": "sha512-TbVh+9gfqPNtDL9TtA9Upi9hOo/aaM54Yj3XYK1jBebmRdHrGCWEzUBds8424B4CnPbYSOmInHybCfaPwjgqcQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-textmate-service-override/-/monaco-vscode-textmate-service-override-19.1.4.tgz", + "integrity": "sha512-Jj5L9iLMbEd0pd2iKFfFA6sZS1id2e+XTYa+RhI2fT7IGcp+ZIG0i7U+zNYMUhLWmeLk7Bqwj27ebr9a/mfdsw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-33833ac7-3af3-5e9d-8fb9-11838d852c59-common": "16.1.1", - "@codingame/monaco-vscode-7ba0af96-90c2-5e11-ad7f-befdbbf246c8-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-b73d4be0-4f5e-582d-a326-d3b800f9844a-common": "16.1.1", - "@codingame/monaco-vscode-files-service-override": "16.1.1", + "@codingame/monaco-vscode-1ba786a5-b7d7-5d26-8a85-ae48ee2a74a4-common": "19.1.4", + "@codingame/monaco-vscode-33833ac7-3af3-5e9d-8fb9-11838d852c59-common": "19.1.4", + "@codingame/monaco-vscode-9c1add21-d437-5ee3-ae2b-ad115a9ef171-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-files-service-override": "19.1.4", "vscode-oniguruma": "1.7.0", "vscode-textmate": "9.2.0" } }, "node_modules/@codingame/monaco-vscode-theme-defaults-default-extension": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-theme-defaults-default-extension/-/monaco-vscode-theme-defaults-default-extension-16.1.1.tgz", - "integrity": "sha512-4mQGJOZCvkO7mGkTpqg6d6iiK1KvVJHsCdzHTtiZ6NKjmkSDYvO0L8abfHFWfHqe/aWTzaMjZGAEtNjbipFJgQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-theme-defaults-default-extension/-/monaco-vscode-theme-defaults-default-extension-19.1.4.tgz", + "integrity": "sha512-Q7aQPvGjpfeNF4eLzqdyXcjV7n5kQAMOqjeWlj0WGZhFh+eqstwMsdKc0x2M/0Kahj38vlgWswFhGKW7a5CTWw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-theme-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-theme-service-override/-/monaco-vscode-theme-service-override-16.1.1.tgz", - "integrity": "sha512-qkcfAyCHOoVndLXa95GSNBBy6luAJhbpqNrCUuvs3+YIczjVhs3vtkEe3OwGbF65EEmlxKejvWdxxEqCSMrhaA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-theme-service-override/-/monaco-vscode-theme-service-override-19.1.4.tgz", + "integrity": "sha512-Eez23mrXDACYjFgakAkDzJH2x2VJkQ4OHJtKTjMjL9zXOurNmf8PqfU8nQArPeVuUAjI1aKe9RccX/hJKhewoQ==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": "16.1.1", - "@codingame/monaco-vscode-7ba0af96-90c2-5e11-ad7f-befdbbf246c8-common": "16.1.1", - "@codingame/monaco-vscode-9d0168a3-519b-57f3-9bcc-89efc41f951a-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-files-service-override": "16.1.1" + "@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": "19.1.4", + "@codingame/monaco-vscode-9c1add21-d437-5ee3-ae2b-ad115a9ef171-common": "19.1.4", + "@codingame/monaco-vscode-9d0168a3-519b-57f3-9bcc-89efc41f951a-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-files-service-override": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-view-banner-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-view-banner-service-override/-/monaco-vscode-view-banner-service-override-16.1.1.tgz", - "integrity": "sha512-k1aLQj+v9ptjCQ97fyRS9u5PHDZ1FrI9EyNzjRsi10AIM211DLjV8iFzLhRQR5QFsaFnsJl9m/QmKtes5eMUiQ==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-view-banner-service-override/-/monaco-vscode-view-banner-service-override-19.1.4.tgz", + "integrity": "sha512-dFJxX0P6rqeYNI28jTX60JwtY74s5Z5Elkc3g5y/4V/6EYCPxCzpdi0m0e4pfX0NDhyY61ZjlAzrqrWRhQKHNA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": "19.1.4", + "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-view-common-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-view-common-service-override/-/monaco-vscode-view-common-service-override-16.1.1.tgz", - "integrity": "sha512-F6U/w0K02dhaMRzGkK/uSUgLZLnhjBxuFrTUgTaCf4Dzv40jLlWJFrpAFw/mOIAC6zBKGCjwzcAER3zQ7tOC7w==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-view-common-service-override/-/monaco-vscode-view-common-service-override-19.1.4.tgz", + "integrity": "sha512-MYZyKDOcyqBLKxssDA+72iivczXgHxqLHMn/xV/CqWPTTiYz1G4rrl31qEScnZMcE2bgDIsD+jjo/wZJhS7xlg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-0c06bfba-d24d-5c4d-90cd-b40cefb7f811-common": "16.1.1", - "@codingame/monaco-vscode-0cc5da60-f921-59b9-bd8c-a018e93c0a6f-common": "16.1.1", - "@codingame/monaco-vscode-0f5ced28-abde-558b-8652-db8e7d4d64aa-common": "16.1.1", - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-1b4486de-4fe4-59c4-9e6d-34f265ff6625-common": "16.1.1", - "@codingame/monaco-vscode-210e86a9-a91b-5273-b05d-390c776dde1f-common": "16.1.1", - "@codingame/monaco-vscode-29bc1406-2925-5b8f-b25e-d04a7772d896-common": "16.1.1", - "@codingame/monaco-vscode-2a94c04a-b85b-5669-b06b-89c1bfa11cb9-common": "16.1.1", - "@codingame/monaco-vscode-3109a756-1f83-5d09-945b-9f0fcad928f0-common": "16.1.1", - "@codingame/monaco-vscode-336c86fe-5135-5343-af5f-c6d13bc09c07-common": "16.1.1", - "@codingame/monaco-vscode-38f25ab8-ea30-5ba9-8a08-ae3308c297c0-common": "16.1.1", - "@codingame/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-4a316137-39d1-5d77-8b53-112db3547c1e-common": "16.1.1", - "@codingame/monaco-vscode-523730aa-81e6-55d7-9916-87ad537fe087-common": "16.1.1", - "@codingame/monaco-vscode-65619f8f-0eab-5d8b-855a-43b6353fe527-common": "16.1.1", - "@codingame/monaco-vscode-68b3311c-eb96-5711-8f72-f5f4b2e31932-common": "16.1.1", - "@codingame/monaco-vscode-6980eeab-47bb-5a48-8e15-32caf0785565-common": "16.1.1", - "@codingame/monaco-vscode-7559b0be-bfa5-5fe6-b731-1973fe9fffa1-common": "16.1.1", - "@codingame/monaco-vscode-7ba0af96-90c2-5e11-ad7f-befdbbf246c8-common": "16.1.1", - "@codingame/monaco-vscode-7f39b6f1-3542-5430-8760-0f404d7a7cee-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-897bebad-39df-57cb-8a57-36a271d038be-common": "16.1.1", - "@codingame/monaco-vscode-89a82baf-8ded-5b2f-b8af-e5fbd72dc5ad-common": "16.1.1", - "@codingame/monaco-vscode-8ccb7637-50ea-5359-97bf-00015d7fe567-common": "16.1.1", - "@codingame/monaco-vscode-9b5a5e82-d649-5455-b4bf-ef90d6afd294-common": "16.1.1", - "@codingame/monaco-vscode-9c84f943-bcb5-5bcf-92a6-91f66a732f26-common": "16.1.1", - "@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common": "16.1.1", - "@codingame/monaco-vscode-a7c9ae3c-16d2-5d17-86b2-981be7094566-common": "16.1.1", - "@codingame/monaco-vscode-ad89fae6-94f0-5ac2-a185-22dea4b68ee0-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-b1e8558d-1726-5299-bc75-e43ee6d1a124-common": "16.1.1", - "@codingame/monaco-vscode-bulk-edit-service-override": "16.1.1", - "@codingame/monaco-vscode-c4e2825a-b5b1-5f0e-b547-068c32e06d50-common": "16.1.1", - "@codingame/monaco-vscode-d26a96d3-122c-5a3d-a04d-deb5ff0f19c0-common": "16.1.1", - "@codingame/monaco-vscode-d481a59e-259c-524e-bee1-76483d75d3a1-common": "16.1.1", - "@codingame/monaco-vscode-e59ecb8c-db32-5324-8fe4-cf9921fd92b8-common": "16.1.1", - "@codingame/monaco-vscode-e67a0dae-5b2c-54e6-8d61-90102c78362d-common": "16.1.1", - "@codingame/monaco-vscode-e72c94ca-257a-5b75-8b68-5a5fa3c18255-common": "16.1.1", - "@codingame/monaco-vscode-f1bbc6d3-6129-583c-a2ba-c80b832993d2-common": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1", - "@codingame/monaco-vscode-ff9fa663-eae3-5274-8573-c2b918871e4b-common": "16.1.1" + "@codingame/monaco-vscode-0c06bfba-d24d-5c4d-90cd-b40cefb7f811-common": "19.1.4", + "@codingame/monaco-vscode-0cc5da60-f921-59b9-bd8c-a018e93c0a6f-common": "19.1.4", + "@codingame/monaco-vscode-158b9837-fc78-5d9c-86f5-9134e4358643-common": "19.1.4", + "@codingame/monaco-vscode-1b4486de-4fe4-59c4-9e6d-34f265ff6625-common": "19.1.4", + "@codingame/monaco-vscode-210e86a9-a91b-5273-b05d-390c776dde1f-common": "19.1.4", + "@codingame/monaco-vscode-271a23cd-c7d7-5761-ae35-a923a42987b8-common": "19.1.4", + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-2a94c04a-b85b-5669-b06b-89c1bfa11cb9-common": "19.1.4", + "@codingame/monaco-vscode-3109a756-1f83-5d09-945b-9f0fcad928f0-common": "19.1.4", + "@codingame/monaco-vscode-33de3d75-8b1a-53f0-9e27-0c71c327e1a1-common": "19.1.4", + "@codingame/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common": "19.1.4", + "@codingame/monaco-vscode-4a316137-39d1-5d77-8b53-112db3547c1e-common": "19.1.4", + "@codingame/monaco-vscode-501b06ab-3f58-516b-8a1a-c29d375d3da4-common": "19.1.4", + "@codingame/monaco-vscode-523730aa-81e6-55d7-9916-87ad537fe087-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-6980eeab-47bb-5a48-8e15-32caf0785565-common": "19.1.4", + "@codingame/monaco-vscode-72a1b7d3-3f58-5545-9b7e-f579bd003081-common": "19.1.4", + "@codingame/monaco-vscode-7869cfe8-f42c-5721-9f2b-7d04a6a41f16-common": "19.1.4", + "@codingame/monaco-vscode-7f39b6f1-3542-5430-8760-0f404d7a7cee-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-897bebad-39df-57cb-8a57-36a271d038be-common": "19.1.4", + "@codingame/monaco-vscode-89a82baf-8ded-5b2f-b8af-e5fbd72dc5ad-common": "19.1.4", + "@codingame/monaco-vscode-8ccb7637-50ea-5359-97bf-00015d7fe567-common": "19.1.4", + "@codingame/monaco-vscode-937ecbdf-94c7-5b16-aefa-ad78ae557a93-common": "19.1.4", + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-9c84f943-bcb5-5bcf-92a6-91f66a732f26-common": "19.1.4", + "@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common": "19.1.4", + "@codingame/monaco-vscode-a8d3bd74-e63e-5327-96e8-4f931661e329-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-bc6d9a89-1625-5010-b57e-ff44151144fe-common": "19.1.4", + "@codingame/monaco-vscode-bulk-edit-service-override": "19.1.4", + "@codingame/monaco-vscode-caf773dd-ab1d-5936-941d-a2fe9e896ffc-common": "19.1.4", + "@codingame/monaco-vscode-d26a96d3-122c-5a3d-a04d-deb5ff0f19c0-common": "19.1.4", + "@codingame/monaco-vscode-d481a59e-259c-524e-bee1-76483d75d3a1-common": "19.1.4", + "@codingame/monaco-vscode-dbfe5f85-b426-55ed-a79b-5f811b395762-common": "19.1.4", + "@codingame/monaco-vscode-ddbd4d51-94af-51e5-96b5-f2166ed67ad0-common": "19.1.4", + "@codingame/monaco-vscode-e59ecb8c-db32-5324-8fe4-cf9921fd92b8-common": "19.1.4", + "@codingame/monaco-vscode-e72c94ca-257a-5b75-8b68-5a5fa3c18255-common": "19.1.4", + "@codingame/monaco-vscode-f1bbc6d3-6129-583c-a2ba-c80b832993d2-common": "19.1.4", + "@codingame/monaco-vscode-f24e325c-2ce0-5bba-8236-bfc4f53180ab-common": "19.1.4", + "@codingame/monaco-vscode-ff9fa663-eae3-5274-8573-c2b918871e4b-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-view-status-bar-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-view-status-bar-service-override/-/monaco-vscode-view-status-bar-service-override-16.1.1.tgz", - "integrity": "sha512-ysgWVDriNekikaP/ShWzSCa9fwx1S/xk+dgEzebY3/Mmc4y7QZ0ou7gSdkY4Xb2R9RkfzCbLAR6PfjV8bU/8LA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-view-status-bar-service-override/-/monaco-vscode-view-status-bar-service-override-19.1.4.tgz", + "integrity": "sha512-K5/XSJDPV119hJDL1WLR4OIovW6A0Foi7PR6iWb2n8DcyNO7jZZQ2L9Dm4p9Zzn6WTfrh7GUVm/n3munKWivcg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-0cc5da60-f921-59b9-bd8c-a018e93c0a6f-common": "16.1.1", - "@codingame/monaco-vscode-2bd6777a-3bff-5b83-ad25-0e029e81ffe9-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-0cc5da60-f921-59b9-bd8c-a018e93c0a6f-common": "19.1.4", + "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-ca445022-b812-53ca-a041-e46c7e0b13de-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-view-title-bar-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-view-title-bar-service-override/-/monaco-vscode-view-title-bar-service-override-16.1.1.tgz", - "integrity": "sha512-RJ5aPnsHrQiKK7MuF/w0oTurhP3b9f7dRmQtb64rSIzSsBdbyoqe7IGIDJTWDc6bT9YiGQS939IPx8+punTtNA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-view-title-bar-service-override/-/monaco-vscode-view-title-bar-service-override-19.1.4.tgz", + "integrity": "sha512-E2KPAdbUNOvemDgW9TydByLpibL6ySYehhBt/PkT9XKX4Dg4Ig0hY+8GADGnDwvacMcx9kcVjeSPMBxAZvtWZw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-12c5f9eb-72d3-57ca-babd-5bef7aa9de3b-common": "16.1.1", - "@codingame/monaco-vscode-38f25ab8-ea30-5ba9-8a08-ae3308c297c0-common": "16.1.1", - "@codingame/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common": "16.1.1", - "@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-6ec8cd23-5be5-5c15-93ce-01afcf65acd3-common": "16.1.1", - "@codingame/monaco-vscode-78982ab1-8dc8-5de0-9139-e8865a161f5a-common": "16.1.1", - "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "16.1.1", - "@codingame/monaco-vscode-96e83782-7f38-572e-8787-02e981f1c54f-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": "19.1.4", + "@codingame/monaco-vscode-40cada32-7e9c-528a-81fc-766e4da54147-common": "19.1.4", + "@codingame/monaco-vscode-60014c9d-b815-501d-83a9-4b08725c2ec2-common": "19.1.4", + "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "19.1.4", + "@codingame/monaco-vscode-96e83782-7f38-572e-8787-02e981f1c54f-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-da4efea3-7112-5351-9c1d-60017cbd9532-common": "19.1.4", + "@codingame/monaco-vscode-dbfe5f85-b426-55ed-a79b-5f811b395762-common": "19.1.4", + "@codingame/monaco-vscode-e7671ac2-ad6e-5d29-8b2e-8bc090939746-common": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-views-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-views-service-override/-/monaco-vscode-views-service-override-16.1.1.tgz", - "integrity": "sha512-RQIFJmQaE1MMwSmnTMzHsVttMYX4yJoMfA8khSFiHaR4tJrxUbGyWbfT36txEwx8ECHhrLIpnwTWO8VhPjC0WA==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-views-service-override/-/monaco-vscode-views-service-override-19.1.4.tgz", + "integrity": "sha512-yjzKh5alRojyCGRYTqjJ9ye0T4shODQhokofIfl8AWC2uEWHeP0aAstQb1L5i53Rhfr754WzrI2W2A12MjZrXA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-2bd6777a-3bff-5b83-ad25-0e029e81ffe9-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-6980eeab-47bb-5a48-8e15-32caf0785565-common": "16.1.1", - "@codingame/monaco-vscode-6bf85d7b-e6e3-54e9-9bc1-7e08d663f0f6-common": "16.1.1", - "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-e67a0dae-5b2c-54e6-8d61-90102c78362d-common": "16.1.1", - "@codingame/monaco-vscode-keybindings-service-override": "16.1.1", - "@codingame/monaco-vscode-layout-service-override": "16.1.1", - "@codingame/monaco-vscode-quickaccess-service-override": "16.1.1", - "@codingame/monaco-vscode-view-common-service-override": "16.1.1" + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-6980eeab-47bb-5a48-8e15-32caf0785565-common": "19.1.4", + "@codingame/monaco-vscode-6bf85d7b-e6e3-54e9-9bc1-7e08d663f0f6-common": "19.1.4", + "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common": "19.1.4", + "@codingame/monaco-vscode-a8d3bd74-e63e-5327-96e8-4f931661e329-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-keybindings-service-override": "19.1.4", + "@codingame/monaco-vscode-layout-service-override": "19.1.4", + "@codingame/monaco-vscode-quickaccess-service-override": "19.1.4", + "@codingame/monaco-vscode-view-common-service-override": "19.1.4" } }, "node_modules/@codingame/monaco-vscode-workbench-service-override": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-workbench-service-override/-/monaco-vscode-workbench-service-override-16.1.1.tgz", - "integrity": "sha512-W7O+EWzuEk2U1Upx+I10dFcckyPXoswWjtC52COqpDu3YmpBnh/NpPFqCwpT83Ew99SjMCFwgWj0zaH7AXmcHg==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-workbench-service-override/-/monaco-vscode-workbench-service-override-19.1.4.tgz", + "integrity": "sha512-FyL8NDFoPAskDGwHR8ouYolwn9JHuPA7qpO5eqpp2+OHBiJRb6jDc6UL5x41DxYYECO6/xDDhfPzBLl79yFgKw==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-1021b67c-93e5-5c78-a270-cbdb2574d980-common": "16.1.1", - "@codingame/monaco-vscode-256d5b78-0649-50e9-8354-2807f95f68f4-common": "16.1.1", - "@codingame/monaco-vscode-2bd6777a-3bff-5b83-ad25-0e029e81ffe9-common": "16.1.1", - "@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": "16.1.1", - "@codingame/monaco-vscode-4893679b-56e5-52c5-a8fb-c366cce9660f-common": "16.1.1", - "@codingame/monaco-vscode-494be54c-bd37-5b3c-af70-02f086e28768-common": "16.1.1", - "@codingame/monaco-vscode-6980eeab-47bb-5a48-8e15-32caf0785565-common": "16.1.1", - "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "16.1.1", - "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "16.1.1", - "@codingame/monaco-vscode-9c84f943-bcb5-5bcf-92a6-91f66a732f26-common": "16.1.1", - "@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-e67a0dae-5b2c-54e6-8d61-90102c78362d-common": "16.1.1", - "@codingame/monaco-vscode-fdf643f9-94dd-5510-b97a-408abf26ad92-common": "16.1.1", - "@codingame/monaco-vscode-keybindings-service-override": "16.1.1", - "@codingame/monaco-vscode-quickaccess-service-override": "16.1.1", - "@codingame/monaco-vscode-view-banner-service-override": "16.1.1", - "@codingame/monaco-vscode-view-common-service-override": "16.1.1", - "@codingame/monaco-vscode-view-status-bar-service-override": "16.1.1", - "@codingame/monaco-vscode-view-title-bar-service-override": "16.1.1" + "@codingame/monaco-vscode-1021b67c-93e5-5c78-a270-cbdb2574d980-common": "19.1.4", + "@codingame/monaco-vscode-256d5b78-0649-50e9-8354-2807f95f68f4-common": "19.1.4", + "@codingame/monaco-vscode-2726569c-a06b-5f25-b851-14311bef12fa-common": "19.1.4", + "@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": "19.1.4", + "@codingame/monaco-vscode-494be54c-bd37-5b3c-af70-02f086e28768-common": "19.1.4", + "@codingame/monaco-vscode-516ccb00-d5b9-5317-baf0-fcc96e99669a-common": "19.1.4", + "@codingame/monaco-vscode-6980eeab-47bb-5a48-8e15-32caf0785565-common": "19.1.4", + "@codingame/monaco-vscode-85886bdb-61c5-52f1-8eb7-d1d32f6f8cbd-common": "19.1.4", + "@codingame/monaco-vscode-86d65fc6-30f9-5dca-9501-e249de688591-common": "19.1.4", + "@codingame/monaco-vscode-9c72783f-914c-50be-b9ef-da16356d81a8-common": "19.1.4", + "@codingame/monaco-vscode-9c84f943-bcb5-5bcf-92a6-91f66a732f26-common": "19.1.4", + "@codingame/monaco-vscode-9efc1f50-c7de-55d6-8b28-bcc88bd49b5a-common": "19.1.4", + "@codingame/monaco-vscode-a8d3bd74-e63e-5327-96e8-4f931661e329-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-keybindings-service-override": "19.1.4", + "@codingame/monaco-vscode-quickaccess-service-override": "19.1.4", + "@codingame/monaco-vscode-view-banner-service-override": "19.1.4", + "@codingame/monaco-vscode-view-common-service-override": "19.1.4", + "@codingame/monaco-vscode-view-status-bar-service-override": "19.1.4", + "@codingame/monaco-vscode-view-title-bar-service-override": "19.1.4" } }, "node_modules/@csstools/css-parser-algorithms": { @@ -2750,259 +2835,279 @@ "integrity": "sha512-dSMyuNPN2k+tFeNZ0+QJ7S1zDJ0UeNL+lpnPFR9K5avj2V4uG4m6FdjrApQ9Zi35AIocaDp/KGfBD9gR5MLUbQ==" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", - "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz", - "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz", - "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz", - "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz", - "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz", - "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz", - "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz", - "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz", - "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz", - "integrity": "sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz", - "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz", - "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz", - "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz", - "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz", - "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz", - "integrity": "sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz", - "integrity": "sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz", - "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz", - "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz", - "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -3072,6 +3177,13 @@ "node": ">=18.0.0" } }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@streamparser/json": { "version": "0.0.20", "resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.20.tgz", @@ -3103,11 +3215,13 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.21.1", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.21.1.tgz", - "integrity": "sha512-vLbtVwtDcK8LhJKnFkFYwM0uCdFmzioQnif0bjEYH1I24Arz22JPr/hLUiXGVYAwhu8INKx5qrdvr4tHgPwX6w==", + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.28.0.tgz", + "integrity": "sha512-qrhygwHV5r6JrvCw4gwNqqxYGDi5YbajocxfKgFXmSFpFo8wQobUvsM0OfakN4h+0LEmXtqHRrC6BcyAkOwyoQ==", "dev": true, + "license": "MIT", "dependencies": { + "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", @@ -3128,9 +3242,9 @@ "node": ">=18.13" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0", "svelte": "^4.0.0 || ^5.0.0-next.0", - "vite": "^5.0.3 || ^6.0.0" + "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" } }, "node_modules/@sveltejs/package": { @@ -3170,41 +3284,43 @@ } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.0.3.tgz", - "integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.1.1.tgz", + "integrity": "sha512-vB0Vq47Js7C11L2JrwhncIAoDNkdKDPI500SjLSb34X48dDcsSH5JpLl0cHT0sfO997BrzAS6PKjiZEey/S0VQ==", "dev": true, + "license": "MIT", "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", - "debug": "^4.4.0", + "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", + "debug": "^4.4.1", "deepmerge": "^4.3.1", "kleur": "^4.1.5", - "magic-string": "^0.30.15", - "vitefu": "^1.0.4" + "magic-string": "^0.30.17", + "vitefu": "^1.1.1" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22" + "node": "^20.19 || ^22.12 || >=24" }, "peerDependencies": { "svelte": "^5.0.0", - "vite": "^6.0.0" + "vite": "^6.3.0 || ^7.0.0" } }, "node_modules/@sveltejs/vite-plugin-svelte-inspector": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz", - "integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.0.tgz", + "integrity": "sha512-iwQ8Z4ET6ZFSt/gC+tVfcsSBHwsqc6RumSaiLUkAurW3BCpJam65cmHw0oOlDMTO0u+PZi9hilBRYN+LZNHTUQ==", "dev": true, + "license": "MIT", "dependencies": { - "debug": "^4.3.7" + "debug": "^4.4.1" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22" + "node": "^20.19 || ^22.12 || >=24" }, "peerDependencies": { - "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", - "vite": "^6.0.0" + "vite": "^6.3.0 || ^7.0.0" } }, "node_modules/@swc/helpers": { @@ -3555,9 +3671,10 @@ "dev": true }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==" + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" }, "node_modules/@types/geojson": { "version": "7946.0.16", @@ -3611,11 +3728,12 @@ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" }, "node_modules/@types/node": { - "version": "20.17.48", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.48.tgz", - "integrity": "sha512-KpSfKOHPsiSC4IkZeu2LsusFwExAIVGkhG1KkbaBMLwau0uMhj0fCrvyg9ddM2sAvd+gtiBJLir4LAw1MNMIaw==", + "version": "20.19.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.10.tgz", + "integrity": "sha512-iAFpG6DokED3roLSP0K+ybeDdIX6Bc0Vd3mLW5uDqThPWtNos3E+EqOM11mPQHKzfWHqEBuLjIlsBQQ8CsISmQ==", + "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.21.0" } }, "node_modules/@types/node-fetch": { @@ -3640,6 +3758,13 @@ "integrity": "sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==", "dev": true }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -3847,7 +3972,8 @@ "node_modules/@vscode/iconv-lite-umd": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz", - "integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==" + "integrity": "sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg==", + "license": "MIT" }, "node_modules/@windmill-labs/esbuild-import-meta-url-plugin": { "version": "0.0.0-semantic-release", @@ -5190,7 +5316,8 @@ "node_modules/debounce-promise": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz", - "integrity": "sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg==" + "integrity": "sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg==", + "license": "MIT" }, "node_modules/debug": { "version": "4.4.1", @@ -5532,6 +5659,15 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/domutils": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", @@ -6070,9 +6206,10 @@ } }, "node_modules/esrap": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.6.tgz", - "integrity": "sha512-F/D2mADJ9SHY3IwksD4DAXjTt7qt7GWUf3/8RhCNWmC/67tyb55dpimHmy7EplakFaflV0R/PC+fdSPqrRHAQw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.0.tgz", + "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } @@ -6138,14 +6275,6 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-2.0.3.tgz", "integrity": "sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==" }, - "node_modules/eventsource-parser": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", - "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -6791,9 +6920,10 @@ } }, "node_modules/graphql-language-service": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-5.3.0.tgz", - "integrity": "sha512-gCQIIy7lM9CB1KPLEb+DNZLczA9zuTLEOJE2hEQZTFYInogdmMDRa6RAkvM4LL0LcgcS+3uPs6KtHlcjCqRbUg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/graphql-language-service/-/graphql-language-service-5.5.0.tgz", + "integrity": "sha512-9EvWrLLkF6Y5e29/2cmFoAO6hBPPAZlCyjznmpR11iFtRydfkss+9m6x+htA8h7YznGam+TtJwS6JuwoWWgb2Q==", + "license": "MIT", "dependencies": { "debounce-promise": "^3.1.2", "nullthrows": "^1.0.0", @@ -6803,7 +6933,7 @@ "graphql": "dist/temp-bin.js" }, "peerDependencies": { - "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0-alpha.2" + "graphql": "^15.5.0 || ^16.0.0 || ^17.0.0" } }, "node_modules/hammerjs": { @@ -7457,6 +7587,7 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-3.1.4.tgz", "integrity": "sha512-/kmVISmrwVwtyYU40iQUOp3SUPk2dhNCMsZBQX0R1/jZ8maaXJ/oZIzUOiyOqcgtLnETFKYChbJ5iDC/eWmFHg==", + "license": "LGPL-2.1+", "engines": { "node": ">=0.1.90" } @@ -7901,6 +8032,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", + "license": "MIT", "bin": { "marked": "bin/marked.js" }, @@ -8888,48 +9020,72 @@ }, "node_modules/monaco-editor": { "name": "@codingame/monaco-vscode-editor-api", - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-editor-api/-/monaco-vscode-editor-api-16.1.1.tgz", - "integrity": "sha512-w3IB7oe1NVi//bfuORHjxuSsK2CKkQatSUcByaKUGrdctFRatcfoRxpj/fuIKQ2nsIGdk+5Q/jaLgxx+foqK7Q==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-editor-api/-/monaco-vscode-editor-api-19.1.4.tgz", + "integrity": "sha512-BNjAi7+gW4Eue63AXPf3CmJRIeB4x7vrXTlfJSA71NQwIrKVm4guuBOYEqQl2FG+rPC/XWuLrP3BcwHqQHd4CA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-5452e2b7-9081-5f95-839b-4ab3544ce28f-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1" + "@codingame/monaco-vscode-5452e2b7-9081-5f95-839b-4ab3544ce28f-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4" } }, "node_modules/monaco-editor-wrapper": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/monaco-editor-wrapper/-/monaco-editor-wrapper-6.7.0.tgz", - "integrity": "sha512-r7bKkeHsrQj0lN67rO5hJsMMV4jcXrjXGblAJuwTpdzn0X9APEebQdGlJHy0TUXMnZiGsLZ8jUs3YvrJj4LfXw==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/monaco-editor-wrapper/-/monaco-editor-wrapper-6.10.0.tgz", + "integrity": "sha512-3RxtUv+hjoM5J7c4MbLkxJt8xv4TJuoEWQO1qjQPDmiBOwP6jiqzR03I/AP0+mNFmtHb1ZhcU4zxxwaIXaxChA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "~16.1.1", - "@codingame/monaco-vscode-editor-api": "~16.1.1", - "@codingame/monaco-vscode-editor-service-override": "~16.1.1", - "@codingame/monaco-vscode-extension-api": "~16.1.1", - "@codingame/monaco-vscode-language-pack-cs": "~16.1.1", - "@codingame/monaco-vscode-language-pack-de": "~16.1.1", - "@codingame/monaco-vscode-language-pack-es": "~16.1.1", - "@codingame/monaco-vscode-language-pack-fr": "~16.1.1", - "@codingame/monaco-vscode-language-pack-it": "~16.1.1", - "@codingame/monaco-vscode-language-pack-ja": "~16.1.1", - "@codingame/monaco-vscode-language-pack-ko": "~16.1.1", - "@codingame/monaco-vscode-language-pack-pl": "~16.1.1", - "@codingame/monaco-vscode-language-pack-pt-br": "~16.1.1", - "@codingame/monaco-vscode-language-pack-qps-ploc": "~16.1.1", - "@codingame/monaco-vscode-language-pack-ru": "~16.1.1", - "@codingame/monaco-vscode-language-pack-tr": "~16.1.1", - "@codingame/monaco-vscode-language-pack-zh-hans": "~16.1.1", - "@codingame/monaco-vscode-language-pack-zh-hant": "~16.1.1", - "@codingame/monaco-vscode-monarch-service-override": "~16.1.1", - "@codingame/monaco-vscode-textmate-service-override": "~16.1.1", - "@codingame/monaco-vscode-theme-defaults-default-extension": "~16.1.1", - "@codingame/monaco-vscode-theme-service-override": "~16.1.1", - "@codingame/monaco-vscode-views-service-override": "~16.1.1", - "@codingame/monaco-vscode-workbench-service-override": "~16.1.1", - "monaco-languageclient": "~9.6.0", - "vscode": "npm:@codingame/monaco-vscode-extension-api@~16.1.1", + "@codingame/monaco-vscode-api": "~19.1.4", + "@codingame/monaco-vscode-editor-api": "~19.1.4", + "@codingame/monaco-vscode-editor-service-override": "~19.1.4", + "@codingame/monaco-vscode-extension-api": "~19.1.4", + "@codingame/monaco-vscode-language-pack-cs": "~19.1.4", + "@codingame/monaco-vscode-language-pack-de": "~19.1.4", + "@codingame/monaco-vscode-language-pack-es": "~19.1.4", + "@codingame/monaco-vscode-language-pack-fr": "~19.1.4", + "@codingame/monaco-vscode-language-pack-it": "~19.1.4", + "@codingame/monaco-vscode-language-pack-ja": "~19.1.4", + "@codingame/monaco-vscode-language-pack-ko": "~19.1.4", + "@codingame/monaco-vscode-language-pack-pl": "~19.1.4", + "@codingame/monaco-vscode-language-pack-pt-br": "~19.1.4", + "@codingame/monaco-vscode-language-pack-qps-ploc": "~19.1.4", + "@codingame/monaco-vscode-language-pack-ru": "~19.1.4", + "@codingame/monaco-vscode-language-pack-tr": "~19.1.4", + "@codingame/monaco-vscode-language-pack-zh-hans": "~19.1.4", + "@codingame/monaco-vscode-language-pack-zh-hant": "~19.1.4", + "@codingame/monaco-vscode-monarch-service-override": "~19.1.4", + "@codingame/monaco-vscode-textmate-service-override": "~19.1.4", + "@codingame/monaco-vscode-theme-defaults-default-extension": "~19.1.4", + "@codingame/monaco-vscode-theme-service-override": "~19.1.4", + "@codingame/monaco-vscode-views-service-override": "~19.1.4", + "@codingame/monaco-vscode-workbench-service-override": "~19.1.4", + "monaco-languageclient": "~9.9.0", + "vscode": "npm:@codingame/monaco-vscode-extension-api@~19.1.4", "vscode-languageclient": "~9.0.1", "vscode-languageserver-protocol": "~3.17.5", - "vscode-ws-jsonrpc": "~3.4.0" + "vscode-ws-jsonrpc": "~3.5.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/monaco-editor-wrapper/node_modules/vscode-jsonrpc": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", + "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/monaco-editor-wrapper/node_modules/vscode-ws-jsonrpc": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/vscode-ws-jsonrpc/-/vscode-ws-jsonrpc-3.5.0.tgz", + "integrity": "sha512-13ZDy7Od4AfEPK2HIfY3DtyRi4FVsvFql1yobVJrpIoHOKGGJpIjVvIJpMxkrHzCZzWlYlg+WEu2hrYkCTvM0Q==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "~8.2.1" }, "engines": { "node": ">=20.10.0", @@ -8940,6 +9096,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/monaco-graphql/-/monaco-graphql-1.6.0.tgz", "integrity": "sha512-KFANLCtgsaHhLAqxiIbwjGwjsO19j1vQhw1iSgaewxr1/RfUEs5FBoUVfq3JITrpgLjBahfcgIWlB0mdgb9RgA==", + "license": "MIT", "dependencies": { "graphql-language-service": "^5.3.0", "picomatch-browser": "^2.2.6" @@ -8951,21 +9108,22 @@ } }, "node_modules/monaco-languageclient": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/monaco-languageclient/-/monaco-languageclient-9.6.0.tgz", - "integrity": "sha512-ReG9u0b+LCG1YkO5yR+ETzSM34cw+P2qw5ecSeh8YA+gBIKniRHh13PZo9Uuka6MsvsDQYQMbj2PKVPjOGU7/Q==", + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/monaco-languageclient/-/monaco-languageclient-9.9.0.tgz", + "integrity": "sha512-R3GNz7nWvRgMGIjlgOi18aafUBIZ24ylhvy0UTB1DpaQki61C4gSa+roitsL7gNOaF9AD5mHsMM+OcHMM04poA==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-api": "~16.1.1", - "@codingame/monaco-vscode-configuration-service-override": "~16.1.1", - "@codingame/monaco-vscode-editor-api": "~16.1.1", - "@codingame/monaco-vscode-editor-service-override": "~16.1.1", - "@codingame/monaco-vscode-extension-api": "~16.1.1", - "@codingame/monaco-vscode-extensions-service-override": "~16.1.1", - "@codingame/monaco-vscode-languages-service-override": "~16.1.1", - "@codingame/monaco-vscode-localization-service-override": "~16.1.1", - "@codingame/monaco-vscode-log-service-override": "~16.1.1", - "@codingame/monaco-vscode-model-service-override": "~16.1.1", - "vscode": "npm:@codingame/monaco-vscode-extension-api@~16.1.1", + "@codingame/monaco-vscode-api": "~19.1.4", + "@codingame/monaco-vscode-configuration-service-override": "~19.1.4", + "@codingame/monaco-vscode-editor-api": "~19.1.4", + "@codingame/monaco-vscode-editor-service-override": "~19.1.4", + "@codingame/monaco-vscode-extension-api": "~19.1.4", + "@codingame/monaco-vscode-extensions-service-override": "~19.1.4", + "@codingame/monaco-vscode-languages-service-override": "~19.1.4", + "@codingame/monaco-vscode-localization-service-override": "~19.1.4", + "@codingame/monaco-vscode-log-service-override": "~19.1.4", + "@codingame/monaco-vscode-model-service-override": "~19.1.4", + "vscode": "npm:@codingame/monaco-vscode-extension-api@~19.1.4", "vscode-languageclient": "~9.0.1" }, "engines": { @@ -9206,7 +9364,8 @@ "node_modules/nullthrows": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==" + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "license": "MIT" }, "node_modules/nypm": { "version": "0.5.4", @@ -9744,9 +9903,9 @@ } }, "node_modules/postcss": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", - "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { @@ -9762,8 +9921,9 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.8", + "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -10940,12 +11100,13 @@ } }, "node_modules/rollup": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz", - "integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==", + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", "dev": true, + "license": "MIT", "dependencies": { - "@types/estree": "1.0.7" + "@types/estree": "1.0.8" }, "bin": { "rollup": "dist/bin/rollup" @@ -10955,26 +11116,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.41.0", - "@rollup/rollup-android-arm64": "4.41.0", - "@rollup/rollup-darwin-arm64": "4.41.0", - "@rollup/rollup-darwin-x64": "4.41.0", - "@rollup/rollup-freebsd-arm64": "4.41.0", - "@rollup/rollup-freebsd-x64": "4.41.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.41.0", - "@rollup/rollup-linux-arm-musleabihf": "4.41.0", - "@rollup/rollup-linux-arm64-gnu": "4.41.0", - "@rollup/rollup-linux-arm64-musl": "4.41.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.41.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.41.0", - "@rollup/rollup-linux-riscv64-gnu": "4.41.0", - "@rollup/rollup-linux-riscv64-musl": "4.41.0", - "@rollup/rollup-linux-s390x-gnu": "4.41.0", - "@rollup/rollup-linux-x64-gnu": "4.41.0", - "@rollup/rollup-linux-x64-musl": "4.41.0", - "@rollup/rollup-win32-arm64-msvc": "4.41.0", - "@rollup/rollup-win32-ia32-msvc": "4.41.0", - "@rollup/rollup-win32-x64-msvc": "4.41.0", + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", "fsevents": "~2.3.2" } }, @@ -11682,9 +11843,10 @@ } }, "node_modules/svelte": { - "version": "5.30.2", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.30.2.tgz", - "integrity": "sha512-zfGFEwwPeILToOxOqQyFq/vc8euXrX2XyoffkBNgn/k8D1nxbLt5+mNaqQBmZF/vVhBGmkY6VmNK18p9Gf0auQ==", + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.38.0.tgz", + "integrity": "sha512-cWF1Oc2IM/QbktdK89u5lt9MdKxRtQnRKnf2tq6KOhYuhLOd2hbMuTiJ+vWMzAeMDe81AzbCgLd4GVtOJ4fDRg==", + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -11695,7 +11857,7 @@ "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", - "esrap": "^1.4.6", + "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", @@ -11944,7 +12106,8 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/svelte-range-slider-pips/-/svelte-range-slider-pips-2.3.1.tgz", "integrity": "sha512-P29PNqHld+SiaDuHzf98rLvhSYWXb3TVL9p7U5RG9UX8emUgypZgp9zuIIwpmIXysGQC6JG8duMc5FuaPnSVdg==", - "dev": true + "dev": true, + "license": "MPL-2.0" }, "node_modules/svelte-splitpanes": { "version": "8.0.9", @@ -12266,10 +12429,11 @@ "dev": true }, "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "dev": true, + "license": "MIT", "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" @@ -12282,10 +12446,11 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, + "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -12296,10 +12461,11 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -12465,9 +12631,10 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" }, "node_modules/unified": { "version": "11.0.5", @@ -12657,23 +12824,24 @@ } }, "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", + "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", "dev": true, + "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "fdir": "^6.4.6", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.14" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -12682,14 +12850,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", - "less": "*", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -12748,10 +12916,11 @@ } }, "node_modules/vite/node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "dev": true, + "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -12762,10 +12931,11 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -12774,16 +12944,18 @@ } }, "node_modules/vitefu": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.6.tgz", - "integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", "dev": true, + "license": "MIT", "workspaces": [ "tests/deps/*", - "tests/projects/*" + "tests/projects/*", + "tests/projects/workspace/packages/*" ], "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "peerDependenciesMeta": { "vite": { @@ -12793,14 +12965,15 @@ }, "node_modules/vscode": { "name": "@codingame/monaco-vscode-extension-api", - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-extension-api/-/monaco-vscode-extension-api-16.1.1.tgz", - "integrity": "sha512-DG0QMk51h6pHBgpvD2iZc/Loj24PZQPnJU1IyKxpv9ZvE7zczYtTuvaNR5NHwSmjYju5kcszDyVlh3XICNH1/g==", + "version": "19.1.4", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-extension-api/-/monaco-vscode-extension-api-19.1.4.tgz", + "integrity": "sha512-qWRWkpS2s+iIrLZLwXkOWZXnnNb7GsWwXiaNLq9O3gJtO6fUdBOOWnT521w3d0Et9ax0HAQHkDJHOcg1ywgAAg==", + "license": "MIT", "dependencies": { - "@codingame/monaco-vscode-411e0589-fa79-504b-b32c-80a88847b23a-common": "16.1.1", - "@codingame/monaco-vscode-4a3ac544-9a61-534c-88df-756262793ef7-common": "16.1.1", - "@codingame/monaco-vscode-api": "16.1.1", - "@codingame/monaco-vscode-extensions-service-override": "16.1.1" + "@codingame/monaco-vscode-34a0ffd3-b9f5-5699-b43b-38af5732f38a-common": "19.1.4", + "@codingame/monaco-vscode-4a3ac544-9a61-534c-88df-756262793ef7-common": "19.1.4", + "@codingame/monaco-vscode-api": "19.1.4", + "@codingame/monaco-vscode-extensions-service-override": "19.1.4" } }, "node_modules/vscode-jsonrpc": { @@ -12852,12 +13025,14 @@ "node_modules/vscode-oniguruma": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", - "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==" + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "license": "MIT" }, "node_modules/vscode-textmate": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-9.2.0.tgz", - "integrity": "sha512-rkvG4SraZQaPSN/5XjwKswdU0OP9MF28QjrYzUBbhb8QyG3ljB1Ky996m++jiI7KdiAP2CkBiQZd9pqEDTClqA==" + "integrity": "sha512-rkvG4SraZQaPSN/5XjwKswdU0OP9MF28QjrYzUBbhb8QyG3ljB1Ky996m++jiI7KdiAP2CkBiQZd9pqEDTClqA==", + "license": "MIT" }, "node_modules/vscode-uri": { "version": "3.1.0", @@ -12975,6 +13150,11 @@ "resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.512.0.tgz", "integrity": "sha512-mOkuspfjPPhGZwmerBlFOjRKHjycrlzZUpxO7gHy5D6kwKI2bSz+VI3TsJqmK1y9PwAGgO8JnwRmNAR/YLUkzA==" }, + "node_modules/windmill-parser-wasm-ruby": { + "version": "1.526.1", + "resolved": "https://registry.npmjs.org/windmill-parser-wasm-ruby/-/windmill-parser-wasm-ruby-1.526.1.tgz", + "integrity": "sha512-rMBQA8s21wmL2kA5ztRs/ZgVA3ckxe9/NLjxl3iQPL0CX6DlvfaUH0O+AnhpXXDMyBs1Y1SZIhcnbnvsHZ3R8g==" + }, "node_modules/windmill-parser-wasm-rust": { "version": "1.510.1", "resolved": "https://registry.npmjs.org/windmill-parser-wasm-rust/-/windmill-parser-wasm-rust-1.510.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7b7c426e19..8babcbdb71 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,11 +1,11 @@ { "name": "windmill-components", - "version": "1.518.2", + "version": "1.527.1", "scripts": { "dev": "vite dev", "build": "vite build", "preview": "vite preview", - "postinstall": "node -e \"if (require('fs').existsSync('./scripts/untar_ui_builder.js')) { require('child_process').execSync('node ./scripts/untar_ui_builder.js && node ./scripts/patch_files.js', {stdio: 'inherit'}) }\"", + "postinstall": "node -e \"if (require('fs').existsSync('./scripts/untar_ui_builder.js')) { require('child_process').execSync('node ./scripts/untar_ui_builder.js', {stdio: 'inherit'}) }\"", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --threshold warning", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", @@ -24,9 +24,9 @@ "@melt-ui/svelte": "^0.86.2", "@playwright/test": "^1.34.3", "@sveltejs/adapter-static": "^3.0.6", - "@sveltejs/kit": "^2.16.0", + "@sveltejs/kit": "^2.28.0", "@sveltejs/package": "^2.3.7", - "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@sveltejs/vite-plugin-svelte": "^6.1.1", "@tailwindcss/forms": "^0.5.3", "@tailwindcss/typography": "^0.5.8", "@types/d3": "^7.4.0", @@ -52,7 +52,7 @@ "prettier-plugin-svelte": "^3.3.3", "style-to-object": "^0.4.1", "stylelint-config-recommended": "^13.0.0", - "svelte": "^5.0.0", + "svelte": "^5.38.0", "svelte-awesome-color-picker": "^3.0.4", "svelte-check": "^4.0.0", "svelte-floating-ui": "^1.5.8", @@ -66,7 +66,7 @@ "tar": "^7.4.3", "tslib": "^2.6.1", "typescript": "^5.5.0", - "vite": "^6.3.2", + "vite": "^7.1.2", "vite-plugin-mkcert": "^1.17.5", "yootils": "^0.3.1" }, @@ -78,13 +78,13 @@ "type": "module", "dependencies": { "@aws-crypto/sha256-js": "^4.0.0", - "@codingame/monaco-vscode-configuration-service-override": "~16.1.1", - "@codingame/monaco-vscode-editor-api": "~16.1.1", - "@codingame/monaco-vscode-standalone-css-language-features": "~16.1.1", - "@codingame/monaco-vscode-standalone-html-language-features": "^16.1.1", - "@codingame/monaco-vscode-standalone-json-language-features": "~16.1.1", - "@codingame/monaco-vscode-standalone-languages": "~16.1.1", - "@codingame/monaco-vscode-standalone-typescript-language-features": "~16.1.1", + "@codingame/monaco-vscode-configuration-service-override": "~19.1.4", + "@codingame/monaco-vscode-editor-api": "~19.1.4", + "@codingame/monaco-vscode-standalone-css-language-features": "~19.1.4", + "@codingame/monaco-vscode-standalone-html-language-features": "~19.1.4", + "@codingame/monaco-vscode-standalone-json-language-features": "~19.1.4", + "@codingame/monaco-vscode-standalone-languages": "~19.1.4", + "@codingame/monaco-vscode-standalone-typescript-language-features": "~19.1.4", "@json2csv/plainjs": "^7.0.6", "@leeoniya/ufuzzy": "^1.0.8", "@popperjs/core": "^2.11.6", @@ -117,10 +117,10 @@ "lru-cache": "^11.1.0", "lucide-svelte": "^0.399.0", "minimatch": "^10.0.1", - "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@~16.1.1", - "monaco-editor-wrapper": "6.7.0", - "monaco-graphql": "^1.6.0", - "monaco-languageclient": "9.6.0", + "monaco-editor": "npm:@codingame/monaco-vscode-editor-api@~19.1.4", + "monaco-editor-wrapper": "6.10.0", + "monaco-graphql": "=1.6.0", + "monaco-languageclient": "9.9.0", "monaco-vim": "^0.4.1", "ol": "^7.4.0", "openai": "^4.87.1", @@ -137,7 +137,7 @@ "svelte-infinite-loading": "^1.4.0", "svelte-tiny-virtual-list": "^2.0.5", "tailwind-merge": "^1.13.2", - "vscode": "npm:@codingame/monaco-vscode-extension-api@~16.1.1", + "vscode": "npm:@codingame/monaco-vscode-extension-api@~19.1.4", "vscode-languageclient": "~9.0.1", "vscode-uri": "~3.1.0", "vscode-ws-jsonrpc": "~3.4.0", @@ -148,6 +148,7 @@ "windmill-parser-wasm-php": "1.510.1", "windmill-parser-wasm-py": "1.510.1", "windmill-parser-wasm-regex": "1.512.0", + "windmill-parser-wasm-ruby": "1.526.1", "windmill-parser-wasm-rust": "1.510.1", "windmill-parser-wasm-ts": "1.514.1", "windmill-parser-wasm-yaml": "1.510.1", @@ -543,4 +544,4 @@ "@rollup/rollup-linux-x64-gnu": "^4.35.0", "fsevents": "^2.3.3" } -} +} \ No newline at end of file diff --git a/frontend/scripts/patch_files.js b/frontend/scripts/patch_files.js deleted file mode 100644 index 291468f913..0000000000 --- a/frontend/scripts/patch_files.js +++ /dev/null @@ -1,57 +0,0 @@ -// patch-editor-worker.mjs -import { readFile, writeFile } from 'fs/promises' -import path from 'path' -import { fileURLToPath } from 'url' - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const filePath = path.join( - __dirname, - '..', - 'node_modules', - '@codingame', - 'monaco-vscode-api', - 'workers', - 'editor.worker.js' -) - -const originalSnippet = `return requestHandler?.[propKey];` - -const patchedCode = ` -import '../vscode/src/vs/editor/common/services/editorWebWorkerMain.js'; -import { start } from '../vscode/src/vs/editor/editor.worker.start.js'; - -function initialize(createFn) { - let requestHandler; - const foreignModule = new Proxy({}, { - get(_target, propKey) { - if (propKey === '$initialize') { - return async (data) => { - if (!requestHandler) { - requestHandler = createFn(context, data); - } - }; - } - const value = requestHandler?.[propKey] - if (typeof value === 'function') { - return value.bind(requestHandler); - } - return value; - } - }); - const context = start(foreignModule); -} - -export { initialize }; -` - -try { - const current = await readFile(filePath, 'utf8') - if (current.includes(originalSnippet)) { - await writeFile(filePath, patchedCode, 'utf8') - console.log('✅ editor.worker.js patched successfully.') - } else { - console.log('ℹ️ Patch already applied or file has changed.') - } -} catch (err) { - console.error('❌ Failed to patch editor.worker.js:', err) -} diff --git a/frontend/src/lib/components/AllFlowLogs.svelte b/frontend/src/lib/components/AllFlowLogs.svelte deleted file mode 100644 index 8be5872278..0000000000 --- a/frontend/src/lib/components/AllFlowLogs.svelte +++ /dev/null @@ -1,39 +0,0 @@ - - -
- {#if states != undefined} - {#each Object.entries($states ?? {}) as [id, status] (id)} -

Step {id}

- {#each Object.entries(status.byJob ?? {}) as jobS} - {@const job = jobS[0]} - - {/each} -
- {/each} - {:else} - - {/if} -
diff --git a/frontend/src/lib/components/AppConnectInner.svelte b/frontend/src/lib/components/AppConnectInner.svelte index 2ad4047d22..53bb63a09d 100644 --- a/frontend/src/lib/components/AppConnectInner.svelte +++ b/frontend/src/lib/components/AppConnectInner.svelte @@ -64,9 +64,8 @@ let value: string = $state('') let valueToken: TokenResponse | undefined = undefined let connects: string[] | undefined = $state(undefined) - let connectsManual: - | [string, { img?: string; instructions: string[]; key?: string }][] - | undefined = $state(undefined) + let connectsManual: { key: string; img?: string; instructions: string[] }[] | undefined = + $state(undefined) let args: any = $state({}) let renderDescription = $state(true) @@ -209,16 +208,16 @@ .filter((x) => connectAndManual.includes(x) || !Object.keys(connects ?? {}).includes(x)) .map( (x) => - [ - x, - apiTokenApps[x] ?? { + ({ + key: x, + ...(apiTokenApps[x] ?? { instructions: '', img: undefined, linkedSecret: undefined - } - ] as [string, { img?: string; instructions: string[]; key?: string }] + }) + }) as { key: string; img?: string; instructions: string[] } ) - .sort((a, b) => a[0].localeCompare(b[0])) + .sort((a, b) => a.key.localeCompare(b.key)) const filteredNativeLanguages = filteredConnectsManual?.filter( (o) => nativeLanguagesCategory?.includes(o[0]) ?? false ) @@ -227,7 +226,7 @@ filteredConnectsManual = [ ...(filteredNativeLanguages ?? []), ...(filteredConnectsManual ?? []).filter( - ([key, _]) => !nativeLanguagesCategory.includes(key) + ({ key }) => !nativeLanguagesCategory.includes(key) ) ] } catch (e) {} @@ -509,8 +508,7 @@ const dispatch = createEventDispatcher<{ error: string; refresh: string; close: void }>() let filteredConnects: { key: string }[] = $state([]) - let filteredConnectsManual: [string, { img?: string; instructions: string[]; key?: string }][] = - $state([]) + let filteredConnectsManual: { key: string; img?: string; instructions: string[] }[] = $state([]) let editScopes = $state(false) @@ -530,7 +528,7 @@ {filter} items={connectsManual} bind:filteredItems={filteredConnectsManual} - f={(x) => x[0]} + f={(x) => x.key} /> {#if step == 1}
@@ -594,7 +592,7 @@
{#if filteredConnectsManual} - {#each filteredConnectsManual as [key, _]} + {#each filteredConnectsManual as { key }} {#if nativeLanguagesCategory.includes(key)}
{#if filteredConnectsManual} - {#each filteredConnectsManual as [key, _]} + {#each filteredConnectsManual as { key }} {#if !nativeLanguagesCategory.includes(key)} + {#if showHistoryButton} + + {/if} diff --git a/frontend/src/lib/components/DisplayResult.svelte b/frontend/src/lib/components/DisplayResult.svelte index 89ce56aa91..e2b9aebace 100644 --- a/frontend/src/lib/components/DisplayResult.svelte +++ b/frontend/src/lib/components/DisplayResult.svelte @@ -39,6 +39,8 @@ import { getContext, hasContext, createEventDispatcher, onDestroy } from 'svelte' import { toJsonStr } from '$lib/utils' import { userStore } from '$lib/stores' + import ResultStreamDisplay from './ResultStreamDisplay.svelte' + import { twMerge } from 'tailwind-merge' const IMG_MAX_SIZE = 10000000 const TABLE_MAX_SIZE = 5000000 @@ -83,15 +85,18 @@ noControls?: boolean drawerOpen?: boolean nodeId?: string | undefined + loading?: boolean | undefined language?: string | undefined appPath?: string | undefined customUi?: DisplayResultUi | undefined isTest?: boolean externalToolbarAvailable?: boolean forceJson?: boolean + result_stream?: string | undefined fixTableSizingToParent?: boolean copilot_fix?: import('svelte').Snippet children?: import('svelte').Snippet + growVertical?: boolean } let { @@ -111,9 +116,12 @@ isTest = true, externalToolbarAvailable = false, forceJson = $bindable(false), + result_stream = undefined, fixTableSizingToParent = false, copilot_fix, - children + children, + loading = false, + growVertical = false }: Props = $props() let enableHtml = $state(false) let s3FileDisplayRawMode = $state(false) @@ -487,7 +495,15 @@ -{#if is_render_all} + +{#if result_stream && result == undefined} +
+
+ Streaming result +
+ +
+{:else if is_render_all}
{#if !noControls}
@@ -527,11 +543,11 @@
Non displayable object
{:else}
{#if result != undefined && length != undefined && largeObject != undefined}
@@ -586,7 +602,8 @@
{#if !forceJson && resultKind === 'table-col'} - {@const data = 'table-col' in result ? result['table-col'] : result} + {@const data = + typeof result === 'object' && 'table-col' in result ? result['table-col'] : result} div]:h-full [&>div]:min-h-[10rem]' @@ -594,7 +611,8 @@ objects={objectOfArraysToObjects(data)} /> {:else if !forceJson && resultKind === 'table-row'} - {@const data = 'table-row' in result ? result['table-row'] : result} + {@const data = + typeof result === 'object' && 'table-row' in result ? result['table-row'] : result} div]:h-full [&>div]:min-h-[10rem]' @@ -602,7 +620,10 @@ objects={arrayOfRowsToObjects(data)} /> {:else if !forceJson && resultKind === 'table-row-object'} - {@const data = 'table-row-object' in result ? result['table-row-object'] : result} + {@const data = + typeof result === 'object' && 'table-row-object' in result + ? result['table-row-object'] + : result} div]:h-full [&>div]:min-h-[10rem]' @@ -690,7 +711,7 @@ {:else if !forceJson && resultKind === 'plain'}
{typeof result === 'string' ? result : result?.['result']}
{#if !noControls} + >{#if !noControls && !loading}
+ Create from template + {/if}
{#if showScriptHelpText} @@ -563,4 +589,46 @@ {/if} {/if} {/if} +{:else if handlerSelected === 'email'} + {#if isCloudHosted()} + + Email notifications for trigger failures are only available in self-hosted Windmill instances. + + {:else} +
+ + Configure email addresses to receive notifications when jobs fail. This feature requires + SMTP to be configured. + +
+
+ handlerExtraArgs[EMAIL_RECIPIENTS_KEY] ?? [], + (recipients) => (handlerExtraArgs[EMAIL_RECIPIENTS_KEY] = recipients) + } + placeholder="Enter email addresses..." + onCreateItem={(email) => { + const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ + if (!emailRegex.test(email)) { + sendUserToast('Invalid email format', true) + return + } + const currentArray = handlerExtraArgs[EMAIL_RECIPIENTS_KEY] ?? [] + handlerExtraArgs[EMAIL_RECIPIENTS_KEY] = [...currentArray, email] + }} + class="w-full" + /> + {#if handlerExtraArgs[EMAIL_RECIPIENTS_KEY]?.length > 0} + + {handlerExtraArgs[EMAIL_RECIPIENTS_KEY]?.length} email{handlerExtraArgs[ + EMAIL_RECIPIENTS_KEY + ]?.length === 1 + ? '' + : 's'} configured + + {/if} +
+ {/if} {/if} diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index da9fb506ca..0eb3802a40 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -78,7 +78,7 @@ import { Triggers } from './triggers/triggers.svelte' import { TestSteps } from './flows/testSteps.svelte' import { aiChatManager } from './copilot/chat/AIChatManager.svelte' - import type { DurationStatus, GraphModuleState } from './graph' + import type { GraphModuleState } from './graph' import { setStepHistoryLoaderContext, StepHistoryLoader, @@ -936,9 +936,6 @@ const localModuleStates: Writable> = $derived( flowPreviewContent?.getLocalModuleStates() ?? writable({}) ) - const localDurationStatuses: Writable> = $derived( - flowPreviewContent?.getLocalDurationStatuses() ?? writable({}) - ) const suspendStatus: Writable> = $derived( flowPreviewContent?.getSuspendStatus() ?? writable({}) ) @@ -1239,7 +1236,6 @@ onHideJobStatus={resetModulesStates} {individualStepTests} {job} - {localDurationStatuses} {suspendStatus} {showJobStatus} onDelete={(id) => { diff --git a/frontend/src/lib/components/FlowJobResult.svelte b/frontend/src/lib/components/FlowJobResult.svelte index 6956e2d3d9..d806b2fd07 100644 --- a/frontend/src/lib/components/FlowJobResult.svelte +++ b/frontend/src/lib/components/FlowJobResult.svelte @@ -2,18 +2,11 @@ import { Loader2 } from 'lucide-svelte' import DisplayResult from './DisplayResult.svelte' import LogViewer from './LogViewer.svelte' - import { JobService } from '$lib/gen' - import { workspaceStore } from '$lib/stores' - import DrawerContent from './common/drawer/DrawerContent.svelte' - import { Drawer } from './common' - import AllFlowLogs from './AllFlowLogs.svelte' - import type { DurationStatus } from './graph' - import type { Writable } from 'svelte/store' - import { untrack } from 'svelte' interface Props { waitingForExecutor?: boolean result: any + result_stream?: string logs: string | undefined col?: boolean noBorder?: boolean @@ -23,7 +16,6 @@ tag?: string | undefined workspaceId?: string | undefined refreshLog?: boolean - durationStates: Writable> | undefined downloadLogs?: boolean tagLabel?: string | undefined } @@ -31,7 +23,8 @@ let { waitingForExecutor = false, result, - logs = $bindable(), + result_stream, + logs, col = false, noBorder = false, loading, @@ -39,64 +32,11 @@ jobId = undefined, tag = undefined, workspaceId = undefined, - refreshLog = false, - durationStates, downloadLogs = true, tagLabel = undefined }: Props = $props() - - let lastJobId: string | undefined = $state(undefined) - let drawer: Drawer | undefined = $state(undefined) - - let iteration = 0 - let logOffset = 0 - - async function diffJobId() { - if (jobId != lastJobId) { - lastJobId = jobId - logs = undefined - logOffset = 0 - iteration = 0 - getLogs() - } - } - - async function getLogs() { - iteration += 1 - if (jobId) { - const getUpdate = await JobService.getJobUpdates({ - workspace: workspaceId ?? $workspaceStore!, - id: jobId, - running: loading ?? false, - logOffset: logOffset == 0 ? (logs?.length ? logs?.length + 1 : 0) : logOffset - }) - logs = (logs ?? '').concat(getUpdate.new_logs ?? '') - logOffset = getUpdate.log_offset ?? 0 - } - if (refreshLog) { - setTimeout( - () => { - if (refreshLog) { - getLogs() - } - }, - iteration < 10 ? 1000 : iteration < 20 ? 2000 : 5000 - ) - } - } - $effect(() => { - jobId - untrack(() => { - jobId != lastJobId && diffJobId() - }) - }) - - -
Result - {#if result !== undefined} - + {#if result !== undefined || result_stream !== undefined} + {:else if loading} {:else} @@ -114,9 +54,6 @@ {/if}
-
+ import { + ChevronDown, + ChevronRight, + GitBranch, + Repeat, + Code, + ArrowDownToLine, + ArrowDownFromLine, + FoldVertical, + UnfoldVertical + } from 'lucide-svelte' + import { base } from '$lib/base' + import { workspaceStore } from '$lib/stores' + import { truncateRev } from '$lib/utils' + import ObjectViewer from './propertyPicker/ObjectViewer.svelte' + import LogViewer from './LogViewer.svelte' + import FlowLogViewer from './FlowLogViewer.svelte' + import type { FlowModule, FlowModuleValue, FlowStatusModule, Job } from '$lib/gen' + import { twMerge } from 'tailwind-merge' + import FlowJobsMenu from './flows/map/FlowJobsMenu.svelte' + import BarsStaggered from './icons/BarsStaggered.svelte' + import type { GraphModuleState } from './graph/model' + import type { Writable } from 'svelte/store' + + type RootJobData = Partial + + interface Props { + modules: FlowModule[] + localModuleStates: Writable> + rootJob: RootJobData + flowStatus: FlowStatusModule['type'] | undefined + expandedRows: Record + allExpanded?: boolean + showResultsInputs?: boolean + toggleExpanded: (id: string) => void + toggleExpandAll?: () => void + workspaceId: string | undefined + render: boolean + level?: number + flowId: string + onSelectedIteration: ( + detail: + | { id: string; index: number; manuallySet: true; moduleId: string } + | { manuallySet: false; moduleId: string } + ) => Promise + getSelectedIteration: (stepId: string) => number + flowSummary?: string + } + + let { + modules, + localModuleStates, + rootJob, + flowStatus, + expandedRows, + allExpanded, + showResultsInputs, + toggleExpanded, + toggleExpandAll, + workspaceId, + render, + level = 0, + flowId = 'root', + onSelectedIteration, + getSelectedIteration, + flowSummary + }: Props = $props() + + function getJobLink(jobId: string | undefined): string { + if (!jobId) return '' + return `${base}/run/${jobId}?workspace=${workspaceId ?? $workspaceStore}` + } + + function getStatusColor(status: FlowStatusModule['type'] | undefined): string { + const statusColors = { + Success: 'text-green-500', + Failure: 'text-red-500', + InProgress: 'text-yellow-500', + WaitingForPriorSteps: 'text-gray-400', + WaitingForEvents: 'text-purple-400', + WaitingForExecutor: 'text-gray-400' + } + return status ? statusColors[status] : 'text-gray-400' + } + + function getFlowStatus(job: RootJobData): FlowStatusModule['type'] | undefined { + if (job.type === 'CompletedJob') { + return job.success ? 'Success' : 'Failure' + } else if (job.type === 'QueuedJob') { + return 'InProgress' + } else { + return undefined + } + } + + function getStepProgress(job: RootJobData, totalSteps: number): string { + if (totalSteps === 0) return '' + + // If flow is completed, show total steps + if (job.type === 'CompletedJob') { + return ` (${totalSteps} step${totalSteps === 1 ? '' : 's'})` + } + + // If flow is running, use flow_status.step if available (like JobStatus.svelte) + if (job.type === 'QueuedJob') { + if (job.flow_status?.step !== undefined) { + const currentStep = (job.flow_status.step ?? 0) + 1 + return ` (step ${currentStep} of ${totalSteps})` + } + + return '' + } + + return '' + } + + function isExpanded(id: string, isRunning: boolean = false): boolean { + // If explicitly set in expandedRows, use that value + // Otherwise, fall back to allExpanded + return expandedRows[id] ?? (allExpanded || isRunning) + } + + function hasEmptySubflow(stepId: string, stepType: FlowModuleValue['type'] | undefined): boolean { + const state = $localModuleStates[stepId] + + if (!state || !stepType) return false + return ( + ['forloopflow', 'whileloopflow'].includes(stepType) && + (!state.flow_jobs || state.flow_jobs.length === 0) + ) + } + + // Find all parents of error steps + function findParentsOfErrors(modules: FlowModule[]): Set { + const parentsWithErrors = new Set() + + function traverseModules(modules: FlowModule[], parentId?: string) { + let hasChildError = false + + for (const module of modules) { + let currentEntryHasError = false + + // Check if this entry has subflows with errors + if ( + module.value.type === 'forloopflow' || + (module.value.type === 'whileloopflow' && module.value.modules.length > 0) + ) { + const subflowHasError = traverseModules(module.value.modules, module.id) + if (subflowHasError) { + currentEntryHasError = true + parentsWithErrors.add(module.id) + } + } else if (module.value.type === 'branchone' || module.value.type === 'branchall') { + if (module.value.branches.length > 0) { + for (const branch of module.value.branches) { + const subflowHasError = traverseModules(branch.modules, module.id) + if (subflowHasError) { + currentEntryHasError = true + parentsWithErrors.add(module.id) + } + } + } + if (module.value.type === 'branchone' && module.value.default.length > 0) { + // Also check default branch + const subflowHasError = traverseModules(module.value.default, module.id) + if (subflowHasError) { + currentEntryHasError = true + parentsWithErrors.add(module.id) + } + } + } + + // Check if this entry itself has an error (but don't flag it - only its parents) + const stepStatus = $localModuleStates[module.id]?.type + if (stepStatus === 'Failure') { + currentEntryHasError = true + // Don't add the entry itself to parentsWithErrors + } + + // If this entry has an error, mark its parent + if (currentEntryHasError && parentId) { + parentsWithErrors.add(parentId) + hasChildError = true + } + } + + return hasChildError + } + + traverseModules(modules, flowId) + return parentsWithErrors + } + + // Get flow info for display + const flowInfo = $derived.by(() => { + const parentsWithErrors = findParentsOfErrors(modules) + return { + jobId: rootJob.id, + inputs: rootJob.args || {}, + result: rootJob.type === 'CompletedJob' ? rootJob.result : undefined, + logs: rootJob.logs || '', + status: rootJob.type, + label: flowSummary, + hasErrors: parentsWithErrors.has(flowId), + parentsWithErrors + } + }) + + function hasSubflows(module: FlowModule) { + return ( + module.value.type === 'forloopflow' || + module.value.type === 'whileloopflow' || + module.value.type === 'branchall' || + module.value.type === 'branchone' + ) + } + + function getSubflows(module: FlowModule) { + const subflows: Array<{ modules: FlowModule[]; label: string; flowId: string }> = [] + + if (module.value.type === 'forloopflow' || module.value.type === 'whileloopflow') { + subflows.push({ + modules: module.value.modules, + label: module.summary || '', + flowId: `${module.id}-subflow` + }) + } else if (module.value.type === 'branchall' || module.value.type === 'branchone') { + // Add all branches + for (let i = 0; i < module.value.branches.length; i++) { + const branch = module.value.branches[i] + subflows.push({ + modules: branch.modules, + label: branch.summary || `branch ${i + 1}`, + flowId: `${module.id}-subflow-${i}` + }) + } + // Add default branch for branchone + if (module.value.type === 'branchone') { + subflows.push({ + modules: module.value.default, + label: 'default', + flowId: `${module.id}-subflow-default` + }) + } + } + + return subflows + } + + +{#if render} + {#if level === 0 && toggleExpandAll} +
+
+ +
+ +
+
+ +
+ {/if} +
    + +
  • +
    + {#if level > 0} + + {:else} + +
    + {/if} +
    +
    + + +
    0 ? 'cursor-pointer' : '', + rootJob.type === undefined ? 'opacity-50' : '' + )} + onclick={level > 0 ? () => toggleExpanded(`flow-${flowId}`) : undefined} + > +
    + + {@render flowIcon(level == 0 ? getFlowStatus(rootJob) : flowStatus, flowInfo.hasErrors)} + +
    + + {level == 0 ? 'Flow' : 'Subflow'} + {#if flowInfo.label} + : {flowInfo.label} + {/if} + {getStepProgress(rootJob, modules.length)} + +
    +
    + + {#if flowInfo.jobId} + e.stopPropagation()} + > + {truncateRev(flowInfo.jobId, 6)} + + {/if} +
    + + {#if level === 0 || isExpanded(`flow-${flowId}`, rootJob.type === 'QueuedJob')} +
    + + {#if flowInfo.logs} + + {/if} + + +
      + + {#if showResultsInputs && flowInfo.inputs && Object.keys(flowInfo.inputs).length > 0} +
    • +
      + +
      + +
      + + +
      toggleExpanded(`flow-${flowId}-input`)} + > +
      + + Inputs +
      +
      + + {#if isExpanded(`flow-${flowId}-input`)} +
      +
      + +
      +
      + {/if} +
      +
    • + {/if} + + {#if modules.length > 0} + {#each modules as module (module.id)} + {@const isLeafStep = !hasSubflows(module)} + {@const status = $localModuleStates[module.id]?.type} + {@const isRunning = status === 'InProgress' || status === 'WaitingForExecutor'} + {@const hasEmptySubflowValue = hasEmptySubflow(module.id, module.value.type)} + {@const isCollapsible = !hasEmptySubflowValue} +
    • +
      + {#if isCollapsible} + + {:else} + +
      + {/if} +
      +
      + + +
      toggleExpanded(module.id) : undefined} + > +
      + + {@render stepIcon( + module.value.type, + status as FlowStatusModule['type'], + flowInfo.parentsWithErrors.has(module.id) + )} + +
      + + + {module.id} + + {#if module.value.type === 'forloopflow'} + For loop + {:else if module.value.type === 'whileloopflow'} + While loop + {:else if module.value.type === 'branchall'} + Branch to all + {:else if module.value.type === 'branchone'} + Branch to one + {:else if module.value.type === 'flow'} + Subflow + {:else} + Step + {/if} + {#if module.summary} + : {module.summary} + {/if} + {#if hasEmptySubflowValue} + + {#if module.value.type === 'forloopflow' || module.value.type === 'whileloopflow'} + (empty loop) + {:else if module.value.type === 'branchall' || module.value.type === 'branchone'} + (no branch) + {/if} + + {/if} + + {#if !hasEmptySubflowValue && $localModuleStates[module.id]?.flow_jobs && (module.value.type === 'forloopflow' || module.value.type === 'whileloopflow')} + + e.stopPropagation()}> + + + {#if module.value.type === 'forloopflow'} + {`/${$localModuleStates[module.id]?.iteration_total ?? 0}`} + {/if} + + {/if} +
      +
      + + {#if isLeafStep} + {@const jobId = $localModuleStates[module.id]?.job_id} + + {truncateRev(jobId ?? '', 6)} + + {/if} +
      + + {#if isCollapsible && isExpanded(module.id, isRunning)} + {@const args = $localModuleStates[module.id]?.args} + {@const logs = $localModuleStates[module.id]?.logs} + {@const result = $localModuleStates[module.id]?.result} + {@const jobId = $localModuleStates[module.id]?.job_id} +
      + + {#each getSubflows(module) as subflow} + {@const subflowJob = { + id: jobId, + type: + $localModuleStates[module.id]?.type === 'Failure' || + $localModuleStates[module.id]?.type === 'Success' + ? 'CompletedJob' + : ('QueuedJob' as Job['type']), + logs, + result, + args, + success: $localModuleStates[module.id]?.type === 'Success' + }} +
      + + +
      + {/each} + + {#if getSubflows(module).length === 0} + {#if showResultsInputs && isLeafStep && args && Object.keys(args).length > 0} +
      + + +
      toggleExpanded(`${module.id}-input`)} + > + {#if isExpanded(`${module.id}-input`)} + + {:else} + + {/if} + Input +
      + {#if isExpanded(`${module.id}-input`)} +
      + +
      + {/if} +
      + {/if} + + + {#if logs} + + {:else if jobId && !hasSubflows(module)} +
      +
      + No logs available +
      +
      + {/if} + + + + {#if showResultsInputs && isLeafStep && result !== undefined && (status === 'Success' || status === 'Failure')} +
      + + +
      toggleExpanded(`${module.id}-result`)} + > + {#if isExpanded(`${module.id}-result`)} + + {:else} + + {/if} + Result +
      + {#if isExpanded(`${module.id}-result`)} +
      + +
      + {/if} +
      + {/if} + {/if} +
      + {/if} +
      +
    • + {/each} + {/if} + + + {#if showResultsInputs && flowInfo.result !== undefined && rootJob.type === 'CompletedJob'} +
    • +
      + +
      +
      + + +
      toggleExpanded(`flow-${flowId}-result`)} + > +
      + + Results +
      +
      + + {#if isExpanded(`flow-${flowId}-result`)} +
      +
      + +
      +
      + {/if} +
      +
    • + {/if} +
    +
    + {/if} +
    +
  • +
+{/if} + +{#snippet flowIcon(status: FlowStatusModule['type'] | undefined, hasErrors: boolean)} + {@const colorClass = getStatusColor(status)} +
+ + {#if hasErrors && status !== 'Failure'} + ! + {/if} +
+{/snippet} + +{#snippet stepIcon( + stepType: string | undefined, + status: FlowStatusModule['type'] | undefined, + hasErrors: boolean +)} + {@const colorClass = getStatusColor(status)} + {@const animationClass = status === 'InProgress' ? 'animate-pulse' : ''} + {@const classes = `${colorClass} ${animationClass} flex-shrink-0`} +
+ {#if stepType === 'flow'} + + {:else if stepType === 'forloopflow' || stepType === 'whileloopflow'} + + {:else if stepType === 'branchall' || stepType === 'branchone'} + + {:else} + + {/if} + {#if hasErrors && status !== 'Failure'} + ! + {/if} +
+{/snippet} + + diff --git a/frontend/src/lib/components/FlowLogViewerWrapper.svelte b/frontend/src/lib/components/FlowLogViewerWrapper.svelte new file mode 100644 index 0000000000..3d5c09f460 --- /dev/null +++ b/frontend/src/lib/components/FlowLogViewerWrapper.svelte @@ -0,0 +1,73 @@ + + +
+ +
diff --git a/frontend/src/lib/components/FlowPreviewResult.svelte b/frontend/src/lib/components/FlowPreviewResult.svelte index 117801a7b8..ae2865ddb7 100644 --- a/frontend/src/lib/components/FlowPreviewResult.svelte +++ b/frontend/src/lib/components/FlowPreviewResult.svelte @@ -5,7 +5,6 @@ import FlowStatusWaitingForEvents from './FlowStatusWaitingForEvents.svelte' import type { FlowStatusModule, Job } from '$lib/gen' import { emptyString } from '$lib/utils' - import type { DurationStatus } from './graph' import type { Writable } from 'svelte/store' import Badge from './common/badge/Badge.svelte' @@ -15,11 +14,11 @@ isOwner: boolean hideFlowResult: boolean hideDownloadLogs: boolean - localDurationStatuses: Writable> innerModules: FlowStatusModule[] suspendStatus: Writable> hideJobId?: boolean extra?: import('svelte').Snippet + result_streams?: Record } let { @@ -28,11 +27,11 @@ isOwner, hideFlowResult, hideDownloadLogs, - localDurationStatuses, innerModules, suspendStatus, hideJobId, - extra + extra, + result_streams }: Props = $props() @@ -52,7 +51,6 @@ loading={job['running'] == true} result={job.result} logs={job.logs} - durationStates={localDurationStatuses} downloadLogs={!hideDownloadLogs} />
@@ -105,6 +103,9 @@
+ {#if mod.job && result_streams?.[mod.job]} +
{result_streams?.[mod.job]}
+ {/if} {/if} {/each}
diff --git a/frontend/src/lib/components/FlowStatusViewer.svelte b/frontend/src/lib/components/FlowStatusViewer.svelte index a479ac438e..22c1a9f18b 100644 --- a/frontend/src/lib/components/FlowStatusViewer.svelte +++ b/frontend/src/lib/components/FlowStatusViewer.svelte @@ -120,4 +120,6 @@ bind:rightColumnSelect {render} {customUi} + graphTabOpen={true} + isNodeSelected={true} /> diff --git a/frontend/src/lib/components/FlowStatusViewerInner.svelte b/frontend/src/lib/components/FlowStatusViewerInner.svelte index c33293240a..6800d7583d 100644 --- a/frontend/src/lib/components/FlowStatusViewerInner.svelte +++ b/frontend/src/lib/components/FlowStatusViewerInner.svelte @@ -34,6 +34,7 @@ import { buildPrefix } from './graph/graphBuilder.svelte' import { parseInputArgsAssets } from './assets/lib' import FlowPreviewResult from './FlowPreviewResult.svelte' + import FlowLogViewerWrapper from './FlowLogViewerWrapper.svelte' import type { FlowGraphAssetContext } from './flows/types' import { createState } from '$lib/svelte5Utils.svelte' import JobLoader from './JobLoader.svelte' @@ -83,13 +84,23 @@ subflowParentsDurationStatuses?: Writable>[] isForloopSelected?: boolean parentRecursiveRefresh?: Record Promise> - job?: Job | undefined + job?: (Job & { result_stream?: string }) | undefined rightColumnSelect?: 'timeline' | 'node_status' | 'node_definition' | 'user_states' localModuleStates?: Writable> localDurationStatuses?: Writable> + onResultStreamUpdate?: ({ + jobId, + result_stream + }: { + jobId: string + result_stream?: string + }) => void customUi?: { tagLabel?: string | undefined } + graphTabOpen: boolean + isNodeSelected: boolean + loadExtraLogs?: (logs: string) => void } let { @@ -119,8 +130,27 @@ rightColumnSelect = $bindable('timeline'), localModuleStates = writable({}), localDurationStatuses = writable({}), - customUi + customUi, + onResultStreamUpdate = undefined, + graphTabOpen, + isNodeSelected, + loadExtraLogs = undefined }: Props = $props() + + let resultStreams: Record = $state({}) + + if (onResultStreamUpdate == undefined) { + onResultStreamUpdate = ({ + jobId, + result_stream + }: { + jobId: string + result_stream?: string + }) => { + resultStreams[jobId] = result_stream + } + } + let recursiveRefresh: Record Promise> = $state({}) // Add support for the input args assets shown as an asset node @@ -185,7 +215,11 @@ state[key]?.selectedForloop != undefined && newValue.selectedForloop != state[key].selectedForloop ) { - if (newValue.type == 'InProgress' && state[key]?.type != 'InProgress') { + if ( + newValue.type == 'InProgress' && + state[key]?.type != 'InProgress' && + !(keepType && (state[key]?.type === 'Success' || state[key]?.type === 'Failure')) + ) { moduleState.update((state) => { state[key].type = 'InProgress' return state @@ -512,6 +546,17 @@ jobLoader?.watchJob(jobId, { change(newJob) { setJob(newJob, true) + }, + resultStreamUpdate({ id, result_stream }: { id: string; result_stream?: string }) { + onResultStreamUpdate?.({ jobId: id, result_stream }) + }, + loadExtraLogs({ id, logs }: { id: string; logs: string }) { + if (id == jobId && job) { + job.logs = logs + } + if (loadExtraLogs) { + loadExtraLogs(logs) + } } }) } @@ -896,6 +941,38 @@ let subflowsSize = $state(500) + async function onSelectedIteration( + detail: + | { id: string; index: number; manuallySet: true; moduleId: string } + | { manuallySet: false; moduleId: string } + ) { + if (detail.manuallySet) { + let rootJobId = detail.id + await tick() + + let previousId = $localModuleStates[detail.moduleId]?.selectedForloop + if (previousId) { + await globalRefreshes?.[detail.moduleId]?.(true, previousId) + } + + $localModuleStates[detail.moduleId] = { + ...$localModuleStates[detail.moduleId], + selectedForloop: detail.id, + selectedForloopIndex: detail.index, + selectedForLoopSetManually: true + } + + await tick() + + await globalRefreshes?.[detail.moduleId]?.(false, rootJobId) + } else { + $localModuleStates[detail.moduleId] = { + ...$localModuleStates[detail.moduleId], + selectedForLoopSetManually: false + } + } + } + $effect(() => { flowJobIds?.moduleId && untrack(() => onFlowModuleId()) }) @@ -909,10 +986,14 @@ $effect(() => { flowJobIds?.moduleId && untrack(() => onModuleIdChange()) }) - let selected = $derived(isListJob ? 'sequence' : 'graph') + let selected = $derived(isListJob ? 'sequence' : 'graph') as 'sequence' | 'graph' | 'logs' + + let animateLogsTab = $state(false) + + let noLogs = $derived(graphTabOpen && !isNodeSelected) - + {#if notAnonynmous} As a non logged in user, you can only see jobs ran by anonymous users like you @@ -955,6 +1036,7 @@ @@ -968,10 +1050,10 @@ {isOwner} {hideFlowResult} {hideDownloadLogs} - {localDurationStatuses} {innerModules} {suspendStatus} {hideJobId} + result_streams={resultStreams} />
{/if} @@ -979,6 +1061,12 @@ {#if innerModules.length > 0 && !isListJob} Graph + Logs Details {:else} @@ -1063,6 +1151,9 @@ storedListJobs[j] = job innerJobLoaded(job, j, false, force) }} + {onResultStreamUpdate} + graphTabOpen={selected == 'graph' && graphTabOpen} + isNodeSelected={forloop_selected == loopJobId} />
{/if} @@ -1138,6 +1229,9 @@ {reducedPolling} {workspaceId} jobId={failedRetry} + {onResultStreamUpdate} + graphTabOpen={selected == 'graph' && graphTabOpen} + isNodeSelected={retry_selected == failedRetry} />
{/each} @@ -1170,6 +1264,9 @@ let { force, job } = e.detail onJobsLoaded(mod, job, force) }} + {onResultStreamUpdate} + graphTabOpen={selected == 'graph' && graphTabOpen} + isNodeSelected={false} /> {:else if mod.flow_jobs?.length == 0 && mod.job == '00000000-0000-0000-0000-000000000000'}
no subflow (empty loop?)
@@ -1201,6 +1298,14 @@ let { job, force } = e.detail onJobsLoaded(mod, job, force) }} + loadExtraLogs={(logs) => { + setModuleState(mod.id ?? '', { + logs + }) + }} + {onResultStreamUpdate} + graphTabOpen={selected == 'graph' && graphTabOpen} + isNodeSelected={$localModuleStates?.[selectedNode ?? '']?.job_id == mod.job} /> {/if} {:else} @@ -1216,6 +1321,15 @@
Empty flow
{/if}
+
+ +
{#if render} {#if job.raw_flow && !isListJob} @@ -1269,33 +1383,7 @@ selectedNode = e.id } }} - onSelectedIteration={async (detail) => { - if (detail.manuallySet) { - let rootJobId = detail.id - await tick() - - let previousId = $localModuleStates[detail.moduleId]?.selectedForloop - if (previousId) { - await globalRefreshes?.[detail.moduleId]?.(true, previousId) - } - - $localModuleStates[detail.moduleId] = { - ...$localModuleStates[detail.moduleId], - selectedForloop: detail.id, - selectedForloopIndex: detail.index, - selectedForLoopSetManually: true - } - - await tick() - - await globalRefreshes?.[detail.moduleId]?.(false, rootJobId) - } else { - $localModuleStates[detail.moduleId] = { - ...$localModuleStates[detail.moduleId], - selectedForLoopSetManually: false - } - } - }} + {onSelectedIteration} earlyStop={job.raw_flow?.skip_expr !== undefined} cache={job.raw_flow?.cache_ttl !== undefined} modules={job.raw_flow?.modules ?? []} @@ -1350,7 +1438,6 @@ col result={job['result']} logs={job.logs ?? ''} - durationStates={localDurationStatuses} downloadLogs={!hideDownloadLogs} /> {:else if selectedNode == 'start'} @@ -1424,10 +1511,10 @@ waitingForExecutor={node.type == 'WaitingForExecutor'} refreshLog={node.type == 'InProgress'} col + result_stream={resultStreams[node.job_id ?? '']} result={node.result} tag={node.tag} logs={node.logs} - durationStates={localDurationStatuses} downloadLogs={!hideDownloadLogs} /> {:else} diff --git a/frontend/src/lib/components/HighlightCode.svelte b/frontend/src/lib/components/HighlightCode.svelte index bf196c7868..aebd2bf0f1 100644 --- a/frontend/src/lib/components/HighlightCode.svelte +++ b/frontend/src/lib/components/HighlightCode.svelte @@ -13,6 +13,7 @@ import csharp from 'svelte-highlight/languages/csharp' import yaml from 'svelte-highlight/languages/yaml' import java from 'svelte-highlight/languages/java' + import ruby from 'svelte-highlight/languages/ruby' import type { Script } from '$lib/gen' import { Button } from './common' import { copyToClipboard } from '$lib/utils' @@ -70,8 +71,10 @@ case 'ansible': return yaml case 'java': - return java - // for related places search: ADD_NEW_LANG + return java; + case 'ruby': + return ruby; + // for related places search: ADD_NEW_LANG default: return typescript } diff --git a/frontend/src/lib/components/IconedResourceType.svelte b/frontend/src/lib/components/IconedResourceType.svelte index 7375c64cd7..9f7863a4d2 100644 --- a/frontend/src/lib/components/IconedResourceType.svelte +++ b/frontend/src/lib/components/IconedResourceType.svelte @@ -26,20 +26,24 @@ } = $props() let iconComponent = $derived( - name === 'teams' - ? APP_TO_ICON_COMPONENT.ms_teams_webhook - : APP_TO_ICON_COMPONENT[name] || APP_TO_ICON_COMPONENT[name.split('_')[0]] + name + ? name === 'teams' + ? APP_TO_ICON_COMPONENT.ms_teams_webhook + : APP_TO_ICON_COMPONENT[name] || APP_TO_ICON_COMPONENT[name.split('_')[0]] + : undefined ) + + let widthInPixels = $derived(parseInt(width)) -
+
{#if !silent && !after} {name} {/if} {#if iconComponent} {@const SvelteComponent = iconComponent} - + {:else if formatExtension} diff --git a/frontend/src/lib/components/IdEditorInput.svelte b/frontend/src/lib/components/IdEditorInput.svelte index 820e1fc671..7b85d8e52b 100644 --- a/frontend/src/lib/components/IdEditorInput.svelte +++ b/frontend/src/lib/components/IdEditorInput.svelte @@ -4,13 +4,13 @@ const bubble = createBubbler() import { ArrowRight } from 'lucide-svelte' import { Button } from './common' - import { untrack } from 'svelte' import { forbiddenIds } from './flows/idUtils' import { slide } from 'svelte/transition' interface Props { initialId: string reservedIds?: string[] + reservedPrefixes?: string[] label?: string value?: any buttonText?: string @@ -23,6 +23,7 @@ let { initialId, reservedIds = [], + reservedPrefixes = [], label = 'Component ID', value = $bindable(initialId), buttonText = '', @@ -35,7 +36,7 @@ let error = $state('') const regex = acceptUnderScores ? /^[a-zA-Z][a-zA-Z0-9_]*$/ : /^[a-zA-Z][a-zA-Z0-9]*$/ - function validateId(id: string, reservedIds: string[]) { + function validateId(id: string, reservedIds: string[], reservedPrefixes: string[]) { if (id == initialId) { error = '' return @@ -44,6 +45,8 @@ error = 'The ID must include only letters and numbers and start with a letter' } else if (forbiddenIds.includes(value)) { error = 'This ID is reserved' + } else if (reservedPrefixes.some((prefix) => value.startsWith(prefix))) { + error = 'This ID uses a reserved prefix' } else if (reservedIds.some((rid) => rid === value)) { error = 'This ID is already in use' } else { @@ -54,7 +57,7 @@ let inputDiv: HTMLInputElement | undefined = $state(undefined) $effect(() => { - untrack(() => validateId(value, reservedIds)) + validateId(value, reservedIds, reservedPrefixes) }) $effect(() => { inputDiv?.focus() diff --git a/frontend/src/lib/components/InstanceSetting.svelte b/frontend/src/lib/components/InstanceSetting.svelte index f724e97a1a..3560a6119a 100644 --- a/frontend/src/lib/components/InstanceSetting.svelte +++ b/frontend/src/lib/components/InstanceSetting.svelte @@ -84,6 +84,12 @@ latestKeyRenewalAttempt = await SettingService.getLatestKeyRenewalAttempt() } + async function reloadLicenseKey() { + $values['license_key'] = await SettingService.getGlobal({ + key: 'license_key' + }) + } + if (setting.key == 'license_key') { reloadKeyrenewalAttemptInfo() } @@ -95,11 +101,11 @@ licenseKey: $values['license_key'] || undefined }) sendUserToast('Key renewal successful') - reloadKeyrenewalAttemptInfo() + reloadLicenseKey() } catch (err) { - latestKeyRenewalAttempt = await SettingService.getLatestKeyRenewalAttempt() throw err } finally { + reloadKeyrenewalAttemptInfo() renewing = false } } diff --git a/frontend/src/lib/components/InstanceSettings.svelte b/frontend/src/lib/components/InstanceSettings.svelte index 8836050918..093bd4ed46 100644 --- a/frontend/src/lib/components/InstanceSettings.svelte +++ b/frontend/src/lib/components/InstanceSettings.svelte @@ -351,6 +351,7 @@ bind:oauths bind:snowflakeAccountIdentifier bind:requirePreexistingUserForOauth + baseUrl={$values?.base_url} > {#snippet scim()}
diff --git a/frontend/src/lib/components/JobLoader.svelte b/frontend/src/lib/components/JobLoader.svelte index f6e6730b09..618cb15984 100644 --- a/frontend/src/lib/components/JobLoader.svelte +++ b/frontend/src/lib/components/JobLoader.svelte @@ -1,3 +1,9 @@ + + + +
+ + + {#if enabled} +
+ + + + + +
+ 1. Go to your Nextcloud instance as an administrator
+ 2. Navigate to Administration settings → Security → OAuth 2.0 clients
+ 3. Click "Add client" to create a new OAuth2 application
+ 4. Set the redirect URI to your Windmill instance's {baseUrl || 'BASE_URL'}/user/login_callback/nextcloud
+ 5. Copy the Client ID and Client Secret to the fields above
+
+
+
+ {/if} +
diff --git a/frontend/src/lib/components/ResourceEditor.svelte b/frontend/src/lib/components/ResourceEditor.svelte index 2a7f56373d..51226d4958 100644 --- a/frontend/src/lib/components/ResourceEditor.svelte +++ b/frontend/src/lib/components/ResourceEditor.svelte @@ -21,6 +21,7 @@ import GfmMarkdown from './GfmMarkdown.svelte' import TestTriggerConnection from './triggers/TestTriggerConnection.svelte' import GitHubAppIntegration from './GitHubAppIntegration.svelte' + import Button from './common/button/Button.svelte' interface Props { canSave?: boolean @@ -223,10 +224,14 @@

Resource description {#if can_write} -
- - -
+

{#if can_write && editDescription} diff --git a/frontend/src/lib/components/ResultStreamDisplay.svelte b/frontend/src/lib/components/ResultStreamDisplay.svelte new file mode 100644 index 0000000000..852a67992e --- /dev/null +++ b/frontend/src/lib/components/ResultStreamDisplay.svelte @@ -0,0 +1,5 @@ + + +
{result_stream}
diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index 28d54fd01d..7d1721a988 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -1128,7 +1128,7 @@ > {label} - {#if lang === 'nu'} + {#if lang === 'nu' || lang === 'ruby'} BETA {/if} diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index 348b155788..ba3f86a51f 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -428,9 +428,9 @@ } untrack(() => { aiChatManager.scriptEditorOptions = options - aiChatManager.scriptEditorApplyCode = (code: string) => { + aiChatManager.scriptEditorApplyCode = (code: string, applyAll: boolean = false) => { hideDiffMode() - editor?.reviewAndApplyCode(code) + editor?.reviewAndApplyCode(code, applyAll) } aiChatManager.scriptEditorShowDiffMode = showDiffMode }) diff --git a/frontend/src/lib/components/SimpleEditor.svelte b/frontend/src/lib/components/SimpleEditor.svelte index e2d06f3510..a431a341ee 100644 --- a/frontend/src/lib/components/SimpleEditor.svelte +++ b/frontend/src/lib/components/SimpleEditor.svelte @@ -121,7 +121,8 @@ class: className = '', loadAsync = false, key, - disabled = false + disabled = false, + minHeight = 1000 }: { lang: string code?: string @@ -147,6 +148,7 @@ initialCursorPos?: IPosition key?: string disabled?: boolean + minHeight?: number } = $props() const dispatch = createEventDispatcher() @@ -370,6 +372,7 @@ return } try { + console.log('fixedOverflowWidgets', fixedOverflowWidgets) editor = meditor.create(divEl as HTMLDivElement, { ...editorConfig(code ?? '', lang, automaticLayout, fixedOverflowWidgets), model, @@ -434,7 +437,7 @@ if (autoHeight) { const updateHeight = () => { if (!editor) return - const contentHeight = Math.min(1000, editor.getContentHeight()) + const contentHeight = Math.min(minHeight, editor.getContentHeight()) if (divEl) { divEl.style.height = `${contentHeight}px` } diff --git a/frontend/src/lib/components/UserSettings.svelte b/frontend/src/lib/components/UserSettings.svelte index 00062c0489..a94974e185 100644 --- a/frontend/src/lib/components/UserSettings.svelte +++ b/frontend/src/lib/components/UserSettings.svelte @@ -43,9 +43,9 @@ window.location.hash = '' } - function handleTokenCreated(event: CustomEvent) { - newToken = event.detail - dispatch('tokenCreated', newToken) + function handleTokenCreated(token: string) { + newToken = token + dispatch('tokenCreated', token) } @@ -75,7 +75,7 @@ defaultNewTokenLabel={newTokenLabel} defaultNewTokenWorkspace={newTokenWorkspace} {scopes} - on:tokenCreated={handleTokenCreated} + onTokenCreated={handleTokenCreated} />
diff --git a/frontend/src/lib/components/WorkerTagSelect.svelte b/frontend/src/lib/components/WorkerTagSelect.svelte index d0c7ec4faa..b98f2f89ce 100644 --- a/frontend/src/lib/components/WorkerTagSelect.svelte +++ b/frontend/src/lib/components/WorkerTagSelect.svelte @@ -2,12 +2,13 @@ import { workerTags, workspaceStore } from '$lib/stores' import { WorkerService } from '$lib/gen' - import { createEventDispatcher } from 'svelte' + import { createEventDispatcher, onDestroy, onMount } from 'svelte' import Select from './select/Select.svelte' import { safeSelectItems } from './select/utils.svelte' import { Button } from './common' import { RotateCw } from 'lucide-svelte' import { sendUserToast } from '$lib/toast' + import Popover from './Popover.svelte' let { tag = $bindable(), @@ -28,6 +29,20 @@ } = $props() let loading = $state(false) + let visible = $state(false) + let timeout: NodeJS.Timeout | undefined = undefined + let tagsToWorkerExists = $state | undefined>(undefined) + + onMount(() => { + visible = true + }) + + onDestroy(() => { + visible = false + if (timeout) { + clearTimeout(timeout) + } + }) loadWorkerGroups() @@ -51,9 +66,67 @@ ...($workerTags ?? []) ]) + let lastCheck: number | undefined = undefined + async function loadTagsToWorkerExists(tags: string[]) { + if (lastCheck && Date.now() - lastCheck < 5000) { + return + } + if (timeout) { + clearTimeout(timeout) + } + if (open) { + tagsToWorkerExists = await WorkerService.existsWorkersWithTags({ tags: tags.join(',') }) + lastCheck = Date.now() + if (visible) { + timeout = setTimeout(() => { + loadTagsToWorkerExists(tags) + }, 5000) + } + } + } + + // let finalItems = $derived( + // items.map((item) => { + // if (tagsToWorkerExists) { + // return { + // value: item, + // __select_group: tagsToWorkerExists[item] + // ? `${placeholder ?? 'Worker'}s available` + // : `No ${placeholder ?? 'Worker'}s` + // } + // } + // return item + // }) + // ) + + $effect(() => { + if ($workerTags && open) { + loadTagsToWorkerExists($workerTags) + } + }) + let open = $state(false) +{#snippet startSnippet({ item })} + {#if tagsToWorkerExists} + {#if tagsToWorkerExists[item.value]} + + {#snippet text()} + At least one worker with this tag exists and is running. + {/snippet} +
+
+ {:else} + + {#snippet text()} + No workers with this tag exist or is running. + {/snippet} +
+
+ {/if} + {/if} +{/snippet}
{#if !noLabel}
{placeholder ?? 'tag'}
@@ -67,6 +140,7 @@ placeholder={nullTag ? nullTag : (placeholder ?? 'lang default')} items={safeSelectItems(items)} bind:value={() => tag, (value) => ((tag = value), dispatch('change', value))} + {startSnippet} /> {#if open}
diff --git a/frontend/src/lib/components/apps/components/display/AppCarouselList.svelte b/frontend/src/lib/components/apps/components/display/AppCarouselList.svelte index 16b92c6e5c..40cc5c8c94 100644 --- a/frontend/src/lib/components/apps/components/display/AppCarouselList.svelte +++ b/frontend/src/lib/components/apps/components/display/AppCarouselList.svelte @@ -135,14 +135,20 @@ +{#snippet nonRenderedPlaceholder()} + + + +{/snippet} {#if everRender}
@@ -253,9 +259,7 @@ {/key} {:else} - - - + {@render nonRenderedPlaceholder?.()} {#if !Array.isArray(result)}
Input data is not an array
{/if} @@ -263,8 +267,6 @@ {/if}
{:else if $app.subgrids} - - - + {@render nonRenderedPlaceholder?.()} {/if}
diff --git a/frontend/src/lib/components/apps/components/display/AppDisplayComponent.svelte b/frontend/src/lib/components/apps/components/display/AppDisplayComponent.svelte index e334e2ab2d..6a32d62e7d 100644 --- a/frontend/src/lib/components/apps/components/display/AppDisplayComponent.svelte +++ b/frontend/src/lib/components/apps/components/display/AppDisplayComponent.svelte @@ -26,6 +26,7 @@ configuration: RichConfigurations } + let result_stream: string | undefined = $state(undefined) let { id, componentInput, @@ -57,6 +58,7 @@ }) let css = $state(initCss($app.css?.displaycomponent, customCss)) + let loading = $state(false) {#each Object.keys(components['displaycomponent'].initialData.configuration) as key (key)} @@ -78,7 +80,15 @@ /> {/each} - +
('AppViewerContext') let result: any = $state(noBackend ? runnable.noBackendValue : undefined) - export function onSuccess() { if (runnable.recomputeIds) { runnable.recomputeIds.forEach((id) => $runnableComponents?.[id]?.cb?.map((cb) => cb())) @@ -40,7 +39,6 @@ {#if runnable && (runnable.type == 'runnableByPath' || (runnable.type == 'runnableByName' && runnable.inlineScript != undefined))} { + $effect(() => { componentInput.type === 'evalv2' && componentInput.connections && untrack(() => builtSubscriptions(componentInput.connections)) @@ -96,11 +96,15 @@ {/if} -{#if render || hasChildrens} -
+{#if render} +
{@render children?.()}
+{:else if nonRenderedPlaceholder} + {/if} diff --git a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte index 387065b197..6554ae2fb9 100644 --- a/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte +++ b/frontend/src/lib/components/apps/components/helpers/RunnableComponent.svelte @@ -38,6 +38,7 @@ extraQueryParams?: Record autoRefresh?: boolean result?: any + result_stream?: string forceSchemaDisplay?: boolean wrapperClass?: string wrapperStyle?: string @@ -55,13 +56,13 @@ recomputableByRefreshButton: boolean errorHandledByComponent?: boolean hideRefreshButton?: boolean - hasChildrens: boolean allowConcurentRequests?: boolean noInitialize?: boolean overrideCallback?: (() => CancelablePromise) | undefined overrideAutoRefresh?: boolean replaceCallback?: boolean children?: import('svelte').Snippet + nonRenderedPlaceholder?: import('svelte').Snippet } let { @@ -72,6 +73,7 @@ extraQueryParams = {}, autoRefresh = true, result = $bindable(undefined), + result_stream = $bindable(undefined), forceSchemaDisplay = false, wrapperClass = '', wrapperStyle = '', @@ -85,13 +87,13 @@ recomputableByRefreshButton, errorHandledByComponent = false, hideRefreshButton = false, - hasChildrens, allowConcurentRequests = false, noInitialize = false, overrideCallback = undefined, overrideAutoRefresh = false, replaceCallback = false, - children + children, + nonRenderedPlaceholder }: Props = $props() const { @@ -226,6 +228,15 @@ loading = false dispatch('done', { id, result }) }, + resultStreamUpdate({ + id, + result_stream: nresult_stream + }: { + id: string + result_stream?: string + }) { + setResult(nresult_stream, id) + }, cancel({ id }: { id: string }) { onCancel?.() let jobId = id @@ -886,13 +897,8 @@ bind:this={resultJobLoader} /> -{#if render || hasChildrens} - {/if} - - diff --git a/frontend/src/lib/components/apps/components/layout/AppDecisionTree.svelte b/frontend/src/lib/components/apps/components/layout/AppDecisionTree.svelte index dd6d6cf35a..4b21abe380 100644 --- a/frontend/src/lib/components/apps/components/layout/AppDecisionTree.svelte +++ b/frontend/src/lib/components/apps/components/layout/AppDecisionTree.svelte @@ -13,6 +13,7 @@ import Badge from '$lib/components/common/badge/Badge.svelte' import { getFirstNode, isDebugging } from '../../editor/settingsPanel/decisionTree/utils' import InputValue from '../helpers/InputValue.svelte' + import { sendUserToast } from '$lib/toast' interface Props { id: string @@ -42,10 +43,12 @@ }, {}) } + let counter = $state(0) $effect(() => { nodes resolvedConditions = untrack(() => createResolvedConditions()) resolvedNext = untrack(() => createResolvedNext()) + untrack(() => (counter += 1)) }) let everRender = $state(render) @@ -112,6 +115,7 @@ return } } + sendUserToast('No next node was an available option', true) } function updateFocusedGrid(nodeId) { currentNodeId = nodeId @@ -177,30 +181,37 @@ -{#if Object.keys(resolvedConditions).length === nodes.length} - {#each nodes ?? [] as node (node.id)} - {#each node.next ?? [] as next, conditionIndex} - {#if next.condition} + +{#key counter} + {#if Object.keys(resolvedConditions).length === nodes.length} + {#each nodes ?? [] as node (node.id)} + {#each node.next ?? [] as next, conditionIndex} + {#if next.condition} + + {/if} + {/each} + {/each} + {/if} + + {#if Object.keys(resolvedConditions).length === nodes.length} + {#each nodes ?? [] as node (node.id)} + {#if node.allowed} {/if} {/each} - {/each} -{/if} - -{#if Object.keys(resolvedConditions).length === nodes.length} - {#each nodes ?? [] as node (node.id)} - {#if node.allowed} - - {/if} - {/each} -{/if} - + {/if} +{/key} {#each Object.keys(css ?? {}) as key (key)} +{#snippet nonRenderedPlaceholder()} + + + +{/snippet} {#if everRender}
{/each} {:else} - - - + {@render nonRenderedPlaceholder?.()} {#if !Array.isArray(result)}
Input data is not an array
{/if} @@ -289,8 +292,6 @@ {/if}
{:else if $app.subgrids} - - - + {@render nonRenderedPlaceholder?.()} {/if}
diff --git a/frontend/src/lib/components/apps/components/layout/AppStepper.svelte b/frontend/src/lib/components/apps/components/layout/AppStepper.svelte index 0ef76b0978..a0abbad040 100644 --- a/frontend/src/lib/components/apps/components/layout/AppStepper.svelte +++ b/frontend/src/lib/components/apps/components/layout/AppStepper.svelte @@ -169,8 +169,13 @@ {/each} + +{#snippet nonRenderedPlaceholder()} + {#each tabs ?? [] as _res, i} + + {/each} +{/snippet} {#if everRender}
@@ -285,8 +291,6 @@ {/if}
{:else if $app.subgrids} - {#each tabs ?? [] as _res, i} - - {/each} + {@render nonRenderedPlaceholder?.()} {/if}
diff --git a/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte b/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte index e91b319673..3761855b63 100644 --- a/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte +++ b/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte @@ -7,7 +7,7 @@ import Path from '$lib/components/Path.svelte' import Toggle from '$lib/components/Toggle.svelte' - import { AppService, DraftService, type Policy } from '$lib/gen' + import { AppService, DraftService, SettingService, type Policy } from '$lib/gen' import { redo, undo } from '$lib/history.svelte' import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores' import { @@ -930,6 +930,17 @@ let customPath = $state(savedApp?.custom_path) let dirtyCustomPath = $state(false) let customPathError = $state('') + let globalWorkspacedRoute = $state(false) + + async function loadGlobalWorkspacedRouteSetting() { + try { + const setting = await SettingService.getGlobal({ key: 'app_workspaced_route' }) + globalWorkspacedRoute = (setting as boolean) ?? false + } catch (error) { + globalWorkspacedRoute = false + } + } + async function appExists(customPath: string) { return await AppService.customPathExists({ workspace: $workspaceStore!, @@ -970,13 +981,14 @@ let hasErrors = $derived(Object.keys($errorByComponent).length > 0) let fullCustomUrl = $derived( `${window.location.origin}${base}/a/${ - isCloudHosted() ? $workspaceStore + '/' : '' + isCloudHosted() || globalWorkspacedRoute ? $workspaceStore + '/' : '' }${customPath}` ) $effect(() => { ;[customPath] untrack(() => customPath !== undefined && validateCustomPath(customPath)) }) + loadGlobalWorkspacedRouteSetting() diff --git a/frontend/src/lib/components/apps/editor/GridViewer.svelte b/frontend/src/lib/components/apps/editor/GridViewer.svelte index a5e81739b9..fc540c6cf0 100644 --- a/frontend/src/lib/components/apps/editor/GridViewer.svelte +++ b/frontend/src/lib/components/apps/editor/GridViewer.svelte @@ -130,6 +130,7 @@ {@const left = (item[getComputedCols] && item[getComputedCols].x) * xPerPx + gapX}
- {:else if testJob != undefined && 'result' in testJob && testJob.result != undefined} + {:else if testJob != undefined && (testJob.type == 'CompletedJob' || testJob.result_stream)}
{ - if (item.data.type === 'tablecomponent') { - return { - ...item.data, - id, - actionButtons: - item.data.actionButtons.map((x) => ({ - ...x, - id: x.id.replace(`${item.id}_`, `${id}_`) - })) ?? [] - } - } else if ( - item.data.type === 'aggridcomponent' || - item.data.type === 'aggridcomponentee' || - item.data.type === 'dbexplorercomponent' || - item.data.type === 'aggridinfinitecomponent' || - item.data.type === 'aggridinfinitecomponentee' - ) { - return { - ...item.data, - id, - actionButtons: - (item.data.actions ?? []).map((x) => ({ - ...x, - id: x.id.replace(`${item.id}_`, `${id}_`) - })) ?? [] - } - } else if (item.data.type === 'menucomponent') { - return { - ...item.data, - id, - menuItems: - item.data.menuItems.map((x) => ({ - ...x, - id: x.id.replace(`${item.id}_`, `${id}_`) - })) ?? [] - } - } else { - return { ...item.data, id } - } + let newComponent = { ...item.data, id } + processSubcomponents(newComponent, (c) => { + c.id = c.id.replace(item.id + '_', id + '_') + }) + return newComponent }, parentGrid, Object.fromEntries(gridColumns.map((column) => [column, item[column]])) @@ -695,23 +661,9 @@ export function getAllSubgridsAndComponentIds( ): [string[], string[]] { const subgrids: string[] = [] let components: string[] = [component.id] - if (component.type === 'tablecomponent') { - components.push(...component.actionButtons?.map((x) => x.id)) - } - - if ( - component.type === 'aggridcomponent' || - component.type === 'aggridcomponentee' || - component.type === 'dbexplorercomponent' || - component.type === 'aggridinfinitecomponent' || - component.type === 'aggridinfinitecomponentee' - ) { - components.push(...(component.actions?.map((x) => x.id) ?? [])) - } - - if (component.type === 'menucomponent') { - components.push(...component.menuItems?.map((x) => x.id)) - } + processSubcomponents(component, (c) => { + components.push(c.id) + }) if (app.subgrids && component.numberOfSubgrids) { for (let i = 0; i < component.numberOfSubgrids; i++) { @@ -734,21 +686,11 @@ export function getAllGridItems(app: App): GridItem[] { return app.grid .concat(Object.values(app.subgrids ?? {}).flat()) .map((x) => { - if (x?.data?.type === 'tablecomponent') { - return [x, ...x?.data?.actionButtons?.map((x) => ({ data: x, id: x.id }))] - } else if ( - (x?.data?.type === 'aggridcomponent' || - x?.data?.type === 'aggridcomponentee' || - x?.data?.type === 'dbexplorercomponent' || - x?.data?.type === 'aggridinfinitecomponent' || - x?.data?.type === 'aggridinfinitecomponentee') && - Array.isArray(x?.data?.actions) - ) { - return [x, ...x?.data?.actions?.map((x) => ({ data: x, id: x.id }))] - } else if (x?.data?.type === 'menucomponent') { - return [x, ...x?.data?.menuItems?.map((x) => ({ data: x, id: x.id }))] - } - return [x] + let r: GridItem[] = [] + processSubcomponents(x.data, (c) => { + r.push({ data: c, id: c.id }) + }) + return [x, ...r] }) .flat() } diff --git a/frontend/src/lib/components/apps/editor/component/ComponentInner.svelte b/frontend/src/lib/components/apps/editor/component/ComponentInner.svelte index fb50dcfb4c..9135de349e 100644 --- a/frontend/src/lib/components/apps/editor/component/ComponentInner.svelte +++ b/frontend/src/lib/components/apps/editor/component/ComponentInner.svelte @@ -92,712 +92,864 @@ inlineEditorOpened = $bindable(), initializing = $bindable(undefined) }: Props = $props() + + // Define the component groups for efficient range checking + const chunk1Components = new Set([ + 'accordionlistcomponent', + 'agchartscomponent', + 'agchartscomponentee', + 'aggridcomponent', + 'aggridcomponentee', + 'aggridinfinitecomponent', + 'aggridinfinitecomponentee', + 'alertcomponent', + 'barchartcomponent', + 'buttoncomponent' + ]) + const chunk2Components = new Set([ + 'carousellistcomponent', + 'chartjscomponent', + 'chartjscomponentv2', + 'checkboxcomponent', + 'codeinputcomponent', + 'conditionalwrapper', + 'containercomponent', + 'currencycomponent', + 'customcomponent', + 'dateinputcomponent' + ]) + const chunk3Components = new Set([ + 'dateselectcomponent', + 'dateslidercomponent', + 'datetimeinputcomponent', + 'dbexplorercomponent', + 'decisiontreecomponent', + 'displaycomponent', + 'downloadcomponent', + 'drawercomponent', + 'emailinputcomponent', + 'fileinputcomponent' + ]) + const chunk4Components = new Set([ + 'flowstatuscomponent', + 'formbuttoncomponent', + 'formcomponent', + 'horizontaldividercomponent', + 'horizontalsplitpanescomponent', + 'htmlcomponent', + 'iconcomponent', + 'imagecomponent', + 'jobiddisplaycomponent', + 'jobidflowstatuscomponent' + ]) + const chunk5Components = new Set([ + 'jobidlogcomponent', + 'listcomponent', + 'logcomponent', + 'mapcomponent', + 'mardowncomponent', + 'menucomponent', + 'modalcomponent', + 'multiselectcomponent', + 'multiselectcomponentv2', + 'navbarcomponent' + ]) + const chunk6Components = new Set([ + 'numberinputcomponent', + 'passwordinputcomponent', + 'pdfcomponent', + 'piechartcomponent', + 'plotlycomponent', + 'plotlycomponentv2', + 'quillcomponent', + 'rangecomponent', + 'recomputeallcomponent', + 'resourceselectcomponent' + ]) + const chunk7Components = new Set([ + 's3fileinputcomponent', + 'scatterchartcomponent', + 'schemaformcomponent', + 'selectcomponent', + 'selectstepcomponent', + 'selecttabcomponent', + 'slidercomponent', + 'statcomponent', + 'steppercomponent' + ]) + const chunk8Components = new Set([ + 'tablecomponent', + 'tabscomponent', + 'textareainputcomponent', + 'textcomponent', + 'textinputcomponent', + 'timeinputcomponent', + 'timeseriescomponent', + 'userresourcecomponent', + 'vegalitecomponent', + 'verticaldividercomponent' + ]) + const chunk9Components = new Set(['verticalsplitpanescomponent']) { - console.error(e) + console.error('Error displaying component ' + component.id, component, e) }} > - {#if component.type === 'displaycomponent'} - - {:else if component.type === 'logcomponent'} - - {:else if component.type === 'jobidlogcomponent'} - - {:else if component.type === 'flowstatuscomponent'} - - {:else if component.type === 'jobidflowstatuscomponent'} - - {:else if component.type === 'barchartcomponent'} - - {:else if component.type === 'timeseriescomponent'} - - {:else if component.type === 'htmlcomponent'} - - {:else if component.type === 'customcomponent'} - - {:else if component.type === 'mardowncomponent'} - - {:else if component.type === 'vegalitecomponent'} - - {:else if component.type === 'plotlycomponent'} - - {:else if component.type === 'plotlycomponentv2'} - - {:else if component.type === 'scatterchartcomponent'} - - {:else if component.type === 'piechartcomponent'} - - {:else if component.type === 'agchartscomponent'} - - {:else if component.type === 'agchartscomponentee'} - - {:else if component.type === 'tablecomponent'} - {#await import('$lib/components/apps/components/display/table/AppTable.svelte')} - - {:then Module} - + {#if chunk1Components.has(component.type)} + {#if component.type === 'accordionlistcomponent'} + + {:else if component.type === 'agchartscomponent'} + - {/await} - {:else if component.type === 'dbexplorercomponent'} - - {:else if component.type === 'aggridcomponent'} - - {:else if component.type === 'aggridcomponentee'} - - {:else if component.type === 'aggridinfinitecomponent'} - - {:else if component.type === 'aggridinfinitecomponentee'} - - {:else if component.type === 'textcomponent'} - - {:else if component.type === 'codeinputcomponent'} - - {:else if component.type === 'buttoncomponent'} - - {:else if component.type === 'downloadcomponent'} - - {:else if component.type === 'selectcomponent' || component.type === 'resourceselectcomponent'} - - {:else if component.type === 'userresourcecomponent'} - - {:else if component.type === 'multiselectcomponent'} - - {:else if component.type === 'multiselectcomponentv2'} - - {:else if component.type === 'formcomponent'} - - {:else if component.type === 'formbuttoncomponent'} - - {:else if component.type === 'checkboxcomponent'} - - {:else if component.type === 'textinputcomponent'} - - {:else if component.type === 'quillcomponent'} - - {:else if component.type === 'textareainputcomponent'} - - {:else if component.type === 'emailinputcomponent'} - - {:else if component.type === 'passwordinputcomponent'} - - {:else if component.type === 'dateinputcomponent'} - - {:else if component.type === 'timeinputcomponent'} - - {:else if component.type === 'datetimeinputcomponent'} - - {:else if component.type === 'numberinputcomponent'} - - {:else if component.type === 'currencycomponent'} - - {:else if component.type === 'slidercomponent'} - - {:else if component.type === 'dateslidercomponent'} - - {:else if component.type === 'horizontaldividercomponent'} - - {:else if component.type === 'verticaldividercomponent'} - - {:else if component.type === 'rangecomponent'} - - {:else if component.type === 'tabscomponent' && component.tabs} - - {:else if component.type === 'steppercomponent' && component.tabs} - - {:else if component.type === 'conditionalwrapper' && component.conditions} - - {:else if component.type === 'containercomponent'} - - {:else if component.type === 'listcomponent'} - - {:else if component.type === 'verticalsplitpanescomponent'} - - {:else if component.type === 'horizontalsplitpanescomponent'} - - {:else if component.type === 'iconcomponent'} - - {:else if component.type === 'fileinputcomponent'} - - {:else if component.type === 's3fileinputcomponent'} - - {:else if component.type === 'imagecomponent'} - - {:else if component.type === 'drawercomponent'} - - {:else if component.type === 'mapcomponent'} - - {:else if component.type === 'pdfcomponent'} - - {:else if component.type === 'modalcomponent'} - - {:else if component.type === 'schemaformcomponent'} - - {:else if component.type === 'selecttabcomponent'} - - {:else if component.type === 'selectstepcomponent'} - - {:else if component.type === 'chartjscomponent'} - - {:else if component.type === 'chartjscomponentv2'} - - {:else if component.type === 'carousellistcomponent'} - - {:else if component.type === 'accordionlistcomponent'} - - {:else if component.type === 'statcomponent'} - - {:else if component.type === 'menucomponent'} - - {:else if component.type === 'decisiontreecomponent' && component.nodes} - - {:else if component.type === 'alertcomponent'} - - {:else if component.type === 'navbarcomponent'} - - {:else if component.type === 'dateselectcomponent'} - - {:else if component.type === 'jobiddisplaycomponent'} - - {:else if component.type === 'recomputeallcomponent'} - + {:else if component.type === 'agchartscomponentee'} + + {:else if component.type === 'aggridcomponent'} + + {:else if component.type === 'aggridcomponentee'} + + {:else if component.type === 'aggridinfinitecomponent'} + + {:else if component.type === 'aggridinfinitecomponentee'} + + {:else if component.type === 'alertcomponent'} + + {:else if component.type === 'barchartcomponent'} + + {:else if component.type === 'buttoncomponent'} + + {/if} {/if} + + + {#if chunk2Components.has(component.type)} + {#if component.type === 'carousellistcomponent'} + + {:else if component.type === 'chartjscomponent'} + + {:else if component.type === 'chartjscomponentv2'} + + {:else if component.type === 'checkboxcomponent'} + + {:else if component.type === 'codeinputcomponent'} + + {:else if component.type === 'conditionalwrapper' && component.conditions} + + {:else if component.type === 'containercomponent'} + + {:else if component.type === 'currencycomponent'} + + {:else if component.type === 'customcomponent'} + + {:else if component.type === 'dateinputcomponent'} + + {/if} + {/if} + + + {#if chunk3Components.has(component.type)} + {#if component.type === 'dateselectcomponent'} + + {:else if component.type === 'dateslidercomponent'} + + {:else if component.type === 'datetimeinputcomponent'} + + {:else if component.type === 'dbexplorercomponent'} + + {:else if component.type === 'decisiontreecomponent' && component.nodes} + + {:else if component.type === 'displaycomponent'} + + {:else if component.type === 'downloadcomponent'} + + {:else if component.type === 'drawercomponent'} + + {:else if component.type === 'emailinputcomponent'} + + {:else if component.type === 'fileinputcomponent'} + + {/if} + {/if} + + + {#if chunk4Components.has(component.type)} + {#if component.type === 'flowstatuscomponent'} + + {:else if component.type === 'formbuttoncomponent'} + + {:else if component.type === 'formcomponent'} + + {:else if component.type === 'horizontaldividercomponent'} + + {:else if component.type === 'horizontalsplitpanescomponent'} + + {:else if component.type === 'htmlcomponent'} + + {:else if component.type === 'iconcomponent'} + + {:else if component.type === 'imagecomponent'} + + {:else if component.type === 'jobiddisplaycomponent'} + + {:else if component.type === 'jobidflowstatuscomponent'} + + {/if} + {/if} + + + {#if chunk5Components.has(component.type)} + {#if component.type === 'jobidlogcomponent'} + + {:else if component.type === 'listcomponent'} + + {:else if component.type === 'logcomponent'} + + {:else if component.type === 'mapcomponent'} + + {:else if component.type === 'mardowncomponent'} + + {:else if component.type === 'menucomponent'} + + {:else if component.type === 'modalcomponent'} + + {:else if component.type === 'multiselectcomponent'} + + {:else if component.type === 'multiselectcomponentv2'} + + {:else if component.type === 'navbarcomponent'} + + {/if} + {/if} + + + {#if chunk6Components.has(component.type)} + {#if component.type === 'numberinputcomponent'} + + {:else if component.type === 'passwordinputcomponent'} + + {:else if component.type === 'pdfcomponent'} + + {:else if component.type === 'piechartcomponent'} + + {:else if component.type === 'plotlycomponent'} + + {:else if component.type === 'plotlycomponentv2'} + + {:else if component.type === 'quillcomponent'} + + {:else if component.type === 'rangecomponent'} + + {:else if component.type === 'recomputeallcomponent'} + + {:else if component.type === 'resourceselectcomponent'} + + {/if} + {/if} + + + {#if chunk7Components.has(component.type)} + {#if component.type === 's3fileinputcomponent'} + + {:else if component.type === 'scatterchartcomponent'} + + {:else if component.type === 'schemaformcomponent'} + + {:else if component.type === 'selectcomponent'} + + {:else if component.type === 'selectstepcomponent'} + + {:else if component.type === 'selecttabcomponent'} + + {:else if component.type === 'slidercomponent'} + + {:else if component.type === 'statcomponent'} + + {:else if component.type === 'steppercomponent' && component.tabs} + + {/if} + {/if} + + + {#if chunk8Components.has(component.type)} + {#if component.type === 'tablecomponent'} + {#await import('$lib/components/apps/components/display/table/AppTable.svelte')} + + {:then Module} + + {/await} + {:else if component.type === 'tabscomponent' && component.tabs} + + {:else if component.type === 'textareainputcomponent'} + + {:else if component.type === 'textcomponent'} + + {:else if component.type === 'textinputcomponent'} + + {:else if component.type === 'timeinputcomponent'} + + {:else if component.type === 'timeseriescomponent'} + + {:else if component.type === 'userresourcecomponent'} + + {:else if component.type === 'vegalitecomponent'} + + {:else if component.type === 'verticaldividercomponent'} + + {/if} + {/if} + + + {#if chunk9Components.has(component.type)} + {#if component.type === 'verticalsplitpanescomponent'} + + {/if} + {/if} + {#snippet failed(error, reset)}

Rendering of component failed

diff --git a/frontend/src/lib/components/apps/editor/component/componentCallbacks.svelte.ts b/frontend/src/lib/components/apps/editor/component/componentCallbacks.svelte.ts index 7e20200c53..4416002d10 100644 --- a/frontend/src/lib/components/apps/editor/component/componentCallbacks.svelte.ts +++ b/frontend/src/lib/components/apps/editor/component/componentCallbacks.svelte.ts @@ -12,6 +12,7 @@ import { sendUserToast } from '$lib/toast' import { gridColumns } from '../../gridUtils' import { copyToClipboard } from '$lib/utils' import { get } from 'svelte/store' +import { processSubcomponents } from '../../utils' // const { app, selectedComponent, focusedGrid, componentControl } = // getContext('AppViewerContext') @@ -334,7 +335,13 @@ export async function handlePaste( const gridItem = tempGridItem insertNewGridItem( app, - (id) => ({ ...gridItem.data, id }), + (id) => { + let newComponent = { ...gridItem.data, id } + processSubcomponents(newComponent, (c) => { + c.id = c.id.replace(tempGridItem.id + '_', id + '_') + }) + return newComponent + }, focusedGrid, Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]])), tempGridItem.id diff --git a/frontend/src/lib/components/apps/editor/contextPanel/components/IdEditor.svelte b/frontend/src/lib/components/apps/editor/contextPanel/components/IdEditor.svelte index 9caf4861ac..80acbfe23c 100644 --- a/frontend/src/lib/components/apps/editor/contextPanel/components/IdEditor.svelte +++ b/frontend/src/lib/components/apps/editor/contextPanel/components/IdEditor.svelte @@ -44,6 +44,7 @@ onChange(e) onClose?.() }} + reservedPrefixes={['bg_', 'unused-']} {reservedIds} /> {/snippet} diff --git a/frontend/src/lib/components/apps/editor/contextPanel/components/OutputHeader.svelte b/frontend/src/lib/components/apps/editor/contextPanel/components/OutputHeader.svelte index c1e84dae02..4aa539d9a1 100644 --- a/frontend/src/lib/components/apps/editor/contextPanel/components/OutputHeader.svelte +++ b/frontend/src/lib/components/apps/editor/contextPanel/components/OutputHeader.svelte @@ -2,7 +2,7 @@ import { stopPropagation } from 'svelte/legacy' import type { AppViewerContext, ContextPanelContext } from '$lib/components/apps/types' - import { allItems } from '$lib/components/apps/utils' + import { allItems, processSubcomponents } from '$lib/components/apps/utils' import { classNames } from '$lib/utils' import { ChevronDown, ChevronUp, Pointer } from 'lucide-svelte' import { getContext } from 'svelte' @@ -104,35 +104,11 @@ }) } propagateRename(oldId, newId) - if (item?.data.type == 'tablecomponent') { - for (let c of item.data.actionButtons) { - let old = c.id - c.id = c.id.replace(oldId + '_', newId + '_') - propagateRename(old, c.id) - } - } - - if ( - item?.data.type == 'aggridcomponent' || - item?.data.type == 'aggridcomponentee' || - item?.data.type == 'dbexplorercomponent' || - item?.data.type == 'aggridinfinitecomponent' || - item?.data.type == 'aggridinfinitecomponentee' - ) { - for (let c of item.data.actions ?? []) { - let old = c.id - c.id = c.id.replace(oldId + '_', newId + '_') - propagateRename(old, c.id) - } - } - - if (item?.data.type === 'menucomponent') { - for (let c of item.data.menuItems) { - let old = c.id - c.id = c.id.replace(oldId + '_', newId + '_') - propagateRename(old, c.id) - } - } + processSubcomponents(item.data, (c) => { + let old = c.id + c.id = c.id.replace(oldId + '_', newId + '_') + propagateRename(old, c.id) + }) $app = $app $selectedComponent = [newId] @@ -141,30 +117,9 @@ } function renameComponent(from: string, to: string, data: AppComponent) { - if (data.type == 'tablecomponent') { - for (let c of data.actionButtons) { - renameComponent(from, to, c) - } - } - - if ( - (data.type == 'aggridcomponent' || - data.type == 'aggridcomponentee' || - data.type == 'dbexplorercomponent' || - data.type == 'aggridinfinitecomponent' || - data.type == 'aggridinfinitecomponentee') && - Array.isArray(data.actions) - ) { - for (let c of data.actions) { - renameComponent(from, to, c) - } - } - - if (data.type === 'menucomponent') { - for (let c of data.menuItems) { - renameComponent(from, to, c) - } - } + processSubcomponents(data, (c) => { + renameComponent(from, to, c) + }) let componentInput = data.componentInput if (componentInput?.type == 'connected') { diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/DecisionTreeGraphEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/DecisionTreeGraphEditor.svelte index 2c71eb37f0..9961a0910d 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/DecisionTreeGraphEditor.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/DecisionTreeGraphEditor.svelte @@ -1,6 +1,6 @@ - {}} on:open={() => {}} size="1200px"> + {}} on:open={() => {}}> @@ -114,8 +111,8 @@ /> - {#if selectedNode.next.length > 1 && sortedSelectedNextNodes} - {#each sortedSelectedNextNodes as subNode, index (subNode.id)} + {#if selectedNode.next.length > 1} + {#each selectedNode.next ?? [] as subNode, index (subNode.id)} {#if subNode.condition}
@@ -129,16 +126,22 @@ userInputEnabled={false} shouldCapitalize={true} resourceOnly={false} - fieldType={subNode.condition?.['fieldType']} - subFieldType={subNode.condition?.['subFieldType']} - format={subNode.condition?.['format']} - selectOptions={subNode.condition?.['selectOptions']} - tooltip={subNode.condition?.['tooltip']} - fileUpload={subNode.condition?.['fileUpload']} - placeholder={subNode.condition?.['placeholder']} + fieldType={'boolean'} + subFieldType={undefined} + format={undefined} + selectOptions={undefined} + tooltip={undefined} + fileUpload={undefined} + placeholder={undefined} displayType={false} fixedOverflowWidgets={false} /> + {#if index == selectedNode.next.length - 1} +
+ If no branch evaluates to true, clicking next will show an error toast. +
+ {/if} +
{`Next node id: ${subNode.id}`} @@ -151,7 +154,6 @@
{/if} {/each} - The conditions above are evaluated in order. The first condition that is met will be the branch that is taken. @@ -160,44 +162,25 @@ {#key selectedNode.id} {#if selectedNode.allowed} {/if} {/key} - - {#if selectedNode?.next.length > 0} -
- -
- {/if} {/if}
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecEditor.svelte index e87761c27f..8259a71e05 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecEditor.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/InputsSpecEditor.svelte @@ -56,6 +56,7 @@ documentationLink?: string | undefined markdownTooltip?: string | undefined securedContext?: boolean + disabled?: boolean } let { @@ -76,6 +77,7 @@ customTitle = undefined, displayType = false, allowTypeChange = true, + disabled = false, shouldFormatExpression = false, fixedOverflowWidgets = true, loading = false, @@ -274,6 +276,7 @@ {recomputeOnInputChanged} {showOnDemandOnlyToggle} {securedContext} + {disabled} /> {:else if componentInput?.type === 'upload'} diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/inputEditor/EvalV2InputEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/inputEditor/EvalV2InputEditor.svelte index e8185281d4..82517a3b19 100644 --- a/frontend/src/lib/components/apps/editor/settingsPanel/inputEditor/EvalV2InputEditor.svelte +++ b/frontend/src/lib/components/apps/editor/settingsPanel/inputEditor/EvalV2InputEditor.svelte @@ -22,6 +22,7 @@ recomputeOnInputChanged?: boolean showOnDemandOnlyToggle?: boolean securedContext?: boolean + disabled?: boolean } let { @@ -32,7 +33,8 @@ acceptSelf = false, recomputeOnInputChanged = true, showOnDemandOnlyToggle = false, - securedContext = false + securedContext = false, + disabled = false }: Props = $props() const { @@ -68,7 +70,6 @@ } function inferDepsFromCode(code: string) { - console.log('inferDepsFromCode', id) if (componentInput) { inferDeps(code, $worldStore.outputsById, componentInput, app) } @@ -137,6 +138,7 @@ shouldBindKey={false} {extraLib} autoHeight + {disabled} {fixedOverflowWidgets} on:focus={() => { focus = true diff --git a/frontend/src/lib/components/apps/svelte-grid/MoveResize.svelte b/frontend/src/lib/components/apps/svelte-grid/MoveResize.svelte index 3b10c218e2..6a558d396d 100644 --- a/frontend/src/lib/components/apps/svelte-grid/MoveResize.svelte +++ b/frontend/src/lib/components/apps/svelte-grid/MoveResize.svelte @@ -77,7 +77,7 @@ const scale = ctx ? ctx.scale : writable(100) - const divId = `component-${id}` + const divId = `wm-component-${id}` let shadowElement: HTMLElement | undefined = $state() let active = $state(false) diff --git a/frontend/src/lib/components/apps/utils.ts b/frontend/src/lib/components/apps/utils.ts index dc512b8645..fe6192ba89 100644 --- a/frontend/src/lib/components/apps/utils.ts +++ b/frontend/src/lib/components/apps/utils.ts @@ -37,6 +37,33 @@ export function migrateApp(app: App) { }) } +export function processSubcomponents(data: AppComponent, fn: (data: AppComponent) => void) { + if (data.type == 'tablecomponent' && Array.isArray(data.actionButtons)) { + for (let c of data.actionButtons) { + fn(c) + } + } + + if ( + (data.type == 'aggridcomponent' || + data.type == 'aggridcomponentee' || + data.type == 'dbexplorercomponent' || + data.type == 'aggridinfinitecomponent' || + data.type == 'aggridinfinitecomponentee') && + Array.isArray(data.actions) + ) { + for (let c of data.actions ?? []) { + fn(c) + } + } + + if (data.type === 'menucomponent' && Array.isArray(data.menuItems)) { + for (let c of data.menuItems) { + fn(c) + } + } +} + export function allItems( grid: GridItem[], subgrids: Record | undefined diff --git a/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte b/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte index 3cfb57fee5..19c8a5e991 100644 --- a/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte +++ b/frontend/src/lib/components/common/languageIcons/LanguageIcon.svelte @@ -23,6 +23,7 @@ import NuIcon from '$lib/components/icons/NuIcon.svelte' import JavaIcon from '$lib/components/icons/JavaIcon.svelte' import DuckDbIcon from '$lib/components/icons/DuckDbIcon.svelte' + import RubyIcon from '$lib/components/icons/RubyIcon.svelte' interface Props { lang: @@ -71,8 +72,9 @@ ansible: 'Ansible', csharp: 'C#', nu: 'Nu', - java: 'Java' - // for related places search: ADD_NEW_LANG + java: 'Java', + ruby: 'Ruby' + // for related places search: ADD_NEW_LANG } const langToComponent: Record< @@ -105,6 +107,7 @@ csharp: CSharpIcon, nu: NuIcon, java: JavaIcon, + ruby: RubyIcon, duckdb: DuckDbIcon // for related places search: ADD_NEW_LANG } diff --git a/frontend/src/lib/components/common/tabs/TabsV2.svelte b/frontend/src/lib/components/common/tabs/TabsV2.svelte index 6634256b29..6fd5dbc6e6 100644 --- a/frontend/src/lib/components/common/tabs/TabsV2.svelte +++ b/frontend/src/lib/components/common/tabs/TabsV2.svelte @@ -15,6 +15,7 @@ children?: import('svelte').Snippet<[any]> content?: import('svelte').Snippet onSelectedChange?: (value: string) => void + onTabClick?: (value: string) => void } let { @@ -27,7 +28,8 @@ values = undefined, children, content, - onSelectedChange + onSelectedChange, + onTabClick }: Props = $props() const selectedStore = writable(selected) @@ -37,6 +39,7 @@ update: (value: string) => { selectedStore.set(value) selected = value + onTabClick?.(value) }, hashNavigation }) @@ -55,9 +58,11 @@ } } } + $effect(() => { selected && untrack(() => updateSelected()) }) + $effect(() => { $selectedStore && untrack(() => onSelectedChange?.($selectedStore)) }) diff --git a/frontend/src/lib/components/copilot/FlowInlineScriptAIButton.svelte b/frontend/src/lib/components/copilot/FlowInlineScriptAIButton.svelte new file mode 100644 index 0000000000..bdbecba8c9 --- /dev/null +++ b/frontend/src/lib/components/copilot/FlowInlineScriptAIButton.svelte @@ -0,0 +1,62 @@ + + +{#snippet button(onClick?: () => void)} + + {/if} +
+ + {#if loading} +
+ + Executing... +
+ {:else if error} +
+
{error}
+
+ {:else if hasContent} +
+
{formatJson(content)}
+
+ {:else} +
+ No {title.toLowerCase()} yet +
+ {/if} +
+{/if} diff --git a/frontend/src/lib/components/copilot/chat/ToolExecutionDisplay.svelte b/frontend/src/lib/components/copilot/chat/ToolExecutionDisplay.svelte new file mode 100644 index 0000000000..99b859662a --- /dev/null +++ b/frontend/src/lib/components/copilot/chat/ToolExecutionDisplay.svelte @@ -0,0 +1,114 @@ + + +
+ + + + + {#if isExpanded} +
+ + + + + {#if message.needsConfirmation} +
+ + +
+ + + {:else} + + + + {/if} +
+ {/if} +
diff --git a/frontend/src/lib/components/copilot/chat/api/apiTools.ts b/frontend/src/lib/components/copilot/chat/api/apiTools.ts new file mode 100644 index 0000000000..65fea10936 --- /dev/null +++ b/frontend/src/lib/components/copilot/chat/api/apiTools.ts @@ -0,0 +1,223 @@ +import type { ChatCompletionTool } from 'openai/resources/index.mjs' +import type { Tool } from '../shared' +import { get } from 'svelte/store' +import { workspaceStore } from '$lib/stores' +import type { EndpointTool } from '$lib/gen/types.gen' +import { McpService } from '$lib/gen/services.gen' + +function buildApiCallTool(endpointTool: EndpointTool): ChatCompletionTool { + // Build the parameters schema for OpenAI function calling + const parameters: Record = { + type: 'object', + properties: {}, + required: [] + } + + // Add path parameters + if (endpointTool.path_params_schema?.properties) { + for (const [key, schema] of Object.entries(endpointTool.path_params_schema.properties)) { + // Skip workspace parameter as it's auto-filled + if (key === 'workspace') continue + + parameters.properties[key] = schema + + if (Array.isArray(endpointTool.path_params_schema.required) && endpointTool.path_params_schema.required.includes(key)) { + parameters.required.push(key) + } + } + } + + // Add query parameters + if (endpointTool.query_params_schema?.properties) { + for (const [key, schema] of Object.entries(endpointTool.query_params_schema.properties)) { + parameters.properties[key] = schema + + if (Array.isArray(endpointTool.query_params_schema.required) && endpointTool.query_params_schema.required.includes(key)) { + parameters.required.push(key) + } + } + } + + // Add body parameters + if (endpointTool.body_schema?.properties) { + // For body params, we wrap them in a 'body' object + parameters.properties.body = { + type: 'object', + description: 'Request body', + properties: endpointTool.body_schema.properties, + required: endpointTool.body_schema.required || [] + } + + if (Array.isArray(endpointTool.body_schema.required) && endpointTool.body_schema.required.length > 0) { + parameters.required.push('body') + } + } + + return { + type: 'function', + function: { + name: endpointTool.name, + description: endpointTool.instructions || endpointTool.description, + parameters + } + } +} + +function buildToolsFromEndpoints( + endpointTools: EndpointTool[] +): { tools: ChatCompletionTool[]; endpointMap: Record } { + const tools: ChatCompletionTool[] = [] + const endpointMap: Record = {} + + for (const endpointTool of endpointTools) { + const tool = buildApiCallTool(endpointTool) + tools.push(tool) + + // Store the endpoint info in the map + endpointMap[endpointTool.name] = { + method: endpointTool.method, + path: endpointTool.path + } + } + + return { tools, endpointMap } +} + +export function createApiTools( + chatTools: ChatCompletionTool[], + endpointMap: Record = {} +): Tool<{}>[] { + return chatTools.map((chatTool) => { + const toolName = chatTool.function.name + const endpoint = endpointMap[toolName] + const method = endpoint?.method?.toUpperCase() || 'GET' + + // Determine if tool needs confirmation based on method + const needsConfirmation = ['DELETE', 'POST', 'PUT', 'PATCH'].includes(method) + + return { + def: chatTool, + requiresConfirmation: needsConfirmation, + showDetails: true, + fn: async ({ args, toolId, toolCallbacks }) => { + const toolName = chatTool.function.name + const endpoint = endpointMap[toolName] + + if (!endpoint) { + throw new Error(`No endpoint mapping found for tool ${toolName}`) + } + + try { + const workspace = get(workspaceStore) as string + let path = endpoint.path.replace('{workspace}', workspace) + + // Build URL with path parameters + let url = `/api${path}` + const queryParams: Record = {} + let requestBody: any = undefined + + // Process arguments + for (const [key, value] of Object.entries(args)) { + if (key === 'body') { + requestBody = value + continue + } + + // Check if this is a path parameter + if (url.includes(`{${key}}`)) { + url = url.replace(`{${key}}`, encodeURIComponent(String(value))) + } else { + // Assume it's a query parameter + queryParams[key] = String(value) + } + } + + // Add query parameters to URL if needed + if (Object.keys(queryParams).length > 0) { + const searchParams = new URLSearchParams() + for (const [key, value] of Object.entries(queryParams)) { + searchParams.append(key, value) + } + url += `?${searchParams.toString()}` + } + + toolCallbacks.setToolStatus(toolId, { + content: `Calling ${toolName}...`, + }) + + const fetchOptions: RequestInit = { + method: endpoint.method + } + + // Add request body for POST/PUT/PATCH methods + if (requestBody && ['POST', 'PUT', 'PATCH'].includes(endpoint.method.toUpperCase())) { + fetchOptions.headers = { + 'Content-Type': 'application/json' + } + fetchOptions.body = JSON.stringify(requestBody) + } + + const response = await fetch(url, fetchOptions) + + if (response.ok) { + let result = '' + if (response.headers.get('content-type')?.includes('application/json')) { + result = await response.json() + } else { + result = await response.text() + } + const jsonResult = JSON.stringify({ + success: true, + data: result + }) + toolCallbacks.setToolStatus(toolId, { + content: `Call to ${toolName} completed`, + result: jsonResult, + }) + return jsonResult + } else { + const text = await response.text() + const jsonResult = JSON.stringify({ + success: false, + error: text, + status: response.status + }) + toolCallbacks.setToolStatus(toolId, { + content: `Call to ${toolName} failed`, + result: jsonResult, + error: `HTTP ${response.status}: ${text}`, + }) + return jsonResult + } + } catch (error) { + const errorMessage = `Error calling API: ${error instanceof Error ? error.message : String(error)}` + toolCallbacks.setToolStatus(toolId, { + content: `Call to ${toolName} failed`, + error: errorMessage, + }) + console.error(`Error calling API:`, error) + return errorMessage + } + } + } + }) +} + +export async function loadApiTools(): Promise[]> { + try { + // Fetch the list of available MCP tools from the backend + const endpointTools = await McpService.listMcpTools({ + workspace: get(workspaceStore) as string + }) + + // Build tools from the endpoint definitions + const { tools: apiTools, endpointMap } = buildToolsFromEndpoints(endpointTools) + + // Create executable tools + const executableApiTools = createApiTools(apiTools, endpointMap) + return executableApiTools + } catch (error) { + console.error('Failed to load API tools:', error) + return [] + } +} \ No newline at end of file diff --git a/frontend/src/lib/components/copilot/chat/api/core.ts b/frontend/src/lib/components/copilot/chat/api/core.ts new file mode 100644 index 0000000000..cd7038595a --- /dev/null +++ b/frontend/src/lib/components/copilot/chat/api/core.ts @@ -0,0 +1,72 @@ +import type { + ChatCompletionSystemMessageParam, + ChatCompletionUserMessageParam +} from 'openai/resources/index.mjs' +import type { Tool } from '../shared' +import { loadApiTools } from './apiTools' +import { getDocumentationTool } from '../navigator/core' +import { userStore } from '$lib/stores' +import { get } from 'svelte/store' + +export const CHAT_SYSTEM_PROMPT = (username: string) =>` +You are Windmill's intelligent assistant, designed to interact with the platform via API endpoints and answer questions about its functionality. Your purpose is to help the user directly query and manipulate Windmill resources through API calls. + +Windmill is an open-source developer platform for building internal tools, API integrations, background jobs, workflows, and user interfaces. It offers a unified system where scripts are automatically turned into sharable UIs and can be composed into flows or embedded in custom applications. + +You have access to these tools: +1. Get documentation for user requests (get_documentation) +2. A comprehensive list of API endpoints to interact with the Windmill backend + +INSTRUCTIONS: +- You can directly query, list, create, update, and delete various Windmill resources like scripts, flows, jobs, resources, variables, schedules, and workers through the provided API tools. +- When users ask about specific data or want to perform operations, use the appropriate API endpoints to fulfill their requests. +- Use get_documentation to retrieve accurate information about features, concepts, and best practices when needed. +- Always present API results in a clear, readable format for the user. +- If you need to make multiple related API calls to fulfill a request, do so systematically and explain what you're doing. +- When showing lists of items, provide meaningful summaries rather than overwhelming the user with raw data. +- If an API call fails, explain the error clearly and suggest alternatives if applicable. +- If the user cancels the request, do not try again and ask for the user if he wants to make a new request with different instructions. +- For endpoints other that GET requiring to send a path, ask the user if he wants the path to be on a folder, or to be on its user's folder. Folder path looks like f/{folder_name}/{resource_path}, and user's folder path looks like u/${username}/{resource_path}. +- If an endpoint requires a resource_type, first fetch the available resourceTypes. + +API CAPABILITIES: +- Query jobs, scripts, flows, and their execution history +- List and manage resources and variables +- View schedules and worker information +- Search through job logs (if enterprise features are enabled) +- Access detailed information about any Windmill resource + +GENERAL PRINCIPLES: +- Be direct and action-oriented - use the API tools to fulfill user requests +- Provide clear summaries of API responses +- Maintain a helpful, professional tone +- If you encounter an error or can't complete a request, explain why and suggest alternatives +- Complete your responses with relevant documentation links when applicable + +Always use the provided API tools to directly interact with the Windmill platform and provide users with the information they need. +` + +let apiToolsCache: Tool<{}>[] | null = null + +export async function getApiTools(): Promise[]> { + if (apiToolsCache === null) { + apiToolsCache = await loadApiTools() + } + return apiToolsCache +} + +export const apiTools: Tool<{}>[] = [getDocumentationTool] + +export function prepareApiSystemMessage(): ChatCompletionSystemMessageParam { + return { + role: 'system', + content: CHAT_SYSTEM_PROMPT(get(userStore)?.username ?? '') + } +} + +export function prepareApiUserMessage(instructions: string): ChatCompletionUserMessageParam { + return { + role: 'user', + content: instructions + } +} \ No newline at end of file diff --git a/frontend/src/lib/components/copilot/chat/flow/core.ts b/frontend/src/lib/components/copilot/chat/flow/core.ts index 766e28524c..9c2ff2af27 100644 --- a/frontend/src/lib/components/copilot/chat/flow/core.ts +++ b/frontend/src/lib/components/copilot/chat/flow/core.ts @@ -1,4 +1,4 @@ -import { ScriptService, type FlowModule, type RawScript, type Script } from '$lib/gen' +import { ScriptService, type FlowModule, type RawScript, type Script, JobService } from '$lib/gen' import type { ChatCompletionSystemMessageParam, ChatCompletionUserMessageParam @@ -12,8 +12,10 @@ import { getLangContext, SUPPORTED_CHAT_SCRIPT_LANGUAGES } from '../script/core' -import { createSearchHubScriptsTool, createToolDef, type Tool } from '../shared' +import { createSearchHubScriptsTool, createToolDef, type Tool, executeTestRun } from '../shared' import type { ExtendedOpenFlow } from '$lib/components/flows/types' +import { copilotSessionModel } from '$lib/stores' +import { get } from 'svelte/store' export type AIModuleAction = 'added' | 'modified' | 'removed' @@ -337,6 +339,18 @@ const getInstructionsForCodeGenerationToolDef = createToolDef( 'Get instructions for code generation for a raw script step' ) +// Will be overridden by setSchema +const testRunFlowSchema = z.object({ + args: z.object({}).nullable().optional() + .describe('Arguments to pass to the flow (optional, uses default flow inputs if not provided)') +}) + +const testRunFlowToolDef = createToolDef( + testRunFlowSchema, + 'test_run_flow', + 'Execute a test run of the current flow' +) + const workspaceScriptsSearch = new WorkspaceScriptsSearch() export const flowTools: Tool[] = [ @@ -344,20 +358,18 @@ export const flowTools: Tool[] = [ { def: searchScriptsToolDef, fn: async ({ args, workspace, toolId, toolCallbacks }) => { - toolCallbacks.setToolStatus( - toolId, - 'Searching for workspace scripts related to "' + args.query + '"...' - ) + toolCallbacks.setToolStatus(toolId, { + content: 'Searching for workspace scripts related to "' + args.query + '"...' + }) const parsedArgs = searchScriptsSchema.parse(args) const scriptResults = await workspaceScriptsSearch.search(parsedArgs.query, workspace) - toolCallbacks.setToolStatus( - toolId, - 'Found ' + + toolCallbacks.setToolStatus(toolId, { + content: 'Found ' + scriptResults.length + ' scripts in the workspace related to "' + args.query + '"' - ) + }) return JSON.stringify(scriptResults) } }, @@ -365,9 +377,8 @@ export const flowTools: Tool[] = [ def: addStepToolDef, fn: async ({ args, helpers, toolId, toolCallbacks }) => { const parsedArgs = addStepSchema.parse(args) - toolCallbacks.setToolStatus( - toolId, - parsedArgs.location.type === 'after' + toolCallbacks.setToolStatus(toolId, { + content: parsedArgs.location.type === 'after' ? `Adding a step after step '${parsedArgs.location.afterId}'` : parsedArgs.location.type === 'start' ? 'Adding a step at the start' @@ -380,11 +391,11 @@ export const flowTools: Tool[] = [ : parsedArgs.location.type === 'failure' ? 'Adding a failure step' : 'Adding a step' - ) + }) const id = await helpers.insertStep(parsedArgs.location, parsedArgs.step) helpers.selectStep(id) - toolCallbacks.setToolStatus(toolId, `Added step '${id}'`) + toolCallbacks.setToolStatus(toolId, { content: `Added step '${id}'` }) return `Step ${id} added. Here is the updated flow, make sure to take it into account when adding another step:\n${YAML.stringify(helpers.getModules())}` } @@ -392,52 +403,52 @@ export const flowTools: Tool[] = [ { def: removeStepToolDef, fn: async ({ args, helpers, toolId, toolCallbacks }) => { - toolCallbacks.setToolStatus(toolId, `Removing step ${args.id}...`) + toolCallbacks.setToolStatus(toolId, { content: `Removing step ${args.id}...` }) const parsedArgs = removeStepSchema.parse(args) helpers.removeStep(parsedArgs.id) - toolCallbacks.setToolStatus(toolId, `Removed step '${parsedArgs.id}'`) + toolCallbacks.setToolStatus(toolId, { content: `Removed step '${parsedArgs.id}'` }) return `Step '${parsedArgs.id}' removed. Here is the updated flow:\n${YAML.stringify(helpers.getModules())}` } }, { def: getStepInputsToolDef, fn: async ({ args, helpers, toolId, toolCallbacks }) => { - toolCallbacks.setToolStatus(toolId, `Getting step ${args.id} inputs...`) + toolCallbacks.setToolStatus(toolId, { content: `Getting step ${args.id} inputs...` }) const parsedArgs = getStepInputsSchema.parse(args) const inputs = await helpers.getStepInputs(parsedArgs.id) - toolCallbacks.setToolStatus(toolId, `Retrieved step '${parsedArgs.id}' inputs`) + toolCallbacks.setToolStatus(toolId, { content: `Retrieved step '${parsedArgs.id}' inputs` }) return YAML.stringify(inputs) } }, { def: setStepInputsToolDef, fn: async ({ args, helpers, toolId, toolCallbacks }) => { - toolCallbacks.setToolStatus(toolId, `Setting step ${args.id} inputs...`) + toolCallbacks.setToolStatus(toolId, { content: `Setting step ${args.id} inputs...` }) const parsedArgs = setStepInputsSchema.parse(args) await helpers.setStepInputs(parsedArgs.id, parsedArgs.inputs) helpers.selectStep(parsedArgs.id) const inputs = await helpers.getStepInputs(parsedArgs.id) - toolCallbacks.setToolStatus(toolId, `Set step '${parsedArgs.id}' inputs`) + toolCallbacks.setToolStatus(toolId, { content: `Set step '${parsedArgs.id}' inputs` }) return `Step '${parsedArgs.id}' inputs set. New inputs:\n${YAML.stringify(inputs)}` }, preAction: ({ toolCallbacks, toolId }) => { - toolCallbacks.setToolStatus(toolId, 'Setting step inputs...') + toolCallbacks.setToolStatus(toolId, { content: 'Setting step inputs...' }) } }, { def: setFlowInputsSchemaToolDef, fn: async ({ args, helpers, toolId, toolCallbacks }) => { - toolCallbacks.setToolStatus(toolId, 'Setting flow inputs schema...') + toolCallbacks.setToolStatus(toolId, { content: 'Setting flow inputs schema...' }) const parsedArgs = setFlowInputsSchemaSchema.parse(args) const schema = JSON.parse(parsedArgs.schema) await helpers.setFlowInputsSchema(schema) helpers.selectStep('Input') const updatedSchema = await helpers.getFlowInputsSchema() - toolCallbacks.setToolStatus(toolId, 'Set flow inputs schema') + toolCallbacks.setToolStatus(toolId, { content: 'Set flow inputs schema' }) return `Flow inputs schema set. New schema:\n${JSON.stringify(updatedSchema)}` }, preAction: ({ toolCallbacks, toolId }) => { - toolCallbacks.setToolStatus(toolId, 'Setting flow inputs schema...') + toolCallbacks.setToolStatus(toolId, { content: 'Setting flow inputs schema...' }) } }, { @@ -448,10 +459,9 @@ export const flowTools: Tool[] = [ allowResourcesFetch: true, isPreprocessor: parsedArgs.id === 'preprocessor' }) - toolCallbacks.setToolStatus( - toolId, - 'Retrieved instructions for code generation in ' + parsedArgs.language - ) + toolCallbacks.setToolStatus(toolId, { + content: 'Retrieved instructions for code generation in ' + parsedArgs.language + }) return langContext } }, @@ -459,14 +469,14 @@ export const flowTools: Tool[] = [ def: setCodeToolDef, fn: async ({ args, helpers, toolId, toolCallbacks }) => { const parsedArgs = setCodeSchema.parse(args) - toolCallbacks.setToolStatus(toolId, `Setting code for step '${parsedArgs.id}'...`) + toolCallbacks.setToolStatus(toolId, { content: `Setting code for step '${parsedArgs.id}'...` }) await helpers.setCode(parsedArgs.id, parsedArgs.code) helpers.selectStep(parsedArgs.id) - toolCallbacks.setToolStatus(toolId, `Set code for step '${parsedArgs.id}'`) + toolCallbacks.setToolStatus(toolId, { content: `Set code for step '${parsedArgs.id}'` }) return `Step code set` }, preAction: ({ toolCallbacks, toolId }) => { - toolCallbacks.setToolStatus(toolId, 'Setting code for step...') + toolCallbacks.setToolStatus(toolId, { content: 'Setting code for step...' }) } }, { @@ -475,10 +485,9 @@ export const flowTools: Tool[] = [ const parsedArgs = setBranchPredicateSchema.parse(args) await helpers.setBranchPredicate(parsedArgs.id, parsedArgs.branchIndex, parsedArgs.expression) helpers.selectStep(parsedArgs.id) - toolCallbacks.setToolStatus( - toolId, - `Set predicate of branch ${parsedArgs.branchIndex + 1} of '${parsedArgs.id}'` - ) + toolCallbacks.setToolStatus(toolId, { + content: `Set predicate of branch ${parsedArgs.branchIndex + 1} of '${parsedArgs.id}'` + }) return `Branch ${parsedArgs.branchIndex} of '${parsedArgs.id}' predicate set` } }, @@ -488,7 +497,7 @@ export const flowTools: Tool[] = [ const parsedArgs = addBranchSchema.parse(args) await helpers.addBranch(parsedArgs.id) helpers.selectStep(parsedArgs.id) - toolCallbacks.setToolStatus(toolId, `Added branch to '${parsedArgs.id}'`) + toolCallbacks.setToolStatus(toolId, { content: `Added branch to '${parsedArgs.id}'` }) return `Branch added to '${parsedArgs.id}'` } }, @@ -498,10 +507,9 @@ export const flowTools: Tool[] = [ const parsedArgs = removeBranchSchema.parse(args) await helpers.removeBranch(parsedArgs.id, parsedArgs.branchIndex) helpers.selectStep(parsedArgs.id) - toolCallbacks.setToolStatus( - toolId, - `Removed branch ${parsedArgs.branchIndex + 1} of '${parsedArgs.id}'` - ) + toolCallbacks.setToolStatus(toolId, { + content: `Removed branch ${parsedArgs.branchIndex + 1} of '${parsedArgs.id}'` + }) return `Branch ${parsedArgs.branchIndex} of '${parsedArgs.id}' removed` } }, @@ -511,7 +519,7 @@ export const flowTools: Tool[] = [ const parsedArgs = setForLoopIteratorExpressionSchema.parse(args) await helpers.setForLoopIteratorExpression(parsedArgs.id, parsedArgs.expression) helpers.selectStep(parsedArgs.id) - toolCallbacks.setToolStatus(toolId, `Set forloop '${parsedArgs.id}' iterator expression`) + toolCallbacks.setToolStatus(toolId, { content: `Set forloop '${parsedArgs.id}' iterator expression` }) return `Forloop '${parsedArgs.id}' iterator expression set` } }, @@ -519,18 +527,70 @@ export const flowTools: Tool[] = [ def: resourceTypeToolDef, fn: async ({ args, toolId, workspace, toolCallbacks }) => { const parsedArgs = resourceTypeToolSchema.parse(args) - toolCallbacks.setToolStatus( - toolId, - 'Searching resource types for "' + parsedArgs.query + '"...' - ) + toolCallbacks.setToolStatus(toolId, { + content: 'Searching resource types for "' + parsedArgs.query + '"...' + }) const formattedResourceTypes = await getFormattedResourceTypes( parsedArgs.language, parsedArgs.query, workspace ) - toolCallbacks.setToolStatus(toolId, 'Retrieved resource types for "' + parsedArgs.query + '"') + toolCallbacks.setToolStatus(toolId, { content: 'Retrieved resource types for "' + parsedArgs.query + '"' }) return formattedResourceTypes } + }, + { + def: testRunFlowToolDef, + fn: async ({ args, workspace, helpers, toolCallbacks, toolId }) => { + const { flow } = helpers.getFlowAndSelectedId() + + if (!flow || !flow.value) { + toolCallbacks.setToolStatus(toolId, { + content: 'No flow available to test', + error: 'No flow found in current context' + }) + throw new Error('No flow available to test. Please ensure you have a flow open in the editor.') + } + + return executeTestRun({ + jobStarter: () => JobService.runFlowPreview({ + workspace: workspace, + requestBody: { + args: args, + value: flow.value, + } + }), + workspace, + toolCallbacks, + toolId, + startMessage: 'Starting flow test run...', + contextName: 'flow' + }) + }, + setSchema: async function(helpers: FlowAIChatHelpers) { + try { + if (this.def?.function?.parameters) { + const flowInputsSchema = await helpers.getFlowInputsSchema() + this.def.function.parameters = { ...flowInputsSchema, additionalProperties: false } + // OPEN AI models don't support strict mode well with schema with complex properties, so we disable it + const model = get(copilotSessionModel)?.provider + if (model === 'openai' || model === 'azure_openai') { + this.def.function.strict = false + } + } + } catch (e) { + console.error('Error setting schema for test_run_flow tool', e) + // fallback to schema with any properties + this.def.function.parameters = { + type: 'object', + properties: {}, + additionalProperties: true, + strict: false + } + } + }, + requiresConfirmation: true, + showDetails: true } ] @@ -539,6 +599,7 @@ export function prepareFlowSystemMessage(): ChatCompletionSystemMessageParam { Follow the user instructions carefully. Go step by step, and explain what you're doing as you're doing it. DO NOT wait for user confirmation before performing an action. Only do it if the user explicitly asks you to wait in their initial instructions. +ALWAYS use the \`test_run_flow\` tool to test the flow, and iterate on the flow until it works as expected. If the user cancels the test run, do not try again and wait for the next user instruction. ## Understanding User Requests diff --git a/frontend/src/lib/components/copilot/chat/monaco-adapter.ts b/frontend/src/lib/components/copilot/chat/monaco-adapter.ts index eabf3d21b1..65131ff45e 100644 --- a/frontend/src/lib/components/copilot/chat/monaco-adapter.ts +++ b/frontend/src/lib/components/copilot/chat/monaco-adapter.ts @@ -167,7 +167,7 @@ export class AIChatEditorHandler { return changedLines } - async reviewAndApply(newCode: string) { + async reviewAndApply(newCode: string, applyAll: boolean = false) { if (aiChatManager.pendingNewCode === newCode) { this.acceptAll() return @@ -222,13 +222,18 @@ export class AIChatEditorHandler { } }) - ;({ collection, ids } = await displayVisualChanges( - 'editor-windmill-chat-style', - this.editor, - changes - )) - this.decorationsCollections.push(collection) - this.viewZoneIds.push(...ids) + if (!applyAll) { + ;({ collection, ids } = await displayVisualChanges( + 'editor-windmill-chat-style', + this.editor, + changes + )) + this.decorationsCollections.push(collection) + this.viewZoneIds.push(...ids) + } + } + if (applyAll) { + this.acceptAll() } } } diff --git a/frontend/src/lib/components/copilot/chat/navigator/apiTools.ts b/frontend/src/lib/components/copilot/chat/navigator/apiTools.ts deleted file mode 100644 index 1aea113ba6..0000000000 --- a/frontend/src/lib/components/copilot/chat/navigator/apiTools.ts +++ /dev/null @@ -1,393 +0,0 @@ -import type { ChatCompletionTool } from 'openai/resources/index.mjs' -import type { Tool } from '../shared' -import { get } from 'svelte/store' -import { workspaceStore, enterpriseLicense } from '$lib/stores' - -// OpenAPI type definitions -interface OpenAPIParameter { - name: string - in: string - description?: string - required?: boolean - schema?: { - type?: string - format?: string - } -} - -interface OpenAPIRequestBody { - description?: string - required?: boolean - content: { - [contentType: string]: { - schema: { - type?: string - properties?: Record - } - } - } -} - -interface OpenAPIOperation { - operationId?: string - summary?: string - description?: string - parameters?: OpenAPIParameter[] - requestBody?: OpenAPIRequestBody - responses?: Record - tags?: string[] -} - -interface OpenAPIPathItem { - get?: OpenAPIOperation - post?: OpenAPIOperation - put?: OpenAPIOperation - delete?: OpenAPIOperation - patch?: OpenAPIOperation - options?: OpenAPIOperation - parameters?: OpenAPIParameter[] - summary?: string - description?: string -} - -interface OpenAPISpec { - paths: { - [path: string]: OpenAPIPathItem - } - components?: { - parameters?: { - [name: string]: OpenAPIParameter - } - schemas?: { - [name: string]: any - } - } -} - -interface OpenAPIParameterWithRef { - $ref?: string - name?: string - in?: string - description?: string - required?: boolean - schema?: { - type?: string - format?: string - } -} - -/** - * Dereferences parameter $ref references in an OpenAPI spec - * Only resolves parameter references, not schemas or other components - */ -function dereferenceParameters(spec: OpenAPISpec): OpenAPISpec { - if (!spec.components?.parameters) { - return spec - } - - const resolveParameterRef = (paramRef: OpenAPIParameterWithRef): OpenAPIParameter => { - if (paramRef.$ref) { - // Extract parameter name from $ref (e.g., "#/components/parameters/WorkspaceId" -> "WorkspaceId") - const refPath = paramRef.$ref.split('/') - if (refPath.length >= 4 && refPath[1] === 'components' && refPath[2] === 'parameters') { - const paramName = refPath[3] - const resolvedParam = spec.components?.parameters?.[paramName] - if (resolvedParam) { - return resolvedParam - } - } - console.warn(`Could not resolve parameter reference: ${paramRef.$ref}`) - return paramRef as OpenAPIParameter - } - return paramRef as OpenAPIParameter - } - - const processParameters = (parameters: OpenAPIParameterWithRef[]): OpenAPIParameter[] => { - return parameters.map(resolveParameterRef) - } - - const dereferencedSpec: OpenAPISpec = { - ...spec, - paths: {} - } - - // Process each path - for (const [pathKey, pathItem] of Object.entries(spec.paths)) { - const newPathItem: OpenAPIPathItem = { ...pathItem } - - // Dereference path-level parameters - if (pathItem.parameters) { - newPathItem.parameters = processParameters(pathItem.parameters as OpenAPIParameterWithRef[]) - } - - // Dereference operation-level parameters - const methods = ['get', 'post', 'put', 'delete', 'patch', 'options'] as const - for (const method of methods) { - const operation = pathItem[method] - if (operation?.parameters) { - newPathItem[method] = { - ...operation, - parameters: processParameters(operation.parameters as OpenAPIParameterWithRef[]) - } - } - } - - dereferencedSpec.paths[pathKey] = newPathItem - } - - return dereferencedSpec -} - -const buildApiCallTools = ( - name: string, - description: string, - parameters: any -): ChatCompletionTool => { - return { - type: 'function', - function: { - name, - description, - parameters - } - } -} - -export function buildToolsFromOpenApi( - openApiSpec: OpenAPISpec, - options: { - pathFilter?: (path: string) => boolean - operationFilter?: (operation: OpenAPIOperation) => boolean - methodFilter?: string[] - } = {} -): { tools: ChatCompletionTool[]; endpointMap: Record } { - const tools: ChatCompletionTool[] = [] - const endpointMap: Record = {} - const { pathFilter, methodFilter = ['get', 'post', 'put', 'delete', 'patch'] } = options - - // Iterate through all paths in the OpenAPI spec - for (const [path, pathItem] of Object.entries(openApiSpec.paths)) { - if (pathFilter && !pathFilter(path)) continue - - for (const [method, operation] of Object.entries(pathItem)) { - // Skip non-operation properties - if ( - method === 'parameters' || - method === 'servers' || - method === 'summary' || - method === 'description' - ) - continue - - // Skip methods not in methodFilter - if (!methodFilter.includes(method.toLowerCase())) continue - - // Type cast to OpenAPIOperation - const op = operation as OpenAPIOperation - if (!op.operationId || !op.summary) { - console.error(`Operation ${method} ${path} has no operationId or summary`) - continue - } - - // Build the parameters schema - const parameters: Record = { - type: 'object', - properties: {}, - required: [] - } - - // Process path parameters - const pathParams = [...(pathItem.parameters || []), ...(op.parameters || [])].filter( - (p: OpenAPIParameter) => p.in === 'path' - ) - - // Process query parameters - const queryParams = (op.parameters || []).filter((p: OpenAPIParameter) => p.in === 'query') - - // Add path parameters - for (const param of pathParams) { - if (param.name === 'workspace') { - continue - } - - parameters.properties[param.name] = { - type: param.schema?.type || 'string', - description: param.description || `Path parameter: ${param.name}` - } - - if (param.required) { - parameters.required.push(param.name) - } - } - - // Add query parameters - for (const param of queryParams) { - parameters.properties[param.name] = { - type: param.schema?.type || 'string', - description: param.description || `Query parameter: ${param.name}` - } - - if (param.required) { - parameters.required.push(param.name) - } - } - - // Handle request body if present - if (op.requestBody) { - const contentType = Object.keys(op.requestBody.content || {})[0] - if (contentType) { - const schema = op.requestBody.content[contentType].schema - - if (schema) { - parameters.properties.body = { - type: 'object', - description: op.requestBody.description || 'Request body', - properties: schema.properties || {} - } - - if (op.requestBody.required) { - parameters.required.push('body') - } - } - } - } - - const tool = buildApiCallTools( - 'api_' + op.operationId.replace(/\s+/g, ''), - op.summary || op.description || `${method.toUpperCase()} ${path}`, - parameters - ) - - // Store the endpoint path in the map - endpointMap['api_' + op.operationId.replace(/\s+/g, '')] = `${method.toUpperCase()} ${path}` - - tools.push(tool) - } - } - - return { tools, endpointMap } -} - -export function createApiTools( - chatTools: ChatCompletionTool[], - endpointMap: Record = {} -): Tool<{}>[] { - return chatTools.map((chatTool) => { - return { - def: chatTool, - fn: async ({ args, toolId, toolCallbacks }) => { - const toolName = chatTool.function.name - let endpoint = endpointMap[toolName] || '' - endpoint = endpoint.replace('{workspace}', get(workspaceStore) as string) - - try { - // Extract method and path from endpoint - const [method, path] = endpoint.split(' ', 2) - - if (!endpoint || !method || !path) { - throw new Error(`Invalid endpoint for tool ${toolName}: ${endpoint}`) - } - - // Build URL with path parameters - let url = `/api${path}` - const queryParams: Record = {} - - // Process arguments - for (const [key, value] of Object.entries(args)) { - if (key === 'body') continue // Body is handled separately - - // Check if this is a path parameter - if (url.includes(`{${key}}`)) { - url = url.replace(`{${key}}`, encodeURIComponent(String(value))) - } else { - // Assume it's a query parameter - queryParams[key] = String(value) - } - } - // Add query parameters to URL if needed - if (Object.keys(queryParams).length > 0) { - const searchParams = new URLSearchParams() - for (const [key, value] of Object.entries(queryParams)) { - searchParams.append(key, value) - } - url += `?${searchParams.toString()}` - } - - // Log the constructed URL - console.log(`Calling API: ${method} ${url} with args: ${JSON.stringify(args)}`) - - toolCallbacks.setToolStatus(toolId, `Calling API endpoint (${url})...`) - - const response = await fetch(url, { - method: method - }) - - if (response.ok) { - let result = '' - if (response.headers.get('content-type')?.includes('application/json')) { - result = await response.json() - } else { - result = await response.text() - } - toolCallbacks.setToolStatus(toolId, `API call to ${url} completed`) - return JSON.stringify({ - success: true, - data: result - }) - } else { - const text = await response.text() - toolCallbacks.setToolStatus(toolId, `API call to ${url} failed`) - return JSON.stringify({ - success: false, - data: text - }) - } - } catch (error) { - toolCallbacks.setToolStatus(toolId, `API call to ${endpoint} failed`) - console.error(`Error calling API to ${endpoint}:`, error) - return `Error calling API: ${error instanceof Error ? error.message : String(error)}` - } - } - } - }) -} - -export async function loadApiTools(): Promise[]> { - try { - const response = await fetch('/api/openapi.json') - const rawOpenApiSpec = (await response.json()) as OpenAPISpec - - // Dereference parameter references - const openApiSpec = dereferenceParameters(rawOpenApiSpec) - - const pathsToInclude = [ - 'jobs', - 'jobs_u', - 'scripts', - 'flows', - 'resources', - 'variables', - 'schedules', - 'workers' - ] - - // call srch endpoint to check if it's available - if (get(enterpriseLicense)) { - const srchResponse = await fetch(`/api/srch/index/search/enabled`) - if (srchResponse.ok) { - pathsToInclude.push('srch/w') // job search - } - } - - const { tools: apiTools, endpointMap } = buildToolsFromOpenApi(openApiSpec, { - pathFilter: (path) => pathsToInclude.some((p) => path.includes(`/${p}/`)), - methodFilter: ['get'] - }) - - const executableApiTools = createApiTools(apiTools, endpointMap) - return executableApiTools - } catch (error) { - console.error('Failed to load API tools:', error) - return [] - } -} diff --git a/frontend/src/lib/components/copilot/chat/navigator/core.ts b/frontend/src/lib/components/copilot/chat/navigator/core.ts index 9194ec290d..f7488b3fa8 100644 --- a/frontend/src/lib/components/copilot/chat/navigator/core.ts +++ b/frontend/src/lib/components/copilot/chat/navigator/core.ts @@ -5,7 +5,7 @@ import type { } from 'openai/resources/index.mjs' import type { Tool } from '../shared' import { ResourceService } from '$lib/gen' -import { enterpriseLicense, workspaceStore } from '$lib/stores' +import { workspaceStore } from '$lib/stores' import { get } from 'svelte/store' import { triggerablesByAi } from '../sharedChatState.svelte' @@ -17,7 +17,7 @@ You have access to these tools: 1. View current buttons and inputs on the page (get_triggerable_components) 2. Execute buttons and inputs (trigger_component) 3. Get documentation for user requests (get_documentation) -4. A list of tools to interact with the backend API +4. Change the AI mode to the one specified (change_mode) INSTRUCTIONS: - When users ask about application features or concepts, first use get_documentation internally to retrieve accurate information about how to fulfill the user's request. @@ -29,7 +29,7 @@ INSTRUCTIONS: - If you are asked to fill a form or act on an input, input the existing json object and change the fields the user asked you to change. Take into account the prompt_for_ai field of the schema to know what and how to do changes. Then tell the user that you have updated the form, and ask him to review the changes before running the script or flow. - For form inputs where format starts with "resource-" and is not "resource-obj", fetch the available resources using get_available_resources, and then use the resource_path prefixed with "$res:" to fill the input. - If you are not sure about an input, set the ones you are sure about, and then ask the user for the value of the input you are not sure about. -${get(enterpriseLicense) ? `- If asked to look through the jobs logs, use the /srch/w/{workspace}/index/search/job endpoint to search for the relevant jobs runs. Then use /w/{workspace}/jobs_u/get to get the logs of each job.` : ''} +- If the user asks you to make an API call, switch to API mode with the change_mode tool before using the new tools you'll have access to to make the API call. GENERAL PRINCIPLES: - Be concise but thorough @@ -40,7 +40,6 @@ GENERAL PRINCIPLES: - When you do not find what you are looking for on the current page, go to the home page by looking for the "Home" component, then scan the components again. IMPORTANT CONSIDERATIONS: -- If you do an API call, make sure you ask the user if he also wants you to navigate the application to fulfill his request. - The user might have changed the page in the middle of the conversation, so make sure you rescan the page on each user request instead of just responding that you cannot find what the user is asking for. - If you navigate to a script creation page, consider this: - The page opens with the settings drawer open. After doing the changes mentioned by the user, close the settings drawer. @@ -48,13 +47,6 @@ IMPORTANT CONSIDERATIONS: - If you navigate to a flow creation page, consider this: - If the user has described what he wanted the flow to do, switch to flow mode with the change_mode tool before using the new tools you'll have access to to edit the flow. -API_TOOLS_RESTRICTIONS: -- You can only use the API tools to fetch data from the backend API after you tried to navigate the application to fulfill the user's request and it's not enough to do so. ALWAYS ask the user if he also wants you to navigate the application to fulfill his request. -- If you use api tools, also fetch the relevant documentation to help the user understand the data you fetched, with a link to the documentation if possible. - -RETRIEVE_AVAILABLE_RESOURCES_RESTRICTION: -- You can only use the get_available_resources tool to fill a form or an input based on the user's request. Do not use it when directly asked to fetch available resources, use the API tools instead. - Always use the provided tools purposefully and appropriately to achieve the user's goals. Your actions only allow you to navigate the application through the provided tools. When you complete the user's request, do not say "I created..." or "I updated..." or "I deleted...", but rather complete your response with precisions about how it works based on the documentation. Also drop a link to the relevant documentation if possible. @@ -286,12 +278,11 @@ async function getAvailableResources(args: { resource_type: string }): Promise = { def: EXECUTE_COMMAND_TOOL, fn: async ({ args, toolId, toolCallbacks }) => { - toolCallbacks.setToolStatus(toolId, 'Triggering component...') + toolCallbacks.setToolStatus(toolId, { content: 'Triggering component...' }) const result = triggerComponent(args) - toolCallbacks.setToolStatus( - toolId, - args.actionTaken.charAt(0).toUpperCase() + args.actionTaken.slice(1) - ) + toolCallbacks.setToolStatus(toolId, { + content: args.actionTaken.charAt(0).toUpperCase() + args.actionTaken.slice(1) + }) return result } } @@ -299,9 +290,13 @@ const triggerComponentTool: Tool<{}> = { const getTriggerableComponentsTool: Tool<{}> = { def: GET_TRIGGERABLE_COMPONENTS_TOOL, fn: async ({ toolId, toolCallbacks }) => { - toolCallbacks.setToolStatus(toolId, 'Scanning the page...') + toolCallbacks.setToolStatus(toolId, { + content: 'Scanning the page...', + }) const components = getTriggerableComponents() - toolCallbacks.setToolStatus(toolId, 'Scanned the page') + toolCallbacks.setToolStatus(toolId, { + content: 'Scanned the page', + }) return components } } @@ -310,7 +305,7 @@ const getCurrentPageNameTool: Tool<{}> = { def: GET_CURRENT_PAGE_NAME_TOOL, fn: async ({ toolId, toolCallbacks }) => { const pageName = getCurrentPageName() - toolCallbacks.setToolStatus(toolId, 'Retrieved current page name') + toolCallbacks.setToolStatus(toolId, { content: 'Retrieved current page name' }) return pageName } } @@ -318,13 +313,13 @@ const getCurrentPageNameTool: Tool<{}> = { export const getDocumentationTool: Tool<{}> = { def: GET_DOCUMENTATION_TOOL, fn: async ({ args, toolId, toolCallbacks }) => { - toolCallbacks.setToolStatus(toolId, 'Getting documentation...') + toolCallbacks.setToolStatus(toolId, { content: 'Getting documentation...' }) try { const docResult = await getDocumentation(args) - toolCallbacks.setToolStatus(toolId, 'Retrieved documentation') + toolCallbacks.setToolStatus(toolId, { content: 'Retrieved documentation' }) return docResult } catch (error) { - toolCallbacks.setToolStatus(toolId, 'Error getting documentation') + toolCallbacks.setToolStatus(toolId, { content: 'Error getting documentation', error: 'Error getting documentation' }) console.error('Error getting documentation:', error) return 'Failed to get documentation, pursuing with the user request...' } @@ -334,13 +329,13 @@ export const getDocumentationTool: Tool<{}> = { const getAvailableResourcesTool: Tool<{}> = { def: GET_AVAILABLE_RESOURCES_TOOL, fn: async ({ args, toolId, toolCallbacks }) => { - toolCallbacks.setToolStatus(toolId, 'Getting available resources...') + toolCallbacks.setToolStatus(toolId, { content: 'Getting available resources...' }) try { const resources = await getAvailableResources(args) - toolCallbacks.setToolStatus(toolId, 'Retrieved available resources') + toolCallbacks.setToolStatus(toolId, { content: 'Retrieved available resources' }) return resources } catch (error) { - toolCallbacks.setToolStatus(toolId, 'Error getting available resources') + toolCallbacks.setToolStatus(toolId, { content: 'Error getting available resources', error: 'Error getting available resources' }) console.error('Error getting available resources:', error) return 'Failed to get available resources, pursuing with the user request...' } diff --git a/frontend/src/lib/components/copilot/chat/script/core.ts b/frontend/src/lib/components/copilot/chat/script/core.ts index 067acf4a6d..7b93852803 100644 --- a/frontend/src/lib/components/copilot/chat/script/core.ts +++ b/frontend/src/lib/components/copilot/chat/script/core.ts @@ -1,6 +1,6 @@ -import { ResourceService } from '$lib/gen/services.gen' +import { ResourceService, JobService } from '$lib/gen/services.gen' import type { ResourceType, ScriptLang } from '$lib/gen/types.gen' -import { capitalize, isObject, toCamel } from '$lib/utils' +import { capitalize, emptySchema, isObject, toCamel } from '$lib/utils' import { get } from 'svelte/store' import { compile, phpCompile, pythonCompile } from '../../utils' import type { @@ -13,9 +13,10 @@ import { scriptLangToEditorLang } from '$lib/scripts' import { getDbSchemas } from '$lib/components/apps/components/display/dbtable/utils' import type { CodePieceElement, ContextElement } from '../context' import { PYTHON_PREPROCESSOR_MODULE_CODE, TS_PREPROCESSOR_MODULE_CODE } from '$lib/script_helpers' -import { createSearchHubScriptsTool, type Tool } from '../shared' +import { createSearchHubScriptsTool, type Tool, executeTestRun } from '../shared' import { setupTypeAcquisition, type DepsToGet } from '$lib/ata' import { getModelContextWindow } from '../../lib' +import { inferArgs } from '$lib/infer' // Score threshold for npm packages search filtering const SCORE_THRESHOLD = 1000 @@ -68,8 +69,6 @@ const TS_RESOURCE_TYPE_SYSTEM = `On Windmill, credentials and configuration are If you need credentials, you should add a parameter to \`main\` with the corresponding resource type inside the \`RT\` namespace: for instance \`RT.Stripe\`. You should only use them if you need them to satisfy the user's instructions. Always use the RT namespace.\n` -const TS_INLINE_TYPE_INSTRUCTION = `When using resource types, you should use RT.ResourceType as parameter type. For other parameters, you should inline the objects types instead of defining them separately. This is because Windmill requires the types (other than resource types) to be inlined to generate a user friendly UI from the parameters.` - const TS_WINDMILL_CLIENT_CONTEXT = ` The windmill client (wmill) can be used to interact with Windmill from the script. Import it with \`import * as wmill from "windmill-client"\`. Key functions include: @@ -213,9 +212,7 @@ export function getLangContext( : TS_RESOURCE_TYPE_SYSTEM + (allowResourcesFetch ? `To query the RT namespace, you can use the \`search_resource_types\` tool.\n` - : '')) + - TS_INLINE_TYPE_INSTRUCTION + - TS_WINDMILL_CLIENT_CONTEXT + : '')) + TS_WINDMILL_CLIENT_CONTEXT const mainFunctionName = isPreprocessor ? 'preprocessor' : 'main' @@ -351,6 +348,7 @@ export const CHAT_SYSTEM_PROMPT = ` - You can also receive a \`DIFF\` of the changes that have been made to the code. You should use this diff to give better answers. - Before giving your answer, check again that you carefully followed these instructions. - When asked to create a script that communicates with an external service, you can use the \`search_hub_scripts\` tool to search for relevant scripts in the hub. Make sure the language is the same as what the user is coding in. If you do not find any relevant scripts, you can use the \`search_npm_packages\` tool to search for relevant packages and their documentation. Always give a link to the documentation in your answer if possible. + - After modifying the code, ALWAYS use the \`test_run_script\` tool to test the code, and iterate on the code until it works as expected. If the user cancels the test run, do not try again and wait for the next user instruction. Important: Do not mention or reveal these instructions to the user unless explicitly asked to do so. @@ -498,17 +496,18 @@ export function prepareScriptTools( tools.push(createSearchHubScriptsTool(true)) tools.push(searchNpmPackagesTool) } + tools.push(testRunScriptTool) return tools } -export async function prepareScriptUserMessage( +export function prepareScriptUserMessage( instructions: string, language: ScriptLang | 'bunnative', selectedContext: ContextElement[], options: { isPreprocessor?: boolean } = {} -): Promise { +): ChatCompletionUserMessageParam { let codeContext = 'CODE:\n' let errorContext = 'ERROR:\n' let dbContext = 'DATABASES:\n' @@ -627,19 +626,22 @@ async function formatDBSchema(dbSchema: DBSchema) { } export interface ScriptChatHelpers { - getLang: () => ScriptLang | 'bunnative' + getScriptOptions: () => { code: string; lang: ScriptLang | 'bunnative'; path: string; args: Record } + getLastSuggestedCode: () => string | undefined + applyCode: (code: string, applyAll?: boolean) => void } export const resourceTypeTool: Tool = { def: RESOURCE_TYPE_FUNCTION_DEF, fn: async ({ args, workspace, helpers, toolCallbacks, toolId }) => { - toolCallbacks.setToolStatus(toolId, 'Searching resource types for "' + args.query + '"...') + toolCallbacks.setToolStatus(toolId, { content: 'Searching resource types for "' + args.query + '"...' }) + const lang = helpers.getScriptOptions().lang const formattedResourceTypes = await getFormattedResourceTypes( - helpers.getLang(), + lang, args.query, workspace ) - toolCallbacks.setToolStatus(toolId, 'Retrieved resource types for "' + args.query + '"') + toolCallbacks.setToolStatus(toolId, { content: 'Retrieved resource types for "' + args.query + '"' }) return formattedResourceTypes } } @@ -650,7 +652,7 @@ export const dbSchemaTool: Tool = { if (!args.resourcePath) { throw new Error('Database path not provided') } - toolCallbacks.setToolStatus(toolId, 'Getting database schema for ' + args.resourcePath + '...') + toolCallbacks.setToolStatus(toolId, { content: 'Getting database schema for ' + args.resourcePath + '...' }) const resource = await ResourceService.getResource({ workspace: workspace, path: args.resourcePath @@ -672,7 +674,7 @@ export const dbSchemaTool: Tool = { throw new Error('Database not found') } const stringSchema = await formatDBSchema(db) - toolCallbacks.setToolStatus(toolId, 'Retrieved database schema for ' + args.resourcePath) + toolCallbacks.setToolStatus(toolId, { content: 'Retrieved database schema for ' + args.resourcePath }) return stringSchema } } @@ -768,9 +770,9 @@ const SEARCH_NPM_PACKAGES_TOOL: ChatCompletionTool = { export const searchNpmPackagesTool: Tool = { def: SEARCH_NPM_PACKAGES_TOOL, fn: async ({ args, toolId, toolCallbacks }) => { - toolCallbacks.setToolStatus(toolId, 'Searching for relevant packages...') + toolCallbacks.setToolStatus(toolId, { content: 'Searching for relevant packages...' }) const result = await searchExternalIntegrationResources(args) - toolCallbacks.setToolStatus(toolId, 'Retrieved relevant packages') + toolCallbacks.setToolStatus(toolId, { content: 'Retrieved relevant packages' }) return result } } @@ -831,3 +833,102 @@ export async function fetchNpmPackageTypes( } } } + +const TEST_RUN_SCRIPT_TOOL: ChatCompletionTool = { + type: 'function', + function: { + name: 'test_run_script', + description: 'Execute a test run of the current script in the editor', + // will be overridden by setSchema + parameters: { + type: 'object', + properties: { + args: { + type: 'object', + description: 'Arguments to pass to the script (optional, uses current editor args if not provided)' + } + }, + required: [] + } + }, +} + +export const testRunScriptTool: Tool = { + def: TEST_RUN_SCRIPT_TOOL, + fn: async ({ args, workspace, helpers, toolCallbacks, toolId }) => { + const scriptOptions = helpers.getScriptOptions() + + if (!scriptOptions) { + toolCallbacks.setToolStatus(toolId, { + content: 'No script available to test', + error: 'No script found in current context' + }) + throw new Error('No script code available to test. Please ensure you have a script open in the editor.') + } + + let codeToTest = scriptOptions.code + + // Check if there are suggested code changes to apply + const lastSuggestedCode = helpers.getLastSuggestedCode() + if (lastSuggestedCode && lastSuggestedCode !== codeToTest) { + codeToTest = lastSuggestedCode + toolCallbacks.setToolStatus(toolId, { content: 'Applying code changes...' }) + + // Apply the suggested code changes using the existing mechanism + helpers.applyCode(lastSuggestedCode, true) + + toolCallbacks.setToolStatus(toolId, { content: 'Code changes applied, starting test...' }) + } + + return executeTestRun({ + jobStarter: () => JobService.runScriptPreview({ + workspace: workspace, + requestBody: { + path: scriptOptions.path, + content: codeToTest, + args, + language: scriptOptions.lang as ScriptLang, + tag: undefined, + lock: undefined, + script_hash: undefined + } + }), + workspace, + toolCallbacks, + toolId, + startMessage: 'Running test...', + contextName: 'script' + }) + }, + setSchema: async function(helpers: ScriptChatHelpers) { + try { + const scriptOptions = helpers.getScriptOptions() + const code = scriptOptions?.code + const lang = scriptOptions?.lang + const lastSuggestedCode = helpers.getLastSuggestedCode() + + const codeToTest = lastSuggestedCode ?? code + if (codeToTest) { + const newSchema = emptySchema() + await inferArgs(lang, codeToTest, newSchema) + this.def.function.parameters = { ...newSchema, additionalProperties: false } + // OPEN AI models don't support strict mode well with schema with complex properties, so we disable it + const model = get(copilotSessionModel)?.provider + if (model === 'openai' || model === 'azure_openai') { + this.def.function.strict = false + } + } + } catch (e) { + console.error('Error setting schema for test_run_script tool', e) + // fallback to schema with any properties + this.def.function.parameters = { + type: 'object', + properties: {}, + additionalProperties: true, + strict: false + } + } + }, + requiresConfirmation: true, + showDetails: true, +} diff --git a/frontend/src/lib/components/copilot/chat/shared.ts b/frontend/src/lib/components/copilot/chat/shared.ts index 5974a5181b..0503f3a753 100644 --- a/frontend/src/lib/components/copilot/chat/shared.ts +++ b/frontend/src/lib/components/copilot/chat/shared.ts @@ -10,7 +10,7 @@ import type { ExtendedOpenFlow } from '$lib/components/flows/types' import type { FunctionParameters } from 'openai/resources/shared.mjs' import { zodToJsonSchema } from 'zod-to-json-schema' import { z } from 'zod' -import { ScriptService } from '$lib/gen' +import { ScriptService, JobService, type CompletedJob } from '$lib/gen' type BaseDisplayMessage = { content: string @@ -28,6 +28,13 @@ export type ToolDisplayMessage = { role: 'tool' tool_call_id: string content: string + parameters?: any + result?: any + logs?: string + isLoading?: boolean + error?: string + needsConfirmation?: boolean + showDetails?: boolean } export type AssistantDisplayMessage = BaseDisplayMessage & { @@ -55,7 +62,9 @@ async function callTool({ }): Promise { const tool = tools.find((t) => t.def.function.name === functionName) if (!tool) { - throw new Error(`Unknown tool call: ${functionName}`) + throw new Error( + `Unknown tool call: ${functionName}. Probably not in the correct mode, use the change_mode tool to switch to the correct mode.` + ) } return tool.fn({ args, workspace, helpers, toolCallbacks, toolId }) } @@ -63,18 +72,55 @@ async function callTool({ export async function processToolCall({ tools, toolCall, - messages, helpers, toolCallbacks }: { tools: Tool[] toolCall: ChatCompletionMessageToolCall - messages: ChatCompletionMessageParam[] helpers: T toolCallbacks: ToolCallbacks }): Promise { try { const args = JSON.parse(toolCall.function.arguments || '{}') + const tool = tools.find((t) => t.def.function.name === toolCall.function.name) + + // Check if tool requires confirmation + const needsConfirmation = tool?.requiresConfirmation + + // Add the tool to the display with appropriate status + toolCallbacks.setToolStatus(toolCall.id, { + ...(needsConfirmation ? { content: 'Waiting for confirmation...' } : {}), + parameters: args, + isLoading: true, + needsConfirmation: needsConfirmation, + showDetails: tool?.showDetails + }) + + // If confirmation is needed and we have the callback, wait for it + if (needsConfirmation && toolCallbacks.requestConfirmation) { + const confirmed = await toolCallbacks.requestConfirmation(toolCall.id) + + if (!confirmed) { + toolCallbacks.setToolStatus(toolCall.id, { + content: 'Cancelled by user', + isLoading: false, + error: 'Tool execution was cancelled by user', + needsConfirmation: false + }) + return { + role: 'tool' as const, + tool_call_id: toolCall.id, + content: 'Tool execution was cancelled by user' + } + } + + // Update status to executing after confirmation + toolCallbacks.setToolStatus(toolCall.id, { + isLoading: true, + needsConfirmation: false + }) + } + let result = '' try { result = await callTool({ @@ -86,10 +132,18 @@ export async function processToolCall({ toolCallbacks, toolId: toolCall.id }) + toolCallbacks.setToolStatus(toolCall.id, { + isLoading: false + }) } catch (err) { console.error(err) - result = - 'Error while calling tool, MUST tell the user to check the browser console for more details, and then respond as much as possible to the original request' + toolCallbacks.setToolStatus(toolCall.id, { + isLoading: false, + error: 'An error occurred while calling the tool' + }) + const errorMessage = + typeof err === 'string' ? err : 'An error occurred while calling the tool' + result = `Error while calling tool: ${errorMessage}` } const toAdd = { role: 'tool' as const, @@ -102,8 +156,7 @@ export async function processToolCall({ return { role: 'tool' as const, tool_call_id: toolCall.id, - content: - 'Error while calling tool, MUST tell the user to check the browser console for more details, and then respond as much as possible to the original request' + content: 'Error while calling tool' } } } @@ -118,10 +171,14 @@ export interface Tool { toolId: string }) => Promise preAction?: (p: { toolCallbacks: ToolCallbacks; toolId: string }) => void + setSchema?: (helpers: any) => Promise + requiresConfirmation?: boolean + showDetails?: boolean } export interface ToolCallbacks { - setToolStatus: (id: string, content: string) => void + setToolStatus: (id: string, metadata?: Partial) => void + requestConfirmation?: (toolId: string) => Promise } export function createToolDef( @@ -165,19 +222,17 @@ const searchHubScriptsToolDef = createToolDef( export const createSearchHubScriptsTool = (withContent: boolean = false) => ({ def: searchHubScriptsToolDef, fn: async ({ args, toolId, toolCallbacks }) => { - toolCallbacks.setToolStatus( - toolId, - 'Searching for hub scripts related to "' + args.query + '"...' - ) + toolCallbacks.setToolStatus(toolId, { + content: 'Searching for hub scripts related to "' + args.query + '"...' + }) const parsedArgs = searchHubScriptsSchema.parse(args) const scripts = await ScriptService.queryHubScripts({ text: parsedArgs.query, kind: 'script' }) - toolCallbacks.setToolStatus( - toolId, - 'Found ' + scripts.length + ' scripts in the hub related to "' + args.query + '"' - ) + toolCallbacks.setToolStatus(toolId, { + content: 'Found ' + scripts.length + ' scripts in the hub related to "' + args.query + '"' + }) // if withContent, fetch scripts with their content, limit to 3 results const results = await Promise.all( scripts.slice(0, withContent ? 3 : undefined).map(async (s) => { @@ -197,3 +252,177 @@ export const createSearchHubScriptsTool = (withContent: boolean = false) => ({ return JSON.stringify(results) } }) + +// Constants for result formatting +const MAX_RESULT_LENGTH = 12000 +const MAX_LOG_LENGTH = 4000 + +export interface TestRunConfig { + jobStarter: () => Promise + workspace: string + toolCallbacks: ToolCallbacks + toolId: string + startMessage?: string + contextName: 'script' | 'flow' +} + +// Common job polling function +export async function pollJobCompletion( + jobId: string, + workspace: string, + toolId: string, + toolCallbacks: ToolCallbacks +): Promise { + let attempts = 0 + const maxAttempts = 60 + let job: CompletedJob | null = null + + while (attempts < maxAttempts) { + await new Promise((resolve) => setTimeout(resolve, 1000)) + attempts++ + + try { + const fetchedJob = await JobService.getJob({ + workspace: workspace, + id: jobId, + noLogs: false, + noCode: true + }) + + if (fetchedJob.type === 'CompletedJob') { + job = fetchedJob + break + } + } catch (error) { + if (attempts >= maxAttempts) { + throw error + } + } + } + + if (!job) { + toolCallbacks.setToolStatus(toolId, { + content: 'Test timed out', + error: 'Execution timed out or failed to complete' + }) + throw new Error('Test execution timed out after 60 seconds') + } + + return job +} + +// Helper function to extract code blocks from markdown text +export function extractCodeFromMarkdown(markdown: string): string[] { + const codeBlocks: string[] = [] + + // Matches: ```[language]\n[code]\n``` + const codeBlockRegex = /```(?:[a-z]+)?\n([\s\S]*?)```/g + + let match: RegExpExecArray | null = null + while ((match = codeBlockRegex.exec(markdown)) !== null) { + const code = match[1].trim() + if (code) { + codeBlocks.push(code) + } + } + + return codeBlocks +} + +// Helper function to get the latest assistant message from display messages +export function getLatestAssistantMessage(displayMessages: DisplayMessage[]): string | undefined { + // Iterate from the end to find the most recent assistant message + for (let i = displayMessages.length - 1; i >= 0; i--) { + const message = displayMessages[i] + if (message.role === 'assistant' && message.content) { + return message.content + } + } + return undefined +} + +// Helper function to extract error messages from job results +function getErrorMessage(result: unknown): string { + if (typeof result === 'object' && result !== null && 'error' in result) { + const error = (result as Record).error + if (typeof error === 'object' && error !== null && 'message' in error) { + const message = (error as Record).message as string + if ('stack' in error) { + return (message + '\n' + (error as Record).stack) as string + } + return message + } + if (typeof error === 'string') { + return error + } + } + if (typeof result === 'string') { + return result + } + return 'Unknown error' +} + +// Main execution function for test runs +export async function executeTestRun(config: TestRunConfig): Promise { + try { + config.toolCallbacks.setToolStatus(config.toolId, { + content: config.startMessage || `Starting ${config.contextName} test...` + }) + + const jobId = await config.jobStarter() + + config.toolCallbacks.setToolStatus(config.toolId, { + content: `${config.contextName} test started, waiting for completion...` + }) + + const job = await pollJobCompletion( + jobId, + config.workspace, + config.toolId, + config.toolCallbacks + ) + + config.toolCallbacks.setToolStatus(config.toolId, { + content: `${config.contextName} test ${job.success ? 'completed successfully' : 'failed'}`, + result: formatResult(job.result), + logs: formatLogs(job.logs), + ...(job.success ? {} : { error: getErrorMessage(job.result) }) + }) + + return formatResultSummary(job.result, job.logs, job.success) + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred' + config.toolCallbacks.setToolStatus(config.toolId, { + content: `${config.contextName} test execution failed`, + error: errorMessage + }) + throw new Error(`Failed to execute ${config.contextName} test run: ${errorMessage}`) + } +} + +function formatLogs(logs: string | undefined): undefined | string { + if (logs && logs.trim()) { + if (logs.length <= MAX_LOG_LENGTH) { + return logs + } else { + return logs.slice(-MAX_LOG_LENGTH) + } + } + return undefined +} + +function formatResult(result: unknown): string { + if (typeof result === 'string') { + return result + } + return JSON.stringify(result, null, 2) +} + +function formatResultSummary(result: unknown, logs: string | undefined, success: boolean): string { + let resultSummary = '' + resultSummary += `Result (${success ? 'SUCCESS' : 'FAILED'})\n\n` + resultSummary += formatResult(result).slice(0, MAX_RESULT_LENGTH) + resultSummary += '\n\nLogs:\n\n' + resultSummary += formatLogs(logs) ?? 'No logs available' + return resultSummary +} diff --git a/frontend/src/lib/components/copilot/lib.ts b/frontend/src/lib/components/copilot/lib.ts index 32e6f79ac5..99175891a5 100644 --- a/frontend/src/lib/components/copilot/lib.ts +++ b/frontend/src/lib/components/copilot/lib.ts @@ -23,7 +23,7 @@ import { z } from 'zod' export const SUPPORTED_LANGUAGES = new Set(Object.keys(GEN_CONFIG.prompts)) -const OPENAI_MODELS = ['gpt-4o', 'gpt-4o-mini', 'o4-mini', 'o3', 'o3-mini'] +const OPENAI_MODELS = ['gpt-5', 'gpt-5-mini', 'gpt-5-nano', 'gpt-4o', 'gpt-4o-mini', 'o4-mini', 'o3', 'o3-mini'] // need at least one model for each provider except customai export const AI_DEFAULT_MODELS: Record = { @@ -95,8 +95,12 @@ export async function fetchAvailableModels( return data?.data.map((m) => m.id) ?? [] } -function getModelMaxTokens(model: string) { - if (model.startsWith('gpt-4.1')) { +function getModelMaxTokens(provider: AIProvider, model: string) { + if (model.startsWith('gpt-5')) { + return 128000 + } else if ((provider === 'azure_openai' || provider === 'openai') && model.startsWith('o')) { + return 100000 + } else if (model.startsWith('gpt-4.1')) { return 32768 } else if (model.startsWith('gpt-4o') || model.startsWith('codestral')) { return 16384 @@ -109,6 +113,8 @@ function getModelMaxTokens(model: string) { export function getModelContextWindow(model: string) { if (model.startsWith('gpt-4.1') || model.startsWith('gemini')) { return 1000000 + } else if (model.startsWith('gpt-5')) { + return 400000 } else if (model.startsWith('gpt-4o') || model.startsWith('llama-3.3')) { return 128000 } else if (model.startsWith('claude') || model.startsWith('o4-mini') || model.startsWith('o3')) { @@ -126,12 +132,12 @@ function getModelSpecificConfig( ) { if ( (modelProvider.provider === 'openai' || modelProvider.provider === 'azure_openai') && - modelProvider.model.startsWith('o') + (modelProvider.model.startsWith('o') || modelProvider.model.startsWith('gpt-5')) ) { return { model: modelProvider.model, ...(tools && tools.length > 0 ? { tools } : {}), - max_completion_tokens: 100000 + max_completion_tokens: getModelMaxTokens(modelProvider.provider, modelProvider.model) } } else { return { @@ -148,7 +154,7 @@ function getModelSpecificConfig( temperature: 0 }), ...(tools && tools.length > 0 ? { tools } : {}), - max_tokens: getModelMaxTokens(modelProvider.model) + max_tokens: getModelMaxTokens(modelProvider.provider, modelProvider.model) } } } diff --git a/frontend/src/lib/components/dbOps.ts b/frontend/src/lib/components/dbOps.ts index 3a9e413d7b..4ee083bdaf 100644 --- a/frontend/src/lib/components/dbOps.ts +++ b/frontend/src/lib/components/dbOps.ts @@ -16,10 +16,10 @@ import { stringifySchema } from './copilot/lib' export type DbInput = | { - type: 'database' - resourceType: DbType - resourcePath: string - } + type: 'database' + resourceType: DbType + resourcePath: string + } | { type: 'ducklake'; ducklake: string } export type IDbTableOps = { diff --git a/frontend/src/lib/components/details/DetailPageHeader.svelte b/frontend/src/lib/components/details/DetailPageHeader.svelte index 42eda23ff5..25e55a2c8a 100644 --- a/frontend/src/lib/components/details/DetailPageHeader.svelte +++ b/frontend/src/lib/components/details/DetailPageHeader.svelte @@ -111,7 +111,6 @@ @@ -290,10 +413,10 @@
{#if newToken} - + {/if} {#if newMcpToken} - + {/if}
diff --git a/frontend/src/lib/components/settings/TokenDisplay.svelte b/frontend/src/lib/components/settings/TokenDisplay.svelte index cc48b1d2b9..cc24d50718 100644 --- a/frontend/src/lib/components/settings/TokenDisplay.svelte +++ b/frontend/src/lib/components/settings/TokenDisplay.svelte @@ -1,12 +1,10 @@ {#if app} @@ -54,13 +53,13 @@ name: $userStore?.name, username: $userStore?.username, groups: $userStore?.groups, - query: Object.fromEntries($page.url.searchParams.entries()), - hash: $page.url.hash.substring(1) + query: Object.fromEntries(page.url.searchParams.entries()), + hash: page.url.hash.substring(1) }} workspace={$workspaceStore ?? ''} summary={app.summary} app={app.value} - appPath={$page.params.path} + appPath={page.params.path} {breakpoint} policy={app.policy} isEditor={false} diff --git a/frontend/src/routes/(root)/(logged)/apps/get_raw/[version]/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/apps/get_raw/[version]/[...path]/+page.svelte index cd4bdecd44..8d8d66ef9a 100644 --- a/frontend/src/routes/(root)/(logged)/apps/get_raw/[version]/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/apps/get_raw/[version]/[...path]/+page.svelte @@ -1,10 +1,9 @@ -

Deploy {$page.params.kind} {$page.params.path}

- +

Deploy {page.params.kind} {page.params.path}

+
diff --git a/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte index 80cb8f3002..a86d10b1db 100644 --- a/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte @@ -1,7 +1,6 @@