From c97877a8c0696c5160c052fb651f117adceb1b55 Mon Sep 17 00:00:00 2001 From: Jeff Dickey <216188+jdxcode@users.noreply.github.com> Date: Tue, 6 Feb 2018 23:07:13 -0800 Subject: [PATCH] fix: async --- src/config.ts | 82 ++++++++++++++++++++++++++++++--------------------- src/plugin.ts | 56 ++++++++++++++++------------------- src/util.ts | 16 ++++++++++ 3 files changed, 91 insertions(+), 63 deletions(-) diff --git a/src/config.ts b/src/config.ts index a19b93bc..5bb36da6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -10,7 +10,7 @@ import {PJSON} from './pjson' import * as Plugin from './plugin' import {Topic} from './topic' import {tsPath} from './ts_node' -import {compact, flatMap, loadJSONSync, uniq} from './util' +import {compact, flatMap, loadJSON, uniq} from './util' export type PlatformTypes = 'darwin' | 'linux' | 'win32' | 'aix' | 'freebsd' | 'openbsd' | 'sunos' export type ArchTypes = 'arm' | 'arm64' | 'mips' | 'mipsel' | 'ppc' | 'ppc64' | 's390' | 's390x' | 'x32' | 'x64' | 'x86' @@ -113,30 +113,32 @@ const _pjson = require('../package.json') export class Config implements IConfig { _base = `${_pjson.name}@${_pjson.version}` - name: string - version: string - root: string - arch: ArchTypes - bin: string - cacheDir: string - configDir: string - dataDir: string - dirname: string - errlog: string - home: string - platform: PlatformTypes - shell: string - windows: boolean - userAgent: string + name!: string + version!: string + root!: string + arch!: ArchTypes + bin!: string + cacheDir!: string + configDir!: string + dataDir!: string + dirname!: string + errlog!: string + home!: string + platform!: PlatformTypes + shell!: string + windows!: boolean + userAgent!: string debug: number = 0 - npmRegistry: string - pjson: PJSON.CLI + npmRegistry!: string + pjson!: PJSON.CLI userPJSON?: PJSON.User plugins: Plugin.IPlugin[] = [] protected warned = false - constructor(opts: Options) { - this.loadPlugins(opts.root, 'core', [{root: opts.root}], {must: true}) + constructor(public options: Options) {} + + async load() { + await this.loadPlugins(this.options.root, 'core', [{root: this.options.root}], {must: true}) const plugin = this.plugins[0] this.root = plugin.root this.pjson = plugin.pjson @@ -160,31 +162,42 @@ export class Config implements IConfig { this.npmRegistry = this.scopedEnvVar('NPM_REGISTRY') || this.pjson.anycli.npmRegistry || 'https://registry.yarnpkg.com' + debug('config done') + await Promise.all([ + this.loadCorePlugins(), + this.loadUserPlugins(), + this.loadDevPlugins(), + ]) + } + + async loadCorePlugins() { if (this.pjson.anycli.plugins) { - this.loadPlugins(this.root, 'core', this.pjson.anycli.plugins) + await this.loadPlugins(this.root, 'core', this.pjson.anycli.plugins) } + } - if (opts.devPlugins !== false) { + async loadDevPlugins() { + if (this.options.devPlugins !== false) { try { const devPlugins = this.pjson.anycli.devPlugins - if (devPlugins) this.loadPlugins(this.root, 'dev', devPlugins) + if (devPlugins) await this.loadPlugins(this.root, 'dev', devPlugins) } catch (err) { process.emitWarning(err) } } + } - if (opts.userPlugins !== false) { + async loadUserPlugins() { + if (this.options.userPlugins !== false) { try { const userPJSONPath = path.join(this.dataDir, 'package.json') - const pjson = this.userPJSON = loadJSONSync(userPJSONPath) + const pjson = this.userPJSON = await loadJSON(userPJSONPath) if (!pjson.anycli) pjson.anycli = {schema: 1} - this.loadPlugins(userPJSONPath, 'user', pjson.anycli.plugins) + await this.loadPlugins(userPJSONPath, 'user', pjson.anycli.plugins) } catch (err) { if (err.code !== 'ENOENT') process.emitWarning(err) } } - - debug('config done') } async runHook(event: K, opts: T[K]) { @@ -324,11 +337,11 @@ export class Config implements IConfig { } catch {} return 0 } - protected loadPlugins(root: string, type: string, plugins: (string | {root?: string, name?: string, tag?: string})[], options: {must?: boolean} = {}) { + protected async loadPlugins(root: string, type: string, plugins: (string | {root?: string, name?: string, tag?: string})[], options: {must?: boolean} = {}) { if (!plugins.length) return if (!plugins || !plugins.length) return debug('loading plugins', plugins) - for (let plugin of plugins || []) { + await Promise.all((plugins || []).map(async plugin => { try { let opts: Options = {type, root} if (typeof plugin === 'string') { @@ -339,12 +352,13 @@ export class Config implements IConfig { opts.root = plugin.root || opts.root } let instance = new Plugin.Plugin(opts) + await instance.load() this.plugins.push(instance) } catch (err) { if (options.must) throw err this.warn(err, 'loadPlugins') } - } + })) } protected warn(err: any, scope?: string) { @@ -355,10 +369,12 @@ export class Config implements IConfig { } } export type LoadOptions = Options | string | IConfig | undefined -export function load(opts: LoadOptions = (module.parent && module.parent && module.parent.parent && module.parent.parent.filename) || __dirname) { +export async function load(opts: LoadOptions = (module.parent && module.parent && module.parent.parent && module.parent.parent.filename) || __dirname) { if (typeof opts === 'string') opts = {root: opts} if (isConfig(opts)) return opts - return new Config(opts) + let config = new Config(opts) + await config.load() + return config } function isConfig(o: any): o is IConfig { diff --git a/src/plugin.ts b/src/plugin.ts index cd132894..79d1b881 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -1,5 +1,4 @@ import {error} from '@anycli/errors' -import * as fs from 'fs' import * as Globby from 'globby' import * as path from 'path' import {inspect} from 'util' @@ -10,7 +9,7 @@ import {Manifest} from './manifest' import {PJSON} from './pjson' import {Topic} from './topic' import {tsPath} from './ts_node' -import {compact, flatMap, loadJSONSync, mapValues} from './util' +import {compact, exists, flatMap, loadJSON, mapValues} from './util' export interface Options { root: string @@ -75,32 +74,29 @@ const _pjson = require('../package.json') export class Plugin implements IPlugin { // static loadedPlugins: {[name: string]: Plugin} = {} _base = `${_pjson.name}@${_pjson.version}` - name: string - version: string - pjson: PJSON.Plugin - type: string - root: string + name!: string + version!: string + pjson!: PJSON.Plugin + type!: string + root!: string tag?: string - manifest: Manifest - commands: Command.Plugin[] - hooks: {[k: string]: string[]} + manifest!: Manifest + commands!: Command.Plugin[] + hooks!: {[k: string]: string[]} valid = false alreadyLoaded = false protected warned = false - constructor(opts: Options) { - this.type = opts.type || 'core' - this.tag = opts.tag - const root = findRoot(opts.name, opts.root) - if (!root) throw new Error(`could not find package.json with ${inspect(opts)}`) - // if (Plugin.loadedPlugins[root]) { - // Plugin.loadedPlugins[root].alreadyLoaded = true - // return Plugin.loadedPlugins[root] - // } - // Plugin.loadedPlugins[root] = this + constructor(public options: Options) {} + + async load() { + this.type = this.options.type || 'core' + this.tag = this.options.tag + const root = await findRoot(this.options.name, this.options.root) + if (!root) throw new Error(`could not find package.json with ${inspect(this.options)}`) this.root = root debug('reading %s plugin %s', this.type, root) - this.pjson = loadJSONSync(path.join(root, 'package.json')) as any + this.pjson = await loadJSON(path.join(root, 'package.json')) as any this.name = this.pjson.name this.version = this.pjson.version if (this.pjson.anycli) { @@ -111,7 +107,7 @@ export class Plugin implements IPlugin { this.hooks = mapValues(this.pjson.anycli.hooks || {}, i => Array.isArray(i) ? i : [i]) - this.manifest = this._manifest(!!opts.ignoreManifest) + this.manifest = await this._manifest(!!this.options.ignoreManifest) this.commands = Object.entries(this.manifest.commands) .map(([id, c]) => ({...c, load: () => this.findCommand(id, {must: true})})) } @@ -170,11 +166,11 @@ export class Plugin implements IPlugin { return cmd } - protected _manifest(ignoreManifest: boolean): Manifest { - const readManifest = () => { + protected async _manifest(ignoreManifest: boolean): Promise { + const readManifest = async () => { try { const p = path.join(this.root, '.anycli.manifest.json') - const manifest: Manifest = loadJSONSync(p) + const manifest: Manifest = await loadJSON(p) if (!process.env.ANYCLI_NEXT_VERSION && manifest.version !== this.version) { process.emitWarning(`Mismatched version in ${this.name} plugin manifest. Expected: ${this.version} Received: ${manifest.version}`) } else { @@ -186,7 +182,7 @@ export class Plugin implements IPlugin { } } if (!ignoreManifest) { - let manifest = readManifest() + let manifest = await readManifest() if (manifest) return manifest } @@ -231,7 +227,7 @@ function topicsToArray(input: any, base?: string): Topic[] { * * This is needed because of the deduping npm does */ -function findRoot(name: string | undefined, root: string) { +async function findRoot(name: string | undefined, root: string) { // essentially just "cd .." function* up(from: string) { while (path.dirname(from) !== from) { @@ -244,14 +240,14 @@ function findRoot(name: string | undefined, root: string) { let cur if (name) { cur = path.join(next, 'node_modules', name, 'package.json') - if (fs.existsSync(cur)) return path.dirname(cur) + if (await exists(cur)) return path.dirname(cur) try { - let pkg = loadJSONSync(path.join(next, 'package.json')) + let pkg = await loadJSON(path.join(next, 'package.json')) if (pkg.name === name) return next } catch {} } else { cur = path.join(next, 'package.json') - if (fs.existsSync(cur)) return path.dirname(cur) + if (await exists(cur)) return path.dirname(cur) } } } diff --git a/src/util.ts b/src/util.ts index 45016417..2ccbef61 100644 --- a/src/util.ts +++ b/src/util.ts @@ -20,6 +20,22 @@ export function loadJSONSync(path: string): any { return JSON.parse(fs.readFileSync(path, 'utf8')) } +export function exists(path: string): Promise { + return new Promise(resolve => fs.exists(path, resolve)) +} + +export function loadJSON(path: string): Promise { + // let loadJSON + // try { loadJSON = require('load-json-file') } catch {} + // if (loadJSON) return loadJSON.sync(path) + return new Promise((resolve, reject) => { + fs.readFile(path, 'utf8', (err, d) => { + if (err) reject(err) + else resolve(JSON.parse(d)) + }) + }) +} + export function compact(a: (T | undefined)[]): T[] { return a.filter((a): a is T => !!a) }