Files
Nariman Jelveh b55782f066 GUI: add npm start --server=<domain> to run the GUI against a remote backend (#3481)
- npm start --server=puter.com (or -- --server=...) skips the local backend
  and serves the bundled GUI locally, pointed at the remote server's API.
  Bare domains resolve to https://api.<domain>; full origins are used
  verbatim. gui_origin points at the remote origin so /whoarewe, login,
  anti-csrf, socket.io, and builtin apps hit the real backend (CORS-open).
- --extensions=<dir>[;<dir>...] bundles out-of-tree GUI extension
  directories (sugar for PUTER_GUI_EXTENSION_PATHS). Their imports resolve
  as if the files lived in src/gui/src/extensions, with the extension's own
  files taking precedence, and bare imports fall back to the repo-root
  node_modules.
- Fix the bit-rotted dev-server: Express 5 wildcard routes, pass gui()
  params (previously called with none), inject the service_script shim,
  load bundle.min.js + bundle.min.css in prod mode, serve /sdk, and
  properly await the webpack build (it previously resolved immediately).
2026-07-30 16:29:54 -07:00

81 lines
2.9 KiB
JavaScript

#!/usr/bin/env node
// Entry point for `npm start`.
//
// Default: build and run the self-hosted backend.
//
// With a server flag, the local backend is skipped entirely and the GUI is
// served locally against that remote Puter backend:
//
// npm start --server=puter.com
// npm start -- --server=puter.com
// npm start -- --server=http://puter.localhost:4100
//
// A bare domain resolves to the production convention `https://api.<domain>`;
// a full origin (or an `api.`-prefixed host) is used verbatim.
//
// `--extensions=<dir>[;<dir>...]` bundles out-of-tree GUI extension
// directories into the served GUI (sugar for PUTER_GUI_EXTENSION_PATHS),
// so proprietary extensions can live outside this repository:
//
// npm start --server=puter.com --extensions=../puter-private/gui-extensions
import { spawn } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const rootDir = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
// `npm start --flag=x` arrives as the npm_config_<flag> env var;
// `npm start -- --flag=x` arrives as a regular argument.
const getFlag = (name) => {
for ( const arg of process.argv.slice(2) ) {
if ( arg.startsWith(`--${name}=`) ) return arg.slice(name.length + 3);
}
return process.env[`npm_config_${name}`] || null;
};
// Relative paths resolve against where `npm start` was invoked (INIT_CWD),
// not the repo root npm runs scripts from.
const resolveExtensionPaths = (value) => value
.split(';')
.filter(Boolean)
.map(p => path.resolve(process.env.INIT_CWD ?? process.cwd(), p))
.join(';');
const run = (cmd, args, opts = {}) => new Promise((resolve, reject) => {
const child = spawn(cmd, args, { stdio: 'inherit', cwd: rootDir, ...opts });
child.on('error', reject);
child.on('exit', (code, signal) => {
if ( code === 0 || signal ) resolve();
else reject(new Error(`\`${[cmd, ...args].join(' ')}\` exited with code ${code}`));
});
});
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
const server = getFlag('server');
const extensions = getFlag('extensions');
try {
if ( server ) {
const args = ['dev-server.js', `--server=${server}`];
if ( extensions ) args.push(`--extensions=${resolveExtensionPaths(extensions)}`);
await run(process.execPath, args, {
cwd: path.join(rootDir, 'src', 'gui'),
});
} else {
if ( extensions ) {
console.warn('--extensions only applies to --server (GUI-only) mode; ignoring.');
}
await run(npm, ['run', 'setupExtensions']);
await run(npm, ['run', 'build:ts']);
await run(process.execPath, [
'--enable-source-maps',
'-r', './dist/src/backend/telemetry.js',
'./dist/src/backend/index.js',
]);
}
} catch (err) {
console.error(err.message);
process.exit(1);
}