From e3f55b10fc06d169bf0190fbf73577a87925cecd Mon Sep 17 00:00:00 2001 From: Vayun Godara Date: Tue, 4 Aug 2026 13:08:02 +0200 Subject: [PATCH 1/3] gates: run the client and join gates on every OS with a display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The verification client no longer requires Linux + xvfb. Display modes: - xvfb (headless Linux) — unchanged, invisible virtual framebuffer - visible-window (macOS / Windows / Linux desktop) — the real client opens as a small window for a few minutes, announced in the panel log; same client, same verdict, purely cosmetic cost - no display at all — the honest "not verified" skip stays Windows groundwork that the modes stand on: sandbox assembly uses junctions and hardlinks (file symlinks need admin there), the join sandbox picks the per-OS Forge argfile, the HMC gamedir survives .properties backslash escaping, and the launcher JDK is discovered per-OS instead of hardcoded. ENV_SKIP now applies only under Xvfb/Mesa — window mode runs on a real GPU and boots the full mod set. macOS gets -XstartOnFirstThread for LWJGL. Rejected on principle: stubbed-GL headless mode everywhere — a stub can mask or invent render-init crashes, and a gate that can lie is worse than a window on the desktop. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KfRjm8SZCtXnoSB1z6hdfB --- ARCHITECTURE.md | 19 +++- README.md | 8 +- server/src/services/launchgate.ts | 156 ++++++++++++++++++++++-------- server/src/services/platform.ts | 12 ++- server/src/services/preflight.ts | 10 +- 5 files changed, 152 insertions(+), 53 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 9eed00a..ceda1b6 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -101,7 +101,18 @@ remember). Without them, servers are LAN/VPN-only and everything else works. ## Cross-platform honesty -Linux is the full experience. On macOS/Windows the panel and the server-side -dry-boot run fine; the headless-client and join gates require Linux + -`xvfb-run` and **skip with an honest "not verified" verdict** rather than -pretending to pass. +The gates need a display, and there are three answers to that: + +- **Headless Linux + `xvfb-run`** — the client boots invisibly in a virtual + framebuffer. +- **macOS / Windows / a Linux desktop** — *visible-window mode*: the real + verification client opens as a small window on the desktop for a few + minutes, announced in the panel log so nobody closes it mid-verdict. Same + client, same fidelity, purely cosmetic cost. +- **A display-less box that isn't Linux** — the client and join gates **skip + with an honest "not verified" verdict** rather than pretending to pass. + The static scan and server dry-boot run everywhere regardless. + +A stubbed-GL "headless anywhere" mode was considered and rejected: stubbed +rendering can mask or invent render-init crashes, which makes the test lie — +the same reason the boot gates refuse `-Xverify:none`. diff --git a/README.md b/README.md index a531801..59f1d7e 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,11 @@ restarts, and a storage cleaner. - Node.js 22+ - [Crafty Controller](https://craftycontrol.com/) managing your servers -- Linux is the full experience; on macOS/Windows the panel and server-side - preflight run fine, while the client/join gates need Linux + `xvfb` and - will skip with an honest verdict elsewhere. +- Any OS. On headless Linux the client/join gates run invisibly under `xvfb`; + on macOS/Windows (or a Linux desktop) the verification client opens as a + small window for a few minutes instead — same real client, same verdict. + A box with no display at all skips those two gates with an honest + "not verified" verdict; the server-side dry-boot always runs. ## Quick start diff --git a/server/src/services/launchgate.ts b/server/src/services/launchgate.ts index 7f33fbd..a5786e0 100644 --- a/server/src/services/launchgate.ts +++ b/server/src/services/launchgate.ts @@ -6,43 +6,102 @@ import { join, basename, dirname } from 'node:path'; import { PATHS } from '../config.js'; import { serverDir, javaFor } from './servers.js'; import { detect } from './detect.js'; -import { killTree, IS_WIN } from './platform.js'; -import { symlinkSync } from 'node:fs'; +import { killTree, IS_WIN, sandboxLink, KILLABLE_SPAWN_OPTS } from './platform.js'; +import { FORGE_ARGFILE } from './preflight.js'; import { spawnSync } from 'node:child_process'; -// the client gate needs a virtual display — Linux + xvfb-run. Elsewhere the -// gate degrades HONESTLY: verdict says why it skipped, the dry-boot preflight -// still protects the server, and nothing pretends to have tested a client. -let xvfbState: boolean | null = null; -function clientGateAvailable(): boolean { - if (xvfbState !== null) return xvfbState; - xvfbState = !IS_WIN && process.platform === 'linux' - && spawnSync('which', ['xvfb-run'], { stdio: 'ignore' }).status === 0; - return xvfbState; +// the client gate needs a DISPLAY. Three ways to get one, checked in order: +// 'xvfb' — Linux + xvfb-run: invisible virtual framebuffer. +// 'window' — macOS/Windows (or a Linux desktop without xvfb): the REAL +// client opens as a small window on the desktop for a few +// minutes. Full fidelity, zero extra dependencies — a visible +// window beats a stubbed-GL headless hack, because stubbed GL +// can mask or invent render-init crashes and the verdict lies. +// null — headless box that isn't Linux-with-xvfb: the gate degrades +// HONESTLY — the verdict says why it skipped, the dry-boot +// preflight still protects the server, and nothing pretends to +// have tested a client. +type GateMode = 'xvfb' | 'window'; +let modeState: GateMode | null | undefined; +export function gateMode(): GateMode | null { + if (modeState !== undefined) return modeState; + if (process.platform === 'linux' && spawnSync('which', ['xvfb-run'], { stdio: 'ignore' }).status === 0) { + modeState = 'xvfb'; + } else if (IS_WIN || process.platform === 'darwin' || process.env.DISPLAY || process.env.WAYLAND_DISPLAY) { + modeState = 'window'; + } else { + modeState = null; + } + return modeState; } // TIER-2 LAUNCH GATE — AutoModpack is a mirror, not a validator (its own // README: users must ensure the modpack works before it distributes it). So // after every pack regenerate, this boots the EXACT client set friends will -// receive — a real Minecraft client under Xvfb with mc-runtime-test driving -// it into a world — and records PASS/FAIL. A client-side conflict is caught +// receive — a real Minecraft client (under Xvfb on headless Linux, as a small +// desktop window elsewhere) with mc-runtime-test driving it into a world — +// and records PASS/FAIL. A client-side conflict is caught // on this box instead of on a friend's screen. // // Proven live 2026-07-20: main's 43-mod set (sodium+iris+shaders present) // boots and joins in ~2min; the harness caught its first real finding the // same evening (ImmediatelyFast's font-atlas patch NPEs under Xvfb/Mesa). // -// ENV_SKIP: mods that crash ONLY in this headless environment while being -// demonstrably fine on real clients (owner plays with them daily). They ship -// to players but are excluded from gate boots. Keep this list SHORT and -// documented — every entry is a hole in the gate. +// ENV_SKIP: mods that crash ONLY in the Xvfb/Mesa software-GL environment +// while being demonstrably fine on real clients (owner plays with them +// daily). They ship to players but are excluded from xvfb-mode boots — in +// window mode the client runs on a real GPU, so the FULL set is tested. +// Keep this list SHORT and documented — every entry is a hole in the gate. const ENV_SKIP = [ 'immediatelyfast', // FontSet.resetTextures NPE under Xvfb/Mesa only — bisected 2026-07-20 ]; const HMC = join(PATHS.root, 'Tools', 'headlessmc'); const LAUNCHER = join(HMC, 'headlessmc-launcher-2.10.0.jar'); -const JAVA25 = join(PATHS.root, 'Tools', 'jdk-25.0.3+9', 'bin', 'java'); + +// the launcher JVM takes the newest JDK under Tools/ (jdk-25 on the home +// box, java.exe on Windows); which java the GAME runs under is HMC's own +// affair, chosen at version-install time via `--java`. +let gateJavaState: string | null | undefined; +function gateJava(): string | null { + if (gateJavaState !== undefined) return gateJavaState; + gateJavaState = null; + const tools = join(PATHS.root, 'Tools'); + if (existsSync(tools)) { + // numeric-major sort: a legacy jdk-8 must not beat jdk-25 lexically + const jdks = readdirSync(tools).filter((x) => x.startsWith('jdk-')) + .sort((a, b) => (parseInt(b.slice(4), 10) || 0) - (parseInt(a.slice(4), 10) || 0)); + for (const d of jdks) { + const p = join(tools, d, 'bin', IS_WIN ? 'java.exe' : 'java'); + if (existsSync(p)) { gateJavaState = p; break; } + } + } + return gateJavaState; +} + +// low-priority wrappers: Linux gets nice+ionice (the bulk-ops law), macOS +// has nice but no ionice, Windows has neither — and gates only run when zero +// players are online, so priority matters least exactly where it's missing. +const NICE = IS_WIN ? [] : ['nice', '-n', '19']; +const NICE_IO = IS_WIN ? [] : process.platform === 'linux' + ? ['nice', '-n', '19', 'ionice', '-c3'] : ['nice', '-n', '19']; + +/** Full argv for a gate CLIENT boot, per display mode. extraProps are -D + JVM props (gameargs etc.), launcherArgs everything after the launcher jar. */ +function clientArgv(extraProps: string[], launcherArgs: string[]): string[] { + const argv = [...NICE_IO]; + if (gateMode() === 'xvfb') argv.push('xvfb-run', '-a'); + argv.push(gateJava()!); + if (gateMode() === 'xvfb') argv.push('-Dhmc.check.xvfb=true'); + // LWJGL on macOS must create its window on the process's first thread + if (process.platform === 'darwin') argv.push('-XstartOnFirstThread'); + argv.push(...extraProps, '-jar', LAUNCHER, ...launcherArgs); + return argv; +} + +// window mode keeps the visible client small and out of the way; xvfb mode +// leaves geometry alone (the virtual framebuffer nobody sees) +const WINDOW_SIZE_ARGS = '--width 640 --height 360'; // known HMC hang modes — timeout is a FAIL. 15 min: a 70-mod pack with a // 183MB physics mod under software GL genuinely needs >10 (live 2026-07-21) const RUN_TIMEOUT_MS = 15 * 60_000; @@ -196,7 +255,8 @@ const execFileP = promisify(execFile); // for its whole duration. Live 2026-07-21: "panel isnt loading" while the // horrorFabric gate downloaded 1.20.1. async function hmcCmd(args: string[]): Promise { - await execFileP('nice', ['-n', '19', JAVA25, '-jar', LAUNCHER, '--command', ...args], { + const argv = [...NICE, gateJava()!, '-jar', LAUNCHER, '--command', ...args]; + await execFileP(argv[0], argv.slice(1), { cwd: HMC, timeout: 8 * 60_000, maxBuffer: 32 * 1024 * 1024, }); } @@ -286,7 +346,7 @@ async function runJoinGate( const ns = det.loader === 'forge' ? 'minecraftforge' : 'neoforge'; const fdir = join(sdir, 'libraries', 'net', ns, det.loader === 'forge' ? 'forge' : 'neoforge'); const ver = existsSync(fdir) ? readdirSync(fdir)[0] : null; - const rel = ver ? `libraries/net/${ns}/${det.loader === 'forge' ? 'forge' : 'neoforge'}/${ver}/unix_args.txt` : null; + const rel = ver ? `libraries/net/${ns}/${det.loader === 'forge' ? 'forge' : 'neoforge'}/${ver}/${FORGE_ARGFILE}` : null; if (rel && existsSync(join(sdir, rel))) launchArgs = [heap, ...fast, `@${rel}`, 'nogui']; } const java = javaFor(det.mc, det.loader); @@ -297,11 +357,11 @@ async function runJoinGate( mkdirSync(join(srv, 'mods'), { recursive: true }); for (const item of ['fabric.jar', 'libraries', 'versions', '.fabric']) { const src = join(sdir, item); - if (existsSync(src)) symlinkSync(src, join(srv, item)); + if (existsSync(src)) sandboxLink(src, join(srv, item)); } for (const jar of readdirSync(join(sdir, 'mods')).filter((f) => f.endsWith('.jar'))) { if (/automodpack/i.test(jar)) continue; - symlinkSync(join(sdir, 'mods', jar), join(srv, 'mods', jar)); + sandboxLink(join(sdir, 'mods', jar), join(srv, 'mods', jar)); } writeFileSync(join(srv, 'eula.txt'), 'eula=true\n', 'utf8'); writeFileSync(join(srv, 'server.properties'), [ @@ -328,8 +388,9 @@ async function runJoinGate( let cliChild: ReturnType | null = null; let settled = false; let dwellTimer: NodeJS.Timeout | null = null; - const srvChild = spawn('nice', ['-n', '19', java, ...launchArgs], { - cwd: srv, stdio: ['ignore', 'pipe', 'pipe'], detached: true, + const srvArgv = [...NICE, java, ...launchArgs]; + const srvChild = spawn(srvArgv[0], srvArgv.slice(1), { + cwd: srv, stdio: ['ignore', 'pipe', 'pipe'], ...KILLABLE_SPAWN_OPTS, }); const finish = (ok: boolean, detail: string): void => { if (settled) return; @@ -390,10 +451,11 @@ async function runJoinGate( const escaped = (det.mc ?? '').replace(/\./g, '\\.'); const versionRegex = det.loader === 'fabric' ? `fabric-loader-.*-${escaped}` : `${escaped}-forge-.*`; log(`launchgate: join test — sandbox server up, quick-playing client into 127.0.0.1:${JOIN_PORT}`); - cliChild = spawn('nice', ['-n', '19', 'ionice', '-c3', 'xvfb-run', '-a', - JAVA25, '-Dhmc.check.xvfb=true', `-Dhmc.gameargs=--quickPlayMultiplayer 127.0.0.1:${JOIN_PORT}`, - '-jar', LAUNCHER, '--command', 'launch', versionRegex, '-regex', '--jvm', '-Xmx4G', - ], { cwd: HMC, stdio: ['ignore', 'pipe', 'pipe'], detached: true }); + const gameArgs = `--quickPlayMultiplayer 127.0.0.1:${JOIN_PORT}` + + (gateMode() === 'window' ? ` ${WINDOW_SIZE_ARGS}` : ''); + const cliArgv = clientArgv([`-Dhmc.gameargs=${gameArgs}`], + ['--command', 'launch', versionRegex, '-regex', '--jvm', '-Xmx4G']); + cliChild = spawn(cliArgv[0], cliArgv.slice(1), { cwd: HMC, stdio: ['ignore', 'pipe', 'pipe'], ...KILLABLE_SPAWN_OPTS }); cliChild.on('exit', (code) => { if (!joined) inconclusive(`client exited (code ${code}) before ever joining the server`); }); @@ -470,8 +532,11 @@ async function runGate(serverId: string, log: (m: string) => void): Promise void): Promise basename(rel).toLowerCase().includes(s))) { skippedEnv++; continue; } + if (envSkip.some((s) => basename(rel).toLowerCase().includes(s))) { skippedEnv++; continue; } for (const base of [join(sdir, 'automodpack', 'host-modpack', 'main'), sdir]) { const src = join(base, rel); if (existsSync(src)) { @@ -506,21 +574,29 @@ async function runGate(serverId: string, log: (m: string) => void): Promise((resolve) => { - const child = spawn('nice', ['-n', '19', 'ionice', '-c3', 'xvfb-run', '-a', - JAVA25, '-Dhmc.check.xvfb=true', '-jar', LAUNCHER, - '--command', 'launch', versionRegex, '-regex', '--jvm', '-Xmx4G', - // detached: the child owns a process group, so a timeout kill takes the - // WHOLE tree (nice→ionice→xvfb-run→java). child.kill alone only shot the - // nice wrapper and the Minecraft client lived on as an orphan, wedging - // every later gate run (live 2026-07-21, twice). - ], { cwd: HMC, stdio: ['ignore', 'pipe', 'pipe'], detached: true }); + const argv = clientArgv( + gateMode() === 'window' ? [`-Dhmc.gameargs=${WINDOW_SIZE_ARGS}`] : [], + ['--command', 'launch', versionRegex, '-regex', '--jvm', '-Xmx4G'], + ); + // KILLABLE_SPAWN_OPTS: on POSIX the child owns a process group, so a + // timeout kill takes the WHOLE tree (nice→ionice→xvfb-run→java). + // child.kill alone only shot the nice wrapper and the Minecraft client + // lived on as an orphan, wedging every later gate run (live 2026-07-21, + // twice). On Windows killTree's taskkill /T does the same job. + const child = spawn(argv[0], argv.slice(1), { cwd: HMC, stdio: ['ignore', 'pipe', 'pipe'], ...KILLABLE_SPAWN_OPTS }); let out = ''; child.stdout.on('data', (d: Buffer) => { out += d.toString(); }); child.stderr.on('data', (d: Buffer) => { out += d.toString(); }); diff --git a/server/src/services/platform.ts b/server/src/services/platform.ts index ec09f54..688358d 100644 --- a/server/src/services/platform.ts +++ b/server/src/services/platform.ts @@ -1,6 +1,6 @@ import { execFile, spawn } from 'node:child_process'; import { promisify } from 'node:util'; -import { renameSync, statSync } from 'node:fs'; +import { renameSync, statSync, symlinkSync, linkSync, copyFileSync } from 'node:fs'; import { mkdir } from 'node:fs/promises'; import { basename, dirname } from 'node:path'; @@ -134,6 +134,16 @@ export async function chownToDirOwner(destDir: string): Promise { } catch { /* never fail the caller over ownership */ } } +/** Borrow src into a sandbox at dst without copying. POSIX symlinks; Windows + can't symlink without admin/developer-mode, so directories get junctions + (always allowed) and files get hardlinks, falling back to a real copy when + the sandbox sits on a different volume. */ +export function sandboxLink(src: string, dst: string): void { + if (!IS_WIN) { symlinkSync(src, dst); return; } + if (statSync(src).isDirectory()) { symlinkSync(src, dst, 'junction'); return; } + try { linkSync(src, dst); } catch { copyFileSync(src, dst); } +} + /** Kill a spawned child AND its whole process tree. On Windows, spawn's own `timeout`/kill() only signals the cmd.exe wrapper and the real grandchild lives on — taskkill /T tears down the tree. On Linux the caller must have diff --git a/server/src/services/preflight.ts b/server/src/services/preflight.ts index 29cfdf7..fce978a 100644 --- a/server/src/services/preflight.ts +++ b/server/src/services/preflight.ts @@ -1,10 +1,10 @@ -import { existsSync, mkdirSync, renameSync, rmSync, symlinkSync, writeFileSync, readdirSync, readFileSync } from 'node:fs'; +import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync, readdirSync, readFileSync } from 'node:fs'; import { spawn } from 'node:child_process'; import { join, basename } from 'node:path'; import { PATHS } from '../config.js'; import { serverDir, javaFor } from './servers.js'; import { detect } from './detect.js'; -import { IS_WIN } from './platform.js'; +import { IS_WIN, sandboxLink } from './platform.js'; // cross-platform launch: `nice` is a Linux nicety, not a requirement; Forge // ships win_args.txt beside unix_args.txt @@ -97,14 +97,14 @@ async function runPreflightInner( // mods regenerate defaults in the sandbox instead of writing to the real one for (const item of ['fabric.jar', 'libraries', 'versions', '.fabric', 'server.properties']) { const src = join(dir, item); - if (existsSync(src)) symlinkSync(src, join(sandbox, item)); + if (existsSync(src)) sandboxLink(src, join(sandbox, item)); } const skip = new Set((opts.withoutJars ?? []).map((f) => basename(f))); for (const jar of readdirSync(join(dir, 'mods')).filter((f) => f.endsWith('.jar'))) { - if (!skip.has(jar)) symlinkSync(join(dir, 'mods', jar), join(sandbox, 'mods', jar)); + if (!skip.has(jar)) sandboxLink(join(dir, 'mods', jar), join(sandbox, 'mods', jar)); } for (const extra of opts.extraJars ?? []) { - if (existsSync(extra)) symlinkSync(extra, join(sandbox, 'mods', basename(extra))); + if (existsSync(extra)) sandboxLink(extra, join(sandbox, 'mods', basename(extra))); } writeFileSync(join(sandbox, 'eula.txt'), 'eula=false\n', 'utf8'); From da8d58a184892c69eaff9bb5af62012d2ef8111b Mon Sep 17 00:00:00 2001 From: Vayun Godara Date: Tue, 4 Aug 2026 13:18:58 +0200 Subject: [PATCH 2/3] ci: boot-smoke the panel on all three OSes, scan every push for secrets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - smoke-boot.mjs boots the built server against an empty temp layout and requires /api/health to answer within 90s — the shape of a first-run install, on ubuntu/windows/macos alike - gitleaks job with full history on every push (the repo is public) - dependabot for npm and actions, weekly Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01KfRjm8SZCtXnoSB1z6hdfB --- .github/dependabot.yml | 11 +++++++++++ .github/smoke-boot.mjs | 42 ++++++++++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 27 +++++++++++++++++++------- 3 files changed, 73 insertions(+), 7 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/smoke-boot.mjs diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8ae7a74 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 5 + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly diff --git a/.github/smoke-boot.mjs b/.github/smoke-boot.mjs new file mode 100644 index 0000000..21edd7c --- /dev/null +++ b/.github/smoke-boot.mjs @@ -0,0 +1,42 @@ +// CI boot smoke: the built panel must BOOT and serve /api/health on an EMPTY +// layout on every OS — no Crafty, no JDK, no servers, no token. Watchers are +// expected to log errors on the way up; the bar is "starts, serves, and +// degrades honestly", which is exactly what a first-run install looks like. +import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { spawn } from 'node:child_process'; + +const PORT = 25571; +const root = mkdtempSync(join(tmpdir(), 'spawnpoint-smoke-')); +mkdirSync(join(root, 'Spawnpoint', 'data'), { recursive: true }); +writeFileSync(join(root, 'Spawnpoint', 'data', 'settings.json'), JSON.stringify({ port: PORT })); + +let exited = false; +const child = spawn(process.execPath, ['server/dist/index.js'], { + env: { ...process.env, SPAWNPOINT_ROOT: root }, + stdio: 'inherit', +}); +child.on('exit', (code) => { + exited = true; + console.error(`panel exited before the health check passed (code ${code})`); + process.exit(1); +}); + +const deadline = Date.now() + 90_000; +while (Date.now() < deadline && !exited) { + try { + const res = await fetch(`http://127.0.0.1:${PORT}/api/health`); + if (res.ok) { + console.log('panel boots and serves /api/health on', process.platform); + child.removeAllListeners('exit'); + child.kill(); + process.exit(0); + } + } catch { /* not up yet */ } + await new Promise((r) => setTimeout(r, 1000)); +} +console.error('panel did not serve /api/health within 90s'); +child.removeAllListeners('exit'); +child.kill(); +process.exit(1); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5052354..9365f1e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,9 @@ -# Cross-platform build proof: the panel must COMPILE and BOOT on every OS a -# self-hoster might own. The Linux leg is the full experience; mac/windows -# legs prove the panel + preflight degrade gracefully (the client launch gate -# needs Linux + xvfb and says so honestly at runtime). +# Cross-platform proof: the panel must COMPILE and BOOT on every OS a +# self-hoster might own. All three legs now run the same bar — build, then +# boot the real server against an empty layout and serve /api/health (the +# shape of a first-run install). The client/join gates pick their display +# mode at runtime: xvfb on headless Linux, a small visible window on +# desktop OSes, an honest skip where no display exists. name: ci on: push: @@ -25,7 +27,18 @@ jobs: - run: npm ci - run: npm run build -w server - run: npm run build -w web - # smoke: the built server must at least load its entrypoint without a - # crash on this OS (no Crafty/Java present in CI — a clean import and - # config bootstrap is the bar here) + # smoke 1: the built server must load its entrypoint cleanly - run: node -e "import('./server/dist/config.js').then(m => { if (!m.PATHS) throw new Error('config failed'); console.log('config loads on', process.platform); })" + # smoke 2: boot the real panel on an empty layout and hit /api/health + - run: node .github/smoke-boot.mjs + + # the repo is public — every push gets a full-history secret scan + gitleaks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From c3b468069b01a96aab124e9012e57273cc66db74 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 11:20:22 +0000 Subject: [PATCH 3/3] build(deps-dev): Bump @tailwindcss/vite from 4.3.2 to 4.3.3 Bumps [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) from 4.3.2 to 4.3.3. - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.3/packages/@tailwindcss-vite) --- updated-dependencies: - dependency-name: "@tailwindcss/vite" dependency-version: 4.3.3 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 243 ++++++++++++++++++++++++++++------------------ web/package.json | 2 +- 2 files changed, 149 insertions(+), 96 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1a4c1af..72710c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "spawnpoint", "version": "0.1.0", + "license": "MIT", "workspaces": [ "server", "web" @@ -59,7 +60,6 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=18" } @@ -77,7 +77,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -95,7 +94,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -113,7 +111,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -131,7 +128,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -149,7 +145,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -167,7 +162,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -185,7 +179,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -203,7 +196,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -221,7 +213,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -239,7 +230,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -257,7 +247,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -275,7 +264,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -293,7 +281,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -311,7 +298,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -329,7 +315,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -347,7 +332,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -365,7 +349,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -383,7 +366,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -401,7 +383,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -419,7 +400,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -437,7 +417,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": ">=18" } @@ -455,7 +434,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } @@ -473,7 +451,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -491,7 +468,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -509,7 +485,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -1433,49 +1408,49 @@ "license": "MIT" }, "node_modules/@tailwindcss/node": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", - "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "5.21.6", + "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.3.2" + "tailwindcss": "4.3.3" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz", - "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", "dev": true, "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-arm64": "4.3.2", - "@tailwindcss/oxide-darwin-x64": "4.3.2", - "@tailwindcss/oxide-freebsd-x64": "4.3.2", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", - "@tailwindcss/oxide-linux-x64-musl": "4.3.2", - "@tailwindcss/oxide-wasm32-wasi": "4.3.2", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", - "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", "cpu": [ "arm64" ], @@ -1490,9 +1465,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", - "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", "cpu": [ "arm64" ], @@ -1507,9 +1482,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", - "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", "cpu": [ "x64" ], @@ -1524,9 +1499,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", - "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", "cpu": [ "x64" ], @@ -1541,9 +1516,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", - "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", "cpu": [ "arm" ], @@ -1558,13 +1533,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", - "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1575,13 +1553,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", - "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1592,13 +1573,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", - "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1609,13 +1593,16 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", - "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1626,9 +1613,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", - "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -1655,10 +1642,76 @@ "node": ">=14.0.0" } }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", - "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", "cpu": [ "arm64" ], @@ -1673,9 +1726,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz", - "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", "cpu": [ "x64" ], @@ -1690,15 +1743,15 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz", - "integrity": "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz", + "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==", "dev": true, "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.3.2", - "@tailwindcss/oxide": "4.3.2", - "tailwindcss": "4.3.2" + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "tailwindcss": "4.3.3" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" @@ -1996,9 +2049,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.21.6", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", - "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", "dev": true, "license": "MIT", "dependencies": { @@ -3285,9 +3338,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", - "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", "dev": true, "license": "MIT" }, @@ -3548,7 +3601,7 @@ "react-router": "^7.1.0" }, "devDependencies": { - "@tailwindcss/vite": "^4.1.0", + "@tailwindcss/vite": "^4.3.3", "@types/node": "^24.13.2", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", diff --git a/web/package.json b/web/package.json index 643185a..222db3d 100644 --- a/web/package.json +++ b/web/package.json @@ -20,7 +20,7 @@ "react-router": "^7.1.0" }, "devDependencies": { - "@tailwindcss/vite": "^4.1.0", + "@tailwindcss/vite": "^4.3.3", "@types/node": "^24.13.2", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3",