Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36aadbebec | ||
|
|
0aa885db67 | ||
|
|
9686608355 | ||
|
|
f0b7c96d04 | ||
|
|
b60f309a0c | ||
|
|
bedba3b75e | ||
|
|
771d67c849 | ||
|
|
46d486960a | ||
|
|
ca73267cbb | ||
|
|
6eabb8db63 | ||
|
|
5cc8f20cd2 | ||
|
|
ea5312e940 | ||
|
|
c62ba73ce4 | ||
|
|
a00927b300 | ||
|
|
98ac164ac8 | ||
|
|
ed6aaeeea3 | ||
|
|
5e415c0a12 | ||
|
|
35dded1347 | ||
|
|
aedf012c84 | ||
|
|
62fea97547 | ||
|
|
96c2d88d91 | ||
|
|
5eb308ad35 | ||
|
|
cfa04e1188 | ||
|
|
0d520f730b | ||
|
|
3c89c28e71 | ||
|
|
4fedfdfd11 | ||
|
|
8cdc7d6e9e | ||
|
|
51077245c7 | ||
|
|
9e387c3559 | ||
|
|
7aea965803 | ||
|
|
8446e3b551 | ||
|
|
a6d6136d57 | ||
|
|
c93b2e287c | ||
|
|
a91c532eca | ||
|
|
18b3c1ae5c | ||
|
|
93f927c1c1 | ||
|
|
83f21510f3 | ||
|
|
5c1f69ddcd | ||
|
|
a2cefdf0a2 | ||
|
|
3f2bd424c7 | ||
|
|
0d5f42e89e | ||
|
|
dfad07881d | ||
|
|
7b6ba7093a | ||
|
|
8042e33c38 | ||
|
|
57d23c92c5 | ||
|
|
f21140f7cd | ||
|
|
2800226bd4 | ||
|
|
2ae82796fc | ||
|
|
d1290ba777 | ||
|
|
9de0060884 | ||
|
|
3df8964fc6 | ||
|
|
fb0b2234ba | ||
|
|
4064ec3a3d | ||
|
|
0db6cbd10c | ||
|
|
ab91f78017 | ||
|
|
1e0245ca9a | ||
|
|
bead746bb8 | ||
|
|
268b5ee2a8 | ||
|
|
24f2571b37 | ||
|
|
ed1a655317 | ||
|
|
a4b440a20d | ||
|
|
b550be8711 | ||
|
|
071129f03b | ||
|
|
e9ac1ce9eb | ||
|
|
587142ddac | ||
|
|
d9ed0c318f | ||
|
|
4959a0553a | ||
|
|
c4323e40c1 | ||
|
|
696b8de1ed | ||
|
|
5f6dda9060 | ||
|
|
8490f4435d | ||
|
|
fa2f65e512 | ||
|
|
b9dec43d2a | ||
|
|
5227b76c2f | ||
|
|
1643d654a8 | ||
|
|
65a8789dfd | ||
|
|
6299e7a36a | ||
|
|
bcbfe4659d | ||
|
|
b2fac069df | ||
|
|
4ab08cb6a1 | ||
|
|
d6d4d85d8f | ||
|
|
81d386d365 | ||
|
|
a1b878842f | ||
|
|
00c3e9baf0 | ||
|
|
118dcb59af | ||
|
|
bd583be239 | ||
|
|
c1f7cb5d42 | ||
|
|
d502ef5029 | ||
|
|
bc7ca9982b |
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Resolve _ee.rs symlinks to actual files so Claude can read them
|
||||
# This script runs before each user prompt is processed
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-/home/farhad/windmill}"
|
||||
MANIFEST_FILE="$PROJECT_DIR/.claude/hooks/.symlink-manifest"
|
||||
|
||||
# Find all _ee.rs symlinks and store their targets
|
||||
find "$PROJECT_DIR" -name "*_ee.rs" -type l 2>/dev/null | while read -r symlink; do
|
||||
target=$(readlink -f "$symlink" 2>/dev/null) || continue
|
||||
|
||||
# Only process if target file exists
|
||||
if [[ -f "$target" ]]; then
|
||||
# Store symlink path and target in manifest
|
||||
echo "$symlink|$target" >> "$MANIFEST_FILE.tmp"
|
||||
|
||||
# Replace symlink with actual file content
|
||||
rm "$symlink"
|
||||
cp "$target" "$symlink"
|
||||
fi
|
||||
done
|
||||
|
||||
# Atomically replace manifest
|
||||
if [[ -f "$MANIFEST_FILE.tmp" ]]; then
|
||||
mv "$MANIFEST_FILE.tmp" "$MANIFEST_FILE"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Restore _ee.rs symlinks after Claude finishes processing
|
||||
# This script runs when Claude stops
|
||||
# IMPORTANT: Copies any modifications back to the target before restoring symlinks
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-/home/farhad/windmill}"
|
||||
MANIFEST_FILE="$PROJECT_DIR/.claude/hooks/.symlink-manifest"
|
||||
|
||||
# Check if manifest exists
|
||||
if [[ ! -f "$MANIFEST_FILE" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Read manifest and restore symlinks
|
||||
while IFS='|' read -r symlink target; do
|
||||
if [[ -n "$symlink" && -n "$target" ]]; then
|
||||
# If the file exists (not a symlink) and target exists, copy changes back
|
||||
if [[ -f "$symlink" && ! -L "$symlink" && -e "$target" ]]; then
|
||||
# Copy the potentially modified file back to the target
|
||||
cp "$symlink" "$target"
|
||||
fi
|
||||
|
||||
# Remove the regular file (which was a copy)
|
||||
rm -f "$symlink" 2>/dev/null || true
|
||||
|
||||
# Recreate the symlink
|
||||
ln -s "$target" "$symlink" 2>/dev/null || true
|
||||
fi
|
||||
done < "$MANIFEST_FILE"
|
||||
|
||||
# Clean up manifest
|
||||
rm -f "$MANIFEST_FILE"
|
||||
|
||||
exit 0
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"additionalDirectories": [
|
||||
"../windmill-ee-private"
|
||||
],
|
||||
"allow": [
|
||||
"Bash(ls:*)",
|
||||
"Bash(grep:*)",
|
||||
@@ -63,39 +66,6 @@
|
||||
},
|
||||
"enableAllProjectMcpServers": true,
|
||||
"hooks": {
|
||||
"UserPromptSubmit": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/resolve-symlinks.sh",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/restore-symlinks.sh",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"SessionEnd": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/restore-symlinks.sh",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Edit|Write",
|
||||
|
||||
4
.github/DockerfileBackendTests
vendored
4
.github/DockerfileBackendTests
vendored
@@ -44,6 +44,10 @@ RUN /usr/local/bin/python3 -m pip install pip-tools
|
||||
# Bun
|
||||
COPY --from=oven/bun:1.3.8 /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
# Install windmill CLI
|
||||
RUN bun install -g windmill-cli \
|
||||
&& ln -s $(bun pm bin -g)/wmill /usr/bin/wmill
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
# Deno
|
||||
|
||||
29
.github/workflows/backend-test.yml
vendored
29
.github/workflows/backend-test.yml
vendored
@@ -19,7 +19,7 @@ defaults:
|
||||
|
||||
jobs:
|
||||
cargo_test:
|
||||
runs-on: ubicloud-standard-16
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
@@ -70,6 +70,16 @@ jobs:
|
||||
with:
|
||||
ruby-version: "3.3"
|
||||
bundler-cache: false
|
||||
- name: Install windmill CLI from source
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE/cli
|
||||
bash gen_wm_client.sh
|
||||
bun install
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
printf '#!/bin/sh\nexec bun run "%s/cli/src/main.ts" "$@"\n' "$GITHUB_WORKSPACE" > "$HOME/.local/bin/wmill"
|
||||
chmod +x "$HOME/.local/bin/wmill"
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
working-directory: /
|
||||
- name: Install PowerShell, mold and clang
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y powershell mold clang libcurl4-openssl-dev
|
||||
@@ -78,6 +88,20 @@ jobs:
|
||||
with:
|
||||
cache: false
|
||||
toolchain: 1.93.0
|
||||
- name: Cache cargo target directory
|
||||
uses: useblacksmith/stickydisk@v1
|
||||
with:
|
||||
key: cargo-target
|
||||
path: ./backend/target
|
||||
- name: Cache cargo registry
|
||||
uses: useblacksmith/cache@v1
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: cargo-registry-${{ hashFiles('backend/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
cargo-registry-
|
||||
- name: Read EE repo commit hash
|
||||
run: |
|
||||
echo "ee_repo_ref=$(cat ./ee-repo-ref.txt)" >> "$GITHUB_ENV"
|
||||
@@ -205,7 +229,7 @@ jobs:
|
||||
fi
|
||||
echo "Verified: Package requires authentication for @windmill-test/private-pkg"
|
||||
- name: Cache DuckDB FFI module build
|
||||
uses: actions/cache@v3
|
||||
uses: useblacksmith/cache@v1
|
||||
with:
|
||||
path: ./backend/windmill-duckdb-ffi-internal/target
|
||||
key: ${{ runner.os }}-duckdb-ffi-${{ hashFiles('./backend/windmill-duckdb-ffi-internal/src/**/*.rs', './backend/windmill-duckdb-ffi-internal/Cargo.toml', './backend/windmill-duckdb-ffi-internal/Cargo.lock') }}
|
||||
@@ -221,6 +245,7 @@ jobs:
|
||||
RUST_LOG_STYLE: never
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
CARGO_BUILD_JOBS: 12
|
||||
CARGO_INCREMENTAL: 1
|
||||
WMDEBUG_FORCE_V0_WORKSPACE_DEPENDENCIES: 1
|
||||
WMDEBUG_FORCE_RUNNABLE_SETTINGS_V0: 1
|
||||
WMDEBUG_FORCE_NO_LEGACY_DEBOUNCING_COMPAT: 1
|
||||
|
||||
43
.github/workflows/cli-tests.yml
vendored
43
.github/workflows/cli-tests.yml
vendored
@@ -23,16 +23,16 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Deno
|
||||
uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v2.x
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Generate Windmill client
|
||||
working-directory: cli
|
||||
run: ./gen_wm_client.sh
|
||||
@@ -69,11 +69,6 @@ jobs:
|
||||
cache: true
|
||||
cache-workspaces: backend
|
||||
|
||||
- name: Setup Deno
|
||||
uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v2.x
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -90,6 +85,10 @@ jobs:
|
||||
- name: Symlink Node to /usr/bin/node
|
||||
run: sudo ln -sf $(which node) /usr/bin/node
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: cli
|
||||
run: bun install
|
||||
|
||||
- name: Generate Windmill clients
|
||||
working-directory: cli
|
||||
run: |
|
||||
@@ -101,12 +100,10 @@ jobs:
|
||||
env:
|
||||
DATABASE_URL: postgres://postgres:changeme@localhost:5432
|
||||
CI_MINIMAL_FEATURES: "true"
|
||||
run: |
|
||||
deno test --no-check --allow-all test/ \
|
||||
--ignore=test/cargo_backend_example.test.ts
|
||||
run: bun test --timeout 120000 test/
|
||||
|
||||
test-windows:
|
||||
runs-on: windows-latest
|
||||
runs-on: blacksmith-16vcpu-windows-2025
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -126,11 +123,6 @@ jobs:
|
||||
cache: true
|
||||
cache-workspaces: backend
|
||||
|
||||
- name: Setup Deno
|
||||
uses: denoland/setup-deno@v2
|
||||
with:
|
||||
deno-version: v2.x
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -150,6 +142,10 @@ jobs:
|
||||
echo "BUN_PATH=$bunPath" >> $env:GITHUB_OUTPUT
|
||||
echo "NODE_BIN_PATH=$nodePath" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: cli
|
||||
run: bun install
|
||||
|
||||
- name: Generate Windmill clients
|
||||
working-directory: cli
|
||||
shell: bash
|
||||
@@ -165,9 +161,12 @@ jobs:
|
||||
CI_MINIMAL_FEATURES: "true"
|
||||
BUN_PATH: ${{ steps.runtime-paths.outputs.BUN_PATH }}
|
||||
NODE_BIN_PATH: ${{ steps.runtime-paths.outputs.NODE_BIN_PATH }}
|
||||
run: |
|
||||
deno test --no-check --allow-all test/ `
|
||||
--ignore=test/cargo_backend_example.test.ts
|
||||
run: bun test --timeout 120000 test/
|
||||
|
||||
- name: Keep runner alive for SSH debug
|
||||
if: failure()
|
||||
shell: pwsh
|
||||
run: Start-Sleep -Seconds 3600
|
||||
|
||||
# Combined summary job for branch protection
|
||||
test-summary:
|
||||
|
||||
4
.github/workflows/npm_on_release.yml
vendored
4
.github/workflows/npm_on_release.yml
vendored
@@ -25,9 +25,9 @@ jobs:
|
||||
with:
|
||||
node-version: "20.x"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
- uses: denoland/setup-deno@v2
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
deno-version: v2.x
|
||||
bun-version: latest
|
||||
- run: cd cli && ./build.sh && cd npm && npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -17,6 +17,9 @@ rust-client/Cargo.toml
|
||||
# Worktree-generated port isolation
|
||||
.env.local
|
||||
|
||||
# Worktree-specific Claude Code settings (generated by scripts/worktree-env)
|
||||
.claude/settings.local.json
|
||||
|
||||
# Symlinked cache directories (for git worktrees)
|
||||
backend/target
|
||||
frontend/node_modules
|
||||
|
||||
10
.mcp.json
10
.mcp.json
@@ -3,10 +3,12 @@
|
||||
"svelte": {
|
||||
"type": "http",
|
||||
"url": "https://mcp.svelte.dev/mcp"
|
||||
},
|
||||
"playwright": {
|
||||
"command": "npx",
|
||||
"args": ["@playwright/mcp@latest"]
|
||||
}
|
||||
},
|
||||
"playwright": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"@playwright/mcp@latest"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -46,11 +46,20 @@ pre_remove:
|
||||
- ./scripts/worktree-cleanup
|
||||
|
||||
panes:
|
||||
- command: <agent>
|
||||
- command: >-
|
||||
claude --append-system-prompt
|
||||
"You are running inside a tmux session with other panes running services.\n
|
||||
Pane layout (current window):\n
|
||||
- Pane 0: this pane (claude agent)\n
|
||||
- Pane 1: backend (cargo watch -x run)\n
|
||||
- Pane 2: frontend (npm run dev)\n\n
|
||||
To check logs, use: \`tmux capture-pane -t .1 -p -S -50\` (backend) or \`tmux capture-pane -t .2 -p -S -50\` (frontend).\n
|
||||
When restarting backend or frontend, make sure to use the ports listed in .env.local.\n
|
||||
Because we are running backend with cargo watch, to verify your changes, just check the logs in the backend pane. No need for cargo check."
|
||||
focus: true
|
||||
- command: "[ -f .env.local ] && source .env.local; cd backend && PORT=${BACKEND_PORT:-8000} cargo watch -x run"
|
||||
- command: 'ROOT="$(git rev-parse --show-toplevel)"; [ -f "$ROOT/.env.local" ] && source "$ROOT/.env.local"; cd "$ROOT/backend" && PORT=${BACKEND_PORT:-8000} cargo watch -x run'
|
||||
split: horizontal
|
||||
- command: "[ -f .env.local ] && source .env.local; cd frontend && npm install && npm run generate-backend-client && REMOTE=${REMOTE:-http://localhost:${BACKEND_PORT:-8000}} npm run dev -- --port ${FRONTEND_PORT:-3000}"
|
||||
- command: 'ROOT="$(git rev-parse --show-toplevel)"; [ -f "$ROOT/.env.local" ] && source "$ROOT/.env.local"; cd "$ROOT/frontend" && npm install && npm run generate-backend-client && REMOTE=${REMOTE:-http://localhost:${BACKEND_PORT:-8000}} npm run dev -- --port ${FRONTEND_PORT:-3000} --host 0.0.0.0'
|
||||
split: vertical
|
||||
|
||||
files:
|
||||
@@ -61,3 +70,6 @@ files:
|
||||
sandbox:
|
||||
enabled: false
|
||||
toolchain: off
|
||||
# image, host_commands, and extra_mounts configured in global
|
||||
# ~/.config/workmux/config.yaml — see README_WORKMUX_DEV.md for required
|
||||
# extra_mounts (windmill-ee-private access in sandbox)
|
||||
|
||||
13
CHANGELOG.md
13
CHANGELOG.md
@@ -1,5 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## [1.642.0](https://github.com/windmill-labs/windmill/compare/v1.641.0...v1.642.0) (2026-02-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **cli:** add consistent get/list/new subcommands for all item types ([#8047](https://github.com/windmill-labs/windmill/issues/8047)) ([4fedfdf](https://github.com/windmill-labs/windmill/commit/4fedfdfd11aa8ca7fff6f7aed5ae2b313888f878))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* make WM_FLOW_PATH available in flow step previews ([#8042](https://github.com/windmill-labs/windmill/issues/8042)) ([a91c532](https://github.com/windmill-labs/windmill/commit/a91c532ecadce63cea965c497351fa1a6f39697a))
|
||||
* preserve debouncing settings for flows with preprocessors ([#8043](https://github.com/windmill-labs/windmill/issues/8043)) ([a00927b](https://github.com/windmill-labs/windmill/commit/a00927b3008a2d953fde1d461723a3c92f375eb4))
|
||||
|
||||
## [1.641.0](https://github.com/windmill-labs/windmill/compare/v1.640.0...v1.641.0) (2026-02-21)
|
||||
|
||||
|
||||
|
||||
@@ -258,6 +258,10 @@ COPY --from=denoland/deno:2.2.1 --chmod=755 /usr/bin/deno /usr/bin/deno
|
||||
|
||||
COPY --from=oven/bun:1.3.8 /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
# Install windmill CLI
|
||||
RUN bun install -g windmill-cli \
|
||||
&& ln -s $(bun pm bin -g)/wmill /usr/bin/wmill
|
||||
|
||||
COPY --from=php:8.3.7-cli /usr/local/bin/php /usr/bin/php
|
||||
COPY --from=composer:2.7.6 /usr/bin/composer /usr/bin/composer
|
||||
|
||||
|
||||
234
Dockerfile.sandbox
Normal file
234
Dockerfile.sandbox
Normal file
@@ -0,0 +1,234 @@
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
ca-certificates \
|
||||
git \
|
||||
iptables \
|
||||
gosu \
|
||||
sudo \
|
||||
unzip \
|
||||
# Rust native build deps (for cargo check)
|
||||
pkg-config \
|
||||
cmake \
|
||||
clang \
|
||||
mold \
|
||||
libtool \
|
||||
libssl-dev \
|
||||
libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
libxslt1-dev \
|
||||
libffi-dev \
|
||||
zlib1g-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libclang-dev \
|
||||
libkrb5-dev \
|
||||
libsasl2-dev \
|
||||
# PostgreSQL (for local DB during development)
|
||||
postgresql \
|
||||
postgresql-client \
|
||||
# Node.js 22 (for npm run check / frontend dev)
|
||||
&& curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
# Container runs as arbitrary UIDs (--user uid:gid). These three lines make
|
||||
# sudo work for any UID:
|
||||
# 1) NOPASSWD rule so sudo never prompts for a password
|
||||
# 2) Writable passwd/group so the entrypoint can register the dynamic UID
|
||||
# 3) Writable shadow so unix_chkpwd can validate the account (without this,
|
||||
# sudo fails with "account validation failure, is your account locked?")
|
||||
&& echo "ALL ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/sandbox \
|
||||
&& chmod 0440 /etc/sudoers.d/sandbox \
|
||||
&& chmod 666 /etc/passwd /etc/group /etc/shadow
|
||||
|
||||
# ── GitHub CLI (for PR creation) ──────────────────────────────────────────────
|
||||
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
||||
-o /usr/share/keyrings/githubcli-archive-keyring.gpg \
|
||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
||||
> /etc/apt/sources.list.d/github-cli.list \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends gh \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ── Rust toolchain ────────────────────────────────────────────────────────────
|
||||
# Install under /usr/local/lib/ so bins are world-readable with default umask.
|
||||
# CARGO_HOME is overridden to /tmp/.cargo at the end for mutable runtime state.
|
||||
ENV RUSTUP_HOME=/usr/local/lib/rustup CARGO_HOME=/usr/local/lib/cargo
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
|
||||
sh -s -- -y --default-toolchain stable --profile minimal && \
|
||||
ln -s /usr/local/lib/cargo/bin/* /usr/local/bin/
|
||||
RUN cargo install sqlx-cli --no-default-features --features native-tls,postgres && \
|
||||
cargo install cargo-watch && \
|
||||
ln -sf /usr/local/lib/cargo/bin/sqlx /usr/local/bin/sqlx && \
|
||||
ln -sf /usr/local/lib/cargo/bin/cargo-watch /usr/local/bin/cargo-watch
|
||||
|
||||
# ── Register dynamic runtime users ───────────────────────────────────────────
|
||||
RUN cat <<'SCRIPT' > /usr/local/bin/register-dynamic-user.sh
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
uid="${1:-}"
|
||||
gid="${2:-}"
|
||||
|
||||
if [ -z "$uid" ] || [ -z "$gid" ]; then
|
||||
echo "register-dynamic-user: usage: register-dynamic-user <uid> <gid>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! getent group "$gid" >/dev/null 2>&1; then
|
||||
echo "sandbox:x:${gid}:" >> /etc/group
|
||||
fi
|
||||
|
||||
if ! getent passwd "$uid" >/dev/null 2>&1; then
|
||||
echo "sandbox:x:${uid}:${gid}:sandbox:/tmp:/bin/sh" >> /etc/passwd
|
||||
fi
|
||||
|
||||
# Add a shadow entry ("*" = no password) so unix_chkpwd doesn't reject sudo.
|
||||
if ! grep -q "^sandbox:" /etc/shadow 2>/dev/null; then
|
||||
echo "sandbox:*:19000:0:99999:7:::" >> /etc/shadow
|
||||
fi
|
||||
SCRIPT
|
||||
RUN chmod +x /usr/local/bin/register-dynamic-user.sh
|
||||
|
||||
# ── Network init script (iptables firewall + privilege drop) ──────────────────
|
||||
RUN cat <<'SCRIPT' > /usr/local/bin/network-init.sh
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ -n "${WM_PROXY_HOST:-}" ] && [ -n "${WM_PROXY_PORT:-}" ]; then
|
||||
# Resolve hostnames to ALL IPs (multi-A records, round-robin DNS)
|
||||
PROXY_IPS=$(getent ahostsv4 "$WM_PROXY_HOST" | awk '{print $1}' | sort -u)
|
||||
RPC_HOST="${WM_RPC_HOST:-$WM_PROXY_HOST}"
|
||||
RPC_IPS=$(getent ahostsv4 "$RPC_HOST" | awk '{print $1}' | sort -u)
|
||||
|
||||
if [ -z "$PROXY_IPS" ] || [ -z "$RPC_IPS" ]; then
|
||||
echo "network-init: failed to resolve proxy/RPC host" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# IPv4: default deny outbound
|
||||
iptables -P OUTPUT DROP
|
||||
iptables -A OUTPUT -o lo -j ACCEPT
|
||||
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
# Allow DNS (UDP/TCP 53) to configured nameservers.
|
||||
if [ -f /etc/resolv.conf ]; then
|
||||
grep '^nameserver' /etc/resolv.conf | awk '{print $2}' | while read -r ns; do
|
||||
iptables -A OUTPUT -d "$ns" -p udp --dport 53 -j ACCEPT
|
||||
iptables -A OUTPUT -d "$ns" -p tcp --dport 53 -j ACCEPT
|
||||
done
|
||||
fi
|
||||
|
||||
# Allow ALL resolved proxy IPs (handles multi-A DNS)
|
||||
for ip in $PROXY_IPS; do
|
||||
iptables -A OUTPUT -d "$ip" -p tcp --dport "$WM_PROXY_PORT" -j ACCEPT
|
||||
done
|
||||
|
||||
# Allow ALL resolved RPC IPs
|
||||
if [ -n "${WM_RPC_PORT:-}" ]; then
|
||||
for ip in $RPC_IPS; do
|
||||
iptables -A OUTPUT -d "$ip" -p tcp --dport "$WM_RPC_PORT" -j ACCEPT
|
||||
done
|
||||
fi
|
||||
|
||||
# Reject (not drop) everything else to fail fast instead of hanging
|
||||
iptables -A OUTPUT -j REJECT
|
||||
|
||||
# IPv6: block entirely to prevent leaks (fail closed)
|
||||
if ip6tables -L -n >/dev/null 2>&1; then
|
||||
ip6tables -P OUTPUT DROP
|
||||
ip6tables -A OUTPUT -o lo -j ACCEPT
|
||||
ip6tables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
ip6tables -A OUTPUT -j REJECT
|
||||
else
|
||||
if ! sysctl -w net.ipv6.conf.all.disable_ipv6=1 2>/dev/null; then
|
||||
echo "network-init: failed to block IPv6 (neither ip6tables nor sysctl available)" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Add sandbox user/group so sudo works after dropping privileges.
|
||||
if [ -z "${WM_TARGET_UID:-}" ] || [ -z "${WM_TARGET_GID:-}" ]; then
|
||||
echo "network-init: WM_TARGET_UID and WM_TARGET_GID are required" >&2
|
||||
exit 1
|
||||
fi
|
||||
/usr/local/bin/register-dynamic-user.sh "${WM_TARGET_UID}" "${WM_TARGET_GID}"
|
||||
|
||||
# Fix PTY ownership so the unprivileged user can read/write the terminal.
|
||||
if [ -t 0 ]; then
|
||||
chown "${WM_TARGET_UID}:${WM_TARGET_GID}" "$(tty)"
|
||||
fi
|
||||
|
||||
# Drop privileges and exec the user command.
|
||||
exec gosu "${WM_TARGET_UID}:${WM_TARGET_GID}" env HOME=/tmp "$@"
|
||||
SCRIPT
|
||||
RUN chmod +x /usr/local/bin/network-init.sh
|
||||
|
||||
# ── workmux (sandbox RPC) ────────────────────────────────────────────────────
|
||||
RUN curl -fsSL https://raw.githubusercontent.com/raine/workmux/main/scripts/install.sh | bash
|
||||
|
||||
# ── Claude Code ───────────────────────────────────────────────────────────────
|
||||
RUN curl -fsSL https://claude.ai/install.sh | bash && \
|
||||
target="$(readlink -f /root/.local/bin/claude)" && \
|
||||
mv /root/.local/share/claude /usr/local/lib/claude && \
|
||||
ln -s "/usr/local/lib/claude/versions/$(basename "$target")" /usr/local/bin/claude && \
|
||||
mkdir -p /tmp/.local/bin && \
|
||||
ln -s /usr/local/bin/claude /tmp/.local/bin/claude
|
||||
|
||||
# ── Codex ─────────────────────────────────────────────────────────────────────
|
||||
RUN npm i -g @openai/codex
|
||||
|
||||
# ── Bun ───────────────────────────────────────────────────────────────────────
|
||||
ENV BUN_INSTALL=/usr/local/lib/bun
|
||||
RUN curl -fsSL https://bun.sh/install | bash && \
|
||||
ln -s /usr/local/lib/bun/bin/bun /usr/local/bin/bun && \
|
||||
ln -s /usr/local/lib/bun/bin/bunx /usr/local/bin/bunx
|
||||
|
||||
# ── Playwright + Chromium (for screenshots) ──────────────────────────────────
|
||||
ENV PLAYWRIGHT_BROWSERS_PATH=/usr/local/lib/playwright-browsers
|
||||
RUN bun add -g @playwright/test \
|
||||
&& bunx playwright install chromium --with-deps \
|
||||
&& chmod -R a+rwX /usr/local/lib/playwright-browsers \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/bunx-*
|
||||
|
||||
# ── AWS CLI (for S3-compatible uploads to R2) ─────────────────────────────────
|
||||
RUN curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o /tmp/awscliv2.zip \
|
||||
&& unzip -q /tmp/awscliv2.zip -d /tmp \
|
||||
&& /tmp/aws/install \
|
||||
&& rm -rf /tmp/aws /tmp/awscliv2.zip
|
||||
|
||||
ENV AWS_DEFAULT_REGION=auto
|
||||
|
||||
# ── Runtime env for arbitrary UID ─────────────────────────────────────────────
|
||||
# Mutable state goes to /tmp (writable by any UID). Toolchains stay read-only.
|
||||
ENV CARGO_HOME=/tmp/.cargo BUN_TMPDIR=/tmp
|
||||
|
||||
# ── Entrypoint ────────────────────────────────────────────────────────────────
|
||||
RUN cat <<'ENTRY' > /usr/local/bin/entrypoint.sh
|
||||
#!/bin/sh
|
||||
/usr/local/bin/register-dynamic-user.sh "$(id -u)" "$(id -g)"
|
||||
|
||||
# Start PostgreSQL (unix socket in /tmp, owned by postgres user)
|
||||
mkdir -p /tmp/pgdata && sudo chown postgres:postgres /tmp/pgdata
|
||||
if [ ! -f /tmp/pgdata/PG_VERSION ]; then
|
||||
sudo -u postgres /usr/lib/postgresql/15/bin/initdb -D /tmp/pgdata --auth=trust
|
||||
fi
|
||||
sudo -u postgres /usr/lib/postgresql/15/bin/pg_ctl -D /tmp/pgdata -l /tmp/pg.log start -o "-k /tmp"
|
||||
sudo -u postgres psql -h /tmp -c "CREATE ROLE sandbox SUPERUSER LOGIN" 2>/dev/null || true
|
||||
sudo -u postgres createdb -h /tmp windmill 2>/dev/null || true
|
||||
|
||||
# Run database migrations so sqlx compile-time checks work
|
||||
if [ -d "$PWD/backend/migrations" ]; then
|
||||
DATABASE_URL="postgres://sandbox@localhost/windmill?host=/tmp" \
|
||||
sqlx migrate run --source "$PWD/backend/migrations" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Install frontend dependencies and generate backend client
|
||||
if [ -d "$PWD/frontend" ]; then
|
||||
(cd "$PWD/frontend" && npm install && npm run generate-backend-client) 2>/dev/null || true
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
ENTRY
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
@@ -172,6 +172,25 @@ The setup is defined in `.workmux.yaml` at the repo root. Key sections:
|
||||
- **`files.copy`**: Copies `backend/.env` and `scripts/` into each worktree
|
||||
- **`files.symlink`**: Symlinks `node_modules` and `.svelte-kit` to avoid reinstalling per worktree
|
||||
|
||||
## Enterprise (EE) Code Access
|
||||
|
||||
The enterprise source code lives in the `windmill-ee-private` repository (sibling to this repo). When you create a worktree, `scripts/worktree-env` automatically creates a matching EE worktree on the same branch and configures Claude Code's `additionalDirectories` to grant access.
|
||||
|
||||
### Sandbox setup
|
||||
|
||||
When using sandbox mode, the container needs explicit mounts to access the EE repo. Add the following to your global workmux config (`~/.config/workmux/config.yaml`):
|
||||
|
||||
```yaml
|
||||
sandbox:
|
||||
extra_mounts:
|
||||
- host_path: ~/windmill-ee-private
|
||||
writable: true
|
||||
- host_path: ~/windmill-ee-private__worktrees
|
||||
writable: true
|
||||
```
|
||||
|
||||
This mounts both the main EE repo (used by the main worktree) and the EE worktrees directory (used by feature worktrees) into every sandbox container.
|
||||
|
||||
## Login
|
||||
|
||||
Default credentials: `admin@windmill.dev` / `changeme`
|
||||
|
||||
14
backend/.sqlx/query-0681b850c033619e1b9498376263681f875a5aba22170ca50ec8b578f7fa478b.json
generated
Normal file
14
backend/.sqlx/query-0681b850c033619e1b9498376263681f875a5aba22170ca50ec8b578f7fa478b.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag)\n SELECT unnest($1::uuid[]), 'test-workspace', now(), 'flow'",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "0681b850c033619e1b9498376263681f875a5aba22170ca50ec8b578f7fa478b"
|
||||
}
|
||||
26
backend/.sqlx/query-1437b432d2c23e30eb05443e83069cdb049f65ec299b0778ce14677728cf6346.json
generated
Normal file
26
backend/.sqlx/query-1437b432d2c23e30eb05443e83069cdb049f65ec299b0778ce14677728cf6346.json
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH completed AS (\n INSERT INTO v2_job_completed\n (workspace_id, id, started_at, duration_ms, result,\n flow_status, workflow_as_code_status, status, worker)\n SELECT\n q.workspace_id, q.id, q.started_at,\n (EXTRACT('epoch' FROM now()) - EXTRACT('epoch' FROM COALESCE(q.started_at, now()))) * 1000,\n CASE WHEN q.running\n THEN $3::text::jsonb\n ELSE $4::text::jsonb\n END,\n s.flow_status,\n s.workflow_as_code_status,\n 'skipped'::job_status,\n q.worker\n FROM v2_job_queue q\n LEFT JOIN v2_job_status s ON s.id = q.id\n WHERE q.id = $1\n ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status, result = EXCLUDED.result\n RETURNING 1 AS x\n ), _deleted AS (\n DELETE FROM v2_job_queue WHERE id = $1\n ), _logged AS (\n INSERT INTO job_logs (logs, job_id, workspace_id)\n VALUES ($5, $1, $2)\n ON CONFLICT (job_id) DO UPDATE SET logs = concat(job_logs.logs, EXCLUDED.logs)\n )\n SELECT x FROM completed\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "x",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "1437b432d2c23e30eb05443e83069cdb049f65ec299b0778ce14677728cf6346"
|
||||
}
|
||||
22
backend/.sqlx/query-18b6262a60400f2b58ab26615466c23b4c1a7805c66b70b0fcfb7d33b122a7bf.json
generated
Normal file
22
backend/.sqlx/query-18b6262a60400f2b58ab26615466c23b4c1a7805c66b70b0fcfb7d33b122a7bf.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT result::text FROM v2_job_completed WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "result",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "18b6262a60400f2b58ab26615466c23b4c1a7805c66b70b0fcfb7d33b122a7bf"
|
||||
}
|
||||
15
backend/.sqlx/query-1af6885dbc5055281acb82b3e57f7dba2e4b04d9535058fab695660a14bf8890.json
generated
Normal file
15
backend/.sqlx/query-1af6885dbc5055281acb82b3e57f7dba2e4b04d9535058fab695660a14bf8890.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag)\n VALUES ($1, $2, now(), 'flow')",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1af6885dbc5055281acb82b3e57f7dba2e4b04d9535058fab695660a14bf8890"
|
||||
}
|
||||
16
backend/.sqlx/query-27f70ebe788cca2e88732d8bf978883037bebca4cf75ba459858e4fb197f940b.json
generated
Normal file
16
backend/.sqlx/query-27f70ebe788cca2e88732d8bf978883037bebca4cf75ba459858e4fb197f940b.json
generated
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job (id, kind, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, runnable_path)\n VALUES ($1, 'flow', 'flow', 'test-user', 'u/test-user', 'test@windmill.dev', $2, $3)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "27f70ebe788cca2e88732d8bf978883037bebca4cf75ba459858e4fb197f940b"
|
||||
}
|
||||
22
backend/.sqlx/query-2a95f18e80c55a7e8178a4bd2b781d41fa47efd4da5bb9bc2d72b9aa1e33617f.json
generated
Normal file
22
backend/.sqlx/query-2a95f18e80c55a7e8178a4bd2b781d41fa47efd4da5bb9bc2d72b9aa1e33617f.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT debounce_batch FROM v2_job_debounce_batch WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "debounce_batch",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2a95f18e80c55a7e8178a4bd2b781d41fa47efd4da5bb9bc2d72b9aa1e33617f"
|
||||
}
|
||||
14
backend/.sqlx/query-4010328a9f1611064f497726b69c08625a55a4dab25c3d9b5ece07e44d14915b.json
generated
Normal file
14
backend/.sqlx/query-4010328a9f1611064f497726b69c08625a55a4dab25c3d9b5ece07e44d14915b.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job (id, kind, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, runnable_path)\n VALUES ($1, 'flow', 'flow', 'test-user', 'u/test-user', 'test@windmill.dev', 'ws2', 'f/test/flow')",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4010328a9f1611064f497726b69c08625a55a4dab25c3d9b5ece07e44d14915b"
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO debounce_key (job_id, key)\n VALUES ($1, $2)\n ON CONFLICT (key)\n DO UPDATE SET\n previous_job_id = debounce_key.job_id,\n job_id = EXCLUDED.job_id, -- replace current job with new one \n debounced_times = debounce_key.debounced_times + 1 -- evaluated only if conflict,\n -- conflict means there is already existing value,\n -- which means overriding it will also imply adding new entry to v2_job_debounce_batch and thus debouncing the job\n -- so the counter should be incremented\n RETURNING\n debounced_times,\n first_started_at,\n previous_job_id AS job_id_to_debounce\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "debounced_times",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "first_started_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "job_id_to_debounce",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "454ace9ce391725ef4f4c129cd66e4c12a5c40f512b70551958178c8b4d6c183"
|
||||
}
|
||||
22
backend/.sqlx/query-48536968f4173715d4ef8293683c2a3eb4bd22fbe18c34890a3dc4e96e4e6133.json
generated
Normal file
22
backend/.sqlx/query-48536968f4173715d4ef8293683c2a3eb4bd22fbe18c34890a3dc4e96e4e6133.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH ids AS (\n SELECT id as job_id FROM v2_job_debounce_batch WHERE debounce_batch = (\n SELECT debounce_batch FROM v2_job_debounce_batch WHERE id = $1\n )\n ) SELECT args->>'items' FROM ids LEFT JOIN v2_job ON v2_job.id = ids.job_id",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "?column?",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "48536968f4173715d4ef8293683c2a3eb4bd22fbe18c34890a3dc4e96e4e6133"
|
||||
}
|
||||
14
backend/.sqlx/query-539d661500254e2e346490710f5772cb88a1ab6bbddd97a77e06644ac0f61762.json
generated
Normal file
14
backend/.sqlx/query-539d661500254e2e346490710f5772cb88a1ab6bbddd97a77e06644ac0f61762.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag)\n SELECT unnest($1::uuid[]), 'test-workspace', now(), 'deno'",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "539d661500254e2e346490710f5772cb88a1ab6bbddd97a77e06644ac0f61762"
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH job_result AS (\n SELECT result\n FROM v2_job_completed\n WHERE id = $1\n ),\n updated_queue AS (\n UPDATE v2_job_queue\n SET running = false,\n tag = COALESCE($3, tag)\n WHERE id = $2\n )\n UPDATE v2_job\n SET\n tag = COALESCE($3, tag),\n concurrent_limit = COALESCE($4, concurrent_limit),\n concurrency_time_window_s = COALESCE($5, concurrency_time_window_s),\n args = COALESCE(\n CASE\n WHEN job_result.result IS NULL THEN NULL\n WHEN jsonb_typeof(job_result.result) = 'object'\n THEN job_result.result\n WHEN jsonb_typeof(job_result.result) = 'null'\n THEN NULL\n ELSE jsonb_build_object('value', job_result.result)\n END,\n '{}'::jsonb\n ),\n preprocessed = TRUE\n FROM job_result\n WHERE v2_job.id = $2;\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Int4",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5b8c1803f0ccead11517fbc8a9bdc0227dc3922217fa18f0b71ff0484d65838c"
|
||||
}
|
||||
14
backend/.sqlx/query-66342c32f7ae0238803cb1896d9f23a74b64573f77dd32189a25b6e8369f147b.json
generated
Normal file
14
backend/.sqlx/query-66342c32f7ae0238803cb1896d9f23a74b64573f77dd32189a25b6e8369f147b.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE debounce_key SET first_started_at = now() - interval '20 seconds' WHERE key = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "66342c32f7ae0238803cb1896d9f23a74b64573f77dd32189a25b6e8369f147b"
|
||||
}
|
||||
14
backend/.sqlx/query-66faba2137791e0cb1353545c06f9f7c23a1559e7a761db7c2195736b8b30709.json
generated
Normal file
14
backend/.sqlx/query-66faba2137791e0cb1353545c06f9f7c23a1559e7a761db7c2195736b8b30709.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job (id, kind, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, runnable_path)\n SELECT unnest($1::uuid[]), 'flow', 'flow', 'test-user', 'u/test-user', 'test@windmill.dev', 'test-workspace', 'f/test/flow'",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "66faba2137791e0cb1353545c06f9f7c23a1559e7a761db7c2195736b8b30709"
|
||||
}
|
||||
19
backend/.sqlx/query-79b437ad31ddab94310989b8fb6a1c130b9be1ab4b6a100fffffd687677b9c92.json
generated
Normal file
19
backend/.sqlx/query-79b437ad31ddab94310989b8fb6a1c130b9be1ab4b6a100fffffd687677b9c92.json
generated
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "WITH job_result AS (\n SELECT result\n FROM v2_job_completed\n WHERE id = $1\n ),\n updated_queue AS (\n UPDATE v2_job_queue\n SET running = false,\n tag = COALESCE($3, tag),\n scheduled_for = COALESCE($6, scheduled_for)\n WHERE id = $2\n )\n UPDATE v2_job\n SET\n tag = COALESCE($3, tag),\n concurrent_limit = COALESCE($4, concurrent_limit),\n concurrency_time_window_s = COALESCE($5, concurrency_time_window_s),\n args = COALESCE(\n CASE\n WHEN job_result.result IS NULL THEN NULL\n WHEN jsonb_typeof(job_result.result) = 'object'\n THEN job_result.result\n WHEN jsonb_typeof(job_result.result) = 'null'\n THEN NULL\n ELSE jsonb_build_object('value', job_result.result)\n END,\n '{}'::jsonb\n ),\n preprocessed = TRUE\n FROM job_result\n WHERE v2_job.id = $2;\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Int4",
|
||||
"Int4",
|
||||
"Timestamptz"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "79b437ad31ddab94310989b8fb6a1c130b9be1ab4b6a100fffffd687677b9c92"
|
||||
}
|
||||
22
backend/.sqlx/query-7ca599330c9913c7e66b27e2ffcfa18d53cbdd16e179749f0aea7980a901b23c.json
generated
Normal file
22
backend/.sqlx/query-7ca599330c9913c7e66b27e2ffcfa18d53cbdd16e179749f0aea7980a901b23c.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT logs as \"logs!\" FROM job_logs WHERE job_id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "logs!",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "7ca599330c9913c7e66b27e2ffcfa18d53cbdd16e179749f0aea7980a901b23c"
|
||||
}
|
||||
15
backend/.sqlx/query-7ca7dabfe360845a5b57552b0d02267d5dbbc488bc7ab990c0bda1594bf5ef3a.json
generated
Normal file
15
backend/.sqlx/query-7ca7dabfe360845a5b57552b0d02267d5dbbc488bc7ab990c0bda1594bf5ef3a.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag)\n VALUES ($1, $2, now(), 'deno')",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "7ca7dabfe360845a5b57552b0d02267d5dbbc488bc7ab990c0bda1594bf5ef3a"
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job_debounce_batch (id, debounce_batch)\n -- if it the first one, nextval will be evaluated, otherwise take from the job we will debounce\n SELECT\n $2,\n COALESCE(\n (\n SELECT debounce_batch\n FROM v2_job_debounce_batch\n WHERE id = $1\n LIMIT 1\n ), -- maybe use current batch\n nextval('debounce_batch_seq')\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8360ab72d60f07dde6ecae599e6531b5b86862029ab51fdbdd44ec16239108e2"
|
||||
}
|
||||
12
backend/.sqlx/query-8cf5af21cde4e4de45f995efa2a9b56ce20c26869ca78d7e17b3504b92ae85b1.json
generated
Normal file
12
backend/.sqlx/query-8cf5af21cde4e4de45f995efa2a9b56ce20c26869ca78d7e17b3504b92ae85b1.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO workspace_settings (workspace_id) VALUES ('ws2')",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8cf5af21cde4e4de45f995efa2a9b56ce20c26869ca78d7e17b3504b92ae85b1"
|
||||
}
|
||||
34
backend/.sqlx/query-8f442110817244aa9533b014aa3d74a6582937dfe4759932b63b8e531984008e.json
generated
Normal file
34
backend/.sqlx/query-8f442110817244aa9533b014aa3d74a6582937dfe4759932b63b8e531984008e.json
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT job_id, previous_job_id, debounced_times FROM debounce_key WHERE key = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "job_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "previous_job_id",
|
||||
"type_info": "Uuid"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "debounced_times",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "8f442110817244aa9533b014aa3d74a6582937dfe4759932b63b8e531984008e"
|
||||
}
|
||||
35
backend/.sqlx/query-98033aae3182bde22d5b2ff08ef6e8a4f8f3a9bf04238b33e9caf46836df73d9.json
generated
Normal file
35
backend/.sqlx/query-98033aae3182bde22d5b2ff08ef6e8a4f8f3a9bf04238b33e9caf46836df73d9.json
generated
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH dk AS (\n INSERT INTO debounce_key (job_id, key)\n VALUES ($1, $2)\n ON CONFLICT (key)\n DO UPDATE SET\n previous_job_id = debounce_key.job_id,\n job_id = EXCLUDED.job_id,\n debounced_times = debounce_key.debounced_times + 1\n RETURNING\n debounced_times,\n first_started_at,\n previous_job_id AS job_id_to_debounce\n ), _batch AS (\n INSERT INTO v2_job_debounce_batch (id, debounce_batch)\n SELECT\n $1,\n COALESCE(\n (SELECT debounce_batch FROM v2_job_debounce_batch WHERE id = dk.job_id_to_debounce LIMIT 1),\n nextval('debounce_batch_seq')\n )\n FROM dk\n )\n SELECT debounced_times, first_started_at, job_id_to_debounce FROM dk\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "debounced_times",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "first_started_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "job_id_to_debounce",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "98033aae3182bde22d5b2ff08ef6e8a4f8f3a9bf04238b33e9caf46836df73d9"
|
||||
}
|
||||
15
backend/.sqlx/query-9f50ec7681a1fcd11cb452c7aba7e8897e49a4b6affa4e9976680336b6bd3115.json
generated
Normal file
15
backend/.sqlx/query-9f50ec7681a1fcd11cb452c7aba7e8897e49a4b6affa4e9976680336b6bd3115.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job (id, kind, tag, created_by, permissioned_as, permissioned_as_email, workspace_id)\n VALUES ($1, 'noop', 'deno', 'test-user', 'u/test-user', 'test@windmill.dev', $2)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "9f50ec7681a1fcd11cb452c7aba7e8897e49a4b6affa4e9976680336b6bd3115"
|
||||
}
|
||||
12
backend/.sqlx/query-a057ff9f5998a162ae6de05f6127b7eefc826af7bf1bb89fd758f7c03c881033.json
generated
Normal file
12
backend/.sqlx/query-a057ff9f5998a162ae6de05f6127b7eefc826af7bf1bb89fd758f7c03c881033.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO workspace (id, name, owner) VALUES ('ws2', 'Workspace 2', 'test-user')",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a057ff9f5998a162ae6de05f6127b7eefc826af7bf1bb89fd758f7c03c881033"
|
||||
}
|
||||
14
backend/.sqlx/query-a68754521bf751450602f04dd4243199a18885e1739a5a0e7f6100eab6f3c803.json
generated
Normal file
14
backend/.sqlx/query-a68754521bf751450602f04dd4243199a18885e1739a5a0e7f6100eab6f3c803.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job_runtime (id) VALUES ($1)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a68754521bf751450602f04dd4243199a18885e1739a5a0e7f6100eab6f3c803"
|
||||
}
|
||||
15
backend/.sqlx/query-abb56f78aa39c6b6ae8b0ccb7b724c1f80d717e7c9d76b287da63cb5ee8e8b25.json
generated
Normal file
15
backend/.sqlx/query-abb56f78aa39c6b6ae8b0ccb7b724c1f80d717e7c9d76b287da63cb5ee8e8b25.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE v2_job_queue SET runnable_settings_handle = $1 WHERE id = $2",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "abb56f78aa39c6b6ae8b0ccb7b724c1f80d717e7c9d76b287da63cb5ee8e8b25"
|
||||
}
|
||||
22
backend/.sqlx/query-adb98040c8039e5cc27fe0579941f723b38d1784bd2f1b16d8724f1f1612dcbf.json
generated
Normal file
22
backend/.sqlx/query-adb98040c8039e5cc27fe0579941f723b38d1784bd2f1b16d8724f1f1612dcbf.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT 1 as x FROM v2_job_completed WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "x",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "adb98040c8039e5cc27fe0579941f723b38d1784bd2f1b16d8724f1f1612dcbf"
|
||||
}
|
||||
14
backend/.sqlx/query-b4a9abcb38997587b28655b0f4a212a5bd4039b57fab20b163617e33a4c9dd46.json
generated
Normal file
14
backend/.sqlx/query-b4a9abcb38997587b28655b0f4a212a5bd4039b57fab20b163617e33a4c9dd46.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job_runtime (id) SELECT unnest($1::uuid[])",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b4a9abcb38997587b28655b0f4a212a5bd4039b57fab20b163617e33a4c9dd46"
|
||||
}
|
||||
22
backend/.sqlx/query-b795dc228f93c8b9bedb4a3e7467d941819a7202214e0634580bdc2ec30f0b70.json
generated
Normal file
22
backend/.sqlx/query-b795dc228f93c8b9bedb4a3e7467d941819a7202214e0634580bdc2ec30f0b70.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT 1 as x FROM v2_job_queue WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "x",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "b795dc228f93c8b9bedb4a3e7467d941819a7202214e0634580bdc2ec30f0b70"
|
||||
}
|
||||
14
backend/.sqlx/query-c1a1ae759ebb84fde3e6d2727991b2a1fcc73e520fee79653bb960dc00c3e2db.json
generated
Normal file
14
backend/.sqlx/query-c1a1ae759ebb84fde3e6d2727991b2a1fcc73e520fee79653bb960dc00c3e2db.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job_queue (id, workspace_id, scheduled_for, tag) VALUES ($1, 'ws2', now(), 'flow')",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c1a1ae759ebb84fde3e6d2727991b2a1fcc73e520fee79653bb960dc00c3e2db"
|
||||
}
|
||||
35
backend/.sqlx/query-c2347460b73ae9d3167031c263032e97ebefb46be9e58bd3da9067748075311b.json
generated
Normal file
35
backend/.sqlx/query-c2347460b73ae9d3167031c263032e97ebefb46be9e58bd3da9067748075311b.json
generated
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH dk AS (\n INSERT INTO debounce_key (job_id, key)\n VALUES ($1, $2)\n ON CONFLICT (key)\n DO UPDATE SET\n previous_job_id = debounce_key.job_id,\n job_id = EXCLUDED.job_id,\n debounced_times = debounce_key.debounced_times + 1\n RETURNING\n debounced_times,\n first_started_at,\n previous_job_id AS job_id_to_debounce\n ), _batch AS (\n INSERT INTO v2_job_debounce_batch (id, debounce_batch)\n SELECT\n $1,\n COALESCE(\n (SELECT debounce_batch FROM v2_job_debounce_batch WHERE id = dk.job_id_to_debounce LIMIT 1),\n nextval('debounce_batch_seq')\n )\n FROM dk\n )\n SELECT debounced_times, first_started_at, job_id_to_debounce FROM dk\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "debounced_times",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "first_started_at",
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "job_id_to_debounce",
|
||||
"type_info": "Uuid"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Varchar"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "c2347460b73ae9d3167031c263032e97ebefb46be9e58bd3da9067748075311b"
|
||||
}
|
||||
22
backend/.sqlx/query-c63a1949247f1618f6b6acee9bf6b4d3081dfed2e6ff533dbff0bdfd52687cbb.json
generated
Normal file
22
backend/.sqlx/query-c63a1949247f1618f6b6acee9bf6b4d3081dfed2e6ff533dbff0bdfd52687cbb.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) as \"count!\" FROM v2_job_queue WHERE id = ANY($1)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count!",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "c63a1949247f1618f6b6acee9bf6b4d3081dfed2e6ff533dbff0bdfd52687cbb"
|
||||
}
|
||||
14
backend/.sqlx/query-c6d963e5cefeea728414892df9f28a89f435fc0fb7e55f243b021200f33d2151.json
generated
Normal file
14
backend/.sqlx/query-c6d963e5cefeea728414892df9f28a89f435fc0fb7e55f243b021200f33d2151.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job (id, kind, tag, created_by, permissioned_as, permissioned_as_email, workspace_id)\n SELECT unnest($1::uuid[]), 'noop', 'deno', 'test-user', 'u/test-user', 'test@windmill.dev', 'test-workspace'",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c6d963e5cefeea728414892df9f28a89f435fc0fb7e55f243b021200f33d2151"
|
||||
}
|
||||
14
backend/.sqlx/query-c9530931f670eab1208c4a284a55afdc3fcbb0eb5f98fd63e2ec89442becbfaa.json
generated
Normal file
14
backend/.sqlx/query-c9530931f670eab1208c4a284a55afdc3fcbb0eb5f98fd63e2ec89442becbfaa.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n WITH _ AS (\n UPDATE debounce_key\n SET debounced_times = 0,\n first_started_at = now(),\n previous_job_id = NULL\n WHERE job_id = $1\n )\n UPDATE v2_job_debounce_batch\n SET debounce_batch = nextval('debounce_batch_seq')\n WHERE id = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c9530931f670eab1208c4a284a55afdc3fcbb0eb5f98fd63e2ec89442becbfaa"
|
||||
}
|
||||
22
backend/.sqlx/query-cc309de42a3b630bb83d1b2437633ef0b98ce5e5fba1f5c1dda3ddd874ff3a39.json
generated
Normal file
22
backend/.sqlx/query-cc309de42a3b630bb83d1b2437633ef0b98ce5e5fba1f5c1dda3ddd874ff3a39.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT COUNT(*) as \"count!\" FROM v2_job_completed WHERE id = ANY($1)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count!",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "cc309de42a3b630bb83d1b2437633ef0b98ce5e5fba1f5c1dda3ddd874ff3a39"
|
||||
}
|
||||
22
backend/.sqlx/query-ccfed494a8d89eb2c88d72738c341a4dd87701b0636eb9fa001cd1d9cbcd663b.json
generated
Normal file
22
backend/.sqlx/query-ccfed494a8d89eb2c88d72738c341a4dd87701b0636eb9fa001cd1d9cbcd663b.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT debounce_batch FROM v2_job_debounce_batch WHERE id = ANY($1)",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "debounce_batch",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ccfed494a8d89eb2c88d72738c341a4dd87701b0636eb9fa001cd1d9cbcd663b"
|
||||
}
|
||||
22
backend/.sqlx/query-d9400849888dd021b0504b93004ab5e76296ed437c210942363ba06845f9f963.json
generated
Normal file
22
backend/.sqlx/query-d9400849888dd021b0504b93004ab5e76296ed437c210942363ba06845f9f963.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT debounce_batch FROM v2_job_debounce_batch WHERE id = ANY($1) ORDER BY debounce_batch",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "debounce_batch",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"UuidArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d9400849888dd021b0504b93004ab5e76296ed437c210942363ba06845f9f963"
|
||||
}
|
||||
17
backend/.sqlx/query-f8cec94b94098e752f7c71cbe5e9996410a07bacebc37ed21e0bedf1a33a8fdc.json
generated
Normal file
17
backend/.sqlx/query-f8cec94b94098e752f7c71cbe5e9996410a07bacebc37ed21e0bedf1a33a8fdc.json
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO v2_job (id, kind, tag, created_by, permissioned_as, permissioned_as_email, workspace_id, runnable_path, args)\n VALUES ($1, 'flow', 'flow', 'test-user', 'u/test-user', 'test@windmill.dev', $2, $3, $4)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Uuid",
|
||||
"Varchar",
|
||||
"Varchar",
|
||||
"Jsonb"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f8cec94b94098e752f7c71cbe5e9996410a07bacebc37ed21e0bedf1a33a8fdc"
|
||||
}
|
||||
@@ -44,11 +44,22 @@ Windmill uses a workspace-based architecture with multiple crates:
|
||||
## Enterprise Features
|
||||
|
||||
- Enterprise files use the `*_ee.rs` suffix
|
||||
- Enterprise source is in `windmill-ee-private` folder (sibling directory at `../../windmill-ee-private`), symlinked into each crate's `src/`
|
||||
- Enterprise source is in `windmill-ee-private` folder (sibling directory at `../../windmill-ee-private` or `~/windmill-ee-private`), symlinked into each crate's `src/`
|
||||
- The `_ee.rs` files are gitignored in the main repo — they are tracked only in the `windmill-ee-private` repo
|
||||
- You can and should modify `windmill-ee-private` directly when needed (e.g., when creating new crates that need EE code, mirror the package structure there)
|
||||
- Use feature flags: `#[cfg(feature = "enterprise")]`
|
||||
- Isolate enterprise code in separate modules
|
||||
|
||||
### EE PR Workflow (MUST DO when modifying `*_ee.rs` files)
|
||||
|
||||
When you modify any `*_ee.rs` file and create a PR on the windmill repo, you **MUST** also:
|
||||
|
||||
1. **Create a matching branch** in the `windmill-ee-private` repo (use the same branch name). If using worktrees, the EE worktree is at `~/windmill-ee-private__worktrees/<branch-name>/`
|
||||
2. **Commit and push** the `_ee.rs` changes in that branch
|
||||
3. **Create a PR** on `windmill-ee-private` with a link to the companion windmill PR
|
||||
4. **Update `ee-repo-ref.txt`**: Run `bash write_latest_ee_ref.sh` from `backend/` to write the latest EE commit hash. **Important**: the script may fall back to `~/windmill-ee-private` (main branch) instead of the worktree — verify it wrote the correct commit hash from your branch, not from main. If wrong, manually write the correct hash.
|
||||
5. **Commit `ee-repo-ref.txt`** in the windmill repo so CI picks up the correct EE ref
|
||||
|
||||
## Code Validation (MUST DO)
|
||||
|
||||
After making backend changes, you MUST run `cargo check` and fix all errors and warnings before considering the work done.
|
||||
|
||||
140
backend/Cargo.lock
generated
140
backend/Cargo.lock
generated
@@ -15725,7 +15725,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -15789,7 +15789,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-alerting"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -15802,7 +15802,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -15940,7 +15940,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-agent-workers"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -15963,7 +15963,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-assets"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -15976,7 +15976,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-auth"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16002,7 +16002,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
@@ -16012,7 +16012,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-configs"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16029,7 +16029,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-debug"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"base64 0.22.1",
|
||||
@@ -16052,7 +16052,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-embeddings"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16075,7 +16075,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flow-conversations"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16091,7 +16091,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-flows"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16111,7 +16111,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-groups"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16131,7 +16131,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-inputs"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16145,7 +16145,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-integration-tests"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -16171,7 +16171,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-jobs"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16196,7 +16196,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-npm-proxy"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"flate2",
|
||||
@@ -16213,7 +16213,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-openapi"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16234,7 +16234,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-schedule"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16254,7 +16254,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-scripts"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16284,7 +16284,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-settings"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16311,7 +16311,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-sse"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"serde",
|
||||
@@ -16323,7 +16323,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-users"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"axum 0.7.9",
|
||||
@@ -16346,7 +16346,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workers"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16360,7 +16360,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-workspaces"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"axum 0.7.9",
|
||||
"chrono",
|
||||
@@ -16390,7 +16390,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
@@ -16404,7 +16404,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-autoscaling"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -16423,7 +16423,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
@@ -16522,7 +16522,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-dep-map"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"itertools 0.14.0",
|
||||
@@ -16541,7 +16541,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-git-sync"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -16556,7 +16556,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-indexer"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"astral-tokio-tar",
|
||||
@@ -16580,7 +16580,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-jseval"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -16597,7 +16597,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-macros"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"itertools 0.14.0",
|
||||
"lazy_static",
|
||||
@@ -16613,7 +16613,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-mcp"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16634,7 +16634,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-native-triggers"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -16665,7 +16665,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-oauth"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-oauth2",
|
||||
@@ -16689,7 +16689,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-object-store"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@@ -16723,7 +16723,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-operator"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures",
|
||||
@@ -16741,7 +16741,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"convert_case 0.6.0",
|
||||
"serde",
|
||||
@@ -16750,7 +16750,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16762,7 +16762,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-csharp"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -16774,7 +16774,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
@@ -16786,7 +16786,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-graphql"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16798,7 +16798,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-java"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde_json",
|
||||
@@ -16810,7 +16810,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-nu"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"nu-parser",
|
||||
@@ -16821,7 +16821,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-php"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -16832,7 +16832,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.14.0",
|
||||
@@ -16845,7 +16845,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py-imports"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -16869,7 +16869,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ruby"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16883,7 +16883,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-rust"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
@@ -16900,7 +16900,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-sql"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16915,7 +16915,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lazy_static",
|
||||
@@ -16934,7 +16934,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-yaml"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
@@ -16945,7 +16945,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -16982,7 +16982,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-runtime-nativets"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"const_format",
|
||||
@@ -17020,7 +17020,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-sql-datatype-parser-wasm"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-test",
|
||||
@@ -17030,7 +17030,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-store"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -17059,7 +17059,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-test-utils"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum 0.7.9",
|
||||
@@ -17082,7 +17082,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17115,7 +17115,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-email"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17135,7 +17135,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-gcp"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17169,7 +17169,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-http"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17204,7 +17204,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-kafka"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17227,7 +17227,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-mqtt"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17251,7 +17251,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-nats"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-nats",
|
||||
@@ -17275,7 +17275,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-postgres"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17310,7 +17310,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-sqs"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17338,7 +17338,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-trigger-websocket"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -17361,7 +17361,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-types"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.9.4",
|
||||
@@ -17379,7 +17379,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-once-cell",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -76,7 +76,7 @@ members = [
|
||||
exclude = ["./windmill-duckdb-ffi-internal"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.641.0"
|
||||
version = "1.642.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
5f8105b808f3f0186fdf5132d2ee602d8a14aa17
|
||||
0fede4b1086bc1456be9cc55b203228c979c5c5e
|
||||
|
||||
474
backend/test_debounce_e2e.sh
Executable file
474
backend/test_debounce_e2e.sh
Executable file
@@ -0,0 +1,474 @@
|
||||
#!/usr/bin/env bash
|
||||
# End-to-end debounce tests against the running backend API
|
||||
# Usage: BACKEND_PORT=8030 ./test_debounce_e2e.sh
|
||||
set -uo pipefail
|
||||
|
||||
BASE="http://localhost:${BACKEND_PORT:-8030}/api"
|
||||
W="admins"
|
||||
EMAIL="admin@windmill.dev"
|
||||
PASSWORD="changeme"
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
|
||||
log_pass() { echo -e "${GREEN}PASS${NC}: $1"; ((pass++)) || true; }
|
||||
log_fail() { echo -e "${RED}FAIL${NC}: $1 — $2"; ((fail++)) || true; }
|
||||
log_info() { echo -e "${YELLOW}INFO${NC}: $1"; }
|
||||
|
||||
# Unique suffix for idempotent re-runs
|
||||
TS=$(date +%s)
|
||||
|
||||
# --- Auth ---
|
||||
log_info "Logging in..."
|
||||
TOKEN=$(curl -s "$BASE/auth/login" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{\"email\":\"$EMAIL\",\"password\":\"$PASSWORD\"}")
|
||||
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo "Failed to login"; exit 1
|
||||
fi
|
||||
|
||||
AUTH="Authorization: Bearer $TOKEN"
|
||||
log_info "Logged in"
|
||||
|
||||
# --- Helpers ---
|
||||
api() {
|
||||
# Usage: api METHOD path [data]
|
||||
local method="$1" path="$2" data="${3:-}"
|
||||
if [ -n "$data" ]; then
|
||||
curl -s "$BASE/w/$W/$path" -X "$method" -H "$AUTH" -H 'Content-Type: application/json' -d "$data"
|
||||
else
|
||||
curl -s "$BASE/w/$W/$path" -X "$method" -H "$AUTH"
|
||||
fi
|
||||
}
|
||||
|
||||
wait_job() {
|
||||
local job_id="$1" max_wait="${2:-30}"
|
||||
for _ in $(seq 1 "$max_wait"); do
|
||||
local r
|
||||
r=$(api GET "jobs/completed/get_result_maybe/$job_id")
|
||||
if echo "$r" | jq -e '.completed == true' > /dev/null 2>&1; then
|
||||
echo "$r"; return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo '{"completed":false,"error":"timeout"}'; return 1
|
||||
}
|
||||
|
||||
BUN_EMPTY_LOCK=$'{"dependencies": {}}\n//bun.lock\n'
|
||||
|
||||
create_script() {
|
||||
# Usage: create_script path language content [extra_json_fields]
|
||||
# Note: lock must be non-empty; empty string ("") is treated as None by the backend
|
||||
# (scripts.rs:798-800), which triggers dependency resolution instead of direct deployment.
|
||||
# For bun scripts, the lock must contain "//bun.lock" as a split pattern.
|
||||
local path="$1" lang="$2" content="$3" extra="${4:-}"
|
||||
local json
|
||||
json=$(jq -n \
|
||||
--arg path "$path" \
|
||||
--arg lang "$lang" \
|
||||
--arg content "$content" \
|
||||
--arg summary "test" \
|
||||
--arg desc "test" \
|
||||
--arg lock "$BUN_EMPTY_LOCK" \
|
||||
'{path: $path, language: $lang, content: $content, summary: $summary, description: $desc, lock: $lock}')
|
||||
if [ -n "$extra" ]; then
|
||||
json=$(echo "$json" | jq ". + $extra")
|
||||
fi
|
||||
local hash
|
||||
hash=$(api POST "scripts/create" "$json")
|
||||
# Small delay for DB visibility after tx commit
|
||||
sleep 0.2
|
||||
echo "$hash"
|
||||
}
|
||||
|
||||
run_script() {
|
||||
# Usage: run_script path args_json
|
||||
api POST "jobs/run/p/$1" "$2"
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# TEST 1: Deploy a script and run it 5 times in close succession
|
||||
###############################################################################
|
||||
echo ""
|
||||
log_info "=== TEST 1: Deploy & run script 5 times rapidly ==="
|
||||
|
||||
P1="u/admin/e2e_simple_$TS"
|
||||
H1=$(create_script "$P1" "bun" 'export function main(x: number = 0) { return { result: x * 2 }; }')
|
||||
|
||||
if echo "$H1" | grep -qE '^[0-9a-f]{16}$'; then
|
||||
log_pass "Script created: $H1"
|
||||
else
|
||||
log_fail "Script creation" "$H1"
|
||||
fi
|
||||
|
||||
log_info "Running 5 times rapidly..."
|
||||
JOB_IDS=()
|
||||
for i in $(seq 1 5); do
|
||||
JID=$(run_script "$P1" "{\"x\": $i}")
|
||||
JOB_IDS+=("$JID")
|
||||
done
|
||||
log_info "Jobs: ${JOB_IDS[*]}"
|
||||
|
||||
log_info "Waiting for completion..."
|
||||
all_ok=true
|
||||
for i in "${!JOB_IDS[@]}"; do
|
||||
JID="${JOB_IDS[$i]}"
|
||||
R=$(wait_job "$JID" 30)
|
||||
success=$(echo "$R" | jq -r '.success // false')
|
||||
value=$(echo "$R" | jq -r '.result.result // "null"')
|
||||
expected=$(( (i + 1) * 2 ))
|
||||
if [ "$success" = "true" ] && [ "$value" = "$expected" ]; then
|
||||
log_pass "Job $((i+1)): x=$((i+1)) → $value (correct)"
|
||||
else
|
||||
log_fail "Job $((i+1))" "success=$success value=$value expected=$expected"
|
||||
all_ok=false
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$all_ok" = "true" ]; then
|
||||
log_pass "All 5 runs completed correctly (no debounce — different args)"
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
# TEST 2: Redeploy script WITHOUT lock in close succession
|
||||
###############################################################################
|
||||
echo ""
|
||||
log_info "=== TEST 2: Redeploy without lock in rapid succession ==="
|
||||
|
||||
P2="u/admin/e2e_nolock_$TS"
|
||||
|
||||
# Deploy 5 versions of the same script without lock → triggers dependency jobs
|
||||
DEPLOY_HASHES=()
|
||||
for i in $(seq 1 5); do
|
||||
content="export function main(x: number = 0) { return { result: x * $i, version: $i }; }"
|
||||
parent_extra=""
|
||||
if [ "${#DEPLOY_HASHES[@]}" -gt 0 ]; then
|
||||
last_hash="${DEPLOY_HASHES[-1]}"
|
||||
parent_extra="{\"parent_hash\": \"$last_hash\"}"
|
||||
fi
|
||||
|
||||
# Deploy without lock (omit lock field entirely)
|
||||
json=$(jq -n \
|
||||
--arg path "$P2" \
|
||||
--arg content "$content" \
|
||||
--arg summary "v$i" \
|
||||
--arg desc "test" \
|
||||
'{path: $path, language: "bun", content: $content, summary: $summary, description: $desc}')
|
||||
if [ -n "$parent_extra" ]; then
|
||||
json=$(echo "$json" | jq ". + $parent_extra")
|
||||
fi
|
||||
|
||||
hash=$(api POST "scripts/create" "$json")
|
||||
if echo "$hash" | grep -qE '^[0-9a-f]{16}$'; then
|
||||
DEPLOY_HASHES+=("$hash")
|
||||
log_info "Deploy $i: $hash"
|
||||
else
|
||||
log_fail "Deploy $i" "$hash"
|
||||
# If path conflict, the script already exists from a previous version
|
||||
break
|
||||
fi
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
# Wait for dependency resolution
|
||||
log_info "Waiting 15s for dependency jobs..."
|
||||
sleep 15
|
||||
|
||||
# Check the latest script — should have lock resolved
|
||||
SCRIPT_INFO=$(api GET "scripts/get/p/$P2")
|
||||
LOCK=$(echo "$SCRIPT_INFO" | jq -r '.lock // "null"')
|
||||
if [ "$LOCK" != "null" ] && [ -n "$LOCK" ]; then
|
||||
log_pass "Latest version has lock resolved"
|
||||
else
|
||||
log_info "Lock not yet resolved: $LOCK"
|
||||
fi
|
||||
|
||||
# Run the latest version to verify it works
|
||||
sleep 0.5
|
||||
JID2=$(run_script "$P2" '{"x": 10}')
|
||||
if echo "$JID2" | grep -qE '^[0-9a-f-]{36}$'; then
|
||||
R2=$(wait_job "$JID2" 30)
|
||||
success=$(echo "$R2" | jq -r '.success // false')
|
||||
if [ "$success" = "true" ]; then
|
||||
version=$(echo "$R2" | jq -r '.result.version // "?"')
|
||||
log_pass "Latest version runs: version=$version"
|
||||
else
|
||||
err=$(echo "$R2" | jq -r '.result.error.message // "unknown"' 2>/dev/null)
|
||||
log_fail "Run latest version" "success=false err=$err"
|
||||
fi
|
||||
else
|
||||
log_fail "Run latest version" "bad job id: $JID2"
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
# TEST 3: Script with debounce_delay_s — rapid runs with SAME args
|
||||
###############################################################################
|
||||
echo ""
|
||||
log_info "=== TEST 3: Debounce with same args (should debounce) ==="
|
||||
|
||||
P3="u/admin/e2e_debounce_$TS"
|
||||
H3=$(create_script "$P3" "bun" \
|
||||
'export function main(x: number = 0) { return { result: x }; }' \
|
||||
'{"debounce_delay_s": 3}')
|
||||
|
||||
if echo "$H3" | grep -qE '^[0-9a-f]{16}$'; then
|
||||
log_pass "Debounce script created: $H3"
|
||||
else
|
||||
log_fail "Debounce script creation" "$H3"
|
||||
fi
|
||||
|
||||
log_info "Running 5 times with same args {x: 42}..."
|
||||
DEB_IDS=()
|
||||
for i in $(seq 1 5); do
|
||||
JID=$(run_script "$P3" '{"x": 42}')
|
||||
DEB_IDS+=("$JID")
|
||||
log_info " Run $i: $JID"
|
||||
done
|
||||
|
||||
log_info "Waiting 10s for debounce delay (3s) + execution..."
|
||||
sleep 10
|
||||
|
||||
executed=0
|
||||
skipped=0
|
||||
for JID in "${DEB_IDS[@]}"; do
|
||||
if ! echo "$JID" | grep -qE '^[0-9a-f-]{36}$'; then
|
||||
log_info " Invalid job id: $JID"
|
||||
continue
|
||||
fi
|
||||
R=$(wait_job "$JID" 5 2>/dev/null || echo '{"completed":false}')
|
||||
completed=$(echo "$R" | jq -r '.completed // false')
|
||||
success=$(echo "$R" | jq -r '.success // false')
|
||||
if [ "$completed" = "true" ] && [ "$success" = "true" ]; then
|
||||
((executed++)) || true
|
||||
elif [ "$completed" = "true" ]; then
|
||||
((skipped++)) || true
|
||||
fi
|
||||
done
|
||||
|
||||
log_info "Results: $executed executed, $skipped skipped out of ${#DEB_IDS[@]}"
|
||||
if [ "$executed" -eq 1 ] && [ "$skipped" -ge 3 ]; then
|
||||
log_pass "Debouncing perfect: 1 executed, $skipped skipped"
|
||||
elif [ "$executed" -le 2 ] && [ "$skipped" -ge 2 ]; then
|
||||
log_pass "Debouncing working: $executed executed, $skipped skipped"
|
||||
else
|
||||
log_fail "Debounce same args" "executed=$executed skipped=$skipped (want ~1 exec, ~4 skip)"
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
# TEST 3b: Different args should NOT debounce against each other
|
||||
###############################################################################
|
||||
echo ""
|
||||
log_info "=== TEST 3b: Debounce with different args (should NOT debounce) ==="
|
||||
|
||||
DIFF_IDS=()
|
||||
for i in $(seq 1 3); do
|
||||
JID=$(run_script "$P3" "{\"x\": $((i * 100))}")
|
||||
DIFF_IDS+=("$JID")
|
||||
done
|
||||
|
||||
log_info "Waiting 8s..."
|
||||
sleep 8
|
||||
|
||||
diff_exec=0
|
||||
for JID in "${DIFF_IDS[@]}"; do
|
||||
if ! echo "$JID" | grep -qE '^[0-9a-f-]{36}$'; then continue; fi
|
||||
R=$(wait_job "$JID" 5 2>/dev/null || echo '{"completed":false}')
|
||||
success=$(echo "$R" | jq -r '.success // false')
|
||||
if [ "$success" = "true" ]; then ((diff_exec++)) || true; fi
|
||||
done
|
||||
|
||||
if [ "$diff_exec" -eq 3 ]; then
|
||||
log_pass "Different args: all 3 executed independently"
|
||||
else
|
||||
log_fail "Different args" "only $diff_exec/3 executed"
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
# TEST 4: Custom debounce_key with $args interpolation
|
||||
###############################################################################
|
||||
echo ""
|
||||
log_info "=== TEST 4: Custom debounce key ==="
|
||||
|
||||
P4="u/admin/e2e_custom_key_$TS"
|
||||
H4=$(create_script "$P4" "bun" \
|
||||
'export function main(event_id: string = "", data: string = "") { return { event_id, data }; }' \
|
||||
'{"debounce_delay_s": 3, "debounce_key": "event#$args.event_id"}')
|
||||
|
||||
if echo "$H4" | grep -qE '^[0-9a-f]{16}$'; then
|
||||
log_pass "Custom key script created: $H4"
|
||||
else
|
||||
log_fail "Custom key script creation" "$H4"
|
||||
fi
|
||||
|
||||
# Same event_id → should debounce
|
||||
log_info "3 runs with same event_id..."
|
||||
SAME_IDS=()
|
||||
for i in $(seq 1 3); do
|
||||
JID=$(run_script "$P4" "{\"event_id\": \"evt_001\", \"data\": \"payload_$i\"}")
|
||||
SAME_IDS+=("$JID")
|
||||
done
|
||||
|
||||
# Different event_id → should NOT debounce
|
||||
JID_DIFF=$(run_script "$P4" '{"event_id": "evt_002", "data": "different"}')
|
||||
|
||||
log_info "Waiting 8s..."
|
||||
sleep 8
|
||||
|
||||
same_exec=0
|
||||
same_skip=0
|
||||
for JID in "${SAME_IDS[@]}"; do
|
||||
if ! echo "$JID" | grep -qE '^[0-9a-f-]{36}$'; then continue; fi
|
||||
R=$(wait_job "$JID" 5 2>/dev/null || echo '{"completed":false}')
|
||||
completed=$(echo "$R" | jq -r '.completed // false')
|
||||
success=$(echo "$R" | jq -r '.success // false')
|
||||
if [ "$completed" = "true" ] && [ "$success" = "true" ]; then
|
||||
data=$(echo "$R" | jq -r '.result.data // "?"')
|
||||
((same_exec++)) || true
|
||||
log_info " Executed: data=$data"
|
||||
elif [ "$completed" = "true" ]; then
|
||||
((same_skip++)) || true
|
||||
fi
|
||||
done
|
||||
|
||||
log_info "Same event_id: $same_exec executed, $same_skip skipped"
|
||||
if [ "$same_exec" -eq 1 ] && [ "$same_skip" -ge 1 ]; then
|
||||
log_pass "Custom key debounce: same event_id debounced correctly"
|
||||
elif [ "$same_exec" -le 2 ]; then
|
||||
log_pass "Custom key debounce working: $same_exec executed, $same_skip skipped"
|
||||
else
|
||||
log_fail "Custom key debounce" "exec=$same_exec skip=$same_skip"
|
||||
fi
|
||||
|
||||
# Check different event_id ran independently
|
||||
if echo "$JID_DIFF" | grep -qE '^[0-9a-f-]{36}$'; then
|
||||
R_DIFF=$(wait_job "$JID_DIFF" 10 2>/dev/null || echo '{"completed":false}')
|
||||
diff_success=$(echo "$R_DIFF" | jq -r '.success // false')
|
||||
if [ "$diff_success" = "true" ]; then
|
||||
log_pass "Different event_id: executed independently"
|
||||
else
|
||||
log_info "Different event_id: success=$diff_success"
|
||||
fi
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
# TEST 5: Git sync with bad target — debounced deployment callbacks
|
||||
###############################################################################
|
||||
echo ""
|
||||
log_info "=== TEST 5: Git sync debounce + aggregation ==="
|
||||
|
||||
# Create git repo resource
|
||||
api POST "resources/create?update_if_exists=true" '{
|
||||
"path": "u/admin/e2e_bad_git_repo",
|
||||
"description": "Bad git repo for testing",
|
||||
"resource_type": "git_repository",
|
||||
"value": {"url": "https://github.com/nonexistent/nope.git", "branch": "main", "token": "bad"}
|
||||
}' > /dev/null 2>&1
|
||||
log_info "Created git repo resource"
|
||||
|
||||
# Create a sync script at a folder path where the 2nd segment is a number >= 28103.
|
||||
# is_script_meets_min_version parses split("/").skip(1).next() as the version number.
|
||||
# This enables debounce_delay_s=5 and debounce_args_to_accumulate=["items"].
|
||||
api POST "folders/create" '{"name": "28103"}' > /dev/null 2>&1
|
||||
P5="f/28103/e2e_sync_$TS"
|
||||
H5=$(create_script "$P5" "bun" \
|
||||
'export function main(repo_url_resource_path: string = "", workspace_id: string = "", items: any[] = [], use_individual_branch: boolean = false, group_by_folder: boolean = false, parent_workspace_id: string = "") { return { synced: items.length, items }; }')
|
||||
|
||||
if echo "$H5" | grep -qE '^[0-9a-f]{16}$'; then
|
||||
log_pass "Sync script created: $H5"
|
||||
else
|
||||
log_fail "Sync script creation" "$H5"
|
||||
fi
|
||||
|
||||
# Configure git sync with include_path to match deployed scripts.
|
||||
# Without include_path, path_matches_filters returns false and no DeploymentCallback is created.
|
||||
api POST "workspaces/edit_git_sync_config" "{
|
||||
\"git_sync_settings\": {
|
||||
\"include_type\": [\"script\"],
|
||||
\"include_path\": [\"**\"],
|
||||
\"repositories\": [{
|
||||
\"script_path\": \"$P5\",
|
||||
\"git_repo_resource_path\": \"\$res:u/admin/e2e_bad_git_repo\",
|
||||
\"use_individual_branch\": false,
|
||||
\"group_by_folder\": false
|
||||
}]
|
||||
}
|
||||
}" > /dev/null 2>&1
|
||||
log_pass "Git sync configured with include_path and versioned folder script path"
|
||||
|
||||
# Deploy 5 scripts rapidly to trigger git sync.
|
||||
# Scripts are created with lock="" (via create_script), so handle_deployment_metadata
|
||||
# fires immediately after tx commit (not after dependency resolution).
|
||||
log_info "Deploying 5 scripts to trigger git sync..."
|
||||
for i in $(seq 1 5); do
|
||||
dp="u/admin/e2e_gitsync_${TS}_$i"
|
||||
create_script "$dp" "bun" "export function main() { return { v: $i }; }" > /dev/null
|
||||
log_info " Deployed $dp"
|
||||
done
|
||||
|
||||
# Wait for debounce delay (5s) + execution
|
||||
log_info "Waiting 15s for debounce (5s) + execution..."
|
||||
sleep 15
|
||||
|
||||
# Check deployment callback jobs for our sync script.
|
||||
# Debounced jobs have is_skipped=true (but success=true), so we use is_skipped to distinguish.
|
||||
SYNC_JOBS=$(api GET "jobs/completed/list?script_path_exact=$P5&job_kinds=deploymentcallback")
|
||||
SYNC_TOTAL=$(echo "$SYNC_JOBS" | jq 'length')
|
||||
SYNC_EXECUTED=$(echo "$SYNC_JOBS" | jq '[.[] | select(.is_skipped != true)] | length')
|
||||
SYNC_SKIPPED=$(echo "$SYNC_JOBS" | jq '[.[] | select(.is_skipped == true)] | length')
|
||||
|
||||
log_info "Sync jobs: total=$SYNC_TOTAL executed=$SYNC_EXECUTED skipped=$SYNC_SKIPPED"
|
||||
|
||||
if [ "$SYNC_TOTAL" -gt 0 ]; then
|
||||
# With debouncing (5s delay), rapid deploys should be consolidated.
|
||||
# All 5 jobs are created but most should be skipped (debounced).
|
||||
if [ "$SYNC_SKIPPED" -gt 0 ]; then
|
||||
log_pass "Git sync debouncing: $SYNC_EXECUTED executed, $SYNC_SKIPPED debounced out of $SYNC_TOTAL"
|
||||
else
|
||||
log_fail "Git sync debouncing" "No jobs were debounced ($SYNC_TOTAL all executed independently)"
|
||||
fi
|
||||
|
||||
# Check if items were aggregated in the executed (non-skipped) job(s)
|
||||
for idx in $(seq 0 $((SYNC_TOTAL - 1))); do
|
||||
is_skipped=$(echo "$SYNC_JOBS" | jq -r ".[$idx].is_skipped")
|
||||
[ "$is_skipped" = "true" ] && continue
|
||||
jid=$(echo "$SYNC_JOBS" | jq -r ".[$idx].id")
|
||||
r=$(api GET "jobs/completed/get_result/$jid")
|
||||
items_count=$(echo "$r" | jq '.items | length // 0')
|
||||
log_info " Executed sync job $jid: items=$items_count"
|
||||
if [ "$items_count" -gt 1 ]; then
|
||||
log_pass "Items aggregated: $items_count items in single sync job"
|
||||
fi
|
||||
done
|
||||
else
|
||||
# Check queued — jobs may still be pending debounce delay
|
||||
Q=$(api GET "jobs/queue/list?script_path_exact=$P5&job_kinds=deploymentcallback")
|
||||
QC=$(echo "$Q" | jq 'length')
|
||||
log_info "No completed sync jobs. $QC queued."
|
||||
if [ "$QC" -gt 0 ] && [ "$QC" -lt 5 ]; then
|
||||
log_pass "Git sync debouncing (queued): $QC jobs for 5 deploys"
|
||||
elif [ "$QC" -eq 0 ]; then
|
||||
log_fail "Git sync" "No deployment callback jobs found (completed or queued)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Cleanup git sync
|
||||
api POST "workspaces/edit_git_sync_config" '{"git_sync_settings": null}' > /dev/null 2>&1
|
||||
log_info "Git sync config cleared"
|
||||
|
||||
###############################################################################
|
||||
# Summary
|
||||
###############################################################################
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo -e "Results: ${GREEN}$pass passed${NC}, ${RED}$fail failed${NC}"
|
||||
echo "========================================="
|
||||
|
||||
if [ "$fail" -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
10
backend/tests/fixtures/wmill_cli_test.sql
vendored
Normal file
10
backend/tests/fixtures/wmill_cli_test.sql
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
-- Fixture for testing wmill CLI variable/resource get from bash scripts
|
||||
|
||||
INSERT INTO variable (workspace_id, path, value, is_secret, description, extra_perms)
|
||||
VALUES ('test-workspace', 'u/test-user/test_var', 'hello from variable', false, 'A test variable', '{"u/test-user": true}');
|
||||
|
||||
INSERT INTO resource_type (workspace_id, name, schema, description, created_by)
|
||||
VALUES ('test-workspace', 'test_object', '{}', 'Test object type', 'test-user');
|
||||
|
||||
INSERT INTO resource (workspace_id, path, value, description, resource_type, extra_perms, created_by)
|
||||
VALUES ('test-workspace', 'u/test-user/test_res', '{"host": "localhost", "port": 5432}', 'A test resource', 'test_object', '{"u/test-user": true}', 'test-user');
|
||||
@@ -993,6 +993,80 @@ echo "hello $msg"
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base", "wmill_cli_test"))]
|
||||
async fn test_bash_wmill_variable_get(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
// The bash script uses wmill CLI to get the variable value.
|
||||
// The worker sets WM_TOKEN, WM_WORKSPACE, and BASE_INTERNAL_URL as env vars,
|
||||
// and the CLI auto-configures from them when no workspace is explicitly set.
|
||||
// We point WMILL_CONFIG_DIR to a clean temp dir so no local active workspace interferes.
|
||||
let content = r#"
|
||||
export WMILL_CONFIG_DIR=$(mktemp -d)
|
||||
result=$(wmill variable get "u/test-user/test_var" --json | jq -r .value)
|
||||
echo "$result"
|
||||
"#
|
||||
.to_owned();
|
||||
|
||||
let job = RunJob::from(JobPayload::Code(RawCode {
|
||||
hash: None,
|
||||
content,
|
||||
path: None,
|
||||
lock: None,
|
||||
language: ScriptLang::Bash,
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
}))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
assert_eq!(job.json_result(), Some(json!("hello from variable")));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[sqlx::test(fixtures("base", "wmill_cli_test"))]
|
||||
async fn test_bash_wmill_resource_get(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
initialize_tracing().await;
|
||||
let server = ApiServer::start(db.clone()).await?;
|
||||
let port = server.addr.port();
|
||||
|
||||
// The bash script uses wmill CLI to get the resource value.
|
||||
// We point WMILL_CONFIG_DIR to a clean temp dir so no local active workspace interferes.
|
||||
let content = r#"
|
||||
export WMILL_CONFIG_DIR=$(mktemp -d)
|
||||
result=$(wmill resource get "u/test-user/test_res" --json | jq -c .value)
|
||||
echo "$result"
|
||||
"#
|
||||
.to_owned();
|
||||
|
||||
let job = RunJob::from(JobPayload::Code(RawCode {
|
||||
hash: None,
|
||||
content,
|
||||
path: None,
|
||||
lock: None,
|
||||
language: ScriptLang::Bash,
|
||||
cache_ttl: None,
|
||||
cache_ignore_s3_path: None,
|
||||
dedicated_worker: None,
|
||||
concurrency_settings: windmill_common::runnable_settings::ConcurrencySettings::default()
|
||||
.into(),
|
||||
debouncing_settings: windmill_common::runnable_settings::DebouncingSettings::default(),
|
||||
}))
|
||||
.run_until_complete(&db, false, port)
|
||||
.await;
|
||||
// Bash echo outputs are returned as strings, so the JSON is a string value
|
||||
assert_eq!(
|
||||
job.json_result(),
|
||||
Some(json!("{\"host\":\"localhost\",\"port\":5432}"))
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "nu")]
|
||||
#[sqlx::test(fixtures("base"))]
|
||||
async fn test_nu_job(db: Pool<Postgres>) -> anyhow::Result<()> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.641.0
|
||||
version: 1.642.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -19671,6 +19671,8 @@ components:
|
||||
type: boolean
|
||||
lock:
|
||||
type: string
|
||||
flow_path:
|
||||
type: string
|
||||
required:
|
||||
- args
|
||||
|
||||
|
||||
@@ -91,8 +91,8 @@ impl RawWebhookArgs {
|
||||
get_random_file_name, get_workspace_s3_resource, upload_file_internal,
|
||||
};
|
||||
use futures::TryStreamExt;
|
||||
use windmill_object_store::object_store_reexports::{Attribute, Attributes};
|
||||
use windmill_object_store::build_object_store_client;
|
||||
use windmill_object_store::object_store_reexports::{Attribute, Attributes};
|
||||
|
||||
let (_, s3_resource) = get_workspace_s3_resource(authed, db, None, w_id, None).await?;
|
||||
|
||||
@@ -106,20 +106,24 @@ impl RawWebhookArgs {
|
||||
Error::BadRequest(format!("Error reading multipart field: {}", e.body_text()))
|
||||
})? {
|
||||
if let Some(name) = field.name().map(|x| x.to_string()) {
|
||||
if let Some(content_type) = field.content_type() {
|
||||
if field.file_name().is_some() {
|
||||
let content_type = field
|
||||
.content_type()
|
||||
.unwrap_or("application/octet-stream")
|
||||
.to_string();
|
||||
let ext = field
|
||||
.file_name()
|
||||
.map(|x| x.split('.').last())
|
||||
.flatten()
|
||||
.and_then(|x| x.split('.').last())
|
||||
.map(|x| x.to_string());
|
||||
let filename = field.file_name().map(|x| x.to_string());
|
||||
|
||||
let file_key = get_random_file_name(ext);
|
||||
|
||||
let options = Attributes::from_iter(vec![
|
||||
(Attribute::ContentType, content_type.to_string()),
|
||||
(Attribute::ContentType, content_type),
|
||||
(
|
||||
Attribute::ContentDisposition,
|
||||
if let Some(filename) = field.file_name() {
|
||||
if let Some(filename) = filename {
|
||||
format!("inline; filename=\"{}\"", filename)
|
||||
} else {
|
||||
"inline".to_string()
|
||||
|
||||
@@ -42,8 +42,6 @@ use windmill_common::runnable_settings::{
|
||||
};
|
||||
#[cfg(feature = "inline_preview")]
|
||||
use windmill_common::runtime_assets::{register_runtime_asset, InsertRuntimeAssetParams};
|
||||
use windmill_types::s3::BundleFormat;
|
||||
use windmill_object_store::upload_artifact_to_store;
|
||||
use windmill_common::scripts::ScriptRunnableSettingsInline;
|
||||
use windmill_common::triggers::TriggerMetadata;
|
||||
use windmill_common::utils::{RunnableKind, WarnAfterExt};
|
||||
@@ -54,8 +52,10 @@ use windmill_common::workspace_dependencies::{
|
||||
use windmill_common::DYNAMIC_INPUT_CACHE;
|
||||
#[cfg(all(feature = "enterprise", feature = "smtp"))]
|
||||
use windmill_common::{email_oss::send_email_html, server::load_smtp_config};
|
||||
use windmill_object_store::upload_artifact_to_store;
|
||||
#[cfg(feature = "inline_preview")]
|
||||
use windmill_parser::asset_parser::AssetKind;
|
||||
use windmill_types::s3::BundleFormat;
|
||||
#[cfg(feature = "inline_preview")]
|
||||
use windmill_worker::get_worker_internal_server_inline_utils;
|
||||
|
||||
@@ -1386,7 +1386,8 @@ async fn get_logs_from_store(
|
||||
log_file_index: &Option<Vec<String>>,
|
||||
) -> Option<error::Result<Body>> {
|
||||
use futures::StreamExt;
|
||||
let stream = windmill_object_store::get_logs_from_store(log_offset, logs, log_file_index).await?;
|
||||
let stream =
|
||||
windmill_object_store::get_logs_from_store(log_offset, logs, log_file_index).await?;
|
||||
let header = bytes::Bytes::from(
|
||||
r#"to remove ansi colors, use: | sed 's/\x1B\[[0-9;]\{1,\}[A-Za-z]//g'
|
||||
"#
|
||||
@@ -2849,6 +2850,7 @@ struct Preview {
|
||||
dedicated_worker: Option<bool>,
|
||||
lock: Option<String>,
|
||||
format: Option<String>,
|
||||
flow_path: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "inline_preview")]
|
||||
@@ -4509,6 +4511,14 @@ async fn run_preview_script(
|
||||
check_tag_available_for_workspace(&db, &w_id, &tag, &authed).await?;
|
||||
let tx = PushIsolationLevel::Isolated(user_db.clone(), authed.clone().into());
|
||||
|
||||
let preview_args = preview.args.unwrap_or_default();
|
||||
let flow_path_extra = preview.flow_path.map(|fp| {
|
||||
let mut extra = HashMap::new();
|
||||
extra.insert("_FLOW_PATH".to_string(), to_raw_value(&fp));
|
||||
extra
|
||||
});
|
||||
let push_args = PushArgs { extra: flow_path_extra, args: &preview_args };
|
||||
|
||||
let (uuid, tx) = push(
|
||||
&db,
|
||||
tx,
|
||||
@@ -4532,7 +4542,7 @@ async fn run_preview_script(
|
||||
dedicated_worker: preview.dedicated_worker,
|
||||
}),
|
||||
},
|
||||
PushArgs::from(&preview.args.unwrap_or_default()),
|
||||
push_args,
|
||||
authed.display_username(),
|
||||
&authed.email,
|
||||
username_to_permissioned_as(&authed.username),
|
||||
@@ -5772,7 +5782,9 @@ async fn get_log_file(Path((_w_id, file_p)): Path<(String, String)>) -> error::R
|
||||
#[cfg(all(feature = "enterprise", feature = "parquet"))]
|
||||
if let Some(os) = windmill_object_store::get_object_store().await {
|
||||
let file = os
|
||||
.get(&windmill_object_store::object_store_reexports::Path::from(format!("logs/{file_p}")))
|
||||
.get(&windmill_object_store::object_store_reexports::Path::from(
|
||||
format!("logs/{file_p}"),
|
||||
))
|
||||
.await;
|
||||
if let Ok(file) = file {
|
||||
if let Ok(bytes) = file.bytes().await {
|
||||
|
||||
@@ -427,7 +427,7 @@ fn format_pull_query(peek: String) -> String {
|
||||
j.same_worker, j.pre_run_error, j.visible_to_owner,
|
||||
j.tag, j.concurrent_limit, j.concurrency_time_window_s, j.flow_innermost_root_job, j.root_job,
|
||||
j.timeout, j.flow_step_id, j.cache_ttl, q.cache_ignore_s3_path, q.runnable_settings_handle, j.priority, j.raw_code, j.raw_lock, j.raw_flow,
|
||||
j.script_entrypoint_override, j.preprocessed, pj.runnable_path as parent_runnable_path,
|
||||
j.script_entrypoint_override, j.preprocessed, COALESCE(pj.runnable_path, j.args->>'_FLOW_PATH') as parent_runnable_path,
|
||||
COALESCE(p.email, j.permissioned_as_email) as permissioned_as_email, p.username as permissioned_as_username, p.is_admin as permissioned_as_is_admin,
|
||||
p.is_operator as permissioned_as_is_operator, p.groups as permissioned_as_groups, p.folders as permissioned_as_folders, p.end_user_email as permissioned_as_end_user_email
|
||||
FROM q, j
|
||||
|
||||
@@ -5368,7 +5368,6 @@ async fn push_inner<'c, 'd>(
|
||||
job_id,
|
||||
&args,
|
||||
&mut tx,
|
||||
_db,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
@@ -6208,7 +6207,7 @@ pub async fn get_same_worker_job(
|
||||
v2_job.raw_code,
|
||||
v2_job.raw_lock,
|
||||
v2_job.raw_flow,
|
||||
pj.runnable_path as parent_runnable_path,
|
||||
COALESCE(pj.runnable_path, v2_job.args->>'_FLOW_PATH') as parent_runnable_path,
|
||||
p.email as permissioned_as_email, p.username as permissioned_as_username, p.is_admin as permissioned_as_is_admin,
|
||||
p.is_operator as permissioned_as_is_operator, p.groups as permissioned_as_groups, p.folders as permissioned_as_folders, p.end_user_email as permissioned_as_end_user_email
|
||||
FROM v2_job_queue
|
||||
|
||||
3079
backend/windmill-queue/tests/debounce_test.rs
Normal file
3079
backend/windmill-queue/tests/debounce_test.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,6 @@ use tiberius::{
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::compat::TokioAsyncWriteCompatExt;
|
||||
use uuid::Uuid;
|
||||
use windmill_object_store::convert_json_line_stream;
|
||||
use windmill_common::utils::merge_raw_values_to_object;
|
||||
use windmill_common::worker::SqlResultCollectionStrategy;
|
||||
use windmill_common::{
|
||||
@@ -19,6 +18,7 @@ use windmill_common::{
|
||||
utils::empty_as_none,
|
||||
worker::{to_raw_value, Connection},
|
||||
};
|
||||
use windmill_object_store::convert_json_line_stream;
|
||||
use windmill_parser_sql::{parse_db_resource, parse_mssql_sig, parse_s3_mode};
|
||||
use windmill_queue::MiniPulledJob;
|
||||
use windmill_queue::{append_logs, CanceledBy};
|
||||
@@ -428,9 +428,7 @@ fn sql_to_json_value(val: ColumnData) -> Result<Box<RawValue>, Error> {
|
||||
}
|
||||
|
||||
fn numeric_to_raw_value(numeric: &tiberius::numeric::Numeric) -> Result<Box<RawValue>, Error> {
|
||||
// tiberius::Numeric::to_string is broken, don't use it
|
||||
|
||||
let sign = if numeric.int_part().is_negative() {
|
||||
let sign = if numeric.value().is_negative() {
|
||||
"-"
|
||||
} else {
|
||||
""
|
||||
@@ -468,6 +466,7 @@ where
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tiberius::numeric::Numeric;
|
||||
|
||||
#[test]
|
||||
fn test_sql_to_json_value_numeric_null() {
|
||||
@@ -477,7 +476,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_sql_to_json_value_numeric_integer() {
|
||||
use tiberius::numeric::Numeric;
|
||||
let numeric = Numeric::new_with_scale(12345, 0);
|
||||
let result = sql_to_json_value(ColumnData::Numeric(Some(numeric))).unwrap();
|
||||
assert_eq!(result.get(), "12345");
|
||||
@@ -485,7 +483,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_sql_to_json_value_numeric_decimal() {
|
||||
use tiberius::numeric::Numeric;
|
||||
let numeric = Numeric::new_with_scale(123456, 2); // Represents 1234.56
|
||||
let result = sql_to_json_value(ColumnData::Numeric(Some(numeric))).unwrap();
|
||||
assert_eq!(result.get(), "1234.56");
|
||||
@@ -493,7 +490,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_sql_to_json_value_numeric_negative() {
|
||||
use tiberius::numeric::Numeric;
|
||||
let numeric = Numeric::new_with_scale(-98765, 2); // Represents -987.65
|
||||
let result = sql_to_json_value(ColumnData::Numeric(Some(numeric))).unwrap();
|
||||
assert_eq!(result.get(), "-987.65");
|
||||
@@ -501,7 +497,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_sql_to_json_value_numeric_negative_integer() {
|
||||
use tiberius::numeric::Numeric;
|
||||
let numeric = Numeric::new_with_scale(-98765, 0);
|
||||
let result = sql_to_json_value(ColumnData::Numeric(Some(numeric))).unwrap();
|
||||
assert_eq!(result.get(), "-98765");
|
||||
@@ -509,15 +504,21 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_sql_to_json_value_numeric_high_precision() {
|
||||
use tiberius::numeric::Numeric;
|
||||
let numeric = Numeric::new_with_scale(123456789012345, 10); // High precision
|
||||
let result = sql_to_json_value(ColumnData::Numeric(Some(numeric))).unwrap();
|
||||
assert_eq!(result.get(), "12345.6789012345");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sql_to_json_value_numeric_negative_fractional_only() {
|
||||
// -0.4: int_part() is 0, so old code lost the negative sign
|
||||
let numeric = Numeric::new_with_scale(-4, 1);
|
||||
let result = sql_to_json_value(ColumnData::Numeric(Some(numeric))).unwrap();
|
||||
assert_eq!(result.get(), "-0.4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sql_to_json_value_numeric_7_69() {
|
||||
use tiberius::numeric::Numeric;
|
||||
let numeric = Numeric::new_with_scale(769, 2);
|
||||
let result = sql_to_json_value(ColumnData::Numeric(Some(numeric))).unwrap();
|
||||
assert_eq!(result.get(), "7.69");
|
||||
|
||||
@@ -1328,29 +1328,30 @@ pub async fn update_flow_status_after_job_completion_internal(
|
||||
|
||||
if module_step.is_preprocessor_step() && success {
|
||||
let tag_and_concurrency_key = get_tag_and_concurrency(&flow, db).await;
|
||||
let require_args = tag_and_concurrency_key.as_ref().is_some_and(|x| {
|
||||
let has_debouncing = flow_value
|
||||
.debouncing_settings
|
||||
.debounce_delay_s
|
||||
.filter(|x| *x > 0)
|
||||
.is_some();
|
||||
let concurrency_requires_args = tag_and_concurrency_key.as_ref().is_some_and(|x| {
|
||||
x.tag.as_ref().is_some_and(|t| t.contains("$args"))
|
||||
|| x.concurrency_key
|
||||
.as_ref()
|
||||
.is_some_and(|ck| ck.contains("$args"))
|
||||
});
|
||||
let mut tag = tag_and_concurrency_key
|
||||
.as_ref()
|
||||
.map(|x| x.tag.clone())
|
||||
.flatten();
|
||||
let require_args = concurrency_requires_args || has_debouncing;
|
||||
let mut tag = tag_and_concurrency_key.as_ref().and_then(|x| x.tag.clone());
|
||||
let concurrency_key = tag_and_concurrency_key
|
||||
.as_ref()
|
||||
.map(|x| x.concurrency_key.clone())
|
||||
.flatten();
|
||||
.and_then(|x| x.concurrency_key.clone());
|
||||
let concurrent_limit = tag_and_concurrency_key
|
||||
.as_ref()
|
||||
.map(|x| x.concurrent_limit)
|
||||
.flatten();
|
||||
.and_then(|x| x.concurrent_limit);
|
||||
let concurrency_time_window_s = tag_and_concurrency_key
|
||||
.as_ref()
|
||||
.map(|x| x.concurrency_time_window_s)
|
||||
.flatten();
|
||||
if require_args {
|
||||
.and_then(|x| x.concurrency_time_window_s);
|
||||
|
||||
let fetched_args = if require_args {
|
||||
let args = sqlx::query_scalar!(
|
||||
"SELECT result as \"result: Json<HashMap<String, Box<RawValue>>>\"
|
||||
FROM v2_job_completed
|
||||
@@ -1362,8 +1363,13 @@ pub async fn update_flow_status_after_job_completion_internal(
|
||||
.map_err(|e| {
|
||||
Error::internal_err(format!("error while fetching preprocessing args: {e:#}"))
|
||||
})?;
|
||||
let args_hm = args.unwrap_or_default().0;
|
||||
let args = PushArgs::from(&args_hm);
|
||||
Some(args.unwrap_or_default().0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if concurrency_requires_args {
|
||||
let args = PushArgs::from(fetched_args.as_ref().unwrap());
|
||||
if let Some(ck) = concurrency_key {
|
||||
insert_concurrency_key(
|
||||
&flow_job.workspace_id,
|
||||
@@ -1392,8 +1398,31 @@ pub async fn update_flow_status_after_job_completion_internal(
|
||||
.await?;
|
||||
}
|
||||
|
||||
// let tag = tag_and_concurrency_key.and_then(|tc| tc.tag.map(|t| interpolate_args(t.clone(), &args, &workspace_id)));
|
||||
// let concurrency_key = tag_and_concurrency_key.and_then(|tc| tc.concurrency_key.map(|ck| interpolate_args(&ck, &args, &workspace_id)));
|
||||
let scheduled_for: Option<chrono::DateTime<chrono::Utc>> = {
|
||||
#[cfg(feature = "private")]
|
||||
{
|
||||
if has_debouncing {
|
||||
let empty_hm = HashMap::new();
|
||||
let args = PushArgs::from(fetched_args.as_ref().unwrap_or(&empty_hm));
|
||||
windmill_queue::jobs_ee::maybe_debounce_post_preprocessing(
|
||||
&flow_value.debouncing_settings,
|
||||
&flow_job.runnable_path,
|
||||
&flow_job.workspace_id,
|
||||
flow,
|
||||
&args,
|
||||
db,
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "private"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
sqlx::query!(
|
||||
"WITH job_result AS (
|
||||
SELECT result
|
||||
@@ -1403,7 +1432,8 @@ pub async fn update_flow_status_after_job_completion_internal(
|
||||
updated_queue AS (
|
||||
UPDATE v2_job_queue
|
||||
SET running = false,
|
||||
tag = COALESCE($3, tag)
|
||||
tag = COALESCE($3, tag),
|
||||
scheduled_for = COALESCE($6, scheduled_for)
|
||||
WHERE id = $2
|
||||
)
|
||||
UPDATE v2_job
|
||||
@@ -1431,6 +1461,7 @@ pub async fn update_flow_status_after_job_completion_internal(
|
||||
tag,
|
||||
concurrent_limit,
|
||||
concurrency_time_window_s,
|
||||
scheduled_for,
|
||||
)
|
||||
.execute(db)
|
||||
.await
|
||||
|
||||
@@ -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.641.0";
|
||||
export const VERSION = "v1.642.0";
|
||||
|
||||
export async function login(email: string, password: string): Promise<string> {
|
||||
return await windmill.UserService.login({
|
||||
|
||||
1
cli/.npmrc
Normal file
1
cli/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
@jsr:registry=https://npm.jsr.io
|
||||
83
cli/build-npm.ts
Normal file
83
cli/build-npm.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { VERSION } from "./src/main.ts";
|
||||
import { readFileSync, writeFileSync, rmSync, cpSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
const outDir = "./npm";
|
||||
|
||||
// Parser npm packages — used as externals and added to generated package.json
|
||||
const parserPackages = [
|
||||
"windmill-parser-wasm-py", "windmill-parser-wasm-ts",
|
||||
"windmill-parser-wasm-regex", "windmill-parser-wasm-go",
|
||||
"windmill-parser-wasm-php", "windmill-parser-wasm-rust",
|
||||
"windmill-parser-wasm-yaml", "windmill-parser-wasm-csharp",
|
||||
"windmill-parser-wasm-nu", "windmill-parser-wasm-java",
|
||||
"windmill-parser-wasm-ruby",
|
||||
];
|
||||
const parserExternals = parserPackages.flatMap(p => ["--external", p]);
|
||||
|
||||
// Clean output directory
|
||||
rmSync(outDir, { recursive: true, force: true });
|
||||
|
||||
// Build with bun — bundle everything except esbuild (platform-specific binary),
|
||||
// svelte (optional, only needed for `wmill app bundle/dev`), and parser packages
|
||||
// (loaded at runtime via init() with readFileSync for the .wasm binary).
|
||||
console.log("Bundling with bun build...");
|
||||
const buildResult = Bun.spawnSync([
|
||||
"bun", "build", "src/main.ts",
|
||||
"--outdir", join(outDir, "esm"),
|
||||
"--target", "node",
|
||||
"--format", "esm",
|
||||
"--external", "esbuild",
|
||||
"--external", "svelte",
|
||||
"--external", "svelte/compiler",
|
||||
...parserExternals,
|
||||
], { cwd: import.meta.dir, stdout: "inherit", stderr: "inherit" });
|
||||
|
||||
if (buildResult.exitCode !== 0) {
|
||||
console.error("Build failed");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Add shebang to main.js
|
||||
const mainJsPath = join(outDir, "esm", "main.js");
|
||||
const mainJs = readFileSync(mainJsPath, "utf-8");
|
||||
writeFileSync(mainJsPath, "#!/usr/bin/env node\n" + mainJs, "utf-8");
|
||||
|
||||
// Copy LICENSE and README
|
||||
cpSync("../LICENSE", join(outDir, "LICENSE"));
|
||||
cpSync("README.md", join(outDir, "README.md"));
|
||||
|
||||
// Generate package.json
|
||||
const packageJson = {
|
||||
name: "windmill-cli",
|
||||
version: VERSION,
|
||||
description: "CLI for Windmill",
|
||||
license: "Apache 2.0",
|
||||
type: "module",
|
||||
main: "esm/main.js",
|
||||
bin: {
|
||||
wmill: "esm/main.js",
|
||||
},
|
||||
repository: {
|
||||
type: "git",
|
||||
url: "git+https://github.com/windmill-labs/windmill.git",
|
||||
},
|
||||
bugs: {
|
||||
url: "https://github.com/windmill-labs/windmill/issues",
|
||||
},
|
||||
dependencies: {
|
||||
esbuild: "^0.24.2",
|
||||
...Object.fromEntries(parserPackages.map(p => [p, "*"])),
|
||||
},
|
||||
optionalDependencies: {
|
||||
svelte: "^5.0.0",
|
||||
},
|
||||
};
|
||||
|
||||
writeFileSync(
|
||||
join(outDir, "package.json"),
|
||||
JSON.stringify(packageJson, null, 2) + "\n",
|
||||
"utf-8"
|
||||
);
|
||||
|
||||
console.log(`Built npm package v${VERSION} to ${outDir}/`);
|
||||
11
cli/build.sh
11
cli/build.sh
@@ -9,12 +9,11 @@ set -e
|
||||
# Generate utils client files
|
||||
./windmill-utils-internal/gen_wm_client.sh
|
||||
|
||||
# Add .ts extensions to windmill-utils-internal
|
||||
./windmill-utils-internal/remove-ts-ext.sh -r
|
||||
# Install dependencies
|
||||
bun install
|
||||
|
||||
# Run dnt
|
||||
echo "Running dnt..."
|
||||
deno run -A dnt.ts
|
||||
# Build npm package with bun
|
||||
echo "Building npm package..."
|
||||
bun run build-npm.ts
|
||||
|
||||
echo "Build complete!"
|
||||
|
||||
|
||||
312
cli/bun.lock
Normal file
312
cli/bun.lock
Normal file
@@ -0,0 +1,312 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "windmill-cli-dev",
|
||||
"dependencies": {
|
||||
"@cliffy/ansi": "npm:@jsr/cliffy__ansi@1.0.0",
|
||||
"@cliffy/command": "npm:@jsr/cliffy__command@1.0.0",
|
||||
"@cliffy/prompt": "npm:@jsr/cliffy__prompt@1.0.0",
|
||||
"@cliffy/table": "npm:@jsr/cliffy__table@1.0.0",
|
||||
"@windmill-labs/shared-utils": "^1.0.12",
|
||||
"diff": "^5.2.0",
|
||||
"esbuild": "0.24.2",
|
||||
"get-port": "7.1.0",
|
||||
"jszip": "3.8.0",
|
||||
"minimatch": "^10.0.0",
|
||||
"open": "^10.0.0",
|
||||
"svelte": "^5.45.2",
|
||||
"tar-stream": "^3.1.7",
|
||||
"windmill-parser-wasm-csharp": "*",
|
||||
"windmill-parser-wasm-go": "*",
|
||||
"windmill-parser-wasm-java": "*",
|
||||
"windmill-parser-wasm-nu": "*",
|
||||
"windmill-parser-wasm-php": "*",
|
||||
"windmill-parser-wasm-py": "*",
|
||||
"windmill-parser-wasm-regex": "*",
|
||||
"windmill-parser-wasm-ruby": "*",
|
||||
"windmill-parser-wasm-rust": "*",
|
||||
"windmill-parser-wasm-ts": "*",
|
||||
"windmill-parser-wasm-yaml": "*",
|
||||
"windmill-yaml-validator": "1.1.1",
|
||||
"ws": "8.18.0",
|
||||
"yaml": "^2.7.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/diff": "^5.2.3",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/tar-stream": "^3.1.4",
|
||||
"@types/ws": "^8.5.0",
|
||||
"typescript": "^5.7.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@cliffy/ansi": ["@jsr/cliffy__ansi@1.0.0", "https://npm.jsr.io/~/11/@jsr/cliffy__ansi/1.0.0.tgz", { "dependencies": { "@jsr/cliffy__internal": "1.0.0", "@jsr/std__encoding": "^1.0.10", "@jsr/std__fmt": "^1.0.9", "@jsr/std__io": "~0.225.3" } }, "sha512-JesgTdgR0aW1mZv96VqvRHr2efzr4MgDFMnoT+hkhaiCpmyBz33sHM5peAoMJUbGVfEfQAsysIXvvgoFYoveYg=="],
|
||||
|
||||
"@cliffy/command": ["@jsr/cliffy__command@1.0.0", "https://npm.jsr.io/~/11/@jsr/cliffy__command/1.0.0.tgz", { "dependencies": { "@jsr/cliffy__flags": "1.0.0", "@jsr/cliffy__internal": "1.0.0", "@jsr/cliffy__table": "1.0.0", "@jsr/std__fmt": "^1.0.9", "@jsr/std__semver": "^1.0.8", "@jsr/std__text": "^1.0.17" } }, "sha512-oObplVtu1tvpkhgpuPDHZidx9g3axVOfRMQGmw7ZSGxp0+vZIJGiEtpcSvlN0XfuEhOG8neqfVBSSE9txrKanw=="],
|
||||
|
||||
"@cliffy/prompt": ["@jsr/cliffy__prompt@1.0.0", "https://npm.jsr.io/~/11/@jsr/cliffy__prompt/1.0.0.tgz", { "dependencies": { "@jsr/cliffy__ansi": "1.0.0", "@jsr/cliffy__internal": "1.0.0", "@jsr/cliffy__keycode": "1.0.0", "@jsr/std__assert": "^1.0.18", "@jsr/std__fmt": "^1.0.9", "@jsr/std__io": "~0.225.3", "@jsr/std__path": "^1.1.4", "@jsr/std__text": "^1.0.17" } }, "sha512-JDuHcCAjScV0IUj389brneF6AzJyyP0pK8mymsrGN5/PGQfqK8zr96QpFlo1wmo8BY/3JQAdNfy6NZkPCJ6VWA=="],
|
||||
|
||||
"@cliffy/table": ["@jsr/cliffy__table@1.0.0", "https://npm.jsr.io/~/11/@jsr/cliffy__table/1.0.0.tgz", { "dependencies": { "@jsr/std__fmt": "^1.0.9" } }, "sha512-VoLxH0DjofHWPWKUc5N+oCwXB6O6e+carnhp23yJTa7qokBb+SCrTIABEgQdIe/p0bxgmZhz17xt2efaAxXvbQ=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||
|
||||
"@jsr/cliffy__ansi": ["@jsr/cliffy__ansi@1.0.0", "https://npm.jsr.io/~/11/@jsr/cliffy__ansi/1.0.0.tgz", { "dependencies": { "@jsr/cliffy__internal": "1.0.0", "@jsr/std__encoding": "^1.0.10", "@jsr/std__fmt": "^1.0.9", "@jsr/std__io": "~0.225.3" } }, "sha512-JesgTdgR0aW1mZv96VqvRHr2efzr4MgDFMnoT+hkhaiCpmyBz33sHM5peAoMJUbGVfEfQAsysIXvvgoFYoveYg=="],
|
||||
|
||||
"@jsr/cliffy__flags": ["@jsr/cliffy__flags@1.0.0", "https://npm.jsr.io/~/11/@jsr/cliffy__flags/1.0.0.tgz", { "dependencies": { "@jsr/cliffy__internal": "1.0.0", "@jsr/std__text": "^1.0.17" } }, "sha512-j/v3J8MWu0tkYyisZ2w1HxELxxL/qg6vey9+fRkbTJ+S9J0GeLUn2joouikG7aXpULKCXHTjJ9XH9gQx+F3npw=="],
|
||||
|
||||
"@jsr/cliffy__internal": ["@jsr/cliffy__internal@1.0.0", "https://npm.jsr.io/~/11/@jsr/cliffy__internal/1.0.0.tgz", { "dependencies": { "@jsr/std__fmt": "^1.0.9" } }, "sha512-YPkbccbuu+kE55k+nia5jJx5Tu/IolBDXZTAgEA+YRGOzq8I1VkXajwykFXvSbXeVee3zQBU7y0HajVDB7ujQA=="],
|
||||
|
||||
"@jsr/cliffy__keycode": ["@jsr/cliffy__keycode@1.0.0", "https://npm.jsr.io/~/11/@jsr/cliffy__keycode/1.0.0.tgz", {}, "sha512-1ot+y8oZheBTpfgCazWjSOAK2Y2nOQD7NwMuiSAkcRuc1t7VizQZfDpZtBx97NlkYWgjn6ylArt2xyhiyLKRhA=="],
|
||||
|
||||
"@jsr/cliffy__table": ["@jsr/cliffy__table@1.0.0", "https://npm.jsr.io/~/11/@jsr/cliffy__table/1.0.0.tgz", { "dependencies": { "@jsr/std__fmt": "^1.0.9" } }, "sha512-VoLxH0DjofHWPWKUc5N+oCwXB6O6e+carnhp23yJTa7qokBb+SCrTIABEgQdIe/p0bxgmZhz17xt2efaAxXvbQ=="],
|
||||
|
||||
"@jsr/std__assert": ["@jsr/std__assert@1.0.19", "https://npm.jsr.io/~/11/@jsr/std__assert/1.0.19.tgz", { "dependencies": { "@jsr/std__internal": "^1.0.12" } }, "sha512-pEj6RPkGbqlgRmyKwATp4cUs6+ijxtdrv3bq8v1d2I2CEcMEyPaO8cVKro61wGRDH4cNg8Zx6haztvK/9m7gkA=="],
|
||||
|
||||
"@jsr/std__bytes": ["@jsr/std__bytes@1.0.6", "https://npm.jsr.io/~/11/@jsr/std__bytes/1.0.6.tgz", {}, "sha512-St6yKggjFGhxS52IFLJWvkchRFbAKg2Xh8UxA4S1EGz7GJ2Ui+ssDDldj/w2c8vCxvl6qgR0HaYbKeFJNqujmA=="],
|
||||
|
||||
"@jsr/std__encoding": ["@jsr/std__encoding@1.0.10", "https://npm.jsr.io/~/11/@jsr/std__encoding/1.0.10.tgz", {}, "sha512-WK2njnDTyKefroRNk2Ooq7GStp6Y0ccAvr4To+Z/zecRAGe7+OSvH9DbiaHpAKwEi2KQbmpWMOYsdNt+TsdmSw=="],
|
||||
|
||||
"@jsr/std__fmt": ["@jsr/std__fmt@1.0.9", "https://npm.jsr.io/~/11/@jsr/std__fmt/1.0.9.tgz", {}, "sha512-YFJJMozmORj2K91c5J9opWeh0VUwrd+Mwb7Pr0FkVCAKVLu2UhT4LyvJqWiyUT+eF+MdfqQ9F7RtQj4bXn9Smw=="],
|
||||
|
||||
"@jsr/std__internal": ["@jsr/std__internal@1.0.12", "https://npm.jsr.io/~/11/@jsr/std__internal/1.0.12.tgz", {}, "sha512-6xReMW9p+paJgqoFRpOE2nogJFvzPfaLHLIlyADYjKMUcwDyjKZxryIbgcU+gxiTygn8yCjld1HoI0ET4/iZeA=="],
|
||||
|
||||
"@jsr/std__io": ["@jsr/std__io@0.225.3", "https://npm.jsr.io/~/11/@jsr/std__io/0.225.3.tgz", { "dependencies": { "@jsr/std__bytes": "^1.0.6" } }, "sha512-IDXY253ipW6FV34CJVxO+3ubfvSEEzw9N2W303KnLe9K/Y9+v/ID1dQYf9VsCCOFMpFtCmOLqzIZsRqv6yQnWw=="],
|
||||
|
||||
"@jsr/std__path": ["@jsr/std__path@1.1.4", "https://npm.jsr.io/~/11/@jsr/std__path/1.1.4.tgz", { "dependencies": { "@jsr/std__internal": "^1.0.12" } }, "sha512-SK4u9H6NVTfolhPdlvdYXfNFefy1W04AEHWJydryYbk+xqzNiVmr5o7TLJLJFqwHXuwMRhwrn+mcYeUfS0YFaA=="],
|
||||
|
||||
"@jsr/std__regexp": ["@jsr/std__regexp@1.0.1", "https://npm.jsr.io/~/11/@jsr/std__regexp/1.0.1.tgz", {}, "sha512-AnGeP//DHpPvhCWjI5dR4o013JhCQioD8yMF8drD7PWb0X4kvmO35hbZi+NZhfSolz4Ts2cpPzJY+DUpi2XE9A=="],
|
||||
|
||||
"@jsr/std__semver": ["@jsr/std__semver@1.0.8", "https://npm.jsr.io/~/11/@jsr/std__semver/1.0.8.tgz", {}, "sha512-YhkykPU2Majz66e+rQbP0okYc7kKv+U32aguLPCXZZAL+vEVmBA+khHjPHhLBpWR073gzU3WHqGRgB7a/aXCjg=="],
|
||||
|
||||
"@jsr/std__text": ["@jsr/std__text@1.0.17", "https://npm.jsr.io/~/11/@jsr/std__text/1.0.17.tgz", { "dependencies": { "@jsr/std__regexp": "^1.0.1" } }, "sha512-oZsihl1bcTy1Ixzven8rin8kjChj1zDJWqgpS0oSMGCJDzyB365gtIfAvcMmji+M+FcIWo3goDXfHcFYt+k/kg=="],
|
||||
|
||||
"@stoplight/ordered-object-literal": ["@stoplight/ordered-object-literal@1.0.5", "", {}, "sha512-COTiuCU5bgMUtbIFBuyyh2/yVVzlr5Om0v5utQDgBCuQUOPgU1DwoffkTfg4UBQOvByi5foF4w4T+H9CoRe5wg=="],
|
||||
|
||||
"@stoplight/types": ["@stoplight/types@14.1.1", "", { "dependencies": { "@types/json-schema": "^7.0.4", "utility-types": "^3.10.0" } }, "sha512-/kjtr+0t0tjKr+heVfviO9FrU/uGLc+QNX3fHJc19xsCNYqU7lVhaXxDmEID9BZTjG+/r9pK9xP/xU02XGg65g=="],
|
||||
|
||||
"@stoplight/yaml": ["@stoplight/yaml@4.3.0", "", { "dependencies": { "@stoplight/ordered-object-literal": "^1.0.5", "@stoplight/types": "^14.1.1", "@stoplight/yaml-ast-parser": "0.0.50", "tslib": "^2.2.0" } }, "sha512-JZlVFE6/dYpP9tQmV0/ADfn32L9uFarHWxfcRhReKUnljz1ZiUM5zpX+PH8h5CJs6lao3TuFqnPm9IJJCEkE2w=="],
|
||||
|
||||
"@stoplight/yaml-ast-parser": ["@stoplight/yaml-ast-parser@0.0.50", "", {}, "sha512-Pb6M8TDO9DtSVla9yXSTAxmo9GVEouq5P40DWXdOie69bXogZTkgvopCq+yEvTMA0F6PEvdJmbtTV3ccIp11VQ=="],
|
||||
|
||||
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.9", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA=="],
|
||||
|
||||
"@types/diff": ["@types/diff@5.2.3", "", {}, "sha512-K0Oqlrq3kQMaO2RhfrNQX5trmt+XLyom88zS0u84nnIcLvFnRUMRRHmrGny5GSM+kNO9IZLARsdQHDzkhAgmrQ=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/node": ["@types/node@22.19.11", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w=="],
|
||||
|
||||
"@types/tar-stream": ["@types/tar-stream@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-921gW0+g29mCJX0fRvqeHzBlE/XclDaAG0Ousy1LCghsOhvaKacDeRGEVzQP9IPfKn8Vysy7FEXAIxycpc/CMg=="],
|
||||
|
||||
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
|
||||
|
||||
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
|
||||
|
||||
"@windmill-labs/shared-utils": ["@windmill-labs/shared-utils@1.0.12", "", {}, "sha512-n68uEYv2B5q2Pp8J9syMS3qPZbppFEfeM7HIBEUfU5lGqi3hwnv4mPvgRUyb6K9im3frXC4gzdIdZdlrDpudXQ=="],
|
||||
|
||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||
|
||||
"ajv": ["ajv@8.18.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||
|
||||
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
|
||||
|
||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||
|
||||
"b4a": ["b4a@1.8.0", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg=="],
|
||||
|
||||
"balanced-match": ["balanced-match@4.0.3", "", {}, "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g=="],
|
||||
|
||||
"bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@5.0.2", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw=="],
|
||||
|
||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
||||
|
||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||
|
||||
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
|
||||
|
||||
"default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
|
||||
|
||||
"default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="],
|
||||
|
||||
"define-lazy-prop": ["define-lazy-prop@3.0.0", "", {}, "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg=="],
|
||||
|
||||
"devalue": ["devalue@5.6.3", "", {}, "sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg=="],
|
||||
|
||||
"diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="],
|
||||
|
||||
"esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="],
|
||||
|
||||
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
|
||||
|
||||
"esrap": ["esrap@2.2.3", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } }, "sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ=="],
|
||||
|
||||
"events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="],
|
||||
|
||||
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
|
||||
|
||||
"get-port": ["get-port@7.1.0", "", {}, "sha512-QB9NKEeDg3xxVwCCwJQ9+xycaz6pBB6iQ76wiWMl1927n0Kir6alPiP+yuiICLLU4jpMe08dXfpebuQppFA2zw=="],
|
||||
|
||||
"immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
|
||||
|
||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||
|
||||
"is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
|
||||
|
||||
"is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
|
||||
|
||||
"is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
|
||||
|
||||
"is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],
|
||||
|
||||
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"jszip": ["jszip@3.8.0", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "set-immediate-shim": "~1.0.1" } }, "sha512-cnpQrXvFSLdsR9KR5/x7zdf6c3m8IhZfZzSblFEHSqBaVwD2nvJ4CuCKLyvKvwBgZm08CgfSoiTBQLm5WW9hGw=="],
|
||||
|
||||
"lie": ["lie@3.3.0", "", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ=="],
|
||||
|
||||
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"minimatch": ["minimatch@10.2.2", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw=="],
|
||||
|
||||
"open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="],
|
||||
|
||||
"pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
|
||||
|
||||
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
|
||||
|
||||
"readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="],
|
||||
|
||||
"safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
||||
|
||||
"set-immediate-shim": ["set-immediate-shim@1.0.1", "", {}, "sha512-Li5AOqrZWCVA2n5kryzEmqai6bKSIvpz5oUJHPVj6+dsbD3X1ixtsY5tEnsaNpH3pFAHmG8eIHUrtEtohrg+UQ=="],
|
||||
|
||||
"streamx": ["streamx@2.23.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg=="],
|
||||
|
||||
"string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
|
||||
"svelte": ["svelte@5.53.1", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.3", "esm-env": "^1.2.1", "esrap": "^2.2.2", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-WzxFHZhhD23Qzu7JCYdvm1rxvRSzdt9HtHO8TScMBX51bLRFTcJmATVqjqXG+6Ln6hrViGCo9DzwOhAasxwC/w=="],
|
||||
|
||||
"tar-stream": ["tar-stream@3.1.7", "", { "dependencies": { "b4a": "^1.6.4", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ=="],
|
||||
|
||||
"text-decoder": ["text-decoder@1.2.7", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
|
||||
|
||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
||||
|
||||
"utility-types": ["utility-types@3.11.0", "", {}, "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw=="],
|
||||
|
||||
"windmill-parser-wasm-csharp": ["windmill-parser-wasm-csharp@1.510.1", "", {}, "sha512-qm09YmnbeYHLwYn1jUnObVzPhYO9NZKMlIO7nlo7zPJBXqksgG5fK/KCtwGw9rChrnz+DsvM9wP5FhrwRLMtwQ=="],
|
||||
|
||||
"windmill-parser-wasm-go": ["windmill-parser-wasm-go@1.510.1", "", {}, "sha512-HOkk6LXK0wrwvkn+zjm3Gxo90HmyL6TYqmLo2yp8fZuppy7GOngT27zwYeBtwONiPyvDKskzoqPQoEfd8VuUsQ=="],
|
||||
|
||||
"windmill-parser-wasm-java": ["windmill-parser-wasm-java@1.510.1", "", {}, "sha512-Zle+JZT/ZwUArUVacUudYlS+CaHp2lSnkqD/IhWaRUG+gcv26VbERnrrHPonqXbVMS+eA9ElfXrFM5j0ukaXUw=="],
|
||||
|
||||
"windmill-parser-wasm-nu": ["windmill-parser-wasm-nu@1.510.1", "", {}, "sha512-AJLFiUy6af+LpUe7CddDo4+JOmw3c0K/1iOWh8NdTwXcLDj90lL6089mdsVo1apyloLgrTbcuFDzZMXVGBgtCg=="],
|
||||
|
||||
"windmill-parser-wasm-php": ["windmill-parser-wasm-php@1.574.1", "", {}, "sha512-COyid6B1RYs+bpzUCInsA4HY/WZkpDLfkQ90+AqU/TVTpzYSbAC2JCbIwy0cRElBvlhI4bQ+9Wg6hSQKMpEkpA=="],
|
||||
|
||||
"windmill-parser-wasm-py": ["windmill-parser-wasm-py@1.628.3", "", {}, "sha512-TlluqknZpg8cZ+A3m6JFLPseY2PpKtDsxdj26fAnCUzKPtse8TxQR+n0dwC80rfW5TwdWSulvNGRDgcNuf7CTw=="],
|
||||
|
||||
"windmill-parser-wasm-regex": ["windmill-parser-wasm-regex@1.639.0", "", {}, "sha512-qvYM4sYxB6M0xrqwBljS2fWqOMk6rp++60TRltJnzZDzVaWQrKjTGwNMmfepGAIWy1OGVKp0SCVERhe2P+O6tQ=="],
|
||||
|
||||
"windmill-parser-wasm-ruby": ["windmill-parser-wasm-ruby@1.526.1", "", {}, "sha512-rMBQA8s21wmL2kA5ztRs/ZgVA3ckxe9/NLjxl3iQPL0CX6DlvfaUH0O+AnhpXXDMyBs1Y1SZIhcnbnvsHZ3R8g=="],
|
||||
|
||||
"windmill-parser-wasm-rust": ["windmill-parser-wasm-rust@1.558.1", "", {}, "sha512-21S7lm1KF8zO1187rbq14hzPHII2RdM2+D44MoAh1F6VoaScj+Puq0z5B1O/hwn/95R/a9jBlL2D8jbkXtlD1A=="],
|
||||
|
||||
"windmill-parser-wasm-ts": ["windmill-parser-wasm-ts@1.623.1", "", {}, "sha512-FBwi/zXxjhZcCvi04oFdNivazru1ynIqSbafHSArfaaBWesBO3nye9UO/WXUlWZm5a7BExbU+3R/eVJrGaornw=="],
|
||||
|
||||
"windmill-parser-wasm-yaml": ["windmill-parser-wasm-yaml@1.593.0", "", {}, "sha512-Gyx4aR2jsJYuDrD3mCNTmz7LWOQQXPw5yKNCC1xRgUOPfjsD/tINAFfsBLwVOSmlQQcFZO+wHm4KtDtXOcnGVw=="],
|
||||
|
||||
"windmill-yaml-validator": ["windmill-yaml-validator@1.1.1", "", { "dependencies": { "@stoplight/yaml": "^4.3.0", "ajv": "^8.17.1" } }, "sha512-CVgAwEoBdJhF39q2N012QffhlGPRIyIWd8gj7NnfG+/lMWgH2k5CBLtKIt6cPF8Bxz+6DGC3st1ARSsecDtbTg=="],
|
||||
|
||||
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
|
||||
|
||||
"wsl-utils": ["wsl-utils@0.1.0", "", { "dependencies": { "is-wsl": "^3.1.0" } }, "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw=="],
|
||||
|
||||
"yaml": ["yaml@2.8.2", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
|
||||
|
||||
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
|
||||
}
|
||||
}
|
||||
4
cli/bunfig.toml
Normal file
4
cli/bunfig.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[test]
|
||||
preload = ["./test/setup.ts"]
|
||||
timeout = 60000
|
||||
root = "./test"
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"imports": {
|
||||
"@cliffy/ansi": "jsr:@windmill-labs/cliffy-ansi@^1.0.0-rc.5",
|
||||
"@cliffy/command": "jsr:@windmill-labs/cliffy-command@^1.0.0-rc.5",
|
||||
"@cliffy/prompt": "jsr:@windmill-labs/cliffy-prompt@^1.0.0-rc.6",
|
||||
"@cliffy/table": "jsr:@windmill-labs/cliffy-table@^1.0.0-rc.5",
|
||||
"@deno/dnt": "jsr:@deno/dnt@^0.41.3",
|
||||
"@std/encoding": "jsr:@std/encoding@^1.0.10",
|
||||
"@std/fs": "jsr:@std/fs@^1.0.21",
|
||||
"@std/io": "jsr:@std/io@^0.224.9",
|
||||
"@std/log": "jsr:@std/log@^0.224.14",
|
||||
"@std/net": "jsr:@std/net@^1.0.6",
|
||||
"@std/path": "jsr:@std/path@^1.1.4",
|
||||
"@std/streams": "jsr:@std/streams@^1.0.16",
|
||||
"@std/yaml": "jsr:@std/yaml@^1.0.10",
|
||||
"@types/diff": "npm:@types/diff@^5.2.3",
|
||||
"ws": "npm:ws@8.18.0"
|
||||
},
|
||||
"nodeModulesDir": "auto"
|
||||
}
|
||||
1806
cli/deno.lock
generated
1806
cli/deno.lock
generated
File diff suppressed because it is too large
Load Diff
83
cli/deps.ts
83
cli/deps.ts
@@ -1,83 +0,0 @@
|
||||
// cliffy
|
||||
export { Command } from "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5";
|
||||
export { Table } from "jsr:@windmill-labs/cliffy-table@1.0.0-rc.5";
|
||||
export { colors } from "jsr:@windmill-labs/cliffy-ansi@1.0.0-rc.5/colors";
|
||||
export { Secret } from "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.6/secret";
|
||||
export { Select } from "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.6/select";
|
||||
export { Confirm } from "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.6/confirm";
|
||||
export { Input } from "jsr:@windmill-labs/cliffy-prompt@1.0.0-rc.6/input";
|
||||
export { UpgradeCommand } from "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5/upgrade";
|
||||
export { NpmProvider } from "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5/upgrade/provider/npm";
|
||||
export { Provider } from "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5/upgrade";
|
||||
|
||||
export { CompletionsCommand } from "jsr:@windmill-labs/cliffy-command@1.0.0-rc.5/completions";
|
||||
// std
|
||||
export { ensureDir } from "jsr:@std/fs";
|
||||
export { SEPARATOR as SEP } from "jsr:@std/path";
|
||||
export * as path from "jsr:@std/path";
|
||||
export { encodeHex } from "jsr:@std/encoding@1.0.4";
|
||||
export { writeAllSync } from "jsr:@std/io/write-all";
|
||||
export { copy } from "jsr:@std/io/copy";
|
||||
export { readAll } from "jsr:@std/io/read-all";
|
||||
|
||||
export * as log from "jsr:@std/log";
|
||||
export { stringify as yamlStringify } from "jsr:@std/yaml";
|
||||
|
||||
import { parse as yamlParse, ParseOptions } from "jsr:@std/yaml";
|
||||
|
||||
export async function yamlParseFile(path: string, options: ParseOptions = {}) {
|
||||
try {
|
||||
return yamlParse(await Deno.readTextFile(path), options);
|
||||
} catch (e) {
|
||||
throw new Error(`Error parsing yaml ${path}`, { cause: e });
|
||||
}
|
||||
}
|
||||
|
||||
export function yamlParseContent(
|
||||
path: string,
|
||||
content: string,
|
||||
options: ParseOptions = {},
|
||||
) {
|
||||
try {
|
||||
return yamlParse(content, options);
|
||||
} catch (e) {
|
||||
throw new Error(`Error parsing yaml ${path}`, { cause: e });
|
||||
}
|
||||
}
|
||||
|
||||
// other
|
||||
|
||||
export * as Diff from "npm:diff";
|
||||
export { minimatch } from "npm:minimatch";
|
||||
export { default as JSZip } from "npm:jszip@3.8.0";
|
||||
|
||||
export * as express from "npm:express";
|
||||
export * as http from "node:http";
|
||||
export { WebSocket, WebSocketServer } from "npm:ws";
|
||||
export * as getPort from "npm:get-port@7.1.0";
|
||||
export * as open from "npm:open";
|
||||
export * as esMain from "npm:es-main";
|
||||
export * as windmillUtils from "jsr:@windmill-labs/shared-utils@1.0.12";
|
||||
|
||||
// needed for dnt transform
|
||||
import * as wsTypes from "npm:@types/ws";
|
||||
|
||||
import { OpenAPI } from "./gen/index.ts";
|
||||
|
||||
export function setClient(token?: string, baseUrl?: string) {
|
||||
if (baseUrl === undefined) {
|
||||
baseUrl = getEnv("BASE_INTERNAL_URL") ??
|
||||
getEnv("BASE_URL") ??
|
||||
"http://localhost:8000";
|
||||
}
|
||||
if (token === undefined) {
|
||||
token = getEnv("WM_TOKEN") ?? "no_token";
|
||||
}
|
||||
OpenAPI.WITH_CREDENTIALS = true;
|
||||
OpenAPI.TOKEN = token;
|
||||
OpenAPI.BASE = baseUrl + "/api";
|
||||
}
|
||||
|
||||
const getEnv = (key: string) => {
|
||||
return Deno.env.get(key);
|
||||
};
|
||||
87
cli/dnt.ts
87
cli/dnt.ts
@@ -1,87 +0,0 @@
|
||||
// ex. scripts/build_npm.ts
|
||||
import { build, emptyDir } from "jsr:@deno/dnt@0.42.3";
|
||||
import { VERSION } from "./src/main.ts";
|
||||
await emptyDir("./npm");
|
||||
|
||||
await build({
|
||||
entryPoints: [
|
||||
"src/main.ts",
|
||||
{
|
||||
kind: "bin",
|
||||
name: "wmill", // command name
|
||||
path: "./src/main.ts",
|
||||
},
|
||||
],
|
||||
outDir: "./npm",
|
||||
test: false, // Disable all tests in npm build since they use Deno-specific APIs
|
||||
shims: {
|
||||
// see JS docs for overview and more options
|
||||
deno: true,
|
||||
// shims to only use in the tests
|
||||
customDev: [{
|
||||
// this is what `timers: "dev"` does internally
|
||||
package: {
|
||||
name: "@deno/shim-timers",
|
||||
version: "~0.1.0",
|
||||
},
|
||||
globalNames: ["setTimeout", "setInterval"],
|
||||
}],
|
||||
},
|
||||
scriptModule: false,
|
||||
filterDiagnostic(diagnostic) {
|
||||
if (
|
||||
diagnostic.file?.fileName.includes("node_modules/") ||
|
||||
diagnostic.file?.fileName.includes("src/deps/") ||
|
||||
diagnostic.file?.fileName.includes("src/deps.ts") ||
|
||||
diagnostic.file?.fileName.includes("src/utils/utils.ts")
|
||||
) {
|
||||
return false; // ignore all diagnostics in this file
|
||||
}
|
||||
// console.log(diagnostic.file?.fileName);
|
||||
return true;
|
||||
},
|
||||
declaration: "separate",
|
||||
package: {
|
||||
// package.json properties
|
||||
name: "windmill-cli",
|
||||
version: VERSION,
|
||||
description: "CLI for Windmill",
|
||||
license: "Apache 2.0",
|
||||
main: "esm/main.js",
|
||||
repository: {
|
||||
type: "git",
|
||||
url: "git+https://github.com/windmill-labs/windmill.git",
|
||||
},
|
||||
bugs: {
|
||||
url: "https://github.com/windmill-labs/windmill/issues",
|
||||
},
|
||||
},
|
||||
|
||||
postBuild() {
|
||||
// steps to run after building and before running the tests
|
||||
// add shebang to npm/esm/main.js
|
||||
const dirs = [
|
||||
"nu",
|
||||
"ts",
|
||||
"regex",
|
||||
"py",
|
||||
"go",
|
||||
"php",
|
||||
"rust",
|
||||
"yaml",
|
||||
"csharp",
|
||||
"java",
|
||||
"ruby",
|
||||
// for related places search: ADD_NEW_LANG
|
||||
];
|
||||
|
||||
for (const l of dirs) {
|
||||
Deno.copyFileSync(
|
||||
"wasm/" + l + "/windmill_parser_wasm_bg.wasm",
|
||||
"npm/esm/wasm/" + l + "/windmill_parser_wasm_bg.wasm"
|
||||
);
|
||||
}
|
||||
Deno.copyFileSync("../LICENSE", "npm/LICENSE");
|
||||
Deno.copyFileSync("README.md", "npm/README.md");
|
||||
},
|
||||
});
|
||||
@@ -6,8 +6,8 @@ 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 <<EOF - gen/core/OpenAPI.ts > temp_file && mv temp_file gen/core/OpenAPI.ts
|
||||
const getEnv = (key: string) => {
|
||||
return Deno.env.get(key)
|
||||
const getEnv = (key: string): string | undefined => {
|
||||
return process.env[key]
|
||||
};
|
||||
|
||||
const baseUrl = getEnv("BASE_INTERNAL_URL") ?? getEnv("BASE_URL") ?? "http://localhost:8000";
|
||||
|
||||
@@ -2,14 +2,55 @@
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
name="wmill"
|
||||
else
|
||||
name="$1"
|
||||
# Parse options
|
||||
USE_NODE=false
|
||||
name=""
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--node|-node|---node) USE_NODE=true ;;
|
||||
-*) echo "Unknown option: $arg"; echo "Usage: $0 [name] [--node]"; exit 1 ;;
|
||||
*) [ -z "$name" ] && name="$arg" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$name" ]; then
|
||||
name="wmill-dev"
|
||||
fi
|
||||
|
||||
./gen_wm_client.sh
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
./gen_wm_client.sh
|
||||
./windmill-utils-internal/gen_wm_client.sh
|
||||
|
||||
echo "Installing dev cli as $name (pass arg to override)"
|
||||
deno install -f -A -g src/main.ts --name $name --unstable
|
||||
bun install
|
||||
|
||||
INSTALL_DIR="$HOME/.local/bin"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
|
||||
if [ "$USE_NODE" = true ]; then
|
||||
echo "Building npm bundle..."
|
||||
bun run build-npm.ts
|
||||
|
||||
NPM_DIR="$SCRIPT_DIR/npm"
|
||||
cd "$NPM_DIR" && npm install
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
cat > "$INSTALL_DIR/$name" <<EOF
|
||||
#!/bin/sh
|
||||
exec node "$NPM_DIR/esm/main.js" "\$@"
|
||||
EOF
|
||||
else
|
||||
cat > "$INSTALL_DIR/$name" <<EOF
|
||||
#!/bin/sh
|
||||
exec bun run "$SCRIPT_DIR/src/main.ts" "\$@"
|
||||
EOF
|
||||
fi
|
||||
|
||||
chmod +x "$INSTALL_DIR/$name"
|
||||
|
||||
echo "Installed dev cli as '$name' at $INSTALL_DIR/$name"
|
||||
|
||||
if ! echo "$PATH" | tr ':' '\n' | grep -qx "$INSTALL_DIR"; then
|
||||
echo "Warning: $INSTALL_DIR is not in your PATH. Add it with:"
|
||||
echo " export PATH=\"$INSTALL_DIR:\$PATH\""
|
||||
fi
|
||||
|
||||
1498
cli/package-lock.json
generated
Normal file
1498
cli/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
cli/package.json
Normal file
51
cli/package.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "wmill-dev",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"wmill": "src/main.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "bun run src/main.ts",
|
||||
"build": "./build.sh",
|
||||
"test": "bun test test/",
|
||||
"check": "bunx tsc --noEmit",
|
||||
"gen-client": "./gen_wm_client.sh && ./windmill-utils-internal/gen_wm_client.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cliffy/ansi": "npm:@jsr/cliffy__ansi@1.0.0",
|
||||
"@cliffy/command": "npm:@jsr/cliffy__command@1.0.0",
|
||||
"@cliffy/prompt": "npm:@jsr/cliffy__prompt@1.0.0",
|
||||
"@cliffy/table": "npm:@jsr/cliffy__table@1.0.0",
|
||||
"@windmill-labs/shared-utils": "^1.0.12",
|
||||
"diff": "^5.2.0",
|
||||
"esbuild": "0.24.2",
|
||||
"get-port": "7.1.0",
|
||||
"jszip": "3.8.0",
|
||||
"minimatch": "^10.0.0",
|
||||
"open": "^10.0.0",
|
||||
"svelte": "^5.45.2",
|
||||
"tar-stream": "^3.1.7",
|
||||
"windmill-parser-wasm-csharp": "*",
|
||||
"windmill-parser-wasm-go": "*",
|
||||
"windmill-parser-wasm-java": "*",
|
||||
"windmill-parser-wasm-nu": "*",
|
||||
"windmill-parser-wasm-php": "*",
|
||||
"windmill-parser-wasm-py": "*",
|
||||
"windmill-parser-wasm-regex": "*",
|
||||
"windmill-parser-wasm-ruby": "*",
|
||||
"windmill-parser-wasm-rust": "*",
|
||||
"windmill-parser-wasm-ts": "*",
|
||||
"windmill-parser-wasm-yaml": "*",
|
||||
"windmill-yaml-validator": "1.1.1",
|
||||
"ws": "8.18.0",
|
||||
"yaml": "^2.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/diff": "^5.2.3",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/tar-stream": "^3.1.4",
|
||||
"@types/ws": "^8.5.0",
|
||||
"typescript": "^5.7.0"
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
import { resolveWorkspace, validatePath } from "../../core/context.ts";
|
||||
import {
|
||||
colors,
|
||||
Command,
|
||||
log,
|
||||
SEP,
|
||||
Table,
|
||||
windmillUtils,
|
||||
yamlParseFile,
|
||||
} from "../../../deps.ts";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Table } from "@cliffy/table";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import * as windmillUtils from "@windmill-labs/shared-utils";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { ListableApp, Policy } from "../../../gen/types.gen.ts";
|
||||
|
||||
@@ -188,7 +185,7 @@ export async function generatingPolicy(
|
||||
}
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions & { includeDraftOnly?: boolean }) {
|
||||
async function list(opts: GlobalOptions & { includeDraftOnly?: boolean; json?: boolean }) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
@@ -209,12 +206,32 @@ async function list(opts: GlobalOptions & { includeDraftOnly?: boolean }) {
|
||||
}
|
||||
}
|
||||
|
||||
new Table()
|
||||
.header(["path", "summary"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(total.map((x) => [x.path, x.summary]))
|
||||
.render();
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(total));
|
||||
} else {
|
||||
new Table()
|
||||
.header(["path", "summary"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(total.map((x) => [x.path, x.summary]))
|
||||
.render();
|
||||
}
|
||||
}
|
||||
|
||||
async function get(opts: GlobalOptions & { json?: boolean }, path: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
const a = await wmill.getAppByPath({
|
||||
workspace: workspace.workspaceId,
|
||||
path,
|
||||
});
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(a));
|
||||
} else {
|
||||
console.log(colors.bold("Path:") + " " + a.path);
|
||||
console.log(colors.bold("Summary:") + " " + (a.summary ?? ""));
|
||||
console.log(colors.bold("Created by:") + " " + (a.created_by ?? ""));
|
||||
}
|
||||
}
|
||||
|
||||
async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
@@ -230,7 +247,15 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
|
||||
const command = new Command()
|
||||
.description("app related commands")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("list", "list all apps")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("get", "get an app's details")
|
||||
.arguments("<path:string>")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(get as any)
|
||||
.command("push", "push a local app ")
|
||||
.arguments("<file_path:string> <remote_path:string>")
|
||||
.action(push as any)
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import path from "node:path";
|
||||
import {
|
||||
SEP,
|
||||
colors,
|
||||
log,
|
||||
yamlParseFile,
|
||||
yamlStringify,
|
||||
} from "../../../deps.ts";
|
||||
import { readFile, mkdir } from "node:fs/promises";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import {
|
||||
checkifMetadataUptodate,
|
||||
@@ -86,7 +84,7 @@ async function generateAppHash(
|
||||
}
|
||||
} catch (error: any) {
|
||||
// If runnables folder doesn't exist, that's okay
|
||||
if (error.name !== "NotFound") {
|
||||
if (error.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -351,7 +349,7 @@ async function updateRawAppRunnables(
|
||||
|
||||
// Ensure runnables folder exists
|
||||
try {
|
||||
await Deno.mkdir(runnablesFolder, { recursive: true });
|
||||
await mkdir(runnablesFolder, { recursive: true });
|
||||
} catch {
|
||||
// Folder may already exist
|
||||
}
|
||||
@@ -736,7 +734,7 @@ export async function inferRunnableSchemaFromFile(
|
||||
);
|
||||
let content: string;
|
||||
try {
|
||||
content = await Deno.readTextFile(fullFilePath);
|
||||
content = await readFile(fullFilePath, "utf-8");
|
||||
} catch {
|
||||
log.warn(colors.yellow(`Could not read file: ${fullFilePath}`));
|
||||
return undefined;
|
||||
@@ -786,7 +784,7 @@ export async function generateLocksCommand(
|
||||
const { generateAppLocksInternal } = await import("./app_metadata.ts");
|
||||
const { elementsToMap, FSFSElement } = await import("../sync/sync.ts");
|
||||
const { ignoreF } = await import("../sync/sync.ts");
|
||||
const { Confirm } = await import("../../../deps.ts");
|
||||
const { Confirm } = await import("@cliffy/prompt/confirm");
|
||||
|
||||
if (appPath == "") {
|
||||
appPath = undefined;
|
||||
@@ -813,7 +811,7 @@ export async function generateLocksCommand(
|
||||
// Generate metadata for all apps
|
||||
const ignore = await ignoreF(opts);
|
||||
const elems = await elementsToMap(
|
||||
await FSFSElement(Deno.cwd(), [], true),
|
||||
await FSFSElement(process.cwd(), [], true),
|
||||
(p, isD) => {
|
||||
return (
|
||||
ignore(p, isD) ||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import process from "node:process";
|
||||
import { spawn } from "node:child_process";
|
||||
import { log, colors } from "../../../deps.ts";
|
||||
import { windmillUtils } from "../../../deps.ts";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as windmillUtils from "@windmill-labs/shared-utils";
|
||||
export interface BundleOptions {
|
||||
entryPoint?: string;
|
||||
outDir?: string;
|
||||
@@ -66,7 +66,7 @@ function createSveltePlugin(appDir: string): any {
|
||||
setup(build: any) {
|
||||
build.onLoad({ filter: /\.svelte$/ }, async (args: any) => {
|
||||
// Import svelte compiler from the project's node_modules
|
||||
const svelte = await import("npm:svelte@5.45.2/compiler");
|
||||
const svelte = await import("svelte/compiler");
|
||||
|
||||
// Load the file from the file system
|
||||
const source = await fs.promises.readFile(args.path, "utf8");
|
||||
@@ -118,7 +118,7 @@ export async function createFrameworkPlugins(appDir: string): Promise<any[]> {
|
||||
log.info(colors.blue("🔧 Vue detected, adding vue plugin..."));
|
||||
throw new Error("Vue plugin not supported yet");
|
||||
// try {
|
||||
// const esbuildPluginVue = await import("npm:esbuild-plugin-vue3@0.5.1");
|
||||
// const esbuildPluginVue = await import("esbuild-plugin-vue3");
|
||||
// plugins.push(esbuildPluginVue.default());
|
||||
// } catch (error: any) {
|
||||
// log.warn(colors.yellow(`Failed to load vue plugin: ${error.message}`));
|
||||
@@ -164,7 +164,7 @@ export async function createBundle(
|
||||
options: BundleOptions = {}
|
||||
): Promise<BundleResult> {
|
||||
// Dynamically import esbuild
|
||||
const esbuild = await import("npm:esbuild@0.24.2");
|
||||
const esbuild = await import("esbuild");
|
||||
|
||||
// Detect frameworks to determine default entry point
|
||||
const frameworks = detectFrameworks(process.cwd());
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import {
|
||||
colors,
|
||||
Command,
|
||||
getPort,
|
||||
log,
|
||||
open,
|
||||
SEP,
|
||||
windmillUtils,
|
||||
yamlParseFile,
|
||||
} from "../../../deps.ts";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import * as windmillUtils from "@windmill-labs/shared-utils";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import * as getPort from "get-port";
|
||||
import * as open from "open";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import * as http from "node:http";
|
||||
import * as fs from "node:fs";
|
||||
@@ -16,7 +13,8 @@ import * as path from "node:path";
|
||||
import process from "node:process";
|
||||
import { Buffer } from "node:buffer";
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { WebSocket, WebSocketServer } from "npm:ws";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { WebSocket, WebSocketServer } from "ws";
|
||||
import {
|
||||
createFrameworkPlugins,
|
||||
detectFrameworks,
|
||||
@@ -336,7 +334,7 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
log.error(colors.red(`Error: Directory not found: ${targetDir}`));
|
||||
Deno.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,7 +353,7 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
}' or specify one as argument.`,
|
||||
),
|
||||
);
|
||||
Deno.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check for raw_app.yaml in target directory
|
||||
@@ -369,7 +367,7 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
} folder containing a raw_app.yaml file.`,
|
||||
),
|
||||
);
|
||||
Deno.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Resolve workspace and authenticate (from original cwd to find wmill.yaml)
|
||||
@@ -387,7 +385,7 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
const appPath = rawApp?.custom_path ?? "u/unknown/newapp";
|
||||
|
||||
// Dynamically import esbuild only when the dev command is called
|
||||
const esbuild = await import("npm:esbuild@0.24.2");
|
||||
const esbuild = await import("esbuild");
|
||||
|
||||
const port = opts.port ??
|
||||
(await getPort.default({
|
||||
@@ -410,7 +408,7 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
`Entry point "${entryPoint}" not found. Please specify a valid entry point with --entry.`,
|
||||
),
|
||||
);
|
||||
Deno.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Ensure node_modules exists
|
||||
@@ -525,99 +523,85 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
|
||||
// Watch runnables folder for changes
|
||||
const runnablesPath = path.join(process.cwd(), APP_BACKEND_FOLDER);
|
||||
let runnablesWatcher: Deno.FsWatcher | undefined;
|
||||
let runnablesWatcher: fs.FSWatcher | undefined;
|
||||
|
||||
if (fs.existsSync(runnablesPath)) {
|
||||
log.info(
|
||||
colors.blue(`👁️ Watching runnables folder at: ${runnablesPath}\n`),
|
||||
);
|
||||
runnablesWatcher = Deno.watchFs(runnablesPath);
|
||||
runnablesWatcher = fs.watch(runnablesPath, { recursive: true });
|
||||
|
||||
// Per-file debounce timeouts for schema inference (longer debounce for typing)
|
||||
const schemaInferenceTimeouts: Record<string, ReturnType<typeof setTimeout>> = {};
|
||||
const SCHEMA_DEBOUNCE_MS = 500; // Wait 500ms after last change before inferring schema
|
||||
|
||||
// Handle runnables file changes in the background
|
||||
(async () => {
|
||||
try {
|
||||
for await (const event of runnablesWatcher!) {
|
||||
// Process each changed path with individual debouncing
|
||||
for (const changedPath of event.paths) {
|
||||
const relativePath = path.relative(process.cwd(), changedPath);
|
||||
const relativeToRunnables = path.relative(
|
||||
runnablesPath,
|
||||
changedPath,
|
||||
);
|
||||
// Handle runnables file changes via callback
|
||||
runnablesWatcher.on("change", (_eventType, filename) => {
|
||||
if (!filename) return;
|
||||
const fileStr = typeof filename === "string" ? filename : filename.toString();
|
||||
const changedPath = path.join(runnablesPath, fileStr);
|
||||
const relativePath = path.relative(process.cwd(), changedPath);
|
||||
const relativeToRunnables = fileStr;
|
||||
|
||||
// Skip non-modify events for schema inference
|
||||
if (event.kind !== "modify" && event.kind !== "create") {
|
||||
continue;
|
||||
}
|
||||
// Skip lock files
|
||||
if (changedPath.endsWith(".lock")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip lock files
|
||||
if (changedPath.endsWith(".lock")) {
|
||||
continue;
|
||||
}
|
||||
// Log the change event
|
||||
log.info(
|
||||
colors.cyan(
|
||||
`📝 Runnable changed [${_eventType}]: ${relativePath}`,
|
||||
),
|
||||
);
|
||||
|
||||
// Log the change event
|
||||
// Debounce schema inference per file (wait for typing to finish)
|
||||
if (schemaInferenceTimeouts[changedPath]) {
|
||||
clearTimeout(schemaInferenceTimeouts[changedPath]);
|
||||
}
|
||||
|
||||
schemaInferenceTimeouts[changedPath] = setTimeout(async () => {
|
||||
delete schemaInferenceTimeouts[changedPath];
|
||||
|
||||
try {
|
||||
log.info(
|
||||
colors.cyan(
|
||||
`📝 Inferring schema for: ${relativeToRunnables}`,
|
||||
),
|
||||
);
|
||||
// Infer schema for this runnable (returns schema in memory, doesn't write to file)
|
||||
const result = await inferRunnableSchemaFromFile(
|
||||
process.cwd(),
|
||||
relativeToRunnables,
|
||||
);
|
||||
if (result) {
|
||||
// Store inferred schema in memory
|
||||
inferredSchemas[result.runnableId] = result.schema;
|
||||
log.info(
|
||||
colors.cyan(
|
||||
`📝 Runnable changed [${event.kind}]: ${relativePath}`,
|
||||
colors.green(
|
||||
` Inferred Schemas: ${
|
||||
JSON.stringify(
|
||||
inferredSchemas,
|
||||
null,
|
||||
2,
|
||||
)
|
||||
}`,
|
||||
),
|
||||
);
|
||||
|
||||
// Debounce schema inference per file (wait for typing to finish)
|
||||
if (schemaInferenceTimeouts[changedPath]) {
|
||||
clearTimeout(schemaInferenceTimeouts[changedPath]);
|
||||
}
|
||||
|
||||
schemaInferenceTimeouts[changedPath] = setTimeout(async () => {
|
||||
delete schemaInferenceTimeouts[changedPath];
|
||||
|
||||
try {
|
||||
log.info(
|
||||
colors.cyan(
|
||||
`📝 Inferring schema for: ${relativeToRunnables}`,
|
||||
),
|
||||
);
|
||||
// Infer schema for this runnable (returns schema in memory, doesn't write to file)
|
||||
const result = await inferRunnableSchemaFromFile(
|
||||
process.cwd(),
|
||||
relativeToRunnables,
|
||||
);
|
||||
if (result) {
|
||||
// log.info(colors.green(` Schema: ${JSON.stringify(result.schema, null, 2)}`));
|
||||
// log.info(colors.green(` Runnable ID: ${result.runnableId}`));
|
||||
// Store inferred schema in memory
|
||||
inferredSchemas[result.runnableId] = result.schema;
|
||||
log.info(
|
||||
colors.green(
|
||||
` Inferred Schemas: ${
|
||||
JSON.stringify(
|
||||
inferredSchemas,
|
||||
null,
|
||||
2,
|
||||
)
|
||||
}`,
|
||||
),
|
||||
);
|
||||
// Regenerate wmill.d.ts with updated schema from memory
|
||||
await genRunnablesTs(inferredSchemas);
|
||||
}
|
||||
} catch (error: any) {
|
||||
log.error(
|
||||
colors.red(`Error inferring schema: ${error.message}`),
|
||||
);
|
||||
}
|
||||
}, SCHEMA_DEBOUNCE_MS);
|
||||
// Regenerate wmill.d.ts with updated schema from memory
|
||||
await genRunnablesTs(inferredSchemas);
|
||||
}
|
||||
} catch (error: any) {
|
||||
log.error(
|
||||
colors.red(`Error inferring schema: ${error.message}`),
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.name !== "Interrupted") {
|
||||
log.error(colors.red(`Error watching runnables: ${error.message}`));
|
||||
}
|
||||
}
|
||||
})();
|
||||
}, SCHEMA_DEBOUNCE_MS);
|
||||
});
|
||||
|
||||
runnablesWatcher.on("error", (error: Error) => {
|
||||
log.error(colors.red(`Error watching runnables: ${error.message}`));
|
||||
});
|
||||
} else {
|
||||
log.info(
|
||||
colors.gray(
|
||||
@@ -781,7 +765,7 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
const fileName = path.basename(filePath);
|
||||
|
||||
try {
|
||||
const sqlContent = await Deno.readTextFile(filePath);
|
||||
const sqlContent = await readFile(filePath, "utf-8");
|
||||
|
||||
if (!sqlContent.trim()) {
|
||||
log.info(colors.gray(`Skipping empty file: ${fileName}`));
|
||||
@@ -837,7 +821,7 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
// If there's a current SQL file being shown, send it to the new client
|
||||
if (currentSqlFile && fs.existsSync(currentSqlFile)) {
|
||||
try {
|
||||
const sqlContent = await Deno.readTextFile(currentSqlFile);
|
||||
const sqlContent = await readFile(currentSqlFile, "utf-8");
|
||||
const datatable = await getDatatableConfig();
|
||||
const fileName = path.basename(currentSqlFile);
|
||||
|
||||
@@ -1164,7 +1148,7 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
});
|
||||
|
||||
// Watch sql_to_apply folder for SQL migration files
|
||||
let sqlWatcher: Deno.FsWatcher | undefined;
|
||||
let sqlWatcher: fs.FSWatcher | undefined;
|
||||
|
||||
// Helper to scan for existing SQL files and add them to the queue
|
||||
async function scanExistingSqlFiles(): Promise<void> {
|
||||
@@ -1207,53 +1191,46 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
log.info(
|
||||
colors.blue(`🗃️ Watching sql_to_apply folder at: ${sqlToApplyPath}\n`),
|
||||
);
|
||||
sqlWatcher = Deno.watchFs(sqlToApplyPath);
|
||||
sqlWatcher = fs.watch(sqlToApplyPath, { recursive: true });
|
||||
|
||||
// Debounce timeout for SQL file changes
|
||||
const sqlDebounceTimeouts: Record<string, ReturnType<typeof setTimeout>> = {};
|
||||
const SQL_DEBOUNCE_MS = 300;
|
||||
|
||||
// Handle SQL file changes in the background
|
||||
(async () => {
|
||||
try {
|
||||
for await (const event of sqlWatcher!) {
|
||||
for (const changedPath of event.paths) {
|
||||
// Only handle .sql files
|
||||
if (!changedPath.endsWith(".sql")) {
|
||||
continue;
|
||||
}
|
||||
// Handle SQL file changes via callback
|
||||
sqlWatcher.on("change", (_eventType, filename) => {
|
||||
if (!filename) return;
|
||||
const fileStr = typeof filename === "string" ? filename : filename.toString();
|
||||
const changedPath = path.join(sqlToApplyPath, fileStr);
|
||||
|
||||
// Only handle modify and create events
|
||||
if (event.kind !== "modify" && event.kind !== "create") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fileName = path.basename(changedPath);
|
||||
|
||||
// Debounce per file
|
||||
if (sqlDebounceTimeouts[changedPath]) {
|
||||
clearTimeout(sqlDebounceTimeouts[changedPath]);
|
||||
}
|
||||
|
||||
sqlDebounceTimeouts[changedPath] = setTimeout(async () => {
|
||||
delete sqlDebounceTimeouts[changedPath];
|
||||
|
||||
log.info(colors.cyan(`📋 SQL file detected: ${fileName}`));
|
||||
|
||||
// Add to queue and process
|
||||
queueSqlFile(changedPath);
|
||||
await processNextSqlFile();
|
||||
}, SQL_DEBOUNCE_MS);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.name !== "Interrupted") {
|
||||
log.error(
|
||||
colors.red(`Error watching sql_to_apply: ${error.message}`),
|
||||
);
|
||||
}
|
||||
// Only handle .sql files
|
||||
if (!changedPath.endsWith(".sql")) {
|
||||
return;
|
||||
}
|
||||
})();
|
||||
|
||||
const fileName = path.basename(changedPath);
|
||||
|
||||
// Debounce per file
|
||||
if (sqlDebounceTimeouts[changedPath]) {
|
||||
clearTimeout(sqlDebounceTimeouts[changedPath]);
|
||||
}
|
||||
|
||||
sqlDebounceTimeouts[changedPath] = setTimeout(async () => {
|
||||
delete sqlDebounceTimeouts[changedPath];
|
||||
|
||||
log.info(colors.cyan(`📋 SQL file detected: ${fileName}`));
|
||||
|
||||
// Add to queue and process
|
||||
queueSqlFile(changedPath);
|
||||
await processNextSqlFile();
|
||||
}, SQL_DEBOUNCE_MS);
|
||||
});
|
||||
|
||||
sqlWatcher.on("error", (error: Error) => {
|
||||
log.error(
|
||||
colors.red(`Error watching sql_to_apply: ${error.message}`),
|
||||
);
|
||||
});
|
||||
|
||||
// Scan for existing SQL files after a delay (to let WebSocket clients connect)
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { colors, Command, log, yamlParseFile } from "../../../deps.ts";
|
||||
import * as fs from "node:fs";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
|
||||
import { Command } from "@cliffy/command";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { resolveWorkspace } from "../../core/context.ts";
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { DataTableSchema } from "../../../gen/types.gen.ts";
|
||||
import { generateAgentsDocumentation } from "../sync/sync.ts";
|
||||
import path from "node:path";
|
||||
import * as fs from "node:fs";
|
||||
import {
|
||||
getFolderSuffix,
|
||||
hasFolderSuffix,
|
||||
@@ -192,14 +198,14 @@ export async function regenerateAgentDocs(
|
||||
|
||||
// Generate and write AGENTS.md
|
||||
const agentsContent = generateAgentsDocumentation(localData);
|
||||
await Deno.writeTextFile(path.join(targetDir, "AGENTS.md"), agentsContent);
|
||||
await writeFile(path.join(targetDir, "AGENTS.md"), agentsContent, "utf-8");
|
||||
|
||||
// Generate and write CLAUDE.md referencing AGENTS.md
|
||||
await Deno.writeTextFile(path.join(targetDir, "CLAUDE.md"), `Instructions are in @AGENTS.md\n`);
|
||||
await writeFile(path.join(targetDir, "CLAUDE.md"), `Instructions are in @AGENTS.md\n`, "utf-8");
|
||||
|
||||
// Generate and write DATATABLES.md
|
||||
const datatablesContent = generateDatatablesMarkdown(schemas, localData);
|
||||
await Deno.writeTextFile(path.join(targetDir, "DATATABLES.md"), datatablesContent);
|
||||
await writeFile(path.join(targetDir, "DATATABLES.md"), datatablesContent, "utf-8");
|
||||
|
||||
if (!silent) {
|
||||
log.info(colors.green(`✓ Generated AGENTS.md, CLAUDE.md, and DATATABLES.md`));
|
||||
@@ -229,7 +235,7 @@ async function generateAgents(
|
||||
appFolder?: string
|
||||
) {
|
||||
// Resolve the app folder
|
||||
const cwd = Deno.cwd();
|
||||
const cwd = process.cwd();
|
||||
let targetDir = cwd;
|
||||
|
||||
if (appFolder) {
|
||||
@@ -252,7 +258,7 @@ async function generateAgents(
|
||||
)
|
||||
);
|
||||
log.info(colors.gray("Usage: wmill app generate-agents [app_folder]"));
|
||||
Deno.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,7 +268,7 @@ async function generateAgents(
|
||||
log.error(
|
||||
colors.red(`Error: raw_app.yaml not found in ${targetDir}`)
|
||||
);
|
||||
Deno.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Resolve workspace and authenticate
|
||||
@@ -272,7 +278,6 @@ async function generateAgents(
|
||||
await regenerateAgentDocs(workspace.workspaceId, targetDir);
|
||||
}
|
||||
|
||||
// deno-lint-ignore no-explicit-any
|
||||
const command = new Command()
|
||||
.description("regenerate AGENTS.md and DATATABLES.md from remote workspace")
|
||||
.arguments("[app_folder:string]")
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import process from "node:process";
|
||||
import { colors, Command, log, yamlParseFile } from "../../../deps.ts";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { createBundle } from "./bundle.ts";
|
||||
import { APP_BACKEND_FOLDER } from "./app_metadata.ts";
|
||||
@@ -224,7 +226,7 @@ async function lint(opts: LintOptions, appFolder?: string) {
|
||||
log.info(colors.red(` - ${error}`));
|
||||
});
|
||||
log.info(colors.red("\n❌ Lint failed\n"));
|
||||
Deno.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
log.info(colors.green("\n✅ All checks passed\n"));
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import {
|
||||
colors,
|
||||
Command,
|
||||
Confirm,
|
||||
ensureDir,
|
||||
Input,
|
||||
log,
|
||||
Select,
|
||||
yamlStringify,
|
||||
} from "../../../deps.ts";
|
||||
import { stat, writeFile, mkdir } from "node:fs/promises";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import { Input } from "@cliffy/prompt/input";
|
||||
import { Select } from "@cliffy/prompt/select";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { generateAgentsDocumentation, generateDatatablesDocumentation, yamlOptions } from "../sync/sync.ts";
|
||||
import { resolveWorkspace } from "../../core/context.ts";
|
||||
@@ -480,11 +478,11 @@ CREATE SCHEMA IF NOT EXISTS ${schemaName};
|
||||
|
||||
// Create the directory structure - preserve full path (e.g., f/foobar/x/y becomes f/foobar/x/y.raw_app)
|
||||
const folderName = buildFolderPath(appPath, "raw_app");
|
||||
const appDir = path.join(Deno.cwd(), folderName);
|
||||
const appDir = path.join(process.cwd(), folderName);
|
||||
|
||||
// Check if directory already exists
|
||||
try {
|
||||
await Deno.stat(appDir);
|
||||
await stat(appDir);
|
||||
const overwrite = await Confirm.prompt({
|
||||
message: `Directory '${folderName}' already exists. Overwrite?`,
|
||||
default: false,
|
||||
@@ -497,9 +495,9 @@ CREATE SCHEMA IF NOT EXISTS ${schemaName};
|
||||
// Directory doesn't exist, which is good
|
||||
}
|
||||
|
||||
await ensureDir(appDir);
|
||||
await ensureDir(path.join(appDir, "backend"));
|
||||
await ensureDir(path.join(appDir, "sql_to_apply"));
|
||||
await mkdir(appDir, { recursive: true });
|
||||
await mkdir(path.join(appDir, "backend"), { recursive: true });
|
||||
await mkdir(path.join(appDir, "sql_to_apply"), { recursive: true });
|
||||
|
||||
// Create raw_app.yaml with data configuration
|
||||
const rawAppConfig: Record<string, unknown> = {
|
||||
@@ -511,15 +509,15 @@ CREATE SCHEMA IF NOT EXISTS ${schemaName};
|
||||
rawAppConfig.data = dataConfig;
|
||||
}
|
||||
|
||||
await Deno.writeTextFile(
|
||||
await writeFile(
|
||||
path.join(appDir, "raw_app.yaml"),
|
||||
yamlStringify(rawAppConfig, yamlOptions)
|
||||
yamlStringify(rawAppConfig, yamlOptions), "utf-8"
|
||||
);
|
||||
|
||||
// Create template files
|
||||
for (const [filePath, content] of Object.entries(template.files)) {
|
||||
const fullPath = path.join(appDir, filePath.slice(1)); // Remove leading slash
|
||||
await Deno.writeTextFile(fullPath, content.trim() + "\n");
|
||||
await writeFile(fullPath, content.trim() + "\n", "utf-8");
|
||||
}
|
||||
|
||||
// Create AGENTS.md - main documentation for AI agents
|
||||
@@ -532,22 +530,22 @@ CREATE SCHEMA IF NOT EXISTS ${schemaName};
|
||||
: undefined;
|
||||
|
||||
const agentsContent = generateAgentsDocumentation(dataForDocs);
|
||||
await Deno.writeTextFile(
|
||||
await writeFile(
|
||||
path.join(appDir, "AGENTS.md"),
|
||||
agentsContent
|
||||
agentsContent, "utf-8"
|
||||
);
|
||||
|
||||
// Create CLAUDE.md referencing AGENTS.md
|
||||
await Deno.writeTextFile(
|
||||
await writeFile(
|
||||
path.join(appDir, "CLAUDE.md"),
|
||||
`Instructions are in @AGENTS.md\n`
|
||||
`Instructions are in @AGENTS.md\n`, "utf-8"
|
||||
);
|
||||
|
||||
// Create DATATABLES.md with the configured data
|
||||
const datatablesContent = generateDatatablesDocumentation(dataForDocs);
|
||||
await Deno.writeTextFile(
|
||||
await writeFile(
|
||||
path.join(appDir, "DATATABLES.md"),
|
||||
datatablesContent
|
||||
datatablesContent, "utf-8"
|
||||
);
|
||||
|
||||
// Create example backend runnable
|
||||
@@ -555,20 +553,20 @@ CREATE SCHEMA IF NOT EXISTS ${schemaName};
|
||||
type: "inline",
|
||||
path: undefined,
|
||||
};
|
||||
await Deno.writeTextFile(
|
||||
await writeFile(
|
||||
path.join(appDir, "backend", "a.yaml"),
|
||||
yamlStringify(exampleRunnable, yamlOptions)
|
||||
yamlStringify(exampleRunnable, yamlOptions), "utf-8"
|
||||
);
|
||||
await Deno.writeTextFile(
|
||||
await writeFile(
|
||||
path.join(appDir, "backend", "a.ts"),
|
||||
`export async function main(x: number): Promise<string> {
|
||||
return \`Hello from backend! x = \${x}\`;
|
||||
}
|
||||
`
|
||||
`, "utf-8"
|
||||
);
|
||||
|
||||
// Create sql_to_apply README
|
||||
await Deno.writeTextFile(
|
||||
await writeFile(
|
||||
path.join(appDir, "sql_to_apply", "README.md"),
|
||||
`# SQL Migrations Folder
|
||||
|
||||
@@ -601,9 +599,9 @@ This folder is for SQL migration files that will be applied to datatables during
|
||||
|
||||
// Create schema creation SQL file if a new schema was requested
|
||||
if (createSchemaSQL && schemaName) {
|
||||
await Deno.writeTextFile(
|
||||
await writeFile(
|
||||
path.join(appDir, "sql_to_apply", `000_create_schema_${schemaName}.sql`),
|
||||
createSchemaSQL
|
||||
createSchemaSQL, "utf-8"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -666,7 +664,6 @@ This folder is for SQL migration files that will be applied to datatables during
|
||||
log.info(colors.gray(" 4. wmill sync push (to deploy when ready)"));
|
||||
}
|
||||
|
||||
// deno-lint-ignore no-explicit-any
|
||||
const command = new Command()
|
||||
.description("create a new raw app from a template")
|
||||
.action(newApp as any);
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
import { resolveWorkspace, validatePath } from "../../core/context.ts";
|
||||
import {
|
||||
colors,
|
||||
log,
|
||||
SEP,
|
||||
windmillUtils,
|
||||
yamlParseFile,
|
||||
yamlStringify,
|
||||
} from "../../../deps.ts";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import * as windmillUtils from "@windmill-labs/shared-utils";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { Policy } from "../../../gen/types.gen.ts";
|
||||
import path from "node:path";
|
||||
import { readFile, readdir } from "node:fs/promises";
|
||||
|
||||
import { GlobalOptions, isSuperset } from "../../types.ts";
|
||||
import { deepEqual } from "../../utils/utils.ts";
|
||||
@@ -67,8 +65,8 @@ async function findRunnableContentFile(
|
||||
// Check if this is a recognized extension
|
||||
if (EXTENSION_TO_LANGUAGE[ext]) {
|
||||
try {
|
||||
const content = await Deno.readTextFile(
|
||||
path.join(backendPath, fileName),
|
||||
const content = await readFile(
|
||||
path.join(backendPath, fileName), "utf-8",
|
||||
);
|
||||
return { ext, content };
|
||||
} catch {
|
||||
@@ -130,8 +128,9 @@ export async function loadRunnablesFromBackend(
|
||||
try {
|
||||
// First, collect all files in the backend folder
|
||||
const allFiles: string[] = [];
|
||||
for await (const entry of Deno.readDir(backendPath)) {
|
||||
if (entry.isFile) {
|
||||
const _entries = await readdir(backendPath, { withFileTypes: true });
|
||||
for (const entry of _entries) {
|
||||
if (entry.isFile()) {
|
||||
allFiles.push(entry.name);
|
||||
}
|
||||
}
|
||||
@@ -165,8 +164,9 @@ export async function loadRunnablesFromBackend(
|
||||
// Try to load lock file
|
||||
let lock: string | undefined;
|
||||
try {
|
||||
lock = await Deno.readTextFile(
|
||||
lock = await readFile(
|
||||
path.join(backendPath, `${runnableId}.lock`),
|
||||
"utf-8",
|
||||
);
|
||||
} catch {
|
||||
// No lock file, that's fine
|
||||
@@ -226,8 +226,8 @@ export async function loadRunnablesFromBackend(
|
||||
// Try to load lock file
|
||||
let lock: string | undefined;
|
||||
try {
|
||||
lock = await Deno.readTextFile(
|
||||
path.join(backendPath, `${runnableId}.lock`),
|
||||
lock = await readFile(
|
||||
path.join(backendPath, `${runnableId}.lock`), "utf-8",
|
||||
);
|
||||
} catch {
|
||||
// No lock file, that's fine
|
||||
@@ -245,7 +245,7 @@ export async function loadRunnablesFromBackend(
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
if (error.name !== "NotFound") {
|
||||
if (error.code !== "ENOENT") {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -291,11 +291,12 @@ async function collectAppFiles(
|
||||
const files: Record<string, string> = {};
|
||||
|
||||
async function readDirRecursive(dir: string, basePath: string = "/") {
|
||||
for await (const entry of Deno.readDir(dir)) {
|
||||
const dirEntries = await readdir(dir, { withFileTypes: true });
|
||||
for (const entry of dirEntries) {
|
||||
const fullPath = dir + entry.name;
|
||||
const relativePath = basePath + entry.name;
|
||||
|
||||
if (entry.isDirectory) {
|
||||
if (entry.isDirectory()) {
|
||||
// Skip the runnables, node_modules, and sql_to_apply subfolders
|
||||
if (
|
||||
entry.name === APP_BACKEND_FOLDER ||
|
||||
@@ -307,7 +308,7 @@ async function collectAppFiles(
|
||||
continue;
|
||||
}
|
||||
await readDirRecursive(fullPath + SEP, relativePath + "/");
|
||||
} else if (entry.isFile) {
|
||||
} else if (entry.isFile()) {
|
||||
// Skip generated/metadata files that shouldn't be part of the app
|
||||
if (
|
||||
entry.name === "raw_app.yaml" ||
|
||||
@@ -318,7 +319,7 @@ async function collectAppFiles(
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const content = await Deno.readTextFile(fullPath);
|
||||
const content = await readFile(fullPath, "utf-8");
|
||||
files[relativePath] = content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
import { resolveWorkspace } from "../../core/context.ts";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { colors, Command, log } from "../../../deps.ts";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import * as log from "../../core/log.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import fs from "node:fs";
|
||||
import { workspaceDependenciesPathToLanguageAndFilename } from "../../utils/metadata.ts";
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import {
|
||||
Command,
|
||||
SEP,
|
||||
WebSocketServer,
|
||||
express,
|
||||
getPort,
|
||||
http,
|
||||
log,
|
||||
open,
|
||||
WebSocket,
|
||||
yamlParseFile,
|
||||
} from "../../../deps.ts";
|
||||
import { Command } from "@cliffy/command";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import { WebSocket, WebSocketServer } from "ws";
|
||||
|
||||
import * as getPort from "get-port";
|
||||
import * as http from "node:http";
|
||||
import * as open from "open";
|
||||
import { readFile, realpath } from "node:fs/promises";
|
||||
import { watch } from "node:fs";
|
||||
import { getTypeStrFromPath, GlobalOptions } from "../../types.ts";
|
||||
import { ignoreF } from "../sync/sync.ts";
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
@@ -40,25 +39,30 @@ async function dev(opts: GlobalOptions & SyncOptions) {
|
||||
const conf = await readConfigFile();
|
||||
let currentLastEdit: LastEditScript | LastEditFlow | undefined = undefined;
|
||||
|
||||
const watcher = Deno.watchFs(".");
|
||||
const base = await Deno.realPath(".");
|
||||
const fsWatcher = watch(".", { recursive: true });
|
||||
const base = await realpath(".");
|
||||
opts = await mergeConfigWithConfigFile(opts);
|
||||
const ignore = await ignoreF(opts);
|
||||
|
||||
const changesTimeouts: Record<string, number> = {};
|
||||
async function watchChanges() {
|
||||
for await (const event of watcher) {
|
||||
// console.log(">>>> event", event);
|
||||
const key = event.paths.join(",");
|
||||
if (changesTimeouts[key]) {
|
||||
clearTimeout(changesTimeouts[key]);
|
||||
}
|
||||
// @ts-ignore
|
||||
changesTimeouts[key] = setTimeout(async () => {
|
||||
delete changesTimeouts[key];
|
||||
await loadPaths(event.paths);
|
||||
}, 100);
|
||||
}
|
||||
const changesTimeouts: Record<string, ReturnType<typeof setTimeout>> = {};
|
||||
function watchChanges() {
|
||||
return new Promise<void>((_resolve, _reject) => {
|
||||
fsWatcher.on("change", (_eventType, filename) => {
|
||||
if (!filename) return;
|
||||
const filePath = typeof filename === "string" ? filename : filename.toString();
|
||||
const key = filePath;
|
||||
if (changesTimeouts[key]) {
|
||||
clearTimeout(changesTimeouts[key]);
|
||||
}
|
||||
changesTimeouts[key] = setTimeout(async () => {
|
||||
delete changesTimeouts[key];
|
||||
await loadPaths([filePath]);
|
||||
}, 100);
|
||||
});
|
||||
fsWatcher.on("error", (err) => {
|
||||
_reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const flowFolderSuffix = getFolderSuffixWithSep("flow");
|
||||
@@ -72,8 +76,9 @@ async function dev(opts: GlobalOptions & SyncOptions) {
|
||||
if (paths.length == 0) {
|
||||
return;
|
||||
}
|
||||
const cpath = (await Deno.realPath(paths[0])).replace(base + SEP, "");
|
||||
if (!ignore(cpath, false)) {
|
||||
const nativePath = (await realpath(paths[0])).replace(base + SEP, "");
|
||||
const cpath = nativePath.replaceAll("\\", "/");
|
||||
if (!ignore(nativePath, false)) {
|
||||
const typ = getTypeStrFromPath(cpath);
|
||||
log.info("Detected change in " + cpath + " (" + typ + ")");
|
||||
if (typ == "flow") {
|
||||
@@ -83,13 +88,11 @@ async function dev(opts: GlobalOptions & SyncOptions) {
|
||||
)) as FlowFile;
|
||||
await replaceInlineScripts(
|
||||
localFlow.value.modules,
|
||||
async (path: string) => await Deno.readTextFile(localPath + path),
|
||||
async (path: string) => await readFile(localPath + path, "utf-8"),
|
||||
log,
|
||||
localPath,
|
||||
SEP,
|
||||
undefined,
|
||||
// (path: string, newPath: string) => Deno.renameSync(path, newPath),
|
||||
// (path: string) => Deno.removeSync(path),
|
||||
);
|
||||
currentLastEdit = {
|
||||
type: "flow",
|
||||
@@ -99,7 +102,7 @@ async function dev(opts: GlobalOptions & SyncOptions) {
|
||||
log.info("Updated " + localPath);
|
||||
broadcastChanges(currentLastEdit);
|
||||
} else if (typ == "script") {
|
||||
const content = await Deno.readTextFile(cpath);
|
||||
const content = await readFile(cpath, "utf-8");
|
||||
const splitted = cpath.split(".");
|
||||
const wmPath = splitted[0];
|
||||
const lang = inferContentTypeFromFilePath(cpath, conf.defaultTs);
|
||||
@@ -150,8 +153,10 @@ async function dev(opts: GlobalOptions & SyncOptions) {
|
||||
}
|
||||
|
||||
async function startApp() {
|
||||
const app = express.default();
|
||||
const server = http.createServer(app);
|
||||
const server = http.createServer((_req, res) => {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
});
|
||||
const wss = new WebSocketServer({ server });
|
||||
|
||||
// WebSocket server event listeners
|
||||
@@ -224,7 +229,6 @@ const command = new Command()
|
||||
"--includes <pattern...:string>",
|
||||
"Filter paths givena glob pattern or path"
|
||||
)
|
||||
// deno-lint-ignore no-explicit-any
|
||||
.action(dev as any);
|
||||
|
||||
export default command;
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { GlobalOptions, isSuperset } from "../../types.ts";
|
||||
import { Confirm, SEP, log, yamlStringify } from "../../../deps.ts";
|
||||
import { colors, Command, Table, yamlParseFile } from "../../../deps.ts";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import { Table } from "@cliffy/table";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { mkdirSync, writeFileSync } from "node:fs";
|
||||
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
import { resolveWorkspace, validatePath } from "../../core/context.ts";
|
||||
@@ -51,7 +58,7 @@ export async function pushFlow(
|
||||
|
||||
await replaceInlineScripts(
|
||||
localFlow.value.modules,
|
||||
async (path: string) => await Deno.readTextFile(localPath + path),
|
||||
async (path: string) => await readFile(localPath + path, "utf-8"),
|
||||
log,
|
||||
localPath,
|
||||
SEP
|
||||
@@ -106,7 +113,7 @@ async function push(opts: Options, filePath: string, remotePath: string) {
|
||||
}
|
||||
|
||||
async function list(
|
||||
opts: GlobalOptions & { showArchived?: boolean; includeDraftOnly?: boolean }
|
||||
opts: GlobalOptions & { showArchived?: boolean; includeDraftOnly?: boolean; json?: boolean }
|
||||
) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
@@ -129,13 +136,35 @@ async function list(
|
||||
}
|
||||
}
|
||||
|
||||
new Table()
|
||||
.header(["path", "summary", "edited by"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(total.map((x) => [x.path, x.summary, x.edited_by]))
|
||||
.render();
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(total));
|
||||
} else {
|
||||
new Table()
|
||||
.header(["path", "summary", "edited by"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(total.map((x) => [x.path, x.summary, x.edited_by]))
|
||||
.render();
|
||||
}
|
||||
}
|
||||
async function get(opts: GlobalOptions & { json?: boolean }, path: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
const f = await wmill.getFlowByPath({
|
||||
workspace: workspace.workspaceId,
|
||||
path,
|
||||
});
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(f));
|
||||
} else {
|
||||
console.log(colors.bold("Path:") + " " + f.path);
|
||||
console.log(colors.bold("Summary:") + " " + (f.summary ?? ""));
|
||||
console.log(colors.bold("Description:") + " " + (f.description ?? ""));
|
||||
console.log(colors.bold("Edited by:") + " " + (f.edited_by ?? ""));
|
||||
console.log(colors.bold("Edited at:") + " " + (f.edited_at ?? ""));
|
||||
}
|
||||
}
|
||||
|
||||
async function run(
|
||||
opts: GlobalOptions & {
|
||||
data?: string;
|
||||
@@ -225,7 +254,7 @@ async function preview(
|
||||
// Replace inline scripts with their actual content
|
||||
await replaceInlineScripts(
|
||||
localFlow.value.modules,
|
||||
async (path: string) => await Deno.readTextFile(flowPath + path),
|
||||
async (path: string) => await readFile(flowPath + path, "utf-8"),
|
||||
log,
|
||||
flowPath,
|
||||
SEP
|
||||
@@ -286,7 +315,7 @@ async function generateLocks(
|
||||
const ignore = await ignoreF(opts);
|
||||
const elems = Object.keys(
|
||||
await elementsToMap(
|
||||
await FSFSElement(Deno.cwd(), [], true),
|
||||
await FSFSElement(process.cwd(), [], true),
|
||||
(p, isD) => {
|
||||
return (
|
||||
ignore(p, isD) ||
|
||||
@@ -348,7 +377,7 @@ export function bootstrap(
|
||||
}
|
||||
|
||||
const flowDirFullPath = `${flowPath}.flow`;
|
||||
Deno.mkdirSync(flowDirFullPath, { recursive: false });
|
||||
mkdirSync(flowDirFullPath, { recursive: false });
|
||||
|
||||
const newFlowDefinition = defaultFlowDefinition();
|
||||
if (opts.summary !== undefined) {
|
||||
@@ -363,13 +392,22 @@ export function bootstrap(
|
||||
);
|
||||
|
||||
const flowYamlPath = `${flowDirFullPath}/flow.yaml`;
|
||||
Deno.writeTextFile(flowYamlPath, newFlowDefinitionYaml, { createNew: true });
|
||||
writeFileSync(flowYamlPath, newFlowDefinitionYaml, { flag: "wx", encoding: "utf-8" });
|
||||
}
|
||||
|
||||
const command = new Command()
|
||||
.description("flow related commands")
|
||||
.option("--show-archived", "Enable archived scripts in output")
|
||||
.option("--show-archived", "Enable archived flows in output")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("list", "list all flows")
|
||||
.option("--show-archived", "Enable archived flows in output")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("get", "get a flow's details")
|
||||
.arguments("<path:string>")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(get as any)
|
||||
.command(
|
||||
"push",
|
||||
"push a local flow spec. This overrides any remote versions."
|
||||
@@ -416,10 +454,15 @@ const command = new Command()
|
||||
"Comma separated patterns to specify which file to NOT take into account."
|
||||
)
|
||||
.action(generateLocks as any)
|
||||
.command("bootstrap", "create a new empty flow")
|
||||
.command("new", "create a new empty flow")
|
||||
.arguments("<flow_path:string>")
|
||||
.option("--summary <summary:string>", "script summary")
|
||||
.option("--description <description:string>", "script description")
|
||||
.option("--summary <summary:string>", "flow summary")
|
||||
.option("--description <description:string>", "flow description")
|
||||
.action(bootstrap as any)
|
||||
.command("bootstrap", "create a new empty flow (alias for new)")
|
||||
.arguments("<flow_path:string>")
|
||||
.option("--summary <summary:string>", "flow summary")
|
||||
.option("--description <description:string>", "flow description")
|
||||
.action(bootstrap as any);
|
||||
|
||||
export default command;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import {
|
||||
SEP,
|
||||
colors,
|
||||
log,
|
||||
path,
|
||||
yamlParseFile,
|
||||
yamlStringify,
|
||||
} from "../../../deps.ts";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "../../core/log.ts";
|
||||
import * as path from "node:path";
|
||||
import { sep as SEP } from "node:path";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import {
|
||||
readLockfile,
|
||||
@@ -37,7 +36,7 @@ async function generateFlowHash(
|
||||
folder: string,
|
||||
defaultTs: "bun" | "deno" | undefined
|
||||
) {
|
||||
const elems = await FSFSElement(path.join(Deno.cwd(), folder), [], true);
|
||||
const elems = await FSFSElement(path.join(process.cwd(), folder), [], true);
|
||||
const hashes: Record<string, string> = {};
|
||||
for await (const f of elems.getChildren()) {
|
||||
if (exts.some((e) => f.path.endsWith(e))) {
|
||||
@@ -124,13 +123,11 @@ export async function generateFlowLockInternal(
|
||||
log.info(`Recomputing locks of ${changedScripts.join(", ")} in ${folder}`);
|
||||
await replaceInlineScripts(
|
||||
flowValue.value.modules,
|
||||
async (path: string) => await Deno.readTextFile(folder + SEP + path),
|
||||
async (path: string) => await readFile(folder + SEP + path, "utf-8"),
|
||||
log,
|
||||
folder + SEP!,
|
||||
SEP,
|
||||
changedScripts
|
||||
// (path: string, newPath: string) => Deno.renameSync(path, newPath),
|
||||
// (path: string) => Deno.removeSync(path)
|
||||
);
|
||||
|
||||
//removeChangedLocks
|
||||
@@ -148,12 +145,12 @@ export async function generateFlowLockInternal(
|
||||
opts.defaultTs
|
||||
);
|
||||
inlineScripts.forEach((s) => {
|
||||
writeIfChanged(Deno.cwd() + SEP + folder + SEP + s.path, s.content);
|
||||
writeIfChanged(process.cwd() + SEP + folder + SEP + s.path, s.content);
|
||||
});
|
||||
|
||||
// Overwrite `flow.yaml` with the new lockfile references
|
||||
writeIfChanged(
|
||||
Deno.cwd() + SEP + folder + SEP + "flow.yaml",
|
||||
process.cwd() + SEP + folder + SEP + "flow.yaml",
|
||||
yamlStringify(flowValue as Record<string, any>)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { colors, Command, log, SEP, Table } from "../../../deps.ts";
|
||||
import { stat, writeFile, mkdir } from "node:fs/promises";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Table } from "@cliffy/table";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { sep as SEP } from "node:path";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
@@ -13,7 +19,7 @@ export interface FolderFile {
|
||||
display_name: string | undefined;
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions) {
|
||||
async function list(opts: GlobalOptions & { json?: boolean }) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
@@ -21,18 +27,60 @@ async function list(opts: GlobalOptions) {
|
||||
workspace: workspace.workspaceId,
|
||||
});
|
||||
|
||||
new Table()
|
||||
.header(["Name", "Owners", "Extra Perms"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(
|
||||
folders.map((x) => [
|
||||
x.name,
|
||||
x.owners?.join(",") ?? "-",
|
||||
JSON.stringify(x.extra_perms ?? {}),
|
||||
])
|
||||
)
|
||||
.render();
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(folders));
|
||||
} else {
|
||||
new Table()
|
||||
.header(["Name", "Owners", "Extra Perms"])
|
||||
.padding(2)
|
||||
.border(true)
|
||||
.body(
|
||||
folders.map((x) => [
|
||||
x.name,
|
||||
x.owners?.join(",") ?? "-",
|
||||
JSON.stringify(x.extra_perms ?? {}),
|
||||
])
|
||||
)
|
||||
.render();
|
||||
}
|
||||
}
|
||||
|
||||
async function newFolder(opts: GlobalOptions, name: string) {
|
||||
const dirPath = `f${SEP}${name}`;
|
||||
const filePath = `${dirPath}${SEP}folder.meta.yaml`;
|
||||
try {
|
||||
await stat(filePath);
|
||||
throw new Error("File already exists: " + filePath);
|
||||
} catch (e: any) {
|
||||
if (e.message?.startsWith("File already exists")) throw e;
|
||||
}
|
||||
const template: Omit<FolderFile, "display_name"> = {
|
||||
owners: [],
|
||||
extra_perms: {},
|
||||
};
|
||||
await mkdir(dirPath, { recursive: true });
|
||||
await writeFile(filePath, yamlStringify(template as Record<string, any>), {
|
||||
flag: "wx",
|
||||
encoding: "utf-8",
|
||||
});
|
||||
log.info(colors.green(`Created ${filePath}`));
|
||||
}
|
||||
|
||||
async function get(opts: GlobalOptions & { json?: boolean }, name: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
const f = await wmill.getFolder({
|
||||
workspace: workspace.workspaceId,
|
||||
name,
|
||||
});
|
||||
if (opts.json) {
|
||||
console.log(JSON.stringify(f));
|
||||
} else {
|
||||
console.log(colors.bold("Name:") + " " + f.name);
|
||||
console.log(colors.bold("Summary:") + " " + (f.summary ?? ""));
|
||||
console.log(colors.bold("Owners:") + " " + (f.owners?.join(", ") ?? "-"));
|
||||
console.log(colors.bold("Extra Perms:") + " " + JSON.stringify(f.extra_perms ?? {}));
|
||||
}
|
||||
}
|
||||
|
||||
export async function pushFolder(
|
||||
@@ -103,8 +151,8 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fstat = await Deno.stat(filePath);
|
||||
if (!fstat.isFile) {
|
||||
const fstat = await stat(filePath);
|
||||
if (!fstat.isFile()) {
|
||||
throw new Error("file path must refer to a file.");
|
||||
}
|
||||
|
||||
@@ -121,7 +169,18 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
|
||||
const command = new Command()
|
||||
.description("folder related commands")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("list", "list all folders")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(list as any)
|
||||
.command("get", "get a folder's details")
|
||||
.arguments("<name:string>")
|
||||
.option("--json", "Output as JSON (for piping to jq)")
|
||||
.action(get as any)
|
||||
.command("new", "create a new folder locally")
|
||||
.arguments("<name:string>")
|
||||
.action(newFolder as any)
|
||||
.command(
|
||||
"push",
|
||||
"push a local folder spec. This overrides any remote versions."
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Command } from "../../../deps.ts";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { pullGitSyncSettings } from "./pull.ts";
|
||||
import { pushGitSyncSettings } from "./push.ts";
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { colors, Confirm } from "../../../deps.ts";
|
||||
import process from "node:process";
|
||||
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
import { GitSyncRepository } from "./types.ts";
|
||||
|
||||
@@ -24,7 +27,7 @@ export async function handleLegacyRepositoryMigration(
|
||||
const workspaceIncludePath = gitSyncSettings.include_path;
|
||||
const workspaceIncludeType = gitSyncSettings.include_type;
|
||||
|
||||
if (Deno.stdout.isTerminal() && !opts.yes) {
|
||||
if (!!process.stdout.isTTY && !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.`);
|
||||
@@ -139,6 +142,6 @@ export async function handleLegacyRepositoryMigration(
|
||||
console.error('3. Push local settings to override backend settings:');
|
||||
console.error(' wmill gitsync-settings push\n');
|
||||
}
|
||||
Deno.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import { colors, log, yamlStringify } from "../../../deps.ts";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
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, getWmillYamlPath } from "../../core/conf.ts";
|
||||
import { yamlOptions } from "../sync/sync.ts";
|
||||
import { deepEqual } from "../../utils/utils.ts";
|
||||
import { getCurrentGitBranch, isGitRepository } from "../../utils/git.ts";
|
||||
|
||||
@@ -173,7 +177,7 @@ export async function pullGitSyncSettings(
|
||||
}
|
||||
|
||||
// Write the new configuration
|
||||
await Deno.writeTextFile("wmill.yaml", yamlStringify(updatedConfig));
|
||||
await writeFile("wmill.yaml", yamlStringify(updatedConfig, yamlOptions), "utf-8");
|
||||
|
||||
if (opts.jsonOutput) {
|
||||
console.log(
|
||||
@@ -286,7 +290,7 @@ export async function pullGitSyncSettings(
|
||||
);
|
||||
const hasConflict = !deepEqual(gitSyncBackend, gitSyncCurrent);
|
||||
|
||||
if (hasConflict && !opts.yes && Deno.stdin.isTerminal()) {
|
||||
if (hasConflict && !opts.yes && !!process.stdin.isTTY) {
|
||||
// Show the diff first
|
||||
log.info("Changes that would be applied locally:");
|
||||
const changes = generateChanges(effectiveCurrentSettings, backendSyncOptions);
|
||||
@@ -295,7 +299,7 @@ export async function pullGitSyncSettings(
|
||||
}
|
||||
|
||||
// Interactive mode - ask user
|
||||
const { Select } = await import("../../../deps.ts");
|
||||
const { Select } = await import("@cliffy/prompt/select");
|
||||
const choice = await Select.prompt({
|
||||
message: "Settings conflict detected. How would you like to proceed?",
|
||||
options: [
|
||||
@@ -369,7 +373,7 @@ export async function pullGitSyncSettings(
|
||||
}
|
||||
|
||||
// Write updated configuration
|
||||
await Deno.writeTextFile("wmill.yaml", yamlStringify(updatedConfig));
|
||||
await writeFile("wmill.yaml", yamlStringify(updatedConfig, yamlOptions), "utf-8");
|
||||
|
||||
if (opts.jsonOutput) {
|
||||
console.log(
|
||||
@@ -446,7 +450,7 @@ export async function pullGitSyncSettings(
|
||||
}
|
||||
|
||||
// Write updated configuration
|
||||
await Deno.writeTextFile("wmill.yaml", yamlStringify(updatedConfig));
|
||||
await writeFile("wmill.yaml", yamlStringify(updatedConfig, yamlOptions), "utf-8");
|
||||
|
||||
if (opts.jsonOutput) {
|
||||
console.log(
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { colors, log, Confirm } from "../../../deps.ts";
|
||||
import process from "node:process";
|
||||
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
import { resolveWorkspace } from "../../core/context.ts";
|
||||
@@ -34,7 +38,7 @@ export async function pushGitSyncSettings(
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes("overrides")) {
|
||||
log.error(error.message);
|
||||
Deno.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
@@ -51,7 +55,7 @@ export async function pushGitSyncSettings(
|
||||
"No wmill.yaml file found. Please run 'wmill init' first to create the configuration file.",
|
||||
),
|
||||
);
|
||||
Deno.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Read local configuration
|
||||
@@ -247,7 +251,7 @@ export async function pushGitSyncSettings(
|
||||
}
|
||||
|
||||
// Ask for confirmation unless --yes is passed or not in TTY
|
||||
if (!opts.yes && Deno.stdin.isTerminal()) {
|
||||
if (!opts.yes && !!process.stdin.isTTY) {
|
||||
const confirmed = await Confirm.prompt({
|
||||
message: `Do you want to apply these changes to the remote?`,
|
||||
default: true,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { colors, log } from "../../../deps.ts";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import * as log from "../../core/log.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";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// deno-lint-ignore-file no-explicit-any
|
||||
import { Command, log } from "../../../deps.ts";
|
||||
import { Command } from "@cliffy/command";
|
||||
import * as log from "../../core/log.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
|
||||
import { requireLogin } from "../../core/auth.ts";
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { colors, Command, log, yamlStringify, Confirm } from "../../../deps.ts";
|
||||
import { stat, writeFile, rm, mkdir } from "node:fs/promises";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import * as log from "../../core/log.ts";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { GlobalOptions } from "../../types.ts";
|
||||
import { readLockfile } from "../../utils/metadata.ts";
|
||||
import { getActiveWorkspaceOrFallback } from "../workspace/workspace.ts";
|
||||
@@ -36,7 +41,7 @@ export interface InitOptions {
|
||||
* Bootstrap a windmill project with a wmill.yaml file
|
||||
*/
|
||||
async function initAction(opts: InitOptions) {
|
||||
if (await Deno.stat("wmill.yaml").catch(() => null)) {
|
||||
if (await stat("wmill.yaml").catch(() => null)) {
|
||||
log.error(colors.red("wmill.yaml already exists"));
|
||||
} else {
|
||||
// Import DEFAULT_SYNC_OPTIONS from conf.ts
|
||||
@@ -63,7 +68,7 @@ async function initAction(opts: InitOptions) {
|
||||
}
|
||||
|
||||
initialConfig.nonDottedPaths = true;
|
||||
await Deno.writeTextFile("wmill.yaml", yamlStringify(initialConfig));
|
||||
await writeFile("wmill.yaml", yamlStringify(initialConfig), "utf-8");
|
||||
log.info(colors.green("wmill.yaml created with default settings"));
|
||||
|
||||
// Create lock file
|
||||
@@ -80,12 +85,12 @@ async function initAction(opts: InitOptions) {
|
||||
const shouldBind = opts.bindProfile === true;
|
||||
const shouldPrompt =
|
||||
opts.bindProfile === undefined &&
|
||||
Deno.stdin.isTerminal() &&
|
||||
!!process.stdin.isTTY &&
|
||||
!opts.useDefault;
|
||||
|
||||
const shouldSkip =
|
||||
opts.bindProfile != true &&
|
||||
(opts.useDefault || !Deno.stdin.isTerminal());
|
||||
(opts.useDefault || !!!process.stdin.isTTY);
|
||||
|
||||
if (!shouldSkip) {
|
||||
// Show workspace info if we're binding or prompting
|
||||
@@ -132,7 +137,7 @@ async function initAction(opts: InitOptions) {
|
||||
currentConfig.gitBranches[currentBranch].workspaceId =
|
||||
activeWorkspace.workspaceId;
|
||||
|
||||
await Deno.writeTextFile("wmill.yaml", yamlStringify(currentConfig));
|
||||
await writeFile("wmill.yaml", yamlStringify(currentConfig), "utf-8");
|
||||
|
||||
log.info(
|
||||
colors.green(
|
||||
@@ -183,7 +188,7 @@ async function initAction(opts: InitOptions) {
|
||||
|
||||
if (useBackendSettings === undefined) {
|
||||
// Interactive prompt
|
||||
const { Select } = await import("../../../deps.ts");
|
||||
const { Select } = await import("@cliffy/prompt/select");
|
||||
const choice = await Select.prompt({
|
||||
message:
|
||||
"Git-sync settings found on backend. What would you like to do?",
|
||||
@@ -206,13 +211,13 @@ async function initAction(opts: InitOptions) {
|
||||
if (choice === "cancel") {
|
||||
// Clean up the created files
|
||||
try {
|
||||
await Deno.remove("wmill.yaml");
|
||||
await Deno.remove("wmill-lock.yaml");
|
||||
await rm("wmill.yaml");
|
||||
await rm("wmill-lock.yaml");
|
||||
} catch (e) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
log.info("Init cancelled");
|
||||
Deno.exit(0);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
useBackendSettings = choice === "backend";
|
||||
@@ -256,32 +261,32 @@ async function initAction(opts: InitOptions) {
|
||||
).join("\n");
|
||||
|
||||
// Create AGENTS.md file with minimal instructions
|
||||
if (!(await Deno.stat("AGENTS.md").catch(() => null))) {
|
||||
await Deno.writeTextFile(
|
||||
if (!(await stat("AGENTS.md").catch(() => null))) {
|
||||
await writeFile(
|
||||
"AGENTS.md",
|
||||
generateAgentsMdContent(skillsReference)
|
||||
generateAgentsMdContent(skillsReference), "utf-8"
|
||||
);
|
||||
log.info(colors.green("Created AGENTS.md"));
|
||||
}
|
||||
|
||||
// Create CLAUDE.md file, referencing AGENTS.md
|
||||
if (!(await Deno.stat("CLAUDE.md").catch(() => null))) {
|
||||
await Deno.writeTextFile(
|
||||
if (!(await stat("CLAUDE.md").catch(() => null))) {
|
||||
await writeFile(
|
||||
"CLAUDE.md",
|
||||
`Instructions are in @AGENTS.md
|
||||
`
|
||||
`, "utf-8"
|
||||
);
|
||||
log.info(colors.green("Created CLAUDE.md"));
|
||||
}
|
||||
|
||||
// Create .claude/skills/ directory and skill files
|
||||
try {
|
||||
await Deno.mkdir(".claude/skills", { recursive: true });
|
||||
await mkdir(".claude/skills", { recursive: true });
|
||||
|
||||
await Promise.all(
|
||||
SKILLS.map(async (skill) => {
|
||||
const skillDir = `.claude/skills/${skill.name}`;
|
||||
await Deno.mkdir(skillDir, { recursive: true });
|
||||
await mkdir(skillDir, { recursive: true });
|
||||
|
||||
let skillContent = SKILL_CONTENT[skill.name];
|
||||
if (skillContent) {
|
||||
@@ -304,7 +309,7 @@ async function initAction(opts: InitOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
await Deno.writeTextFile(`${skillDir}/SKILL.md`, skillContent);
|
||||
await writeFile(`${skillDir}/SKILL.md`, skillContent, "utf-8");
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import {
|
||||
Command,
|
||||
Confirm,
|
||||
path,
|
||||
Select,
|
||||
setClient,
|
||||
Table,
|
||||
yamlParseFile,
|
||||
yamlStringify,
|
||||
} from "../../../deps.ts";
|
||||
import { readFile, writeFile, readdir, mkdir, rm, stat } from "node:fs/promises";
|
||||
import { appendFile } from "node:fs/promises";
|
||||
import { colors } from "@cliffy/ansi/colors";
|
||||
import { Command } from "@cliffy/command";
|
||||
import { Confirm } from "@cliffy/prompt/confirm";
|
||||
import { Input } from "@cliffy/prompt/input";
|
||||
import { Select } from "@cliffy/prompt/select";
|
||||
import { Table } from "@cliffy/table";
|
||||
import * as log from "../../core/log.ts";
|
||||
import * as path from "node:path";
|
||||
import { stringify as yamlStringify } from "yaml";
|
||||
import { setClient } from "../../core/client.ts";
|
||||
import { yamlParseFile } from "../../utils/yaml.ts";
|
||||
import * as wmill from "../../../gen/services.gen.ts";
|
||||
|
||||
import { colors, Input, log } from "../../../deps.ts";
|
||||
import { loginInteractive } from "../../core/login.ts";
|
||||
import {
|
||||
getActiveInstanceFilePath,
|
||||
@@ -51,7 +52,7 @@ export interface Instance {
|
||||
export async function allInstances(): Promise<Instance[]> {
|
||||
try {
|
||||
const file = await getInstancesConfigFilePath();
|
||||
const txt = await Deno.readTextFile(file);
|
||||
const txt = await readFile(file, "utf-8");
|
||||
return txt
|
||||
.split("\n")
|
||||
.map((line) => {
|
||||
@@ -118,26 +119,19 @@ export async function addInstance(
|
||||
async function appendInstance(instance: Instance) {
|
||||
instance.remote = new URL(instance.remote).toString(); // add trailing slash in all cases!
|
||||
await removeInstance(instance.name);
|
||||
const file = await Deno.open(await getInstancesConfigFilePath(), {
|
||||
append: true,
|
||||
write: true,
|
||||
read: true,
|
||||
create: true,
|
||||
});
|
||||
await file.write(new TextEncoder().encode(JSON.stringify(instance) + "\n"));
|
||||
|
||||
file.close();
|
||||
const filePath = await getInstancesConfigFilePath();
|
||||
await appendFile(filePath, JSON.stringify(instance) + "\n", "utf-8");
|
||||
}
|
||||
|
||||
async function removeInstance(name: string) {
|
||||
const orgWorkspaces = await allInstances();
|
||||
|
||||
await Deno.writeTextFile(
|
||||
await writeFile(
|
||||
await getInstancesConfigFilePath(),
|
||||
orgWorkspaces
|
||||
.filter((x) => x.name !== name)
|
||||
.map((x) => JSON.stringify(x))
|
||||
.join("\n") + "\n",
|
||||
.join("\n") + "\n", "utf-8",
|
||||
);
|
||||
}
|
||||
|
||||
@@ -289,7 +283,7 @@ async function instancePull(opts: InstanceSyncOptions) {
|
||||
|
||||
const totalChanges = uChanges + sChanges + cChanges + gChanges;
|
||||
|
||||
const rootDir = Deno.cwd();
|
||||
const rootDir = process.cwd();
|
||||
|
||||
if (totalChanges > 0) {
|
||||
let confirm = true;
|
||||
@@ -308,7 +302,7 @@ async function instancePull(opts: InstanceSyncOptions) {
|
||||
if (confirm) {
|
||||
if (uChanges > 0) {
|
||||
if (opts.folderPerInstance && opts.prefixSettings) {
|
||||
await Deno.mkdir(path.join(rootDir, opts.prefix), {
|
||||
await mkdir(path.join(rootDir, opts.prefix), {
|
||||
recursive: true,
|
||||
});
|
||||
}
|
||||
@@ -348,10 +342,10 @@ async function instancePull(opts: InstanceSyncOptions) {
|
||||
const workspaceName = opts?.folderPerInstance
|
||||
? instance.prefix + "/" + remoteWorkspace.id
|
||||
: instance.prefix + "_" + remoteWorkspace.id;
|
||||
await Deno.mkdir(path.join(rootDir, workspaceName), {
|
||||
await mkdir(path.join(rootDir, workspaceName), {
|
||||
recursive: true,
|
||||
});
|
||||
await Deno.chdir(path.join(rootDir, workspaceName));
|
||||
process.chdir(path.join(rootDir, workspaceName));
|
||||
await addWorkspace(
|
||||
{
|
||||
remote: instance.remote,
|
||||
@@ -397,7 +391,7 @@ async function instancePull(opts: InstanceSyncOptions) {
|
||||
if (confirmDelete) {
|
||||
for (const workspace of localWorkspacesToDelete) {
|
||||
await removeWorkspace(workspace.id, false, {});
|
||||
await Deno.remove(path.join(rootDir, workspace.dir), {
|
||||
await rm(path.join(rootDir, workspace.dir), {
|
||||
recursive: true,
|
||||
});
|
||||
}
|
||||
@@ -467,7 +461,7 @@ async function instancePush(opts: InstanceSyncOptions) {
|
||||
|
||||
if (opts.includeWorkspaces) {
|
||||
instances = await allInstances();
|
||||
const rootDir = Deno.cwd();
|
||||
const rootDir = process.cwd();
|
||||
|
||||
let localPrefix;
|
||||
if (opts.prefix) {
|
||||
@@ -506,7 +500,7 @@ async function instancePush(opts: InstanceSyncOptions) {
|
||||
for (const localWorkspace of localWorkspaces) {
|
||||
log.info("\nPushing workspace " + localWorkspace.id);
|
||||
try {
|
||||
await Deno.chdir(path.join(rootDir, localWorkspace.dir));
|
||||
process.chdir(path.join(rootDir, localWorkspace.dir));
|
||||
} catch (_) {
|
||||
throw new Error(
|
||||
"Workspace folder not found, are you in the right directory?",
|
||||
@@ -515,7 +509,7 @@ async function instancePush(opts: InstanceSyncOptions) {
|
||||
|
||||
try {
|
||||
const workspaceSettings = (await yamlParseFile(
|
||||
path.join(Deno.cwd(), "settings.yaml"),
|
||||
path.join(process.cwd(), "settings.yaml"),
|
||||
)) as SimplifiedSettings;
|
||||
await workspaceSetup(
|
||||
{
|
||||
@@ -586,12 +580,13 @@ async function getLocalWorkspaces(
|
||||
) {
|
||||
const localWorkspaces: { dir: string; id: string }[] = [];
|
||||
|
||||
if (!(await Deno.stat(localPrefix).catch(() => null))) {
|
||||
await Deno.mkdir(localPrefix);
|
||||
if (!(await stat(localPrefix).catch(() => null))) {
|
||||
await mkdir(localPrefix);
|
||||
}
|
||||
if (folderPerInstance) {
|
||||
for await (const dir of Deno.readDir(rootDir + "/" + localPrefix)) {
|
||||
if (dir.isDirectory) {
|
||||
const prefixEntries = await readdir(rootDir + "/" + localPrefix, { withFileTypes: true });
|
||||
for (const dir of prefixEntries) {
|
||||
if (dir.isDirectory()) {
|
||||
const dirName = dir.name;
|
||||
localWorkspaces.push({
|
||||
dir: localPrefix + "/" + dirName,
|
||||
@@ -600,7 +595,8 @@ async function getLocalWorkspaces(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for await (const dir of Deno.readDir(rootDir)) {
|
||||
const rootEntries = await readdir(rootDir, { withFileTypes: true });
|
||||
for (const dir of rootEntries) {
|
||||
const dirName = dir.name;
|
||||
if (dirName.startsWith(localPrefix + "_")) {
|
||||
localWorkspaces.push({
|
||||
@@ -631,9 +627,9 @@ async function switchI(opts: {}, instanceName: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Deno.writeTextFile(
|
||||
await writeFile(
|
||||
await getActiveInstanceFilePath(),
|
||||
instanceName,
|
||||
instanceName, "utf-8",
|
||||
);
|
||||
|
||||
log.info(colors.green.underline(`Switched to instance ${instanceName}`));
|
||||
@@ -646,7 +642,7 @@ export async function getActiveInstance(opts: {
|
||||
return opts.instance;
|
||||
}
|
||||
try {
|
||||
return await Deno.readTextFile(await getActiveInstanceFilePath());
|
||||
return await readFile(await getActiveInstanceFilePath(), "utf-8");
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
@@ -657,7 +653,7 @@ async function getConfig(opts: InstanceSyncOptions & { outputFile?: string }) {
|
||||
const config = await wmill.getInstanceConfig();
|
||||
const yaml = yamlStringify(config as Record<string, unknown>);
|
||||
if (opts.outputFile) {
|
||||
await Deno.writeTextFile(opts.outputFile, yaml);
|
||||
await writeFile(opts.outputFile, yaml, "utf-8");
|
||||
log.info(colors.green(`Instance config written to ${opts.outputFile}`));
|
||||
} else {
|
||||
console.log(yaml);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user