Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions packages/host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@
"ensure-boxel-ui": "../boxel-ui/addon/bin/conditional-build.sh",
"start": "pnpm ensure-boxel-ui && node scripts/vite-serve.js",
"serve:dist": "node scripts/serve-dist.js",
"serve:dist:legacy": "serve --config ../tests/serve.json --single --cors --no-request-logging --no-etag --listen 4200 dist",
"start:build": "NODE_OPTIONS='--max-old-space-size=8192' ember build --watch",
"test": "concurrently \"pnpm:lint\" \"pnpm:test:*\" --names \"lint,test:\"",
"test-with-percy": "percy exec --parallel -- pnpm test:wait-for-servers",
"test:wait-for-servers": "./scripts/test-wait-for-servers.sh",
Expand Down Expand Up @@ -180,7 +178,6 @@
"qunit": "catalog:",
"qunit-dom": "catalog:",
"safe-stable-stringify": "catalog:",
"serve": "^14.2.5",
"simple-html-tokenizer": "catalog:",
"start-server-and-test": "catalog:",
"statuses": "catalog:",
Expand Down
72 changes: 11 additions & 61 deletions packages/host/scripts/serve-dist.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,16 @@
/**
* Wrapper around `vite preview` that supports dynamic port allocation in environment mode.
* When BOXEL_ENVIRONMENT is set, picks a free port, starts preview, then registers
* with Traefik so that `host.<branch>.localhost` routes to this instance.
* When BOXEL_ENVIRONMENT is not set, previews on port 4200.
* Wrapper around `vite preview` for `pnpm serve:dist`. Delegates to the
* shared launcher, which handles BOXEL_ENVIRONMENT / Traefik registration.
* Mirrors scripts/vite-serve.js, which does the same for the dev server.
*
* CORS headers and SPA fallback are configured in vite.config.mjs under `preview`.
* CORS headers and SPA fallback are configured in vite.config.mjs under
* `preview`.
*/

const { spawn } = require('child_process');
const path = require('path');
const { startWithTraefik } = require('./vite-with-traefik');

const BOXEL_ENVIRONMENT = process.env.BOXEL_ENVIRONMENT;

function runServe(port) {
const child = spawn(
'npx',
['vite', 'preview', '--port', String(port), '--strictPort'],
{ stdio: 'inherit', cwd: path.join(__dirname, '..'), shell: true },
);
child.on('exit', (code) => process.exit(code || 0));
return child;
}

if (!BOXEL_ENVIRONMENT) {
// Standard mode: hardcoded port 4200
runServe(4200);
} else {
const { ensureTraefik } = require('./ensure-traefik');
const { getEnvSlug, registerWithTraefik } = require('./traefik-helpers');

ensureTraefik();

const net = require('net');

const slug = getEnvSlug();
const hostname = `host.${slug}.localhost`;

// Find a free port
const srv = net.createServer();
srv.listen(0, () => {
const port = srv.address().port;
srv.close(() => {
console.log(
`[environment-mode] Starting host app on dynamic port ${port}`,
);
console.log(
`[environment-mode] Will be accessible at http://${hostname}`,
);

runServe(port);

try {
registerWithTraefik(slug, hostname, port);
console.log(
`[environment-mode] Registered host at ${hostname} -> localhost:${port}`,
);
} catch (e) {
console.error(
'[environment-mode] Failed to register with Traefik:',
e.message,
);
}
});
});
}
startWithTraefik({
subcommand: 'preview',
defaultPort: 4200,
label: 'vite preview',
});
3 changes: 2 additions & 1 deletion packages/host/scripts/traefik-helpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* Shared helpers for Traefik registration in environment mode.
* Both vite-serve.js and serve-dist.js use these.
* Used by scripts/vite-with-traefik.js (which both vite-serve.js and
* serve-dist.js delegate to).
*/

const path = require('path');
Expand Down
73 changes: 10 additions & 63 deletions packages/host/scripts/vite-serve.js
Original file line number Diff line number Diff line change
@@ -1,67 +1,14 @@
/**
* Wrapper around `vite` (dev server) that supports dynamic port allocation in
* environment mode. When BOXEL_ENVIRONMENT is set, picks a free port, passes
* --port to vite, then registers with Traefik so that `host.<branch>.localhost`
* routes here. When BOXEL_ENVIRONMENT is not set, serves on port 4200 to match
* the readiness URL the dev-all mise task polls.
* Wrapper around `vite` (dev server) for `pnpm start`. Delegates to the
* shared launcher, which handles BOXEL_ENVIRONMENT / Traefik registration.
* Mirrors scripts/serve-dist.js, which does the same for `vite preview`.
*/

const { spawn } = require('child_process');
const path = require('path');
const net = require('net');
const { startWithTraefik } = require('./vite-with-traefik');

const BOXEL_ENVIRONMENT = process.env.BOXEL_ENVIRONMENT;

function startVite(port) {
const child = spawn('npx', ['vite', '--port', String(port), '--strictPort'], {
stdio: 'inherit',
cwd: path.join(__dirname, '..'),
shell: true,
});
child.on('exit', (code) => process.exit(code || 0));
return child;
}

if (!BOXEL_ENVIRONMENT) {
startVite(4200);
} else {
const { ensureTraefik } = require('./ensure-traefik');
const { getEnvSlug, registerWithTraefik } = require('./traefik-helpers');

ensureTraefik();

const slug = getEnvSlug();
const hostname = `host.${slug}.localhost`;

// Point the client at the per-environment Synapse via Traefik
if (!process.env.MATRIX_URL) {
process.env.MATRIX_URL = `http://matrix.${slug}.localhost`;
}

const srv = net.createServer();
srv.listen(0, () => {
const port = srv.address().port;
srv.close(() => {
console.log(
`[environment-mode] Starting vite dev server on dynamic port ${port}`,
);
console.log(
`[environment-mode] Will be accessible at http://${hostname}`,
);

startVite(port);

try {
registerWithTraefik(slug, hostname, port);
console.log(
`[environment-mode] Registered host at ${hostname} -> localhost:${port}`,
);
} catch (e) {
console.error(
'[environment-mode] Failed to register with Traefik:',
e.message,
);
}
});
});
}
startWithTraefik({
subcommand: null,
defaultPort: 4200,
label: 'vite dev server',
nodeMemory: true,
});
98 changes: 98 additions & 0 deletions packages/host/scripts/vite-with-traefik.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* Shared launcher for `vite` (dev) and `vite preview` (built) that supports
* dynamic port allocation in environment mode. When BOXEL_ENVIRONMENT is set,
* picks a free port, starts vite bound to all interfaces (so the Traefik
* container can reach it via host.docker.internal), then registers with
* Traefik so that `host.<slug>.localhost` routes here. When BOXEL_ENVIRONMENT
* is not set, runs vite on the default port with default host.
*/

const { spawn } = require('child_process');
const path = require('path');
const net = require('net');

function runVite({ subcommand, port, allHosts, extraEnv, nodeMemory }) {
const args = ['vite'];
if (subcommand) args.push(subcommand);
args.push('--port', String(port), '--strictPort');
if (allHosts) {
// Bind to all interfaces so Traefik (running in Docker) can reach
// vite via host.docker.internal:<port>. Vite's default is 127.0.0.1
// only, which is unreachable from inside the container.
args.push('--host');
}
const env = { ...process.env, ...(extraEnv || {}) };
if (nodeMemory) {
env.NODE_OPTIONS = process.env.NODE_OPTIONS || '--max-old-space-size=8192';
}
const child = spawn('npx', args, {
stdio: 'inherit',
cwd: path.join(__dirname, '..'),
shell: true,
env,
});
child.on('exit', (code) => process.exit(code || 0));
return child;
}

function startWithTraefik({ subcommand, defaultPort, label, nodeMemory }) {
const BOXEL_ENVIRONMENT = process.env.BOXEL_ENVIRONMENT;

if (!BOXEL_ENVIRONMENT) {
runVite({ subcommand, port: defaultPort, allHosts: false, nodeMemory });
return;
}

const { ensureTraefik } = require('./ensure-traefik');
const { getEnvSlug, registerWithTraefik } = require('./traefik-helpers');

ensureTraefik();

const slug = getEnvSlug();
const hostname = `host.${slug}.localhost`;

// Point the client at the per-environment Synapse via Traefik
if (!process.env.MATRIX_URL) {
process.env.MATRIX_URL = `http://matrix.${slug}.localhost`;
}

const srv = net.createServer();
srv.listen(0, () => {
const port = srv.address().port;
srv.close(() => {
console.log(
`[environment-mode] Starting ${label} on dynamic port ${port}`,
);
console.log(
`[environment-mode] Will be accessible at http://${hostname}`,
);

runVite({
subcommand,
port,
allHosts: true,
extraEnv: {
// Read by vite.config.mjs to populate server.allowedHosts /
// preview.allowedHosts (and server.hmr.host for dev) so requests
// routed through Traefik aren't rejected by Vite's host check.
BOXEL_HOST_HOSTNAME: hostname,
},
nodeMemory,
});

try {
registerWithTraefik(slug, hostname, port);
console.log(
`[environment-mode] Registered host at ${hostname} -> localhost:${port}`,
);
} catch (e) {
console.error(
'[environment-mode] Failed to register with Traefik:',
e.message,
);
}
});
});
}

module.exports = { startWithTraefik };
16 changes: 16 additions & 0 deletions packages/host/vite.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ const sourceMapJsResolver = {
},
};

// In environment mode (BOXEL_ENVIRONMENT set), scripts/vite-with-traefik.js
// exposes the public Traefik hostname via BOXEL_HOST_HOSTNAME so we can let it
// through Vite's host check (for both `vite` and `vite preview`) and tell the
// HMR client where to reconnect (dev only).
const envHostname = process.env.BOXEL_HOST_HOSTNAME;

export default defineConfig(({ mode }) => ({
// Preserve function/class names. Boxel's card runtime introspects
// `Class.name` in user-visible places — validation errors ("references
Expand Down Expand Up @@ -167,5 +173,15 @@ export default defineConfig(({ mode }) => ({
headers: {
'Cache-Control': 'no-store',
},
...(envHostname ? { allowedHosts: [envHostname] } : {}),
},
server: envHostname
? {
allowedHosts: [envHostname],
hmr: {
host: envHostname,
clientPort: 80,
},
}
: undefined,
}));
Loading
Loading