diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index 5f0b9b7..d5c87f2 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -68,6 +68,7 @@ module.exports = { env: { NODE_ENV: 'production', WEBHOOK_PORT: process.env.WEBHOOK_PORT || 3011, + WEBHOOK_PM2_NAME: process.env.WEBHOOK_PM2_NAME || 'tssbot-webhook', GITHUB_WEBHOOK_SECRET: process.env.GITHUB_WEBHOOK_SECRET || '', GITHUB_WEBHOOK_REFS: process.env.GITHUB_WEBHOOK_REFS || 'refs/heads/main', GITHUB_WEBHOOK_REPOSITORY: process.env.GITHUB_WEBHOOK_REPOSITORY || '', diff --git a/webhook.cjs b/webhook.cjs index e72d4fc..e1c9e38 100644 --- a/webhook.cjs +++ b/webhook.cjs @@ -207,6 +207,49 @@ function commandNotFoundMessage(command) { ].join('. ') } +function restartTargetsInclude(target) { + return RESTART_TARGETS.some((candidate) => candidate === target) +} + +function pushTouchesWebhookRuntime(push) { + const runtimeFiles = new Set(['webhook.cjs', 'ecosystem.config.cjs']) + const commits = Array.isArray(push?.commits) ? push.commits : [] + return commits.some((commit) => { + const changed = [ + ...(Array.isArray(commit.added) ? commit.added : []), + ...(Array.isArray(commit.modified) ? commit.modified : []), + ...(Array.isArray(commit.removed) ? commit.removed : []), + ] + return changed.some((file) => runtimeFiles.has(String(file || '').replace(/^\/+/, ''))) + }) +} + +function scheduleSelfReload(reason) { + let resolvedCommand + try { + resolvedCommand = commandFor('pm2') + } catch (error) { + console.error(`could not schedule ${SELF_PM2_NAME} reload:`, error.message) + return + } + + console.log(`scheduling ${SELF_PM2_NAME} reload: ${reason}`) + setTimeout(() => { + const child = spawn( + resolvedCommand, + ['reload', 'ecosystem.config.cjs', '--only', SELF_PM2_NAME, '--update-env'], + { + cwd: __dirname, + env: process.env, + detached: true, + stdio: 'ignore', + shell: process.platform === 'win32', + }, + ) + child.unref() + }, 1000).unref() +} + function run(command, args, options = {}) { return new Promise((resolve, reject) => { const label = [command, ...args].join(' ') @@ -566,9 +609,8 @@ async function deploy(push) { // Reload via the ecosystem file (not by bare name) with --only so each deploy // re-reads the committed env blocks (e.g. VEHICLE_* paths). `pm2 reload // --update-env` would only merge the CLI's process.env and ignore the file. - // Exclude this webhook process itself: reloading it here kills the process - // running this deploy mid-command, interrupting the remaining reloads. The - // webhook is reloaded separately when its own code changes. + // Exclude this webhook process from the awaited reload: killing the process + // running this deploy mid-command can interrupt the remaining reloads. const reloadTargets = RESTART_TARGETS.filter((t) => t !== SELF_PM2_NAME) if (reloadTargets.length) { await run('pm2', [ @@ -581,6 +623,13 @@ async function deploy(push) { } await notifyDeployCompleted(push, diff) + if (restartTargetsInclude(SELF_PM2_NAME) || pushTouchesWebhookRuntime(push)) { + scheduleSelfReload( + restartTargetsInclude(SELF_PM2_NAME) + ? `${SELF_PM2_NAME} is listed in PM2_RESTART_TARGETS` + : 'webhook runtime files changed', + ) + } } catch (error) { error.deployDiff = diff throw error