From 51022fd4f577a0b226f4d021707599f0a305656c Mon Sep 17 00:00:00 2001 From: Nicolas Froidure Date: Thu, 29 Feb 2024 12:18:28 +0100 Subject: [PATCH] fix(watch): fix the watch server for esm --- packages/whook-example/src/openAPISchema.d.ts | 8 +- packages/whook/src/services/watchResolve.ts | 30 +++++++ packages/whook/src/watch.ts | 81 +++++++++---------- 3 files changed, 76 insertions(+), 43 deletions(-) create mode 100644 packages/whook/src/services/watchResolve.ts diff --git a/packages/whook-example/src/openAPISchema.d.ts b/packages/whook-example/src/openAPISchema.d.ts index c7184a49..df7c05f3 100644 --- a/packages/whook-example/src/openAPISchema.d.ts +++ b/packages/whook-example/src/openAPISchema.d.ts @@ -13,10 +13,15 @@ declare namespace API { } export namespace GetDiagnostic { export type Output = Responses.$200; - export type Input = {}; + export type Input = { + readonly test?: Parameters.Test; + }; export namespace Responses { export type $200 = Components.Responses.Diagnostic<200>; } + export namespace Parameters { + export type Test = Components.Parameters.GetDiagnostic0; + } } export namespace GetOpenAPI { export type Output = Responses.$200; @@ -78,6 +83,7 @@ declare namespace Components { export type Duration = NonNullable; export type PathParam1 = NonNullable; export type PathParam2 = NonNullable; + export type GetDiagnostic0 = NonNullable; export type GetParameters3 = NonNullable; export type QueryParam = NonNullable[]>; export type GetParameters4 = NonNullable[]>; diff --git a/packages/whook/src/services/watchResolve.ts b/packages/whook/src/services/watchResolve.ts new file mode 100644 index 00000000..a19bc5e1 --- /dev/null +++ b/packages/whook/src/services/watchResolve.ts @@ -0,0 +1,30 @@ +import { + initResolve, + type LogService, + type ResolveService, +} from 'common-services'; +import { name, autoService, singleton } from 'knifecycle'; +import { WhookURL } from './WHOOK_RESOLVED_PLUGINS.js'; + +export default singleton(name('resolve', autoService(initWatchResolve))); + +export type WatchResolveDependencies = { + MAIN_FILE_URL: WhookURL; + RESTARTS_COUNTER: number; + log: LogService; +}; + +async function initWatchResolve({ + MAIN_FILE_URL, + RESTARTS_COUNTER, + log, +}: WatchResolveDependencies): Promise { + const baseResolve = await initResolve({ MAIN_FILE_URL, log }); + + return function resolve(...args: Parameters): string { + return ( + baseResolve(...args) + + (RESTARTS_COUNTER ? '?restartsCounter=' + RESTARTS_COUNTER : '') + ); + }; +} diff --git a/packages/whook/src/watch.ts b/packages/whook/src/watch.ts index 68ff4935..1edcc7b5 100644 --- a/packages/whook/src/watch.ts +++ b/packages/whook/src/watch.ts @@ -1,21 +1,20 @@ import chokidar from 'chokidar'; import { dirname, join } from 'node:path'; -import crypto from 'crypto'; -import { PassThrough } from 'stream'; +import crypto from 'node:crypto'; +import { PassThrough } from 'node:stream'; import { createWriteStream } from 'node:fs'; import initGenerateOpenAPITypes from './commands/generateOpenAPITypes.js'; import initGetOpenAPI from './handlers/getOpenAPI.js'; -import { readFile } from 'fs'; -import { promisify } from 'util'; +import initWatchResolve from './services/watchResolve.js'; +import { readFile } from 'node:fs'; +import { promisify } from 'node:util'; import ignore from 'ignore'; -import { createRequire } from 'module'; import { AppEnvVars } from 'application-services'; -import { fileURLToPath } from 'url'; -import type { Dependencies, Knifecycle } from 'knifecycle'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { type Dependencies, Knifecycle, constant } from 'knifecycle'; import type { DelayService, LogService } from 'common-services'; import type { OpenAPITypesConfig } from './commands/generateOpenAPITypes.js'; -const require = createRequire(import.meta.url); let $instance: Knifecycle; let log: LogService; let delay: DelayService; @@ -44,6 +43,7 @@ export async function watchDevServer( injectedNames: [], }, ): Promise { + let restartsCounter = 0; let ignoreFilter; try { @@ -61,7 +61,7 @@ export async function watchDevServer( // log('debug-stack', printStackTrace(err as Error)); } - await restartDevServer({ injectedNames, afterRestartEnd }); + await restartDevServer({ injectedNames, afterRestartEnd, restartsCounter }); await new Promise((resolve, reject) => { chokidar @@ -76,23 +76,22 @@ export async function watchDevServer( reject(err); }) .on('all', (_event, filePath) => { - const absolutePath = join(process.cwd(), filePath).replace( - /.ts$/, - '.js', - ); - + // TODO: determine all the files needing a complete restart if (filePath.match(/package.*\.json/)) { - for (const key in require.cache) { - uncache(key); - } - } else { - uncache(absolutePath, true); + log( + 'warning', + `☢️ - A file changed that may need a full restart (${filePath}).`, + ); } if (delay) { if (!delayPromise) { delayPromise = delay.create(2000); - restartDevServer({ injectedNames, afterRestartEnd }); + restartDevServer({ + injectedNames, + afterRestartEnd, + restartsCounter: restartsCounter++, + }); } } }); @@ -102,17 +101,33 @@ export async function watchDevServer( export async function restartDevServer({ injectedNames = [], afterRestartEnd, -}: WatchServerArgs): Promise { + restartsCounter, +}: WatchServerArgs & { + restartsCounter: number; +}): Promise { if ($instance) { - log('warning', '➡️ - Changes detected : Will restart the server soon...'); + log( + 'warning', + `➡️ - Changes detected : Will restart the server soon (${restartsCounter})...`, + ); await delayPromise; await $instance.destroy(); } const { runServer, prepareEnvironment, prepareServer } = await import( - join(process.cwd(), 'src', 'index.ts') + pathToFileURL(join(process.cwd(), 'src', 'index.ts')).toString() + + (restartsCounter ? '?restartsCounter=' + restartsCounter : '') ); + async function prepareWatchEnvironment( + $: T = new Knifecycle() as T, + ): Promise { + $ = await prepareEnvironment($); + $.register(initWatchResolve); + $.register(constant('RESTARTS_COUNTER', restartsCounter)); + return $; + } + const { ENV, OPEN_API_TYPES_CONFIG, @@ -123,7 +138,7 @@ export async function restartDevServer({ log: _log, ...additionalServices } = (await runServer( - prepareEnvironment, + prepareWatchEnvironment, prepareServer, [ @@ -206,21 +221,3 @@ export async function restartDevServer({ ); } } - -function uncache(key: string, recursively = false) { - const module = require.cache[key]; - - if (!module) { - return; - } - - if (!key.endsWith('.node')) { - delete require.cache[key]; - } - - if (!recursively) { - return; - } - - uncache((module.parent as typeof module).id); -}