diff --git a/packages/compas/src/main/development/integrations/cache-cleanup.js b/packages/compas/src/main/development/integrations/cache-cleanup.js index 73c54cc25e..0aea4a5080 100644 --- a/packages/compas/src/main/development/integrations/cache-cleanup.js +++ b/packages/compas/src/main/development/integrations/cache-cleanup.js @@ -15,14 +15,14 @@ export class CacheCleanupIntegration extends BaseIntegration { this.state.cache.cachesCleaned = true; if (!hasPreviouslyCleaned) { - return await this.state.emitCacheUpdated(); + await this.cleanup(); } } async onConfigUpdated() { await super.onConfigUpdated(); - await this.cleanup(); + this.state.runTask("CachesCleanup", this.cleanup()); } async cleanup() { diff --git a/packages/compas/src/main/development/integrations/file-watcher.js b/packages/compas/src/main/development/integrations/file-watcher.js index 1ed4701643..5dfa4572e0 100644 --- a/packages/compas/src/main/development/integrations/file-watcher.js +++ b/packages/compas/src/main/development/integrations/file-watcher.js @@ -93,7 +93,6 @@ export class FileWatcherIntegration extends BaseIntegration { ); } - // Dangling promise! boundEmitFileChange(events.map((it) => it.path)); }, FileWatcherIntegration.DEFAULT_WATCH_OPTIONS, diff --git a/packages/compas/src/main/development/integrations/inferred-actions.js b/packages/compas/src/main/development/integrations/inferred-actions.js index 0f35235dbb..91e1ddcbde 100644 --- a/packages/compas/src/main/development/integrations/inferred-actions.js +++ b/packages/compas/src/main/development/integrations/inferred-actions.js @@ -15,7 +15,10 @@ export class InferredActionsIntegration extends BaseIntegration { await super.init(); if (isNil(this.state.cache.availableActions)) { - await this.resolveAvailableActions(); + this.state.runTask( + "inferredActionsResolve", + this.resolveAvailableActions(), + ); } this.state.fileChangeRegister.push({ @@ -27,12 +30,18 @@ export class InferredActionsIntegration extends BaseIntegration { async onCacheUpdated() { await super.onCacheUpdated(); - await this.resolveAvailableActions(); + this.state.runTask( + "inferredActionsResolve", + this.resolveAvailableActions(), + ); } async onFileChanged(paths) { await super.onFileChanged(paths); - await this.resolveAvailableActions(); + this.state.runTask( + "inferredActionsResolve", + this.resolveAvailableActions(), + ); } async resolveAvailableActions() { diff --git a/packages/compas/src/main/development/integrations/package-manager.js b/packages/compas/src/main/development/integrations/package-manager.js index 8c7b4ef5c2..778f1ed864 100644 --- a/packages/compas/src/main/development/integrations/package-manager.js +++ b/packages/compas/src/main/development/integrations/package-manager.js @@ -16,8 +16,7 @@ export class PackageManagerIntegration extends BaseIntegration { if (!this.state.cache.packageManager) { await this.resolve(); - // TODO: do we want to start with an 'await this.execute();' - // May want to do that as background task. + this.state.runTask("packageManagerExecute", this.execute()); } this.state.fileChangeRegister.push({ @@ -62,7 +61,7 @@ export class PackageManagerIntegration extends BaseIntegration { } if (installCommand) { - return this.execute(); + this.state.runTask("packageManagerExecute", this.execute()); } } diff --git a/packages/compas/src/main/development/state.js b/packages/compas/src/main/development/state.js index 60c206fb91..076bb6a79c 100644 --- a/packages/compas/src/main/development/state.js +++ b/packages/compas/src/main/development/state.js @@ -84,7 +84,7 @@ export class State { }; } - // ==== generic ==== + // ==== generic lifecycle ==== async init() { debugTimeStart(`State#init`); @@ -108,11 +108,15 @@ export class State { new ConfigLoaderIntegration(this), new RootDirectoriesIntegration(this), new CacheCleanupIntegration(this), - new InferredActionsIntegration(this), new ActionsIntegration(this), + + // Try to keep the above list minimal. + new PackageManagerIntegration(this), + new InferredActionsIntegration(this), - // Should be the last integration, since it will + // Should be the last integration, since it will process file changes since the + // last snapshot. new FileWatcherIntegration(this), ]; @@ -208,6 +212,48 @@ export class State { } } + // ==== background tasks ==== + + /** + * + * @param {string} name + * @param {Promise|(() => Promise)} task + * @param {{ + * exitOnFailure?: boolean, + * }} [options] + * @returns {*} + */ + runTask(name, task, { exitOnFailure } = {}) { + if (typeof task === "function") { + return this.runTask(name, task, { exitOnFailure }); + } + + if (!(task instanceof Promise)) { + throw AppError.serverError({ + message: "Received a task that isn't a promise.", + name, + task, + }); + } + + const _self = this; + + debugPrint(`State#runTask :: ${name} :: registered`); + + task + .then(() => { + debugPrint(`State#runTask :: ${name} :: fulfilled`); + }) + .catch((e) => { + debugPrint(`State#runTask :: ${name} :: rejected`); + debugPrint(AppError.format(e)); + + if (exitOnFailure) { + _self.runTask("State#exit", _self.exit()); + } + }); + } + // ==== integrations ==== /** @@ -249,7 +295,8 @@ export class State { return; } - // Rename a few keypress for easier matching and shorter shortcuts, we may want to expand this setup later. + // Rename a few keypress for easier matching and shorter shortcuts, we may want to + // expand this setup later. if (key.name === "escape") { key.name = "esc"; } @@ -271,21 +318,21 @@ export class State { emitFileChange(paths) { debugPrint(`State#emitFileChange :: ${JSON.stringify(paths)}}`); - for (const integration of this.fileChangeRegister) { - if (micromatch.some(paths, integration.glob)) { + for (const registerItem of this.fileChangeRegister) { + if (micromatch.some(paths, registerItem.glob)) { debugPrint( - `State#emitFileChange :: Matched ${integration.glob} for ${integration.integration.name} debouncing with ${integration.debounceDelay}.`, + `State#emitFileChange :: Matched ${registerItem.glob} for ${registerItem.integration.name} debouncing with ${registerItem.debounceDelay}.`, ); - if (integration.existingTimeout) { - integration.existingTimeout.refresh(); + if (registerItem.existingTimeout) { + registerItem.existingTimeout.refresh(); } else { - integration.existingTimeout = setTimeout(() => { - // We don't ever clear the timeout, since refreshing will restart the timeout. - - // Dangling promise! - integration.integration.onFileChanged(paths); - }, integration.debounceDelay); + registerItem.existingTimeout = setTimeout(() => { + registerItem.integration.state.runTask( + "Integration#onFileChaged", + registerItem.integration.onFileChanged(paths), + ); + }, registerItem.debounceDelay); } } } diff --git a/packages/compas/src/main/development/tui.js b/packages/compas/src/main/development/tui.js index 531997c732..5ca01847c0 100644 --- a/packages/compas/src/main/development/tui.js +++ b/packages/compas/src/main/development/tui.js @@ -13,13 +13,13 @@ export function tuiInit(state) { // Exit listeners process.on("SIGABRT", () => { - state.exit(); + state.runTask("SIGABRT", state.exit(), { exitOnFailure: true }); }); process.on("SIGINT", () => { - state.exit(); + state.runTask("SIGINT", state.exit(), { exitOnFailure: true }); }); process.on("beforeExit", () => { - state.exit(); + state.runTask("beforeExit", state.exit()); }); process.stdout.on("resize", () => state.resizeScreen()); @@ -33,11 +33,12 @@ export function tuiInit(state) { process.stdin.on("keypress", (char, raw) => { if (raw.name === "c" && raw.ctrl) { // Ctrl + C - state.exit(); + state.runTask("Ctrl + C", state.exit(), { exitOnFailure: true }); + return; } - state.emitKeypress(raw); + state.runTask("tuiEmitKeyPress", state.emitKeypress(raw)); }); }