From 02c62b5e23322c3e46d92514d1abceed870727b6 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Thu, 9 Jul 2020 13:30:01 -0500 Subject: [PATCH] fix(cli): export cli types and runTask, move checkVersion to sys, add tests --- bin/stencil | 2 +- scripts/build.ts | 2 +- scripts/bundles/cli.ts | 23 +++- scripts/bundles/sys-node.ts | 17 ++- scripts/test/validate-build.ts | 55 +++++++++- src/cli/check-version.ts | 18 ++++ src/cli/index.ts | 2 +- src/cli/logs.ts | 26 +++-- src/cli/public.ts | 17 +++ src/cli/run.ts | 108 +++++++++++-------- src/cli/task-build.ts | 17 ++- src/cli/task-watch.ts | 21 ++-- src/compiler/cache.ts | 2 +- src/compiler/config/transpile-options.ts | 4 +- src/compiler/transpile/ts-config.ts | 4 +- src/declarations/stencil-private.ts | 9 -- src/declarations/stencil-public-compiler.ts | 7 ++ src/sys/node/index.ts | 4 +- src/sys/node/node-stencil-version-checker.ts | 89 +++++++-------- src/sys/node/node-sys-watch.ts | 69 ------------ src/sys/node/node-sys.ts | 72 ++++++++++++- src/sys/node/public.ts | 17 +++ tsconfig.json | 2 + 23 files changed, 375 insertions(+), 212 deletions(-) create mode 100644 src/cli/check-version.ts create mode 100644 src/cli/public.ts delete mode 100644 src/sys/node/node-sys-watch.ts create mode 100644 src/sys/node/public.ts diff --git a/bin/stencil b/bin/stencil index b2d60b21380..a521e9caf4a 100755 --- a/bin/stencil +++ b/bin/stencil @@ -44,7 +44,7 @@ if (typeof globalThis === 'undefined') { var cli = require('../cli/index.cjs.js'); var nodeApi = require('../sys/node/index.js'); var nodeLogger = nodeApi.createNodeLogger({ process: process }); -var nodeSys = nodeApi.createNodeSysWithWatch({ process: process, logger: nodeLogger }); +var nodeSys = nodeApi.createNodeSys({ process: process, logger: nodeLogger }); nodeApi.setupNodeProcess({ process: process, logger: nodeLogger }); diff --git a/scripts/build.ts b/scripts/build.ts index 5d06a8b20a0..49c543ad372 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -25,7 +25,7 @@ export async function run(rootDir: string, args: string[]) { } if (args.includes('--validate-build')) { - validateBuild(rootDir); + await validateBuild(rootDir); } } catch (e) { console.error(e); diff --git a/scripts/bundles/cli.ts b/scripts/bundles/cli.ts index 93cdbd7ba2d..41b5bb64184 100644 --- a/scripts/bundles/cli.ts +++ b/scripts/bundles/cli.ts @@ -1,3 +1,4 @@ +import fs from 'fs-extra'; import { join } from 'path'; import rollupJson from '@rollup/plugin-json'; import rollupCommonjs from '@rollup/plugin-commonjs'; @@ -8,23 +9,41 @@ import { relativePathPlugin } from './plugins/relative-path-plugin'; import { BuildOptions } from '../utils/options'; import { RollupOptions, OutputOptions } from 'rollup'; import { prettyMinifyPlugin } from './plugins/pretty-minify'; +import { writePkgJson } from '../utils/write-pkg-json'; export async function cli(opts: BuildOptions) { const inputDir = join(opts.transpiledDir, 'cli'); const outputDir = opts.output.cliDir; + const esmFilename = 'index.js'; + const cjsFilename = 'index.cjs.js'; + const dtsFilename = 'index.d.ts'; const esOutput: OutputOptions = { format: 'es', - file: join(outputDir, 'index.js'), + file: join(outputDir, esmFilename), preferConst: true, }; const cjsOutput: OutputOptions = { format: 'cjs', - file: join(outputDir, 'index.cjs.js'), + file: join(outputDir, cjsFilename), preferConst: true, }; + // create public d.ts + let dts = await fs.readFile(join(inputDir, 'public.d.ts'), 'utf8'); + dts = dts.replace('@stencil/core/internal', '../internal/index'); + await fs.writeFile(join(opts.output.cliDir, dtsFilename), dts); + + // write @stencil/core/compiler/package.json + writePkgJson(opts, opts.output.cliDir, { + name: '@stencil/core/cli', + description: 'Stencil CLI.', + main: cjsFilename, + module: esmFilename, + types: dtsFilename, + }); + const cliBundle: RollupOptions = { input: join(inputDir, 'index.js'), output: [esOutput, cjsOutput], diff --git a/scripts/bundles/sys-node.ts b/scripts/bundles/sys-node.ts index 202b6960043..7dec7d23512 100644 --- a/scripts/bundles/sys-node.ts +++ b/scripts/bundles/sys-node.ts @@ -9,11 +9,26 @@ import { RollupOptions } from 'rollup'; import { relativePathPlugin } from './plugins/relative-path-plugin'; import { aliasPlugin } from './plugins/alias-plugin'; import { prettyMinifyPlugin } from './plugins/pretty-minify'; +import { writePkgJson } from '../utils/write-pkg-json'; export async function sysNode(opts: BuildOptions) { - const inputFile = join(opts.transpiledDir, 'sys', 'node', 'index.js'); + const inputDir = join(opts.transpiledDir, 'sys', 'node'); + const inputFile = join(inputDir, 'index.js'); const outputFile = join(opts.output.sysNodeDir, 'index.js'); + // create public d.ts + let dts = await fs.readFile(join(inputDir, 'public.d.ts'), 'utf8'); + dts = dts.replace('@stencil/core/internal', '../../internal/index'); + await fs.writeFile(join(opts.output.sysNodeDir, 'index.d.ts'), dts); + + // write @stencil/core/compiler/package.json + writePkgJson(opts, opts.output.sysNodeDir, { + name: '@stencil/core/sys/node', + description: 'Stencil Node System.', + main: 'index.js', + types: 'index.d.ts', + }); + const sysNodeBundle: RollupOptions = { input: inputFile, output: { diff --git a/scripts/test/validate-build.ts b/scripts/test/validate-build.ts index c2c1f404889..a8a445be538 100644 --- a/scripts/test/validate-build.ts +++ b/scripts/test/validate-build.ts @@ -12,6 +12,7 @@ import ts from 'typescript'; const pkgs: TestPackage[] = [ { // cli + packageJson: 'cli/package.json', files: ['cli/index.js', 'cli/index.cjs.js'], }, { @@ -77,6 +78,7 @@ const pkgs: TestPackage[] = [ }, { // sys/node + packageJson: 'sys/node/package.json', files: ['sys/node/autoprefixer.js', 'sys/node/graceful-fs.js', 'sys/node/node-fetch.js'], }, { @@ -93,16 +95,17 @@ const pkgs: TestPackage[] = [ }, ]; -export function validateBuild(rootDir: string) { +export async function validateBuild(rootDir: string) { const dtsEntries: string[] = []; const opts = getOptions(rootDir); pkgs.forEach(testPkg => { validatePackage(opts, testPkg, dtsEntries); }); + console.log(`๐Ÿก Validated packages`); validateDts(opts, dtsEntries); - console.log(`๐Ÿ‘พ Validated build files and distribution`); + await validateCompiler(opts); } function validatePackage(opts: BuildOptions, testPkg: TestPackage, dtsEntries: string[]) { @@ -198,6 +201,54 @@ function validateDts(opts: BuildOptions, dtsEntries: string[]) { }; throw new Error('๐Ÿงจ ' + ts.formatDiagnostics(tsDiagnostics, host)); } + console.log(`๐ŸŸ Validated dts files`); +} + +async function validateCompiler(opts: BuildOptions) { + const compilerPath = join(opts.output.compilerDir, 'stencil.js'); + const cliPath = join(opts.output.cliDir, 'index.cjs.js'); + const sysNodePath = join(opts.output.sysNodeDir, 'index.js'); + + const compiler = await import(compilerPath); + const cli = await import(cliPath); + const sysNodeApi = await import(sysNodePath); + + const nodeLogger = sysNodeApi.createNodeLogger({ process }); + const nodeSys = sysNodeApi.createNodeSys({ process }); + + if (!nodeSys || nodeSys.name !== 'node' || nodeSys.version.length < 4) { + throw new Error(`๐Ÿงจ unable to validate sys node`); + } + console.log(`๐Ÿณ Validated sys node, current ${nodeSys.name} version: ${nodeSys.version}`); + + const validated = await compiler.loadConfig({ + config: { + logger: nodeLogger, + sys: nodeSys, + }, + }); + console.log(`${compiler.vermoji} Validated compiler: ${compiler.version}`); + + const transpileResults = compiler.transpileSync('const m: string = `transpile!`;', { target: 'es5' }); + if (!transpileResults || transpileResults.diagnostics.length > 0 || !transpileResults.code.startsWith(`var m = "transpile!";`)) { + console.error(transpileResults); + throw new Error(`๐Ÿงจ transpileSync error`); + } + console.log(`๐Ÿ‹ Validated compiler.transpileSync()`); + + const orgConsoleLog = console.log; + let loggedVersion = null; + console.log = (value: string) => (loggedVersion = value); + + await cli.runTask(compiler, validated.config, 'version'); + + console.log = orgConsoleLog; + + if (typeof loggedVersion !== 'string' || loggedVersion.length < 4) { + throw new Error(`๐Ÿงจ unable to validate compiler. loggedVersion: "${loggedVersion}"`); + } + + console.log(`๐Ÿฌ Validated cli`); } interface TestPackage { diff --git a/src/cli/check-version.ts b/src/cli/check-version.ts new file mode 100644 index 00000000000..b2b73f74afe --- /dev/null +++ b/src/cli/check-version.ts @@ -0,0 +1,18 @@ +import type { Config } from '../declarations'; +import { isFunction } from '@utils'; + +export const startCheckVersion = async (config: Config, currentVersion: string) => { + if (config.devMode && !config.flags.ci && isFunction(config.sys.checkVersion)) { + return config.sys.checkVersion(config.logger, currentVersion); + } + return null; +}; + +export const printCheckVersionResults = async (versionChecker: Promise<() => void>) => { + if (versionChecker) { + const checkVersionResults = await versionChecker; + if (isFunction(checkVersionResults)) { + checkVersionResults(); + } + } +}; diff --git a/src/cli/index.ts b/src/cli/index.ts index 1a8f358d78e..cbfec6e6604 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -1 +1 @@ -export { run } from './run'; +export { run, runTask } from './run'; diff --git a/src/cli/logs.ts b/src/cli/logs.ts index 4fa03bf529f..cd753a2bcbc 100644 --- a/src/cli/logs.ts +++ b/src/cli/logs.ts @@ -1,25 +1,31 @@ -import type { Config, Logger, ConfigFlags, CompilerSystem } from '../declarations'; +import type { Config, Logger, ConfigFlags, CompilerSystem, TaskCommand } from '../declarations'; import type { CoreCompiler } from './load-compiler'; -import { version, vermoji } from '../version'; -export const startupLog = (logger: Logger, flags: ConfigFlags) => { - if (flags.task === 'info' || flags.task === 'serve') { +export const startupLog = (logger: Logger, task: TaskCommand) => { + if (task === 'info' || task === 'serve' || task === 'version') { return; } - const isDevBuild = version.includes('-dev.'); + logger.info(logger.cyan(`@stencil/core`)); +}; + +export const startupLogVersion = (logger: Logger, task: TaskCommand, coreCompiler: CoreCompiler) => { + if (task === 'info' || task === 'serve' || task === 'version') { + return; + } + const isDevBuild = coreCompiler.version.includes('-dev.'); - let startupMsg = logger.cyan(`@stencil/core`); + let startupMsg: string if (isDevBuild) { - startupMsg += ' ' + logger.yellow('[LOCAL DEV]'); + startupMsg = logger.yellow('[LOCAL DEV]'); } else { - startupMsg += ' ' + logger.cyan(`v${version}`); + startupMsg = logger.cyan(`v${coreCompiler.version}`); } - startupMsg += logger.emoji(' ' + vermoji); + startupMsg += logger.emoji(' ' + coreCompiler.vermoji); logger.info(startupMsg); -}; +} export const loadedCompilerLog = (sys: CompilerSystem, logger: Logger, flags: ConfigFlags, coreCompiler: CoreCompiler) => { const sysDetails = sys.details; diff --git a/src/cli/public.ts b/src/cli/public.ts new file mode 100644 index 00000000000..21978534b3d --- /dev/null +++ b/src/cli/public.ts @@ -0,0 +1,17 @@ +import { CliInitOptions, CompilerSystem, Config, ConfigFlags, Logger, TaskCommand } from '@stencil/core/internal'; + +/** + * Runs the CLI with the given options. This is used by Stencil's default `bin/stencil` file, + * but can be used externally too. + */ +export declare function run(init: CliInitOptions): Promise; + +/** + * Run individual CLI tasks. + * @param coreCompiler The core Stencil compiler to be used. The `run()` method handles loading the core compiler, however, `runTask()` must be passed it. + * @param config Assumes the config has already been validated and has the "sys" and "logger" properties. + * @param task The task command to run, such as `build`. + */ +export declare function runTask(coreCompiler: any, config: Config, task: TaskCommand): Promise; + +export { CompilerSystem, Config, ConfigFlags, Logger, TaskCommand }; diff --git a/src/cli/run.ts b/src/cli/run.ts index b312a1cb739..20c8fb9839b 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -1,9 +1,9 @@ -import type { CliInitOptions } from '../declarations'; +import type { CliInitOptions, Config, TaskCommand } from '../declarations'; import { dependencies } from '../compiler/sys/dependencies.json'; import { findConfig } from './find-config'; import { hasError, isFunction, shouldIgnoreError } from '@utils'; -import { loadCoreCompiler } from './load-compiler'; -import { loadedCompilerLog, startupLog } from './logs'; +import { loadCoreCompiler, CoreCompiler } from './load-compiler'; +import { loadedCompilerLog, startupLog, startupLogVersion } from './logs'; import { parseFlags } from './parse-flags'; import { taskBuild } from './task-build'; import { taskDocs } from './task-docs'; @@ -13,13 +13,13 @@ import { taskInfo } from './task-info'; import { taskPrerender } from './task-prerender'; import { taskServe } from './task-serve'; import { taskTest } from './task-test'; -import { version } from '../version'; -export async function run(init: CliInitOptions) { - const { args, logger, sys, checkVersion } = init; +export const run = async (init: CliInitOptions) => { + const { args, logger, sys } = init; try { const flags = parseFlags(sys, args); + const task = flags.task; if (flags.debug || flags.verbose) { logger.setLevel('debug'); } @@ -32,17 +32,12 @@ export async function run(init: CliInitOptions) { sys.applyGlobalPatch(sys.getCurrentDirectory()); } - if (flags.task === 'version' || flags.version) { - console.log(version); - return; - } - - if (flags.task === 'help' || flags.help) { + if (task === 'help' || flags.help) { taskHelp(sys, logger); return; } - startupLog(logger, flags); + startupLog(logger, task); const findConfigResults = await findConfig({ sys, configPath: flags.config }); if (hasError(findConfigResults.diagnostics)) { @@ -57,10 +52,17 @@ export async function run(init: CliInitOptions) { } const coreCompiler = await loadCoreCompiler(sys); + + if (task === 'version' || flags.version) { + console.log(coreCompiler.version); + return; + } + + startupLogVersion(logger, task, coreCompiler); loadedCompilerLog(sys, logger, flags, coreCompiler); - if (flags.task === 'info') { + if (task === 'info') { taskInfo(coreCompiler, sys, logger); return; } @@ -88,41 +90,57 @@ export async function run(init: CliInitOptions) { await sys.ensureResources({ rootDir: validated.config.rootDir, logger, dependencies }); - switch (flags.task) { - case 'build': - await taskBuild(coreCompiler, validated.config, checkVersion); - break; - - case 'docs': - await taskDocs(coreCompiler, validated.config); - break; + await runTask(coreCompiler, validated.config, task); - case 'generate': - case 'g': - await taskGenerate(coreCompiler, validated.config); - break; - - case 'prerender': - await taskPrerender(coreCompiler, validated.config); - break; - - case 'serve': - await taskServe(validated.config); - break; - - case 'test': - await taskTest(validated.config); - break; - - default: - logger.error(`${logger.emoji('โŒ ')}Invalid stencil command, please see the options below:`); - taskHelp(sys, logger); - sys.exit(1); - } } catch (e) { if (!shouldIgnoreError(e)) { logger.error(`uncaught cli error: ${e}${logger.getLevel() === 'debug' ? e.stack : ''}`); sys.exit(1); } } -} +}; + +export const runTask = async (coreCompiler: CoreCompiler, config: Config, task: TaskCommand) => { + config.flags = config.flags || {}; + config.outputTargets = config.outputTargets || []; + + switch (task) { + case 'build': + await taskBuild(coreCompiler, config); + break; + + case 'docs': + await taskDocs(coreCompiler, config); + break; + + case 'help': + taskHelp(config.sys, config.logger); + break; + + case 'generate': + case 'g': + await taskGenerate(coreCompiler, config); + break; + + case 'prerender': + await taskPrerender(coreCompiler, config); + break; + + case 'serve': + await taskServe(config); + break; + + case 'test': + await taskTest(config); + break; + + case 'version': + console.log(coreCompiler.version); + break; + + default: + config.logger.error(`${config.logger.emoji('โŒ ')}Invalid stencil command, please see the options below:`); + taskHelp(config.sys, config.logger); + config.sys.exit(1); + } +}; diff --git a/src/cli/task-build.ts b/src/cli/task-build.ts index 434fd882947..2f8b3e0b75b 100644 --- a/src/cli/task-build.ts +++ b/src/cli/task-build.ts @@ -1,13 +1,14 @@ -import type { Config, CheckVersion } from '../declarations'; +import type { Config } from '../declarations'; import type { CoreCompiler } from './load-compiler'; import { runPrerenderTask } from './task-prerender'; +import { startCheckVersion, printCheckVersionResults } from './check-version'; import { startupCompilerLog } from './logs'; import { taskWatch } from './task-watch'; -export async function taskBuild(coreCompiler: CoreCompiler, config: Config, checkVersion: CheckVersion) { +export const taskBuild = async (coreCompiler: CoreCompiler, config: Config) => { if (config.flags.watch) { // watch build - await taskWatch(coreCompiler, config, checkVersion); + await taskWatch(coreCompiler, config); return; } @@ -17,7 +18,8 @@ export async function taskBuild(coreCompiler: CoreCompiler, config: Config, chec try { startupCompilerLog(coreCompiler, config); - const checkVersionPromise = checkVersion ? checkVersion(config, coreCompiler.version) : null; + const versionChecker = startCheckVersion(config, coreCompiler.version); + const compiler = await coreCompiler.createCompiler(config); const results = await compiler.build(); @@ -34,10 +36,7 @@ export async function taskBuild(coreCompiler: CoreCompiler, config: Config, chec } } - if (checkVersionPromise != null) { - const checkVersionResults = await checkVersionPromise; - checkVersionResults(); - } + await printCheckVersionResults(versionChecker); } catch (e) { exitCode = 1; config.logger.error(e); @@ -46,4 +45,4 @@ export async function taskBuild(coreCompiler: CoreCompiler, config: Config, chec if (exitCode > 0) { config.sys.exit(exitCode); } -} +}; diff --git a/src/cli/task-watch.ts b/src/cli/task-watch.ts index a2cda726cdc..3c7167613b2 100644 --- a/src/cli/task-watch.ts +++ b/src/cli/task-watch.ts @@ -1,16 +1,18 @@ -import type { Config, CheckVersion, DevServer } from '../declarations'; +import type { Config, DevServer } from '../declarations'; import type { CoreCompiler } from './load-compiler'; import { runPrerenderTask } from './task-prerender'; +import { startCheckVersion, printCheckVersionResults } from './check-version'; import { startupCompilerLog } from './logs'; -export async function taskWatch(coreCompiler: CoreCompiler, config: Config, checkVersion: CheckVersion) { +export const taskWatch = async (coreCompiler: CoreCompiler, config: Config) => { let devServer: DevServer = null; let exitCode = 0; try { startupCompilerLog(coreCompiler, config); - const checkVersionPromise = checkVersion ? checkVersion(config, coreCompiler.version) : null; + const versionChecker = startCheckVersion(config, coreCompiler.version); + const compiler = await coreCompiler.createCompiler(config); const watcher = await compiler.createWatcher(); @@ -24,16 +26,17 @@ export async function taskWatch(coreCompiler: CoreCompiler, config: Config, chec compiler.destroy(); }); - if (checkVersionPromise != null) { - const checkVersionResults = await checkVersionPromise; - checkVersionResults(); - } + const rmVersionCheckerLog = watcher.on('buildFinish', async () => { + // log the version check one time + rmVersionCheckerLog(); + printCheckVersionResults(versionChecker); + }); if (devServer) { const rmDevServerLog = watcher.on('buildFinish', () => { // log the dev server url one time - config.logger.info(`${config.logger.cyan(devServer.browserUrl)}\n`); rmDevServerLog(); + config.logger.info(`${config.logger.cyan(devServer.browserUrl)}\n`); }); } @@ -62,4 +65,4 @@ export async function taskWatch(coreCompiler: CoreCompiler, config: Config, chec if (exitCode > 0) { config.sys.exit(exitCode); } -} +}; diff --git a/src/compiler/cache.ts b/src/compiler/cache.ts index 290a551ce7f..c798870a5ad 100644 --- a/src/compiler/cache.ts +++ b/src/compiler/cache.ts @@ -13,7 +13,7 @@ export class Cache implements d.Cache { } async initCacheDir() { - if (this.config._isTesting) { + if (this.config._isTesting || !this.config.cacheDir) { return; } diff --git a/src/compiler/config/transpile-options.ts b/src/compiler/config/transpile-options.ts index dc44ed84672..0761d0093e6 100644 --- a/src/compiler/config/transpile-options.ts +++ b/src/compiler/config/transpile-options.ts @@ -35,9 +35,9 @@ export const getTranspileConfig = (input: TranspileOptions) => { transpileCtx.sys = input.sys; } else if (!transpileCtx.sys) { if (IS_NODE_ENV) { - transpileCtx.sys = requireFunc('../sys/node/index.js').createNodeSys(); + transpileCtx.sys = requireFunc('../sys/node/index.js').createNodeSysNoWatch(); } else if (IS_DENO_ENV) { - throw new Error(`sys must be provided in options`); + throw new Error(`"sys" must be provided in options`); } else { transpileCtx.sys = createSystem(); } diff --git a/src/compiler/transpile/ts-config.ts b/src/compiler/transpile/ts-config.ts index 9742850524c..4002cb19479 100644 --- a/src/compiler/transpile/ts-config.ts +++ b/src/compiler/transpile/ts-config.ts @@ -9,8 +9,8 @@ export const getTsOptionsToExtend = (config: d.Config) => { module: ts.ModuleKind.ESNext, moduleResolution: ts.ModuleResolutionKind.NodeJs, noEmitOnError: false, - outDir: config.cacheDir, - sourceMap: config.sourceMap, + outDir: config.cacheDir || config.sys.tmpdir(), + sourceMap: !!config.sourceMap, }; return tsOptions; }; diff --git a/src/declarations/stencil-private.ts b/src/declarations/stencil-private.ts index 3868d70ad02..83b9b5619b4 100644 --- a/src/declarations/stencil-private.ts +++ b/src/declarations/stencil-private.ts @@ -2522,12 +2522,3 @@ export interface ValidateTypesResults { dirPaths: string[]; filePaths: string[]; } - -export interface CliInitOptions { - args: string[]; - logger: Logger; - sys: CompilerSystem; - checkVersion?: CheckVersion; -} - -export type CheckVersion = (config: Config, currentVersion: string) => Promise<() => void>; diff --git a/src/declarations/stencil-public-compiler.ts b/src/declarations/stencil-public-compiler.ts index faf94f6ff99..3ca761273c8 100644 --- a/src/declarations/stencil-public-compiler.ts +++ b/src/declarations/stencil-public-compiler.ts @@ -820,6 +820,7 @@ export interface CompilerSystem { accessSync(p: string): boolean; applyGlobalPatch?(fromDir: string): Promise; cacheStorage?: CacheStorage; + checkVersion?: (logger: Logger, currentVersion: string) => Promise<() => void>; copy?(copyTasks: Required[], srcDir: string): Promise; /** * Always returns a boolean if the files were copied or not. Does not throw. @@ -2291,3 +2292,9 @@ export interface DevServer extends BuildEmitEvents { root: string; close(): Promise; } + +export interface CliInitOptions { + args: string[]; + logger: Logger; + sys: CompilerSystem; +} diff --git a/src/sys/node/index.ts b/src/sys/node/index.ts index 4998e385565..dc0f285b705 100644 --- a/src/sys/node/index.ts +++ b/src/sys/node/index.ts @@ -1,5 +1,3 @@ export { createNodeLogger } from './node-logger'; -export { createNodeSysWithWatch } from './node-sys-watch'; -export { createNodeSys } from './node-sys'; -export { checkVersion } from './node-stencil-version-checker'; +export { createNodeSys, createNodeSysNoWatch } from './node-sys'; export { setupNodeProcess } from './node-setup-process'; diff --git a/src/sys/node/node-stencil-version-checker.ts b/src/sys/node/node-stencil-version-checker.ts index 560c6688783..395c27bee2e 100644 --- a/src/sys/node/node-stencil-version-checker.ts +++ b/src/sys/node/node-stencil-version-checker.ts @@ -1,38 +1,38 @@ -import { Config, PackageJsonData } from '../../declarations'; +import { Logger, PackageJsonData } from '../../declarations'; import { isString, noop } from '@utils'; -import semiver from 'semiver'; +import fs from 'graceful-fs'; import path from 'path'; +import semiver from 'semiver'; +import { tmpdir } from 'os'; const REGISTRY_URL = `https://registry.npmjs.org/@stencil/core`; -const CHECK_INTERVAL = 1000 * 60 * 60 * 24 * 7; - -export async function checkVersion(config: Config, currentVersion: string): Promise<() => void> { - if (config.devMode && !config.flags.ci) { - try { - const latestVersion = await getLatestCompilerVersion(config); - if (latestVersion != null) { - return () => { - if (semiver(currentVersion, latestVersion) < 0) { - printUpdateMessage(config, currentVersion, latestVersion); - } else { - console.debug(`${config.logger.cyan('@stencil/core')} version ${config.logger.green(currentVersion)} is the latest version`); - } - }; - } - } catch (e) { - config.logger.debug(`unable to load latest compiler version: ${e}`); +const CHECK_INTERVAL = 1; //1000 * 60 * 60 * 24 * 7; + +export async function checkVersion(logger: Logger, currentVersion: string): Promise<() => void> { + try { + const latestVersion = await getLatestCompilerVersion(logger); + if (latestVersion != null) { + return () => { + if (semiver(currentVersion, latestVersion) < 0) { + printUpdateMessage(logger, currentVersion, latestVersion); + } else { + console.debug(`${logger.cyan('@stencil/core')} version ${logger.green(currentVersion)} is the latest version`); + } + }; } + } catch (e) { + logger.debug(`unable to load latest compiler version: ${e}`); } return noop; } -async function getLatestCompilerVersion(config: Config) { +async function getLatestCompilerVersion(logger: Logger) { try { - const lastCheck = await getLastCheck(config); + const lastCheck = await getLastCheck(); if (lastCheck == null) { // we've never check before, so probably first install, so don't bother // save that we did just do a check though - setLastCheck(config); + setLastCheck(); return null; } @@ -42,7 +42,7 @@ async function getLatestCompilerVersion(config: Config) { } // remember we just did a check - const setPromise = setLastCheck(config); + const setPromise = setLastCheck(); const body = await requestUrl(REGISTRY_URL); const data = JSON.parse(body) as PackageJsonData; @@ -52,7 +52,7 @@ async function getLatestCompilerVersion(config: Config) { return data['dist-tags'].latest; } catch (e) { // quietly catch, could have no network connection which is fine - config.logger.debug(`getLatestCompilerVersion error: ${e}`); + logger.debug(`getLatestCompilerVersion error: ${e}`); } return null; @@ -88,28 +88,33 @@ function requiresCheck(now: number, lastCheck: number, checkInterval: number) { return lastCheck + checkInterval < now; } -async function getLastCheck(config: Config) { - try { - const data = await config.sys.readFile(getLastCheckStoragePath(config)); - if (isString(data)) { - return JSON.parse(data); - } - } catch (e) {} - return null; +function getLastCheck() { + return new Promise(resolve => { + fs.readFile(getLastCheckStoragePath(), 'utf8', (err, data) => { + if (!err && isString(data)) { + try { + resolve(JSON.parse(data)); + } catch (e) {} + } + resolve(null); + }); + }); } -async function setLastCheck(config: Config) { - try { +function setLastCheck() { + return new Promise(resolve => { const now = JSON.stringify(Date.now()); - await config.sys.writeFile(getLastCheckStoragePath(config), now); - } catch (e) {} + fs.writeFile(getLastCheckStoragePath(), now, () => { + resolve(); + }); + }); } -function getLastCheckStoragePath(config: Config) { - return path.join(config.sys.tmpdir(), 'stencil_last_version_check.json'); +function getLastCheckStoragePath() { + return path.join(tmpdir(), 'stencil_last_version_node.json'); } -function printUpdateMessage(config: Config, currentVersion: string, latestVersion: string) { +function printUpdateMessage(logger: Logger, currentVersion: string, latestVersion: string) { const installMessage = `npm install @stencil/core`; const msg = [`Update available: ${currentVersion} ${ARROW} ${latestVersion}`, `To get the latest, please run:`, installMessage]; @@ -146,9 +151,9 @@ function printUpdateMessage(config: Config, currentVersion: string, latestVersio let output = `\n${INDENT}${o.join(`\n${INDENT}`)}\n`; - output = output.replace(currentVersion, config.logger.red(currentVersion)); - output = output.replace(latestVersion, config.logger.green(latestVersion)); - output = output.replace(installMessage, config.logger.cyan(installMessage)); + output = output.replace(currentVersion, logger.red(currentVersion)); + output = output.replace(latestVersion, logger.green(latestVersion)); + output = output.replace(installMessage, logger.cyan(installMessage)); console.log(output); } diff --git a/src/sys/node/node-sys-watch.ts b/src/sys/node/node-sys-watch.ts deleted file mode 100644 index 53b49c63b02..00000000000 --- a/src/sys/node/node-sys-watch.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { CompilerSystem, Logger } from '../../declarations'; -import { buildEvents } from '../../compiler/events'; -import { createNodeSys } from './node-sys'; -import { normalizePath } from '@utils'; -import type TypeScript from 'typescript'; - -export function createNodeSysWithWatch(c: { process: any; logger: Logger }): CompilerSystem { - const ts = require('typescript') as typeof TypeScript; - const sys = createNodeSys(c); - const tsWatchFile = ts.sys.watchFile; - const tsWatchDirectory = ts.sys.watchDirectory; - - sys.watchTimeout = 80; - sys.events = buildEvents(); - - sys.watchDirectory = (p, callback, recursive) => { - const tsFileWatcher = tsWatchDirectory( - p, - fileName => { - fileName = normalizePath(fileName); - callback(fileName, null); - }, - recursive, - ); - - const close = () => { - tsFileWatcher.close(); - }; - - sys.addDestory(close); - - return { - close() { - sys.removeDestory(close); - tsFileWatcher.close(); - }, - }; - }; - - sys.watchFile = (p, callback) => { - const tsFileWatcher = tsWatchFile(p, (fileName, tsEventKind) => { - fileName = normalizePath(fileName); - if (tsEventKind === ts.FileWatcherEventKind.Created) { - callback(fileName, 'fileAdd'); - sys.events.emit('fileAdd', fileName); - } else if (tsEventKind === ts.FileWatcherEventKind.Changed) { - callback(fileName, 'fileUpdate'); - sys.events.emit('fileUpdate', fileName); - } else if (tsEventKind === ts.FileWatcherEventKind.Deleted) { - callback(fileName, 'fileDelete'); - sys.events.emit('fileDelete', fileName); - } - }); - - const close = () => { - tsFileWatcher.close(); - }; - sys.addDestory(close); - - return { - close() { - sys.removeDestory(close); - tsFileWatcher.close(); - }, - }; - }; - - return sys; -} diff --git a/src/sys/node/node-sys.ts b/src/sys/node/node-sys.ts index 46e803fc64c..6228dc16bd6 100644 --- a/src/sys/node/node-sys.ts +++ b/src/sys/node/node-sys.ts @@ -7,6 +7,8 @@ import type { Diagnostic, } from '../../declarations'; import { asyncGlob, nodeCopyTasks } from './node-copy-tasks'; +import { buildEvents } from '../../compiler/events'; +import { checkVersion } from './node-stencil-version-checker'; import { cpus, freemem, platform, release, tmpdir, totalmem } from 'os'; import { createHash } from 'crypto'; import exit from 'exit'; @@ -16,8 +18,73 @@ import { NodeResolveModule } from './node-resolve-module'; import { NodeWorkerController } from './node-worker-controller'; import { normalizePath, requireFunc, buildError } from '@utils'; import path from 'path'; +import type TypeScript from 'typescript'; export function createNodeSys(c: { process?: any } = {}) { + const sys = createNodeSysNoWatch(c); + const ts = require('typescript') as typeof TypeScript; + const tsWatchFile = ts.sys.watchFile; + const tsWatchDirectory = ts.sys.watchDirectory; + + sys.watchTimeout = 80; + sys.events = buildEvents(); + + sys.watchDirectory = (p, callback, recursive) => { + const tsFileWatcher = tsWatchDirectory( + p, + fileName => { + fileName = normalizePath(fileName); + callback(fileName, null); + }, + recursive, + ); + + const close = () => { + tsFileWatcher.close(); + }; + + sys.addDestory(close); + + return { + close() { + sys.removeDestory(close); + tsFileWatcher.close(); + }, + }; + }; + + sys.watchFile = (p, callback) => { + const tsFileWatcher = tsWatchFile(p, (fileName, tsEventKind) => { + fileName = normalizePath(fileName); + if (tsEventKind === ts.FileWatcherEventKind.Created) { + callback(fileName, 'fileAdd'); + sys.events.emit('fileAdd', fileName); + } else if (tsEventKind === ts.FileWatcherEventKind.Changed) { + callback(fileName, 'fileUpdate'); + sys.events.emit('fileUpdate', fileName); + } else if (tsEventKind === ts.FileWatcherEventKind.Deleted) { + callback(fileName, 'fileDelete'); + sys.events.emit('fileDelete', fileName); + } + }); + + const close = () => { + tsFileWatcher.close(); + }; + sys.addDestory(close); + + return { + close() { + sys.removeDestory(close); + tsFileWatcher.close(); + }, + }; + }; + + return sys; +} + +export function createNodeSysNoWatch(c: { process?: any } = {}) { const prcs: NodeJS.Process = c.process || global.process; const destroys = new Set<() => Promise | void>(); const onInterruptsCallbacks: (() => void)[] = []; @@ -51,6 +118,7 @@ export function createNodeSys(c: { process?: any } = {}) { removeDestory(cb) { destroys.delete(cb); }, + checkVersion, copyFile(src, dst) { return new Promise(resolve => { fs.copyFile(src, dst, err => { @@ -179,9 +247,7 @@ export function createNodeSys(c: { process?: any } = {}) { } return results; }, - nextTick(cb) { - prcs.nextTick(cb); - }, + nextTick: prcs.nextTick, normalizePath, onProcessInterrupt(cb) { onInterruptsCallbacks.push(cb); diff --git a/src/sys/node/public.ts b/src/sys/node/public.ts new file mode 100644 index 00000000000..18d9c6d6fb7 --- /dev/null +++ b/src/sys/node/public.ts @@ -0,0 +1,17 @@ +import { CompilerSystem, Logger } from '@stencil/core/internal'; + +/** + * Creates a "logger", based off of NodeJS APIs, that will be used by the compiler and dev-server. + * The NodeJS "process" object must be provided as a property in the first argument's object. + */ +export declare function createNodeLogger(c: { process: any }): Logger; + +/** + * Creates the "system", based off of NodeJS APIs, used by the compiler. This includes any and + * all file system reads and writes using NodeJS. The compiler itself is unaware of Node's + * `fs` module. Other system APIs include any use of `crypto` to hash content. The NodeJS + * "process" object must be provided as a property in the first argument's object. + */ +export declare function createNodeSys(c: { process: any }): CompilerSystem; + +export { CompilerSystem, Logger }; diff --git a/tsconfig.json b/tsconfig.json index e7d55963ab9..a02100a980f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -45,6 +45,7 @@ "src/app-data/index.ts", "src/app-globals/index.ts", "src/cli/index.ts", + "src/cli/public.ts", "src/client/index.ts", "src/client/client-patch.ts", "src/client/polyfills/css-shim/index.ts", @@ -65,6 +66,7 @@ "src/sys/deno/worker.ts", "src/sys/node/index.ts", "src/sys/node/worker.ts", + "src/sys/node/public.ts", "src/screenshot/index.ts", "src/screenshot/pixel-match.ts", "src/testing/index.ts",