Files
puter/tools/start.mjs
T
Neal Shah 1fc2eae1ae self hosted mail (#3932)
* self hosted mail

* update lock

* address daniel review
2026-09-25 21:48:15 -04:00

98 lines
3.4 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
//
// `--smtp` builds and runs the inbound SMTP receiver instead of the backend.
// It is a separate process and exits immediately unless
// `userEmail.localServer` is set:
//
// npm start -- --smtp
import { spawn } from 'node:child_process';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { npmSpawnArgs } from './npmSpawn.mjs';
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 server = getFlag('server');
const extensions = getFlag('extensions');
const smtp =
process.argv.slice(2).includes('--smtp') ||
Boolean(process.env.npm_config_smtp);
try {
if ( smtp ) {
await run(npm, ['run', 'build:ts']);
await run(process.execPath, [
'--enable-source-maps',
'-r', './dist/src/backend/telemetry.js',
'./dist/src/backend/smtp/index.js',
]);
} else 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(...npmSpawnArgs(['run', 'setupExtensions']));
await run(...npmSpawnArgs(['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);
}