diff --git a/bin/ui5.cjs b/bin/ui5.cjs index b84baaaf..0d380bfe 100755 --- a/bin/ui5.cjs +++ b/bin/ui5.cjs @@ -90,12 +90,19 @@ const ui5 = { }, async invokeCLI(pkg) { + let profile; if (process.env.UI5_CLI_PROFILE) { - const profile = await import("../lib/utils/profile.js"); + profile = await import("../lib/utils/profile.js"); await profile.start(); } const {default: cli} = await import("../lib/cli/cli.js"); - await cli(pkg); + const {command} = await cli(pkg); + + // Stop profiling after CLI finished execution + // Except for "serve" command, which continues running and only stops on sigint (see profile.js) + if (profile && command !== "serve") { + await profile.stop(); + } }, async main() { @@ -105,8 +112,7 @@ const ui5 = { } else { const localInstallationInvoked = await ui5.invokeLocalInstallation(pkg); if (!localInstallationInvoked) { - const exitCode = await ui5.invokeCLI(pkg); - process.exit(exitCode); + await ui5.invokeCLI(pkg); } } } diff --git a/lib/cli/base.js b/lib/cli/base.js index 7f777baa..a6d271ce 100644 --- a/lib/cli/base.js +++ b/lib/cli/base.js @@ -135,5 +135,6 @@ export default function(cli) { process.stderr.write(chalk.dim(`See 'ui5 --help'`)); process.stderr.write("\n"); } + process.exit(1); }); } diff --git a/lib/cli/cli.js b/lib/cli/cli.js index 823a22f2..b37c2fc3 100644 --- a/lib/cli/cli.js +++ b/lib/cli/cli.js @@ -68,20 +68,8 @@ export default async (pkg) => { // Format terminal output to full available width cli.wrap(cli.terminalWidth()); - let exitCode = 0; - try { - // Call parse to run yargs - await cli.parse(); - } catch (err) { - // Error is already handled via .fail callback in ./base.js - exitCode = 1; - } finally { - // Stop profiling after CLI finished execution - if (process.env.UI5_CLI_PROFILE) { - const profile = await import("../utils/profile.js"); - await profile.stop(); - } - } - - return exitCode; + const {_} = await cli.argv; + return { + command: _[0] + }; }; diff --git a/lib/utils/profile.js b/lib/utils/profile.js index a4e3143c..0904b70e 100644 --- a/lib/utils/profile.js +++ b/lib/utils/profile.js @@ -2,6 +2,7 @@ import {writeFile} from "node:fs/promises"; import {Session} from "node:inspector"; let session; +let processSignals; export async function start() { if (session) { @@ -11,7 +12,9 @@ export async function start() { session.connect(); await new Promise((resolve) => { session.post("Profiler.enable", () => { + console.log(`Recording CPU profile...`); session.post("Profiler.start", () => { + processSignals = registerSigHooks(); resolve(); }); }); @@ -19,17 +22,34 @@ export async function start() { } async function writeProfile(profile) { - const d = new Date(); - const timestamp = - `${d.getFullYear()}-${d.getMonth()+1}-${d.getDate()}_${d.getHours()}-${d.getMinutes()}-${d.getSeconds()}`; + const formatter = new Intl.DateTimeFormat("en-GB", { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }); + const dateParts = Object.create(null); + const parts = formatter.formatToParts(new Date()); + parts.forEach((p) => { + dateParts[p.type] = p.value; + }); - await writeFile(`./ui5_${timestamp}.cpuprofile`, JSON.stringify(profile)); + const fileName = `./ui5_${dateParts.year}-${dateParts.month}-${dateParts.day}_` + + `${dateParts.hour}-${dateParts.minute}-${dateParts.second}.cpuprofile`; + console.log(`\nSaving CPU profile to ${fileName}...`); + await writeFile(fileName, JSON.stringify(profile)); } export async function stop() { if (!session) { return; } + if (processSignals) { + deregisterSigHooks(processSignals); + processSignals = null; + } const profile = await new Promise((resolve) => { session.post("Profiler.stop", (err, {profile}) => { if (err) { @@ -38,8 +58,38 @@ export async function stop() { resolve(profile); } }); + session = null; }); if (profile) { await writeProfile(profile); } } + +function registerSigHooks() { + function createListener(exitCode) { + return function() { + // Gracefully end profiling, then exit + stop().then(() => { + process.exit(exitCode); + }); + }; + } + + const processSignals = { + "SIGHUP": createListener(128 + 1), + "SIGINT": createListener(128 + 2), + "SIGTERM": createListener(128 + 15), + "SIGBREAK": createListener(128 + 21) + }; + + for (const signal of Object.keys(processSignals)) { + process.on(signal, processSignals[signal]); + } + return processSignals; +} + +function deregisterSigHooks(signals) { + for (const signal of Object.keys(signals)) { + process.removeListener(signal, signals[signal]); + } +}