/** * Bundles the frontend's raw-app policy derivation into a script the server's * bundle job carries, and writes it next to that job's source. * * The derivation is not re-implemented in Rust, and not shelled out to `wmill` * either. `triggerables_v2` is the allowlist every component run is matched * against, keyed by `:rawscript/` — a key * derived any other way leaves a deployed app's runnables "forbidden by policy", * which reads as a broken app rather than a failed deploy. So the app editor, * `wmill app push` and the server all have to agree, and the way to guarantee * that is one source. * * It rides in the job script rather than in the CLI the job already runs, * because the images install `windmill-cli` unpinned: an image can carry a CLI * older than its server, and falling back to `bun x windmill-cli@` * needs npm reachable at deploy time, which is exactly what the bundler's * installed-CLI branch exists to avoid. The job script is `include_str!`d into * the server binary, so it always matches the server. * * Run `bun run gen:app-policy` after touching * frontend/src/lib/components/raw_apps/rawAppPolicy.ts or anything it imports; * test/app_policy_bundle_unit.test.ts fails when the committed bundle no longer * matches those sources. */ import * as esbuild from "esbuild"; import { readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; const REPO_ROOT = join(import.meta.dir, ".."); const RAW_APPS_DIR = join( REPO_ROOT, "frontend", "src", "lib", "components", "raw_apps", ); export const OUT_FILE = join( REPO_ROOT, "backend", "windmill-api", "src", "apps_raw_policy.gen.js", ); const HEADER = `// Generated by cli/generate-app-policy.ts. Do not edit. // Run \`bun run gen:app-policy\` from cli/ to rebuild it from // frontend/src/lib/components/raw_apps/rawAppPolicy.ts. // // Prepended to the raw-app bundle job (see apps_raw_bundle.rs), which calls // \`__wmillAppPolicy.updateRawAppPolicy\` from its own module scope. `; /** The bundled IIFE, header included. Exported so the staleness test can build * it and compare, rather than keep a hash in step by hand. */ export async function buildAppPolicyBundle(): Promise { const result = await esbuild.build({ stdin: { contents: `export { updateRawAppPolicy } from './rawAppPolicy'`, resolveDir: RAW_APPS_DIR, loader: "ts", }, bundle: true, format: "iife", globalName: "__wmillAppPolicy", target: "es2022", // `hash()` falls back to this only when Web Crypto is missing, which it // never is on a worker's bun. Left unresolved so the bundle needs nothing // installed where it runs. external: ["@aws-crypto/sha256-js"], minify: true, write: false, legalComments: "none", }); return HEADER + result.outputFiles[0].text; } if (import.meta.main) { const js = await buildAppPolicyBundle(); const before = (() => { try { return readFileSync(OUT_FILE, "utf-8"); } catch { return ""; } })(); writeFileSync(OUT_FILE, js, "utf-8"); console.log( `${before === js ? "Unchanged" : "Wrote"} ${OUT_FILE} (${ Math.round(js.length / 1024) } KB)`, ); }