ci/woodpecker/push/publish Pipeline was canceled
Co-authored-by: ops-deploy-01 <[email protected]>
105 lines
3.2 KiB
TypeScript
105 lines
3.2 KiB
TypeScript
import { existsSync, rmSync } from 'node:fs';
|
|
import { createInterface } from 'node:readline';
|
|
import {
|
|
GATEWAY_HOME,
|
|
getDaemonPid,
|
|
readMeta,
|
|
stopDaemon,
|
|
uninstallGatewayPackage,
|
|
} from './daemon.js';
|
|
|
|
export interface UninstallOptions {
|
|
/** Skip the confirmation prompt (headless/scripted uninstall). */
|
|
yes?: boolean;
|
|
/** Also remove all gateway data at GATEWAY_HOME (never implied by --yes). */
|
|
removeData?: boolean;
|
|
}
|
|
|
|
export async function runUninstall(opts: UninstallOptions = {}): Promise<void> {
|
|
const nonInteractive = Boolean(opts.yes) || process.env['MOSAIC_ASSUME_YES'] === '1';
|
|
|
|
// Non-TTY without explicit consent must FAIL LOUD, not quietly do nothing:
|
|
// the pre-fix behavior (prompt on a closed stdin → default No → exit 0,
|
|
// gateway untouched) reported success-by-silence to every scripted caller
|
|
// (#1390). An explicit refusal beats a silent no-op.
|
|
if (!nonInteractive && !process.stdin.isTTY) {
|
|
console.error(
|
|
'gateway uninstall: stdin is not a TTY and no --yes was given — refusing to ' +
|
|
'run an interactive uninstall headlessly (nothing was changed). ' +
|
|
'Use --yes (and --remove-data to also delete gateway data), or run from a terminal.',
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
const rl = nonInteractive
|
|
? null
|
|
: createInterface({ input: process.stdin, output: process.stdout });
|
|
try {
|
|
await doUninstall(rl as NonNullable<typeof rl>, opts, nonInteractive);
|
|
} finally {
|
|
rl?.close();
|
|
}
|
|
}
|
|
|
|
function prompt(
|
|
rl: NonNullable<ReturnType<typeof createInterface>>,
|
|
question: string,
|
|
): Promise<string> {
|
|
return new Promise((resolve) => rl.question(question, resolve));
|
|
}
|
|
|
|
async function doUninstall(
|
|
rl: ReturnType<typeof createInterface>,
|
|
opts: UninstallOptions,
|
|
nonInteractive: boolean,
|
|
): Promise<void> {
|
|
const meta = readMeta();
|
|
if (!meta) {
|
|
console.log('Gateway is not installed.');
|
|
return;
|
|
}
|
|
|
|
if (nonInteractive) {
|
|
console.log(`Uninstalling Mosaic Gateway (--yes${opts.removeData ? ' --remove-data' : ''})...`);
|
|
} else {
|
|
const answer = await prompt(rl, 'Uninstall Mosaic Gateway? [y/N] ');
|
|
if (answer.toLowerCase() !== 'y') {
|
|
console.log('Aborted.');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Stop if running
|
|
if (getDaemonPid() !== null) {
|
|
console.log('Stopping gateway daemon...');
|
|
try {
|
|
await stopDaemon();
|
|
console.log('Stopped.');
|
|
} catch (err) {
|
|
console.warn(`Warning: ${err instanceof Error ? err.message : String(err)}`);
|
|
}
|
|
}
|
|
|
|
// Remove config/data. Interactive: ask. Headless: only with the explicit
|
|
// flag — destructive recursion is never implied by --yes alone (#1390).
|
|
let removeData = Boolean(opts.removeData);
|
|
if (!nonInteractive) {
|
|
const answer = await prompt(rl, `Remove all gateway data at ${GATEWAY_HOME}? [y/N] `);
|
|
removeData = answer.toLowerCase() === 'y';
|
|
}
|
|
if (removeData) {
|
|
if (existsSync(GATEWAY_HOME)) {
|
|
rmSync(GATEWAY_HOME, { recursive: true, force: true });
|
|
console.log('Gateway data removed.');
|
|
}
|
|
} else {
|
|
console.log(`Gateway data kept at ${GATEWAY_HOME}.`);
|
|
}
|
|
|
|
// Uninstall npm package
|
|
console.log('Uninstalling npm package...');
|
|
uninstallGatewayPackage();
|
|
|
|
console.log('\nGateway uninstalled.');
|
|
}
|