@electric-sql/cli
Version:
ElectricSQL command line interface (CLI).
180 lines • 5.1 kB
JavaScript
import { Command } from "commander";
import { buildDatabaseURL, dedent, parsePgProxyPort, parsePort } from '../util/index.js';
import {
addOptionGroupToCommand,
getConfig,
printConfig
} from '../config.js';
import { dockerCompose } from './docker-utils.js';
function makeStartCommand() {
const command = new Command("start");
command.description(
"Start the ElectricSQL sync service, and an optional PostgreSQL"
);
addOptionGroupToCommand(command, "electric");
command.option(
"--detach",
"Run in the background instead of printing logs to the console"
);
command.action(async (opts) => {
if (opts.databaseUrl && opts.withPostgres) {
console.error("You cannot set --database-url when using --with-postgres.");
process.exit(1);
}
const config = getConfig(opts);
if (!config.WITH_POSTGRES && !config.DATABASE_URL) {
console.error(
"You must set --database-url or the ELECTRIC_DATABASE_URL env var when not using --with-postgres."
);
process.exit(1);
}
const startOptions = {
detach: opts.detach,
withPostgres: !!config.WITH_POSTGRES,
config
};
start(startOptions);
});
return command;
}
function start(options) {
return new Promise((resolve) => {
const exitOnDetached = options.exitOnDetached ?? true;
console.log(
`Starting ElectricSQL sync service${options.withPostgres ? " with PostgreSQL" : ""}`
);
const env = configToEnv(options.config);
env.PG_PROXY_PORT_PARSED = parsePgProxyPort(
env.PG_PROXY_PORT
).port.toString();
const dockerConfig = {
...env,
...options.withPostgres ? {
COMPOSE_PROFILES: "with-postgres",
DATABASE_URL: buildDatabaseURL({
user: env.DATABASE_USER,
password: env.DATABASE_PASSWORD,
host: "postgres",
port: parsePort(env.DATABASE_PORT),
dbName: env.DATABASE_NAME
}),
LOGICAL_PUBLISHER_HOST: "electric"
} : {}
};
console.log("Docker compose config:");
printConfig(dockerConfig);
const proc = dockerCompose(
"up",
[...options.detach ? ["--detach"] : []],
options.config.CONTAINER_NAME,
dockerConfig
);
proc.on("close", async (code) => {
if (code === 0) {
if (options.detach) {
if (options.withPostgres) {
await waitForPostgres(options.config.CONTAINER_NAME, dockerConfig);
}
await waitForElectric(options.config.SERVICE);
}
if (exitOnDetached) {
process.exit(0);
}
resolve();
} else {
console.error(
dedent`
Failed to start the Electric backend. Check the output from 'docker compose' above.
If the error message mentions a port already being allocated or address being already in use,
please change the configuration to an alternative port via the ELECTRIC_HTTP_PORT or
ELECTRIC_PG_PROXY_PORT environment variables.
`
);
process.exit(code ?? 1);
}
});
});
}
function checkPostgres(containerName, env) {
return new Promise((resolve, reject) => {
try {
const proc = dockerCompose(
"exec",
[
"postgres",
"pg_isready",
"-U",
`${env.DATABASE_USER}`,
"-p",
`${env.DATABASE_PORT}`
],
containerName,
env
);
proc.on("close", (code) => {
resolve(code === 0);
});
} catch (e) {
reject(e);
}
});
}
async function waitForPostgres(containerName, env) {
console.log("Waiting for PostgreSQL to be ready...");
const start2 = Date.now();
const timeout = 10 * 1e3;
while (Date.now() - start2 < timeout) {
if (await checkPostgres(containerName, env)) {
console.log("PostgreSQL is ready");
return;
}
await new Promise((resolve) => setTimeout(resolve, 500));
}
console.error(
dedent`
Timed out waiting for PostgreSQL to be ready.
Check the output from 'docker compose' above.
`
);
process.exit(1);
}
async function waitForElectric(serviceUrl) {
console.log("Waiting for Electric to be ready...");
const statusUrl = `${serviceUrl}/api/status`;
const start2 = Date.now();
const timeout = 10 * 1e3;
while (Date.now() - start2 < timeout) {
try {
const res = await fetch(statusUrl);
if (res.ok) {
console.log("Electric is ready");
return;
}
} catch (e) {
}
await new Promise((resolve) => setTimeout(resolve, 500));
}
console.error(
dedent`
Timed out waiting for Electric to be ready.
Check the output from 'docker compose' above.
`
);
process.exit(1);
}
function configToEnv(config) {
const env = {};
for (const [key, val] of Object.entries(config)) {
if (val === true) {
env[key] = "true";
} else if (val !== void 0) {
env[key] = val.toString();
}
}
return env;
}
export {
makeStartCommand,
start
};
//# sourceMappingURL=command-start.js.map