UNPKG

@whook/whook

Version:

Build strong and efficient REST web services.

163 lines 6.17 kB
import chokidar from 'chokidar'; import { dirname, join } from 'node:path'; import crypto from 'node:crypto'; import { PassThrough } from 'node:stream'; import { createWriteStream } from 'node:fs'; import initGenerateOpenAPITypes from './commands/generateOpenAPITypes.js'; import initWatchResolve from './services/watchResolve.js'; import { readFile } from 'node:fs/promises'; import ignore from 'ignore'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { Knifecycle, constant } from 'knifecycle'; import { printStackTrace } from 'yerror'; let $instance; let log; let delay; let delayPromise; let hash; export const DEFAULT_IGNORED = ['node_modules', '*.d.ts', '.git']; export const DEFAULT_WATCHED = ['src', 'package.json', 'package-lock.json']; export async function watchDevProcess({ injectedNames = [], ignored, watched, afterRestartEnd, } = { injectedNames: [], }) { let restartsCounter = 0; const ignoreFilter = await (async () => { let ignoreFileContent; let ignoreBuilder = ignore(); ignoreBuilder = ignoreBuilder.add(ignored?.length ? ignored : DEFAULT_IGNORED); try { ignoreFileContent = (await readFile('.gitignore')).toString(); ignoreBuilder = ignoreBuilder.add(ignoreFileContent || []); } catch (err) { // TODO: requires a deeper integration of Knifecycle to // start with a silo containing only the environment // and then another one for the server. It would allow // to wrap the chokidar watch into a service too. log?.('debug', '🤷 - Cannot find/parse .gitignore'); log?.('debug-stack', printStackTrace(err)); } return ignoreBuilder.createFilter(); })(); await restartDevProcess({ injectedNames, afterRestartEnd, restartsCounter }); await new Promise((resolve, reject) => { chokidar .watch(watched?.length ? watched : DEFAULT_WATCHED, { ignored: (str, stats) => { if (!stats?.isFile()) { return false; } return !ignoreFilter(str); }, ignoreInitial: true, }) .once('ready', () => { resolve(); }) .once('error', (err) => { reject(err); }) .on('all', (_event, filePath) => { // TODO: determine all the files needing a complete restart if (filePath.match(/package.*\.json/) || filePath.match(/src\/[^/]*\.ts/)) { log('warning', `☢️ - A file changed that may need a full restart (${filePath}).`); } if (delay) { if (!delayPromise) { delayPromise = delay.create(2000); restartDevProcess({ injectedNames, afterRestartEnd, restartsCounter: restartsCounter++, }); } } }); }); } export async function restartDevProcess({ injectedNames = [], afterRestartEnd, restartsCounter, }) { if ($instance) { log('warning', `➡️ - Changes detected : Will restart the server soon (${restartsCounter})...`); await delayPromise; await $instance.destroy(); } const { runProcess, prepareEnvironment, prepareProcess } = await import(pathToFileURL(join(process.cwd(), 'src', 'index.ts')).toString() + (restartsCounter ? '?restartsCounter=' + restartsCounter : '')); async function prepareWatchEnvironment($ = new Knifecycle()) { $ = await prepareEnvironment($); $.register(initWatchResolve); $.register(constant('RESTARTS_COUNTER', restartsCounter)); return $; } const { ENV, OPEN_API_TYPES_CONFIG, MAIN_FILE_URL, $instance: _instance, delay: _delay, getOpenAPI, log: _log, ...additionalServices } = (await runProcess(prepareWatchEnvironment, prepareProcess, [ ...new Set([ ...injectedNames, 'ENV', 'OPEN_API_TYPES_CONFIG', 'MAIN_FILE_URL', '$instance', 'delay', 'getOpenAPI', 'log', ]), ])); $instance = _instance; delay = _delay; log = _log; delayPromise = undefined; const response = await getOpenAPI({ options: { authenticated: true, }, query: { mutedMethods: ['options'], mutedParameters: [], }, }); const openAPIData = JSON.stringify(response.body); const newHash = crypto.createHash('md5').update(openAPIData).digest('hex'); const apiChanged = hash !== newHash; if (apiChanged) { hash = newHash; log('warning', '🦄 - API Changed : Generating API types...'); const instream = new PassThrough(); const bridge = new PassThrough(); const openAPITypesGenerationPromise = (await initGenerateOpenAPITypes({ OPEN_API_TYPES_CONFIG, instream, outstream: bridge, log, }))({ command: 'whook', namedArguments: {}, rest: [], }); const writeStream = createWriteStream(join(dirname(fileURLToPath(MAIN_FILE_URL)), 'openAPISchema.d.ts')); const writeStreamCompletionPromise = new Promise((resolve, reject) => { writeStream.once('finish', resolve); writeStream.once('error', reject); }); bridge.pipe(writeStream); instream.write(openAPIData); instream.end(); await Promise.all([ openAPITypesGenerationPromise, writeStreamCompletionPromise, ]); log('warning', '🦄 - API types generated!'); } if (afterRestartEnd) { await afterRestartEnd({ ENV, OPEN_API_TYPES_CONFIG, MAIN_FILE_URL, $instance, delay, getOpenAPI, log, ...additionalServices, }, { apiChanged, openAPIData }); } } //# sourceMappingURL=watch.js.map