diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 67e4fe1c..e1552103 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -29,6 +29,14 @@ This starts: - API server: `http://localhost:3100` - UI: served by the API server in dev middleware mode (same origin as API) +Tailscale/private-auth dev mode: + +```sh +pnpm dev --tailscale-auth +``` + +This runs dev as `authenticated/private` and binds the server to `0.0.0.0` for private-network access. + ## One-Command Local Run For a first-time local install, you can bootstrap and run in one command: diff --git a/package.json b/package.json index 333870a8..c6451c30 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "private": true, "type": "module", "scripts": { - "dev": "PAPERCLIP_UI_DEV_MIDDLEWARE=true pnpm --filter @paperclip/server dev", - "dev:watch": "PAPERCLIP_UI_DEV_MIDDLEWARE=true PAPERCLIP_MIGRATION_PROMPT=never pnpm --filter @paperclip/server dev:watch", + "dev": "node scripts/dev-runner.mjs dev", + "dev:watch": "PAPERCLIP_MIGRATION_PROMPT=never node scripts/dev-runner.mjs watch", "dev:server": "pnpm --filter @paperclip/server dev", "dev:ui": "pnpm --filter @paperclip/ui dev", "build": "pnpm -r build", diff --git a/scripts/dev-runner.mjs b/scripts/dev-runner.mjs new file mode 100644 index 00000000..49c4c8d6 --- /dev/null +++ b/scripts/dev-runner.mjs @@ -0,0 +1,60 @@ +#!/usr/bin/env node +import { spawn } from "node:child_process"; + +const mode = process.argv[2] === "watch" ? "watch" : "dev"; +const cliArgs = process.argv.slice(3); + +const tailscaleAuthFlagNames = new Set([ + "--tailscale-auth", + "--authenticated-private", +]); + +let tailscaleAuth = false; +const forwardedArgs = []; + +for (const arg of cliArgs) { + if (tailscaleAuthFlagNames.has(arg)) { + tailscaleAuth = true; + continue; + } + forwardedArgs.push(arg); +} + +if (process.env.npm_config_tailscale_auth === "true") { + tailscaleAuth = true; +} +if (process.env.npm_config_authenticated_private === "true") { + tailscaleAuth = true; +} + +const env = { + ...process.env, + PAPERCLIP_UI_DEV_MIDDLEWARE: "true", +}; + +if (tailscaleAuth) { + env.PAPERCLIP_DEPLOYMENT_MODE = "authenticated"; + env.PAPERCLIP_DEPLOYMENT_EXPOSURE = "private"; + env.PAPERCLIP_AUTH_BASE_URL_MODE = "auto"; + env.HOST = "0.0.0.0"; + console.log("[paperclip] dev mode: authenticated/private (tailscale-friendly) on 0.0.0.0"); +} else { + console.log("[paperclip] dev mode: local_trusted (default)"); +} + +const pnpmBin = process.platform === "win32" ? "pnpm.cmd" : "pnpm"; +const serverScript = mode === "watch" ? "dev:watch" : "dev"; +const child = spawn( + pnpmBin, + ["--filter", "@paperclip/server", serverScript, ...forwardedArgs], + { stdio: "inherit", env }, +); + +child.on("exit", (code, signal) => { + if (signal) { + process.kill(process.pid, signal); + return; + } + process.exit(code ?? 0); +}); +