mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
feat: upgrade bun to v1.3.8 with regression tests (#7761)
* test: add bun executor tests with minimal production code changes - Add comprehensive bun job tests (bun_jobs.rs) covering: - Basic execution, error handling, annotation modes - Relative imports, deeply nested imports - Dedicated worker protocol for both Node.js and Bun runtimes - Builder tests for lockfile generation (import scanning) - Minimize changes to bun_executor.rs by exposing: - RELATIVE_BUN_LOADER and RELATIVE_BUN_BUILDER constants - build_loader() function and LoaderMode enum - BUN_DEDICATED_WORKER_ARGS constant - generate_dedicated_worker_wrapper() function - Tests call production code directly (build_loader) instead of duplicating script generation logic Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * nit * fix: reuse BUN_PATH/NODE_BIN_PATH from windmill-worker, add node to CI - Tests now use exported BUN_PATH and NODE_BIN_PATH constants instead of duplicating env var logic - Update backend-test.yml: - Upgrade bun to v1.3.8 - Add setup-node action - Add NODE_BIN_PATH to cargo test command Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * add private repo test * fix private repo test * try fix again * fix --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,7 +42,7 @@ RUN wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VER
|
||||
RUN /usr/local/bin/python3 -m pip install pip-tools
|
||||
|
||||
# Bun
|
||||
COPY --from=oven/bun:1.2.23 /usr/local/bin/bun /usr/bin/bun
|
||||
COPY --from=oven/bun:1.3.8 /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
|
||||
@@ -44,7 +44,10 @@ jobs:
|
||||
go-version: 1.21.5
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.1.43
|
||||
bun-version: 1.3.8
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- uses: astral-sh/setup-uv@v6.2.1
|
||||
with:
|
||||
version: "0.9.24"
|
||||
@@ -67,6 +70,111 @@ jobs:
|
||||
- name: Substitute EE code (EE logic is behind feature flag)
|
||||
run: |
|
||||
./substitute_ee_code.sh --copy --dir ./windmill-ee-private
|
||||
- name: Setup private npm registry with test package
|
||||
working-directory: /tmp
|
||||
run: |
|
||||
set -e
|
||||
|
||||
# Install Verdaccio globally
|
||||
npm install -g verdaccio
|
||||
|
||||
# Create Verdaccio config that requires authentication for @windmill-test packages
|
||||
mkdir -p /tmp/verdaccio/storage
|
||||
cat > /tmp/verdaccio/config.yaml << 'VERDACCIO_CONFIG'
|
||||
storage: /tmp/verdaccio/storage
|
||||
auth:
|
||||
htpasswd:
|
||||
file: /tmp/verdaccio/htpasswd
|
||||
max_users: 100
|
||||
uplinks:
|
||||
npmjs:
|
||||
url: https://registry.npmjs.org/
|
||||
packages:
|
||||
'@windmill-test/*':
|
||||
access: $authenticated
|
||||
publish: $authenticated
|
||||
'@*/*':
|
||||
access: $all
|
||||
publish: $authenticated
|
||||
proxy: npmjs
|
||||
'**':
|
||||
access: $all
|
||||
publish: $authenticated
|
||||
proxy: npmjs
|
||||
server:
|
||||
keepAliveTimeout: 60
|
||||
middlewares:
|
||||
audit:
|
||||
enabled: true
|
||||
log: { type: stdout, format: pretty, level: warn }
|
||||
VERDACCIO_CONFIG
|
||||
|
||||
# Create empty htpasswd file (users will be created via API)
|
||||
touch /tmp/verdaccio/htpasswd
|
||||
|
||||
# Start Verdaccio in background
|
||||
verdaccio --config /tmp/verdaccio/config.yaml &
|
||||
VERDACCIO_PID=$!
|
||||
|
||||
# Wait for Verdaccio to be ready
|
||||
echo "Waiting for Verdaccio to start..."
|
||||
for i in {1..30}; do
|
||||
if curl -s http://localhost:4873/-/ping > /dev/null 2>&1; then
|
||||
echo "Verdaccio is ready"
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Login to get a token
|
||||
echo "Getting auth token..."
|
||||
RESPONSE=$(curl -s -X PUT \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"testuser","password":"testpass123"}' \
|
||||
http://localhost:4873/-/user/org.couchdb.user:testuser)
|
||||
|
||||
echo "Auth response: $RESPONSE"
|
||||
NPM_TOKEN=$(echo "$RESPONSE" | jq -r '.token')
|
||||
|
||||
if [ -z "$NPM_TOKEN" ] || [ "$NPM_TOKEN" = "null" ]; then
|
||||
echo "Failed to get NPM token from response"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "NPM_TOKEN=${NPM_TOKEN}" >> $GITHUB_ENV
|
||||
echo "Got NPM token successfully: ${NPM_TOKEN:0:10}..."
|
||||
|
||||
# Configure npm globally with the auth token
|
||||
echo "//localhost:4873/:_authToken=${NPM_TOKEN}" > ~/.npmrc
|
||||
echo "Configured ~/.npmrc with auth token"
|
||||
|
||||
# Create a simple test package
|
||||
mkdir -p /tmp/windmill-test-private-pkg
|
||||
cat > /tmp/windmill-test-private-pkg/package.json << 'PKG_JSON'
|
||||
{
|
||||
"name": "@windmill-test/private-pkg",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js"
|
||||
}
|
||||
PKG_JSON
|
||||
cat > /tmp/windmill-test-private-pkg/index.js << 'PKG_JS'
|
||||
module.exports.greet = (name) => `Hello from private package, ${name}!`;
|
||||
PKG_JS
|
||||
|
||||
# Publish to Verdaccio with auth
|
||||
cd /tmp/windmill-test-private-pkg
|
||||
echo "Publishing package..."
|
||||
npm publish --registry http://localhost:4873
|
||||
echo "Package published successfully"
|
||||
|
||||
# Verify the package requires auth by trying anonymous access (should fail)
|
||||
rm -f ~/.npmrc
|
||||
echo "Testing anonymous access (should fail)..."
|
||||
if npm view @windmill-test/private-pkg --registry http://localhost:4873 2>/dev/null; then
|
||||
echo "ERROR: Package should require authentication but anonymous access worked"
|
||||
exit 1
|
||||
fi
|
||||
echo "Verified: Package requires authentication for @windmill-test/private-pkg"
|
||||
- name: Cache DuckDB FFI module build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
@@ -84,9 +192,10 @@ jobs:
|
||||
RUST_LOG_STYLE: never
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
WMDEBUG_FORCE_V0_WORKSPACE_DEPENDENCIES: 1
|
||||
WMDEBUG_FORCE_RUNNABLE_SETTINGS_V0: 1
|
||||
WMDEBUG_FORCE_RUNNABLE_SETTINGS_V0: 1
|
||||
WMDEBUG_FORCE_NO_LEGACY_DEBOUNCING_COMPAT: 1
|
||||
TEST_NPM_REGISTRY: "http://localhost:4873/:_authToken=${{ env.NPM_TOKEN }}"
|
||||
run: |
|
||||
deno --version && bun -v && go version && python3 --version
|
||||
deno --version && bun -v && node --version && go version && python3 --version
|
||||
cd windmill-duckdb-ffi-internal && ./build_dev.sh && cd ..
|
||||
DENO_PATH=$(which deno) BUN_PATH=$(which bun) GO_PATH=$(which go) UV_PATH=$(which uv) cargo test --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,parquet,private --all -- --nocapture
|
||||
DENO_PATH=$(which deno) BUN_PATH=$(which bun) NODE_BIN_PATH=$(which node) GO_PATH=$(which go) UV_PATH=$(which uv) cargo test --features enterprise,deno_core,duckdb,license,python,rust,scoped_cache,parquet,private,private_registry_test --all -- --nocapture
|
||||
|
||||
+1
-1
@@ -234,7 +234,7 @@ COPY --from=windmill_duckdb_ffi_internal_builder /windmill-duckdb-ffi-internal/t
|
||||
|
||||
COPY --from=denoland/deno:2.2.1 --chmod=755 /usr/bin/deno /usr/bin/deno
|
||||
|
||||
COPY --from=oven/bun:1.2.23 /usr/local/bin/bun /usr/bin/bun
|
||||
COPY --from=oven/bun:1.3.8 /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
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
|
||||
|
||||
Generated
+1
@@ -15527,6 +15527,7 @@ dependencies = [
|
||||
"sqlx",
|
||||
"strum 0.27.2",
|
||||
"systemstat",
|
||||
"tempfile",
|
||||
"tikv-jemalloc-ctl",
|
||||
"tikv-jemalloc-sys",
|
||||
"tikv-jemallocator",
|
||||
|
||||
@@ -90,6 +90,7 @@ zip = ["windmill-api/zip"]
|
||||
static_frontend = ["windmill-api/static_frontend"]
|
||||
scoped_cache = ["windmill-common/scoped_cache"]
|
||||
test_job_debouncing = []
|
||||
private_registry_test = []
|
||||
# Languages
|
||||
python = ["windmill-worker/python", "windmill-api/python"]
|
||||
rust = ["windmill-worker/rust"]
|
||||
@@ -182,6 +183,7 @@ axum.workspace = true
|
||||
serde.workspace = true
|
||||
windmill-api-client.workspace = true
|
||||
deno_core = { workspace = true, features = ["include_js_files_for_snapshotting", "unsafe_use_unprotected_platform"] }
|
||||
tempfile.workspace = true
|
||||
|
||||
|
||||
[workspace.dependencies]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -803,3 +803,47 @@ pub async fn rebuild_dmap(client: &windmill_api_client::Client) -> bool {
|
||||
.status()
|
||||
.is_success()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Dedicated Worker Protocol Helpers
|
||||
// ============================================================================
|
||||
|
||||
/// Result from parsing a dedicated worker stdout line
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum DedicatedWorkerResult {
|
||||
/// Worker printed "start" indicating it's ready
|
||||
Start,
|
||||
/// Worker returned a successful result
|
||||
Success(serde_json::Value),
|
||||
/// Worker returned an error result
|
||||
Error(serde_json::Value),
|
||||
/// Line is not a protocol message (e.g., logs)
|
||||
Other(String),
|
||||
}
|
||||
|
||||
/// Parse a line from dedicated worker stdout according to the protocol:
|
||||
/// - "start" -> Ready signal
|
||||
/// - "wm_res[success]:JSON" -> Success with result
|
||||
/// - "wm_res[error]:JSON" -> Error with details
|
||||
/// - anything else -> Other (logs)
|
||||
pub fn parse_dedicated_worker_line(line: &str) -> DedicatedWorkerResult {
|
||||
if line == "start" {
|
||||
return DedicatedWorkerResult::Start;
|
||||
}
|
||||
|
||||
if let Some(json_str) = line.strip_prefix("wm_res[success]:") {
|
||||
match serde_json::from_str(json_str) {
|
||||
Ok(value) => return DedicatedWorkerResult::Success(value),
|
||||
Err(_) => return DedicatedWorkerResult::Other(line.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(json_str) = line.strip_prefix("wm_res[error]:") {
|
||||
match serde_json::from_str(json_str) {
|
||||
Ok(value) => return DedicatedWorkerResult::Error(value),
|
||||
Err(_) => return DedicatedWorkerResult::Other(line.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
DedicatedWorkerResult::Other(line.to_string())
|
||||
}
|
||||
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
-- Fixture for Bun edge case tests
|
||||
-- Tests deeply nested imports (level1 -> level2 -> level3)
|
||||
|
||||
-- Level 3: Base script (deepest level)
|
||||
INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES (
|
||||
'test-workspace',
|
||||
'test-user',
|
||||
'
|
||||
export function main() {
|
||||
return "level3";
|
||||
}
|
||||
',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'',
|
||||
'',
|
||||
'f/nested/level3', 20001, 'bun', '');
|
||||
|
||||
-- Level 2: Imports level3 using RELATIVE path (./level3.ts)
|
||||
INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES (
|
||||
'test-workspace',
|
||||
'test-user',
|
||||
'
|
||||
import { main as level3 } from "./level3.ts";
|
||||
|
||||
export function main() {
|
||||
return "level2 -> " + level3();
|
||||
}
|
||||
',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'',
|
||||
'',
|
||||
'f/nested/level2', 20002, 'bun', '');
|
||||
|
||||
-- Level 1: Imports level2 using RELATIVE path (./level2.ts)
|
||||
INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES (
|
||||
'test-workspace',
|
||||
'test-user',
|
||||
'
|
||||
import { main as level2 } from "./level2.ts";
|
||||
|
||||
export function main() {
|
||||
return "level1 -> " + level2();
|
||||
}
|
||||
',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'',
|
||||
'',
|
||||
'f/nested/level1', 20003, 'bun', '');
|
||||
|
||||
-- Script with preprocessor function
|
||||
INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock, has_preprocessor) VALUES (
|
||||
'test-workspace',
|
||||
'test-user',
|
||||
'
|
||||
export function preprocessor(value: number) {
|
||||
return { value: value * 2 };
|
||||
}
|
||||
|
||||
export function main(value: number) {
|
||||
return value + 100;
|
||||
}
|
||||
',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{"value":{"type":"number"}},"required":["value"],"type":"object"}',
|
||||
'Script with preprocessor',
|
||||
'',
|
||||
'f/edge_cases/with_preprocessor', 20004, 'bun', '', true);
|
||||
|
||||
-- Script with nodejs annotation
|
||||
INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES (
|
||||
'test-workspace',
|
||||
'test-user',
|
||||
'//nodejs
|
||||
|
||||
export function main() {
|
||||
return process.version;
|
||||
}
|
||||
',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'NodeJS mode script',
|
||||
'',
|
||||
'f/edge_cases/nodejs_mode', 20005, 'bun', '');
|
||||
|
||||
-- Script with nobundling annotation
|
||||
INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES (
|
||||
'test-workspace',
|
||||
'test-user',
|
||||
'//nobundling
|
||||
|
||||
export function main() {
|
||||
return "no bundle";
|
||||
}
|
||||
',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'No bundling mode script',
|
||||
'',
|
||||
'f/edge_cases/nobundling_mode', 20006, 'bun', '');
|
||||
|
||||
-- Script that uses circular-ish import pattern (A imports B, B imports C, test imports A and C)
|
||||
INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES (
|
||||
'test-workspace',
|
||||
'test-user',
|
||||
'
|
||||
export const SHARED_VALUE = "shared";
|
||||
|
||||
export function main() {
|
||||
return SHARED_VALUE;
|
||||
}
|
||||
',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'',
|
||||
'',
|
||||
'f/circular/shared', 20007, 'bun', '');
|
||||
|
||||
-- module_a uses RELATIVE path import (./shared.ts)
|
||||
INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES (
|
||||
'test-workspace',
|
||||
'test-user',
|
||||
'
|
||||
import { SHARED_VALUE } from "./shared.ts";
|
||||
|
||||
export function getValue() {
|
||||
return "from_a_" + SHARED_VALUE;
|
||||
}
|
||||
|
||||
export function main() {
|
||||
return getValue();
|
||||
}
|
||||
',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'',
|
||||
'',
|
||||
'f/circular/module_a', 20008, 'bun', '');
|
||||
|
||||
-- module_b uses ABSOLUTE path import (/f/circular/shared.ts)
|
||||
INSERT INTO public.script(workspace_id, created_by, content, schema, summary, description, path, hash, language, lock) VALUES (
|
||||
'test-workspace',
|
||||
'test-user',
|
||||
'
|
||||
import { SHARED_VALUE } from "/f/circular/shared.ts";
|
||||
|
||||
export function getValue() {
|
||||
return "from_b_" + SHARED_VALUE;
|
||||
}
|
||||
|
||||
export function main() {
|
||||
return getValue();
|
||||
}
|
||||
',
|
||||
'{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"required":[],"type":"object"}',
|
||||
'',
|
||||
'',
|
||||
'f/circular/module_b', 20009, 'bun', '');
|
||||
@@ -16,13 +16,13 @@ use crate::{
|
||||
common::{
|
||||
build_command_with_isolation, create_args_and_out_file, get_reserved_variables,
|
||||
parse_npm_config, read_file, read_file_content, read_result, start_child_process,
|
||||
write_file_binary, MaybeLock, OccupancyMetrics, StreamNotifier,
|
||||
DEV_CONF_NSJAIL,
|
||||
write_file_binary, MaybeLock, OccupancyMetrics, StreamNotifier, DEV_CONF_NSJAIL,
|
||||
},
|
||||
get_proxy_envs_for_lang,
|
||||
handle_child::handle_child,
|
||||
BUNFIG_INSTALL_SCOPES, BUN_BUNDLE_CACHE_DIR, BUN_CACHE_DIR, BUN_NO_CACHE, BUN_PATH,
|
||||
DISABLE_NSJAIL, DISABLE_NUSER, HOME_ENV, NODE_BIN_PATH, NODE_PATH, NPM_CONFIG_REGISTRY,
|
||||
NPM_PATH, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, TRACING_PROXY_CA_CERT_PATH, TZ_ENV, get_proxy_envs_for_lang,
|
||||
NPM_PATH, NSJAIL_PATH, PATH_ENV, PROXY_ENVS, TRACING_PROXY_CA_CERT_PATH, TZ_ENV,
|
||||
};
|
||||
use windmill_common::{
|
||||
client::AuthedClient,
|
||||
@@ -51,9 +51,9 @@ use windmill_common::s3_helpers::attempt_fetch_bytes;
|
||||
|
||||
use windmill_parser::Typ;
|
||||
|
||||
const RELATIVE_BUN_LOADER: &str = include_str!("../loader.bun.js");
|
||||
pub const RELATIVE_BUN_LOADER: &str = include_str!("../loader.bun.js");
|
||||
|
||||
const RELATIVE_BUN_BUILDER: &str = include_str!("../loader_builder.bun.js");
|
||||
pub const RELATIVE_BUN_BUILDER: &str = include_str!("../loader_builder.bun.js");
|
||||
|
||||
const NSJAIL_CONFIG_RUN_BUN_CONTENT: &str = include_str!("../nsjail/run.bun.config.proto");
|
||||
|
||||
@@ -64,6 +64,62 @@ pub const BUN_LOCKB_SPLIT_WINDOWS: &str = "\r\n//bun.lockb\r\n";
|
||||
|
||||
pub const EMPTY_FILE: &str = "<empty>";
|
||||
|
||||
/// Bun args for dedicated worker (without the script path)
|
||||
pub const BUN_DEDICATED_WORKER_ARGS: &[&str] = &["run", "-i", "--prefer-offline"];
|
||||
|
||||
/// Generate the dedicated worker wrapper content.
|
||||
/// - `arg_names`: The argument names for the main function (e.g., ["x", "y"])
|
||||
/// - `main_import`: The import path for the main module (e.g., "./main.ts")
|
||||
/// - `date_conversions`: Optional date conversion statements for Datetime args
|
||||
pub fn generate_dedicated_worker_wrapper(
|
||||
arg_names: &[&str],
|
||||
main_import: &str,
|
||||
date_conversions: Option<&str>,
|
||||
) -> String {
|
||||
let spread = arg_names.join(",");
|
||||
let dates = date_conversions.unwrap_or("");
|
||||
let is_debug = std::env::var("RUST_LOG").is_ok_and(|x| x == "windmill=debug");
|
||||
let print_lines = if is_debug {
|
||||
r#"console.log(line);"#
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
format!(
|
||||
r#"
|
||||
import * as Main from "{main_import}";
|
||||
import * as Readline from "node:readline"
|
||||
|
||||
BigInt.prototype.toJSON = function () {{
|
||||
return this.toString();
|
||||
}};
|
||||
|
||||
console.log('start');
|
||||
|
||||
function getArgs(line) {{
|
||||
let {{ {spread} }} = JSON.parse(line)
|
||||
{dates}
|
||||
return [ {spread} ];
|
||||
}}
|
||||
|
||||
for await (const line of Readline.createInterface({{ input: process.stdin }})) {{
|
||||
{print_lines}
|
||||
|
||||
if (line === "end") {{
|
||||
process.exit(0);
|
||||
}}
|
||||
try {{
|
||||
const args = getArgs(line);
|
||||
const res = await Main.main(...args);
|
||||
console.log("wm_res[success]:" + JSON.stringify(res ?? null, (key, value) => typeof value === 'undefined' ? null : value));
|
||||
}} catch (e) {{
|
||||
console.log("wm_res[error]:" + JSON.stringify({{ message: e.message, name: e.name, stack: e.stack, line: line }}));
|
||||
}}
|
||||
}}
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns (package.json, bun.lock(b), is_empty, is_binary)
|
||||
fn split_lockfile(lockfile: &str) -> (&str, Option<&str>, bool, bool) {
|
||||
if let Some(index) = lockfile.find(BUN_LOCK_SPLIT) {
|
||||
@@ -115,24 +171,25 @@ pub async fn gen_bun_lockfile(
|
||||
gen_bunfig(job_dir).await?;
|
||||
write_file(job_dir, "package.json", package_json_content.as_str())?;
|
||||
} else {
|
||||
let loader = RELATIVE_BUN_LOADER
|
||||
.replace("W_ID", w_id)
|
||||
.replace("BASE_INTERNAL_URL", base_internal_url)
|
||||
.replace("TOKEN", token)
|
||||
.replace(
|
||||
"CURRENT_PATH",
|
||||
&crate::common::use_flow_root_path(script_path),
|
||||
)
|
||||
.replace("RAW_GET_ENDPOINT", "raw");
|
||||
|
||||
write_file(
|
||||
&job_dir,
|
||||
"build.js",
|
||||
&format!(
|
||||
r#"
|
||||
{}
|
||||
{loader}
|
||||
|
||||
{RELATIVE_BUN_BUILDER}
|
||||
"#,
|
||||
RELATIVE_BUN_LOADER
|
||||
.replace("W_ID", w_id)
|
||||
.replace("BASE_INTERNAL_URL", base_internal_url)
|
||||
.replace("TOKEN", token)
|
||||
.replace(
|
||||
"CURRENT_PATH",
|
||||
&crate::common::use_flow_root_path(script_path)
|
||||
)
|
||||
.replace("RAW_GET_ENDPOINT", "raw")
|
||||
"#
|
||||
),
|
||||
)?;
|
||||
|
||||
@@ -382,14 +439,14 @@ pub async fn install_bun_lockfile(
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum LoaderMode {
|
||||
pub enum LoaderMode {
|
||||
Node,
|
||||
Bun,
|
||||
BunBundle,
|
||||
NodeBundle,
|
||||
BrowserBundle,
|
||||
}
|
||||
async fn build_loader(
|
||||
pub async fn build_loader(
|
||||
job_dir: &str,
|
||||
base_internal_url: &str,
|
||||
token: &str,
|
||||
@@ -406,13 +463,14 @@ async fn build_loader(
|
||||
&crate::common::use_flow_root_path(current_path),
|
||||
)
|
||||
.replace("RAW_GET_ENDPOINT", "raw_unpinned");
|
||||
|
||||
if mode == LoaderMode::Node {
|
||||
write_file(
|
||||
&job_dir,
|
||||
"node_builder.ts",
|
||||
&format!(
|
||||
r#"
|
||||
{}
|
||||
{loader}
|
||||
|
||||
import {{ readdir }} from "node:fs/promises";
|
||||
|
||||
@@ -420,7 +478,6 @@ let fileNames = []
|
||||
try {{
|
||||
fileNames = await readdir("{job_dir}/node_modules")
|
||||
}} catch (e) {{
|
||||
|
||||
}}
|
||||
|
||||
try {{
|
||||
@@ -437,8 +494,7 @@ try {{
|
||||
console.log("Failed to build node bundle");
|
||||
process.exit(1);
|
||||
}}
|
||||
"#,
|
||||
loader
|
||||
"#
|
||||
),
|
||||
)?;
|
||||
} else if mode == LoaderMode::Bun {
|
||||
@@ -449,11 +505,10 @@ try {{
|
||||
r#"
|
||||
import {{ plugin }} from "bun";
|
||||
|
||||
{}
|
||||
{loader}
|
||||
|
||||
plugin(p)
|
||||
"#,
|
||||
loader
|
||||
"#
|
||||
),
|
||||
)?;
|
||||
} else if mode == LoaderMode::BunBundle
|
||||
@@ -465,7 +520,7 @@ plugin(p)
|
||||
"node_builder.ts",
|
||||
&format!(
|
||||
r#"
|
||||
{}
|
||||
{loader}
|
||||
|
||||
try {{
|
||||
await Bun.build({{
|
||||
@@ -486,7 +541,6 @@ try {{
|
||||
process.exit(1);
|
||||
}}
|
||||
"#,
|
||||
loader,
|
||||
if mode == LoaderMode::BunBundle {
|
||||
"bun"
|
||||
} else if mode == LoaderMode::NodeBundle {
|
||||
@@ -1726,55 +1780,21 @@ pub async fn start_worker(
|
||||
.map(|x| return format!("{x} = {x} ? new Date({x}) : undefined"))
|
||||
.join("\n");
|
||||
|
||||
let spread = args.into_iter().map(|x| x.name).join(",");
|
||||
let arg_names: Vec<&str> = args.iter().map(|x| x.name.as_str()).collect();
|
||||
// logs.push_str(format!("infer args: {:?}\n", start.elapsed().as_micros()).as_str());
|
||||
// we cannot use Bun.read and Bun.write because it results in an EBADF error on cloud
|
||||
|
||||
let is_debug = std::env::var("RUST_LOG").is_ok_and(|x| x == "windmill=debug");
|
||||
let print_lines = if is_debug {
|
||||
r#"console.log(line);"#
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let main_import = if codebase.is_some() {
|
||||
"./main.js"
|
||||
} else {
|
||||
"./main.ts"
|
||||
};
|
||||
let wrapper_content: String = format!(
|
||||
r#"
|
||||
import * as Main from "{main_import}";
|
||||
import * as Readline from "node:readline"
|
||||
|
||||
BigInt.prototype.toJSON = function () {{
|
||||
return this.toString();
|
||||
}};
|
||||
|
||||
console.log('start');
|
||||
|
||||
function getArgs(line) {{
|
||||
let {{ {spread} }} = JSON.parse(line)
|
||||
{dates}
|
||||
return [ {spread} ];
|
||||
}}
|
||||
|
||||
for await (const line of Readline.createInterface({{ input: process.stdin }})) {{
|
||||
{print_lines}
|
||||
|
||||
if (line === "end") {{
|
||||
process.exit(0);
|
||||
}}
|
||||
try {{
|
||||
const args = getArgs(line);
|
||||
const res = await Main.main(...args);
|
||||
console.log("wm_res[success]:" + JSON.stringify(res ?? null, (key, value) => typeof value === 'undefined' ? null : value));
|
||||
}} catch (e) {{
|
||||
console.log("wm_res[error]:" + JSON.stringify({{ message: e.message, name: e.name, stack: e.stack, line: line }}));
|
||||
}}
|
||||
}}
|
||||
"#,
|
||||
);
|
||||
let dates_opt = if dates.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(dates.as_str())
|
||||
};
|
||||
let wrapper_content = generate_dedicated_worker_wrapper(&arg_names, main_import, dates_opt);
|
||||
write_file(job_dir, "wrapper.mjs", &wrapper_content)?;
|
||||
}
|
||||
|
||||
@@ -1865,3 +1885,111 @@ for await (const line of Readline.createInterface({{ input: process.stdin }})) {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_split_lockfile_text_unix() {
|
||||
let lockfile = r#"{"dependencies":{"lodash":"^4.17.21"}}
|
||||
//bun.lock
|
||||
lockfile-content-here"#;
|
||||
|
||||
let (pkg, lock, is_empty, is_binary) = split_lockfile(lockfile);
|
||||
|
||||
assert_eq!(pkg, r#"{"dependencies":{"lodash":"^4.17.21"}}"#);
|
||||
assert_eq!(lock, Some("lockfile-content-here"));
|
||||
assert!(!is_empty);
|
||||
assert!(!is_binary);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_lockfile_text_windows() {
|
||||
let lockfile = "{\"dependencies\":{}}\r\n//bun.lock\r\nlockfile-content";
|
||||
|
||||
let (pkg, lock, is_empty, is_binary) = split_lockfile(lockfile);
|
||||
|
||||
assert_eq!(pkg, "{\"dependencies\":{}}");
|
||||
assert_eq!(lock, Some("lockfile-content"));
|
||||
assert!(!is_empty);
|
||||
assert!(!is_binary);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_lockfile_binary_unix() {
|
||||
let lockfile = r#"{"dependencies":{}}
|
||||
//bun.lockb
|
||||
YmluYXJ5LWNvbnRlbnQ="#; // base64 encoded "binary-content"
|
||||
|
||||
let (pkg, lock, is_empty, is_binary) = split_lockfile(lockfile);
|
||||
|
||||
assert_eq!(pkg, r#"{"dependencies":{}}"#);
|
||||
assert_eq!(lock, Some("YmluYXJ5LWNvbnRlbnQ="));
|
||||
assert!(!is_empty);
|
||||
assert!(is_binary);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_lockfile_binary_windows() {
|
||||
let lockfile = "{\"dependencies\":{}}\r\n//bun.lockb\r\nYmluYXJ5LWNvbnRlbnQ=";
|
||||
|
||||
let (pkg, lock, is_empty, is_binary) = split_lockfile(lockfile);
|
||||
|
||||
assert_eq!(pkg, "{\"dependencies\":{}}");
|
||||
assert_eq!(lock, Some("YmluYXJ5LWNvbnRlbnQ="));
|
||||
assert!(!is_empty);
|
||||
assert!(is_binary);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_lockfile_empty() {
|
||||
let lockfile = r#"{"dependencies":{}}
|
||||
//bun.lock
|
||||
<empty>"#;
|
||||
|
||||
let (pkg, lock, is_empty, is_binary) = split_lockfile(lockfile);
|
||||
|
||||
assert_eq!(pkg, r#"{"dependencies":{}}"#);
|
||||
assert_eq!(lock, Some(EMPTY_FILE));
|
||||
assert!(is_empty);
|
||||
assert!(!is_binary);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_lockfile_no_lock() {
|
||||
let lockfile = r#"{"dependencies":{"lodash":"^4.17.21"}}"#;
|
||||
|
||||
let (pkg, lock, is_empty, is_binary) = split_lockfile(lockfile);
|
||||
|
||||
assert_eq!(pkg, r#"{"dependencies":{"lodash":"^4.17.21"}}"#);
|
||||
assert!(lock.is_none());
|
||||
assert!(!is_empty);
|
||||
assert!(!is_binary);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_lockfile_multiline_package_json() {
|
||||
let lockfile = r#"{
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21"
|
||||
}
|
||||
}
|
||||
//bun.lock
|
||||
lockfile-content"#;
|
||||
|
||||
let (pkg, lock, is_empty, is_binary) = split_lockfile(lockfile);
|
||||
|
||||
assert_eq!(
|
||||
pkg,
|
||||
r#"{
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21"
|
||||
}
|
||||
}"#
|
||||
);
|
||||
assert_eq!(lock, Some("lockfile-content"));
|
||||
assert!(!is_empty);
|
||||
assert!(!is_binary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,8 +95,9 @@ pub use otel_tracing_proxy_ee::{load_internal_otel_exporter, DENO_OTEL_INITIALIZ
|
||||
pub use result_processor::handle_job_error;
|
||||
|
||||
pub use bun_executor::{
|
||||
compute_bundle_local_and_remote_path, get_common_bun_proc_envs, install_bun_lockfile,
|
||||
prebundle_bun_script, prepare_job_dir,
|
||||
build_loader, compute_bundle_local_and_remote_path, generate_dedicated_worker_wrapper,
|
||||
get_common_bun_proc_envs, install_bun_lockfile, prebundle_bun_script, prepare_job_dir,
|
||||
BUN_DEDICATED_WORKER_ARGS, LoaderMode, RELATIVE_BUN_BUILDER, RELATIVE_BUN_LOADER,
|
||||
};
|
||||
pub use deno_executor::generate_deno_lock;
|
||||
pub use prepare_deps::run_prepare_deps_cli;
|
||||
|
||||
@@ -34,7 +34,7 @@ RUN mkdir -p /tmp/windmill/cache && \
|
||||
rm -rf /tmp/build_cache && \
|
||||
mkdir -p -m 777 /tmp/windmill/cache/uv
|
||||
|
||||
COPY --from=oven/bun:1.2.23 /usr/local/bin/bun /usr/bin/bun
|
||||
COPY --from=oven/bun:1.3.8 /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
# add the docker client to call docker from a worker if enabled
|
||||
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
|
||||
|
||||
@@ -34,7 +34,7 @@ RUN mkdir -p /tmp/windmill/cache && \
|
||||
rm -rf /tmp/build_cache && \
|
||||
mkdir -p -m 777 /tmp/windmill/cache/uv
|
||||
|
||||
COPY --from=oven/bun:1.2.23 /usr/local/bin/bun /usr/bin/bun
|
||||
COPY --from=oven/bun:1.3.8 /usr/local/bin/bun /usr/bin/bun
|
||||
|
||||
# add the docker client to call docker from a worker if enabled
|
||||
COPY --from=docker:dind /usr/local/bin/docker /usr/local/bin/
|
||||
|
||||
Reference in New Issue
Block a user