"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const Config = require("@oclif/config"); const errors_1 = require("@oclif/errors"); const cli_ux_1 = require("cli-ux"); const fs = require("fs"); const load_json_file_1 = require("load-json-file"); const path = require("path"); const semver = require("semver"); const util_1 = require("./util"); const yarn_1 = require("./yarn"); const initPJSON = { private: true, oclif: { schema: 1, plugins: [] }, dependencies: {} }; class Plugins { constructor(config) { this.config = config; this.verbose = false; this.yarn = new yarn_1.default({ config }); this.debug = require('debug')('@oclif/plugins'); } async pjson() { try { const pjson = await load_json_file_1.default(this.pjsonPath); return Object.assign({}, initPJSON, { oclif: Object.assign({}, initPJSON.oclif, pjson.oclif), dependencies: {} }, pjson); } catch (err) { this.debug(err); if (err.code !== 'ENOENT') process.emitWarning(err); return initPJSON; } } async list() { const pjson = await this.pjson(); return this.normalizePlugins(pjson.oclif.plugins); } async install(name, tag = 'latest') { try { const yarnOpts = { cwd: this.config.dataDir, verbose: this.verbose }; await this.createPJSON(); let plugin; if (name.includes(':')) { // url const url = name; await this.yarn.exec(['add', url], yarnOpts); name = Object.entries((await this.pjson()).dependencies || {}).find(([, u]) => u === url)[0]; plugin = await Config.load({ devPlugins: false, userPlugins: false, root: path.join(this.config.dataDir, 'node_modules', name), name }); await this.refresh(plugin.root); if (!plugin.valid && !this.config.plugins.find(p => p.name === '@oclif/plugin-legacy')) { throw new Error('plugin is invalid'); } await this.add({ name, url, type: 'user' }); } else { // npm const range = semver.validRange(tag); const unfriendly = this.unfriendlyName(name); if (unfriendly && await this.npmHasPackage(unfriendly)) { name = unfriendly; } await this.yarn.exec(['add', `${name}@${tag}`], yarnOpts); plugin = await Config.load({ devPlugins: false, userPlugins: false, root: path.join(this.config.dataDir, 'node_modules', name), name }); if (!plugin.valid && !this.config.plugins.find(p => p.name === '@oclif/plugin-legacy')) { throw new Error('plugin is invalid'); } await this.refresh(plugin.root); await this.add({ name, tag: range || tag, type: 'user' }); } return plugin; } catch (err) { await this.uninstall(name).catch(err => this.debug(err)); throw err; } } // if yarn.lock exists, fetch locked dependencies async refresh(root, { prod = true } = {}) { if (fs.existsSync(path.join(root, 'yarn.lock'))) { // use yarn.lock to fetch dependencies await this.yarn.exec(prod ? ['--prod'] : [], { cwd: root, verbose: this.verbose }); } } async link(p) { const c = await Config.load(path.resolve(p)); cli_ux_1.default.action.start(`${this.config.name}: linking plugin ${c.name}`); if (!c.valid && !this.config.plugins.find(p => p.name === '@oclif/plugin-legacy')) { throw new errors_1.CLIError('plugin is not a valid oclif plugin'); } await this.refresh(c.root, { prod: false }); await this.add({ type: 'link', name: c.name, root: c.root }); } async add(plugin) { const pjson = await this.pjson(); pjson.oclif.plugins = util_1.uniq([...pjson.oclif.plugins || [], plugin]); await this.savePJSON(pjson); } async remove(name) { const pjson = await this.pjson(); if (pjson.dependencies) delete pjson.dependencies[name]; pjson.oclif.plugins = this.normalizePlugins(pjson.oclif.plugins) .filter(p => p.name !== name); await this.savePJSON(pjson); } async uninstall(name) { try { const pjson = await this.pjson(); if ((pjson.oclif.plugins || []).find(p => typeof p === 'object' && p.type === 'user' && p.name === name)) { await this.yarn.exec(['remove', name], { cwd: this.config.dataDir, verbose: this.verbose }); } } catch (err) { cli_ux_1.default.warn(err); } finally { await this.remove(name); } } async update() { let plugins = (await this.list()).filter((p) => p.type === 'user'); if (plugins.length === 0) return; cli_ux_1.default.action.start(`${this.config.name}: Updating plugins`); // migrate deprecated plugins const aliases = this.config.pjson.oclif.aliases || {}; for (let [name, to] of Object.entries(aliases)) { const plugin = plugins.find(p => p.name === name); if (!plugin) continue; if (to) await this.install(to); await this.uninstall(name); plugins = plugins.filter(p => p.name !== name); } if (plugins.find(p => !!p.url)) { await this.yarn.exec(['upgrade'], { cwd: this.config.dataDir, verbose: this.verbose }); } const npmPlugins = plugins.filter(p => !p.url); if (npmPlugins.length) { await this.yarn.exec(['add', ...npmPlugins.map(p => `${p.name}@${p.tag}`)], { cwd: this.config.dataDir, verbose: this.verbose }); } for (let p of plugins) { await this.refresh(path.join(this.config.dataDir, 'node_modules', p.name)); } cli_ux_1.default.action.stop(); } async hasPlugin(name) { const list = await this.list(); return list.find(p => { if (this.friendlyName(p.name) === this.friendlyName(name)) return true; if (p.type === 'link') { if (path.resolve(p.root) === path.resolve(name)) return true; } return false; }); } async yarnNodeVersion() { try { let f = await load_json_file_1.default(path.join(this.config.dataDir, 'node_modules', '.yarn-integrity')); return f.nodeVersion; } catch (err) { if (err.code !== 'ENOENT') cli_ux_1.default.warn(err); } } unfriendlyName(name) { if (name.includes('@')) return; const scope = this.config.pjson.oclif.scope; if (!scope) return; return `@${scope}/plugin-${name}`; } async maybeUnfriendlyName(name) { const unfriendly = this.unfriendlyName(name); if (unfriendly && await this.npmHasPackage(unfriendly)) { return unfriendly; } return name; } friendlyName(name) { const scope = this.config.pjson.oclif.scope; if (!scope) return name; const match = name.match(`@${scope}/plugin-(.+)`); if (!match) return name; return match[1]; } // private async loadPlugin(plugin: Config.PJSON.PluginTypes) { // return Config.load({...plugin as any, root: this.config.dataDir}) // } async createPJSON() { if (!fs.existsSync(this.pjsonPath)) { await this.savePJSON(initPJSON); } } get pjsonPath() { return path.join(this.config.dataDir, 'package.json'); } get npmRegistry() { return this.config.npmRegistry || 'https://registry.npmjs.org'; } async npmHasPackage(name) { try { const http = require('http-call').HTTP; let url = `${this.npmRegistry}/-/package/${name.replace('/', '%2f')}/dist-tags`; await http.get(url); return true; } catch (err) { this.debug(err); return false; } } async savePJSON(pjson) { pjson.oclif.plugins = this.normalizePlugins(pjson.oclif.plugins); const fs = require('fs-extra'); await fs.outputJSON(this.pjsonPath, pjson, { spaces: 2 }); } normalizePlugins(input) { let plugins = (input || []).map(p => { if (typeof p === 'string') { return { name: p, type: 'user', tag: 'latest' }; } else return p; }); plugins = util_1.uniqWith(plugins, (a, b) => a.name === b.name || (a.type === 'link' && b.type === 'link' && a.root === b.root)); return plugins; } } exports.default = Plugins;