From 208ef8c90dd2e65b46823c0420f7c5811bfa3c86 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Thu, 20 Aug 2020 15:39:05 -0500 Subject: [PATCH] feat(exit): sys.exit() returns a promise exit() returns a promise so it can async close down workers and watchers --- src/cli/run.ts | 16 +++--- src/cli/task-build.ts | 10 +++- src/cli/task-generate.ts | 34 +++++++++--- src/cli/task-prerender.ts | 4 +- src/cli/task-test.ts | 6 +-- src/cli/task-watch.ts | 10 +++- src/compiler/sys/stencil-sys.ts | 21 ++++++-- src/declarations/stencil-public-compiler.ts | 2 +- src/sys/deno/deno-sys.ts | 59 +++++++++++++++++---- src/sys/node/node-sys.ts | 33 ++++++++---- 10 files changed, 146 insertions(+), 49 deletions(-) diff --git a/src/cli/run.ts b/src/cli/run.ts index b57e2aabf08..530e38adc7d 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -42,13 +42,17 @@ export const run = async (init: CliInitOptions) => { const findConfigResults = await findConfig({ sys, configPath: flags.config }); if (hasError(findConfigResults.diagnostics)) { logger.printDiagnostics(findConfigResults.diagnostics); - sys.exit(1); + return sys.exit(1); } - const ensureDepsResults = await sys.ensureDependencies({ rootDir: findConfigResults.rootDir, logger, dependencies: dependencies as any }); + const ensureDepsResults = await sys.ensureDependencies({ + rootDir: findConfigResults.rootDir, + logger, + dependencies: dependencies as any, + }); if (hasError(ensureDepsResults.diagnostics)) { logger.printDiagnostics(ensureDepsResults.diagnostics); - sys.exit(1); + return sys.exit(1); } const coreCompiler = await loadCoreCompiler(sys); @@ -79,7 +83,7 @@ export const run = async (init: CliInitOptions) => { if (validated.diagnostics.length > 0) { logger.printDiagnostics(validated.diagnostics); if (hasError(validated.diagnostics)) { - sys.exit(1); + return sys.exit(1); } } @@ -93,7 +97,7 @@ export const run = async (init: CliInitOptions) => { } catch (e) { if (!shouldIgnoreError(e)) { logger.error(`uncaught cli error: ${e}${logger.getLevel() === 'debug' ? e.stack : ''}`); - sys.exit(1); + return sys.exit(1); } } }; @@ -139,6 +143,6 @@ export const runTask = async (coreCompiler: CoreCompiler, config: Config, task: default: config.logger.error(`${config.logger.emoji('❌ ')}Invalid stencil command, please see the options below:`); taskHelp(config.sys, config.logger); - config.sys.exit(1); + return config.sys.exit(1); } }; diff --git a/src/cli/task-build.ts b/src/cli/task-build.ts index 2f8b3e0b75b..066e5ef9f10 100644 --- a/src/cli/task-build.ts +++ b/src/cli/task-build.ts @@ -28,7 +28,13 @@ export const taskBuild = async (coreCompiler: CoreCompiler, config: Config) => { if (results.hasError) { exitCode = 1; } else if (config.flags.prerender) { - const prerenderDiagnostics = await runPrerenderTask(coreCompiler, config, results.hydrateAppFilePath, results.componentGraph, null); + const prerenderDiagnostics = await runPrerenderTask( + coreCompiler, + config, + results.hydrateAppFilePath, + results.componentGraph, + null, + ); config.logger.printDiagnostics(prerenderDiagnostics); if (prerenderDiagnostics.some(d => d.level === 'error')) { @@ -43,6 +49,6 @@ export const taskBuild = async (coreCompiler: CoreCompiler, config: Config) => { } if (exitCode > 0) { - config.sys.exit(exitCode); + return config.sys.exit(exitCode); } }; diff --git a/src/cli/task-generate.ts b/src/cli/task-generate.ts index f6bdea45880..ed6395d6d43 100644 --- a/src/cli/task-generate.ts +++ b/src/cli/task-generate.ts @@ -9,34 +9,35 @@ import { validateComponentTag } from '@utils'; export const taskGenerate = async (coreCompiler: CoreCompiler, config: Config) => { if (!IS_NODE_ENV) { config.logger.error(`"generate" command is currently only implemented for a NodeJS environment`); - config.sys.exit(1); + return config.sys.exit(1); } const path = coreCompiler.path; if (!config.configPath) { config.logger.error('Please run this command in your root directory (i. e. the one containing stencil.config.ts).'); - config.sys.exit(1); + return config.sys.exit(1); } const absoluteSrcDir = config.srcDir; if (!absoluteSrcDir) { config.logger.error(`Stencil's srcDir was not specified.`); - config.sys.exit(1); + return config.sys.exit(1); } const { prompt } = await import('prompts'); const input = - config.flags.unknownArgs.find(arg => !arg.startsWith('-')) || ((await prompt({ name: 'tagName', type: 'text', message: 'Component tag name (dash-case):' })).tagName as string); + config.flags.unknownArgs.find(arg => !arg.startsWith('-')) || + ((await prompt({ name: 'tagName', type: 'text', message: 'Component tag name (dash-case):' })).tagName as string); const { dir, base: componentName } = path.parse(input); const tagError = validateComponentTag(componentName); if (tagError) { config.logger.error(tagError); - config.sys.exit(1); + return config.sys.exit(1); } const extensionsToGenerate: GeneratableExtension[] = ['tsx', ...(await chooseFilesToGenerate())]; @@ -47,7 +48,16 @@ export const taskGenerate = async (coreCompiler: CoreCompiler, config: Config) = await config.sys.createDir(path.join(outDir, testFolder), { recursive: true }); const writtenFiles = await Promise.all( - extensionsToGenerate.map(extension => writeFileByExtension(coreCompiler, config, outDir, componentName, extension, extensionsToGenerate.includes('css'))), + extensionsToGenerate.map(extension => + writeFileByExtension( + coreCompiler, + config, + outDir, + componentName, + extension, + extensionsToGenerate.includes('css'), + ), + ), ).catch(error => config.logger.error(error)); if (!writtenFiles) { @@ -85,7 +95,14 @@ const chooseFilesToGenerate = async () => { /** * Get a file's boilerplate by its extension and write it to disk. */ -const writeFileByExtension = async (coreCompiler: CoreCompiler, config: Config, path: string, name: string, extension: GeneratableExtension, withCss: boolean) => { +const writeFileByExtension = async ( + coreCompiler: CoreCompiler, + config: Config, + path: string, + name: string, + extension: GeneratableExtension, + withCss: boolean, +) => { if (isTest(extension)) { path = coreCompiler.path.join(path, 'test'); } @@ -205,7 +222,8 @@ describe('${name}', () => { /** * Convert a dash case string to pascal case. */ -const toPascalCase = (str: string) => str.split('-').reduce((res, part) => res + part[0].toUpperCase() + part.substr(1), ''); +const toPascalCase = (str: string) => + str.split('-').reduce((res, part) => res + part[0].toUpperCase() + part.substr(1), ''); /** * Extensions available to generate. diff --git a/src/cli/task-prerender.ts b/src/cli/task-prerender.ts index 6d1d9df6a33..40b52a8587e 100644 --- a/src/cli/task-prerender.ts +++ b/src/cli/task-prerender.ts @@ -10,7 +10,7 @@ export const taskPrerender = async (coreCompiler: CoreCompiler, config: Config) if (typeof hydrateAppFilePath !== 'string') { config.logger.error(`Missing hydrate app script path`); - config.sys.exit(1); + return config.sys.exit(1); } const srcIndexHtmlPath = config.srcIndexHtml; @@ -19,7 +19,7 @@ export const taskPrerender = async (coreCompiler: CoreCompiler, config: Config) config.logger.printDiagnostics(diagnostics); if (diagnostics.some(d => d.level === 'error')) { - config.sys.exit(1); + return config.sys.exit(1); } }; diff --git a/src/cli/task-test.ts b/src/cli/task-test.ts index bf610c6e8ca..589629e703e 100644 --- a/src/cli/task-test.ts +++ b/src/cli/task-test.ts @@ -4,7 +4,7 @@ import { IS_NODE_ENV } from '../compiler/sys/environment'; export const taskTest = async (config: Config) => { if (!IS_NODE_ENV) { config.logger.error(`"test" command is currently only implemented for a NodeJS environment`); - config.sys.exit(1); + return config.sys.exit(1); } try { @@ -48,10 +48,10 @@ export const taskTest = async (config: Config) => { await testing.destroy(); if (!passed) { - config.sys.exit(1); + return config.sys.exit(1); } } catch (e) { config.logger.error(e); - config.sys.exit(1); + return config.sys.exit(1); } }; diff --git a/src/cli/task-watch.ts b/src/cli/task-watch.ts index 3186659ba4a..8239bea8e2c 100644 --- a/src/cli/task-watch.ts +++ b/src/cli/task-watch.ts @@ -44,7 +44,13 @@ export const taskWatch = async (coreCompiler: CoreCompiler, config: Config) => { if (config.flags.prerender) { watcher.on('buildFinish', async results => { if (!results.hasError) { - const prerenderDiagnostics = await runPrerenderTask(coreCompiler, config, results.hydrateAppFilePath, results.componentGraph, null); + const prerenderDiagnostics = await runPrerenderTask( + coreCompiler, + config, + results.hydrateAppFilePath, + results.componentGraph, + null, + ); config.logger.printDiagnostics(prerenderDiagnostics); } }); @@ -64,6 +70,6 @@ export const taskWatch = async (coreCompiler: CoreCompiler, config: Config) => { } if (exitCode > 0) { - config.sys.exit(exitCode); + return config.sys.exit(exitCode); } }; diff --git a/src/compiler/sys/stencil-sys.ts b/src/compiler/sys/stencil-sys.ts index dae9e5618a7..a98af811d38 100644 --- a/src/compiler/sys/stencil-sys.ts +++ b/src/compiler/sys/stencil-sys.ts @@ -87,7 +87,11 @@ export const createSystem = (c?: { logger?: Logger }) => { return results; }; - const createDirRecursiveSync = (p: string, opts: CompilerSystemCreateDirectoryOptions, results: CompilerSystemCreateDirectoryResults) => { + const createDirRecursiveSync = ( + p: string, + opts: CompilerSystemCreateDirectoryOptions, + results: CompilerSystemCreateDirectoryResults, + ) => { const parentDir = dirname(p); if (opts && opts.recursive && !isRootPath(parentDir)) { @@ -279,7 +283,11 @@ export const createSystem = (c?: { logger?: Logger }) => { return results; }; - const remoreDirSyncRecursive = (p: string, opts: CompilerSystemRemoveDirectoryOptions, results: CompilerSystemRemoveDirectoryResults) => { + const remoreDirSyncRecursive = ( + p: string, + opts: CompilerSystemRemoveDirectoryOptions, + results: CompilerSystemRemoveDirectoryResults, + ) => { if (!results.error) { p = normalize(p); @@ -515,7 +523,8 @@ export const createSystem = (c?: { logger?: Logger }) => { return results; }; - const getLocalModulePath = (opts: { rootDir: string; moduleId: string; path: string }) => join(opts.rootDir, 'node_modules', opts.moduleId, opts.path); + const getLocalModulePath = (opts: { rootDir: string; moduleId: string; path: string }) => + join(opts.rootDir, 'node_modules', opts.moduleId, opts.path); const getRemoteModuleUrl = (opts: { moduleId: string; path: string; version?: string }) => { const npmBaseUrl = 'https://cdn.jsdelivr.net/npm/'; @@ -539,7 +548,7 @@ export const createSystem = (c?: { logger?: Logger }) => { createDirSync, destroy, encodeToBase64, - exit: exitCode => logger.warn(`exit ${exitCode}`), + exit: async exitCode => logger.warn(`exit ${exitCode}`), getCurrentDirectory, getCompilerExecutingPath, getLocalModulePath, @@ -571,7 +580,9 @@ export const createSystem = (c?: { logger?: Logger }) => { writeFile, writeFileSync, generateContentHash, - createWorkerController: HAS_WEB_WORKER ? maxConcurrentWorkers => createWebWorkerMainController(sys, maxConcurrentWorkers) : null, + createWorkerController: HAS_WEB_WORKER + ? maxConcurrentWorkers => createWebWorkerMainController(sys, maxConcurrentWorkers) + : null, details: { cpuModel: '', freemem: () => 0, diff --git a/src/declarations/stencil-public-compiler.ts b/src/declarations/stencil-public-compiler.ts index 9090633437e..8e1e4811611 100644 --- a/src/declarations/stencil-public-compiler.ts +++ b/src/declarations/stencil-public-compiler.ts @@ -840,7 +840,7 @@ export interface CompilerSystem { /** * process.exit() */ - exit(exitCode: number): void; + exit(exitCode: number): Promise; /** * Optionally provide a fetch() function rather than using the built-in fetch(). * First arg is a url string or Request object (RequestInfo). diff --git a/src/sys/deno/deno-sys.ts b/src/sys/deno/deno-sys.ts index 3a0f0154845..5fdf9f03cf6 100644 --- a/src/sys/deno/deno-sys.ts +++ b/src/sys/deno/deno-sys.ts @@ -8,7 +8,21 @@ import type { CompilerSystemWriteFileResults, Diagnostic, } from '../../declarations'; -import { basename, delimiter, dirname, extname, isAbsolute, join, normalize, parse, relative, resolve, sep, win32, posix } from './deps'; +import { + basename, + delimiter, + dirname, + extname, + isAbsolute, + join, + normalize, + parse, + relative, + resolve, + sep, + win32, + posix, +} from './deps'; import { convertPathToFileProtocol, isRemoteUrl, normalizePath, catchError, buildError } from '@utils'; import { createDenoWorkerMainController } from './deno-worker-main'; import { denoCopyTasks } from './deno-copy-tasks'; @@ -27,7 +41,8 @@ export function createDenoSys(c: { Deno?: any } = {}) { const hardwareConcurrency = 0; const isRemoteHost = isRemoteUrl(import.meta.url); - const getLocalModulePath = (opts: { rootDir: string; moduleId: string; path: string }) => join(opts.rootDir, 'node_modules', opts.moduleId, opts.path); + const getLocalModulePath = (opts: { rootDir: string; moduleId: string; path: string }) => + join(opts.rootDir, 'node_modules', opts.moduleId, opts.path); const getRemoteModuleUrl = (module: { moduleId: string; path: string; version?: string }) => { const npmBaseUrl = 'https://cdn.jsdelivr.net/npm/'; @@ -159,13 +174,29 @@ export function createDenoSys(c: { Deno?: any } = {}) { stencilRemoteUrl = new URL(`../../compiler/stencil.js`, import.meta.url).href; if (!isRemoteUrl(stencilRemoteUrl)) { - stencilRemoteUrl = sys.getRemoteModuleUrl({ moduleId: stencilDep.name, version: stencilDep.version, path: stencilDep.main }); + stencilRemoteUrl = sys.getRemoteModuleUrl({ + moduleId: stencilDep.name, + version: stencilDep.version, + path: stencilDep.main, + }); } stencilBaseUrl = new URL(`../../`, stencilRemoteUrl); - stencilExePath = sys.getLocalModulePath({ rootDir: opts.rootDir, moduleId: stencilDep.name, path: stencilDep.main }); + stencilExePath = sys.getLocalModulePath({ + rootDir: opts.rootDir, + moduleId: stencilDep.name, + path: stencilDep.main, + }); - typescriptRemoteUrl = sys.getRemoteModuleUrl({ moduleId: typescriptDep.name, version: typescriptDep.version, path: typescriptDep.main }); - typescriptExePath = sys.getLocalModulePath({ rootDir: opts.rootDir, moduleId: typescriptDep.name, path: typescriptDep.main }); + typescriptRemoteUrl = sys.getRemoteModuleUrl({ + moduleId: typescriptDep.name, + version: typescriptDep.version, + path: typescriptDep.main, + }); + typescriptExePath = sys.getLocalModulePath({ + rootDir: opts.rootDir, + moduleId: typescriptDep.name, + path: typescriptDep.main, + }); const ensureStencil = fetchWrite(diagnostics, stencilRemoteUrl, stencilExePath); const ensureTypescript = fetchWrite(diagnostics, typescriptRemoteUrl, typescriptExePath); @@ -188,8 +219,16 @@ export function createDenoSys(c: { Deno?: any } = {}) { const deps: { url: string; path: string }[] = []; - const stencilPkg = sys.getLocalModulePath({ rootDir: opts.rootDir, moduleId: stencilDep.name, path: 'package.json' }); - const typescriptPkg = sys.getLocalModulePath({ rootDir: opts.rootDir, moduleId: typescriptDep.name, path: 'package.json' }); + const stencilPkg = sys.getLocalModulePath({ + rootDir: opts.rootDir, + moduleId: stencilDep.name, + path: 'package.json', + }); + const typescriptPkg = sys.getLocalModulePath({ + rootDir: opts.rootDir, + moduleId: typescriptDep.name, + path: 'package.json', + }); const stencilCheck = sys.access(stencilPkg); const typescriptCheck = sys.access(typescriptPkg); @@ -241,7 +280,9 @@ export function createDenoSys(c: { Deno?: any } = {}) { timespan.finish(`ensure resources end: ${deps.length}`); } }, - exit: deno.exit, + exit: async exitCode => { + deno.exit(exitCode); + }, getCompilerExecutingPath() { return stencilExePath; }, diff --git a/src/sys/node/node-sys.ts b/src/sys/node/node-sys.ts index 37b8f8991e1..6c0cdcaa641 100644 --- a/src/sys/node/node-sys.ts +++ b/src/sys/node/node-sys.ts @@ -15,7 +15,7 @@ import fs from 'graceful-fs'; import { NodeLazyRequire } from './node-lazy-require'; import { NodeResolveModule } from './node-resolve-module'; import { NodeWorkerController } from './node-worker-controller'; -import { normalizePath, isFunction } from '@utils'; +import { normalizePath, isFunction, isPromise } from '@utils'; import path from 'path'; import type TypeScript from 'typescript'; @@ -31,6 +31,23 @@ export function createNodeSys(c: { process?: any } = {}) { const compilerExecutingPath = path.join(__dirname, '..', '..', 'compiler', 'stencil.js'); const devServerExecutingPath = path.join(__dirname, '..', '..', 'dev-server', 'index.js'); + const runInterruptsCallbacks = () => { + const promises: Promise[] = []; + let cb: () => any; + while (isFunction((cb = onInterruptsCallbacks.pop()))) { + try { + const rtn = cb(); + if (isPromise(rtn)) { + promises.push(rtn); + } + } catch (e) {} + } + if (promises.length > 0) { + return Promise.all(promises); + } + return null; + }; + const sys: CompilerSystem = { name: 'node', version: prcs.versions.node, @@ -163,7 +180,10 @@ export function createNodeSys(c: { process?: any } = {}) { }; }, async ensureResources() {}, - exit: exit, + exit: async exitCode => { + await runInterruptsCallbacks(); + exit(exitCode); + }, getCurrentDirectory() { return normalizePath(prcs.cwd()); }, @@ -531,15 +551,6 @@ export function createNodeSys(c: { process?: any } = {}) { 'workbox-build': ['4.3.1', '4.3.1'], }); - const runInterruptsCallbacks = () => { - let cb: () => void; - while (isFunction((cb = onInterruptsCallbacks.pop()))) { - try { - cb(); - } catch (e) {} - } - }; - prcs.on('SIGINT', runInterruptsCallbacks); prcs.on('exit', runInterruptsCallbacks);