mirror of
https://github.com/wassname/talk.git
synced 2026-06-28 19:17:59 +08:00
299 lines
7.9 KiB
JavaScript
Executable File
299 lines
7.9 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
// Interface heavily inspired by the yarn package manager:
|
|
// https://yarnpkg.com/
|
|
|
|
const program = require('./commander');
|
|
|
|
// Make things colorful!
|
|
require('colors');
|
|
const emoji = require('node-emoji');
|
|
|
|
const dir = process.cwd();
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const spawn = require('cross-spawn');
|
|
const semver = require('semver');
|
|
const resolve = require('resolve');
|
|
const {plugins, itteratePlugins, isInternal} = require('../plugins');
|
|
|
|
function existsInNodeModules(name) {
|
|
try {
|
|
resolve.sync(name, {basedir: dir});
|
|
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function versionMatch(name, version) {
|
|
try {
|
|
let matched = false;
|
|
|
|
resolve.sync(name, {
|
|
basedir: dir,
|
|
packageFilter: (pkg) => {
|
|
if (pkg && pkg.version && semver.satisfies(pkg.version, version)) {
|
|
matched = true;
|
|
}
|
|
|
|
return pkg;
|
|
}
|
|
});
|
|
|
|
return matched;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const EXTERNAL = /^\w[a-z\-0-9\.]+$/; // Match "react", "path", "fs", "lodash.random", etc.
|
|
|
|
function reconcilePackages({quiet = false, upgradeRemote = false}) {
|
|
const fetchable = [];
|
|
const local = [];
|
|
const upgradable = [];
|
|
|
|
if (!quiet) {
|
|
console.log();
|
|
console.log(' +local (l) packages in your project');
|
|
console.log(' +external (e) packages are external');
|
|
console.log(' +outofdate (oe) packages are external but are out of date');
|
|
console.log(' +missing (m) packages are not found');
|
|
console.log();
|
|
}
|
|
|
|
for (let i in plugins) {
|
|
let section = itteratePlugins(plugins[i]);
|
|
|
|
for (let j in section) {
|
|
let {name, version} = section[j];
|
|
|
|
let namespaced = name.charAt(0) === '@';
|
|
let dep = name.split('/')
|
|
.slice(0, namespaced ? 2 : 1)
|
|
.join('/');
|
|
|
|
// Ignore relative modules, which aren't installed by NPM
|
|
if (!dep.match(EXTERNAL) && !namespaced) {
|
|
return;
|
|
}
|
|
|
|
if (isInternal(dep)) {
|
|
if (!quiet) {
|
|
console.log(` l ${name}`);
|
|
}
|
|
|
|
local.push({name, version});
|
|
continue;
|
|
}
|
|
|
|
if (!existsInNodeModules(dep)) {
|
|
if (!quiet) {
|
|
console.log(` m ${name}`);
|
|
}
|
|
fetchable.push({name, version});
|
|
} else if (!versionMatch(dep, version)) {
|
|
|
|
// A plugin was found, yet the current version does not match the
|
|
// current version installed. We should warn if upgradeRemote is
|
|
// not enabled that it is currently not supported.
|
|
if (!upgradeRemote) {
|
|
if (!quiet) {
|
|
console.warn(` oe ${name} (package upgrade may be required)`.bgRed);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
console.log(` oe ${name} (package upgrade may be required)`);
|
|
|
|
upgradable.push({name, version});
|
|
} else {
|
|
if (!quiet) {
|
|
console.log(` e ${name}`);
|
|
}
|
|
|
|
if (upgradeRemote) {
|
|
upgradable.push({name, version});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!quiet) {
|
|
console.log();
|
|
}
|
|
|
|
return {local, fetchable, upgradable};
|
|
}
|
|
|
|
async function reconcileRemotePlugins({skipLocal, dryRun, upgradeRemote}) {
|
|
console.log(`\n[${skipLocal ? '1/2' : '2/3'}] ${emoji.get('mag')} Reconciling plugins...`.yellow);
|
|
const {fetchable, upgradable} = reconcilePackages({upgradeRemote});
|
|
|
|
console.log(`[${skipLocal ? '2/2' : '3/3'}] ${emoji.get('truck')} Fetching plugins...\n`.yellow);
|
|
|
|
if (fetchable.length > 0) {
|
|
|
|
console.log(`$ yarn add --ignore-scripts ${fetchable.map(({name, version}) => `${name}@${version}`.cyan)}`);
|
|
|
|
if (!dryRun) {
|
|
|
|
let args = [
|
|
'add',
|
|
'--ignore-scripts',
|
|
...fetchable.map(({name, version}) => `${name}@${version}`)
|
|
];
|
|
|
|
let output = spawn.sync('yarn', args, {
|
|
stdio: ['ignore', 'pipe', 'inherit']
|
|
});
|
|
|
|
if (output.status) {
|
|
throw new Error('Could not install external plugins, errors occured during install');
|
|
}
|
|
|
|
console.log(output.stdout.toString());
|
|
}
|
|
}
|
|
|
|
if (upgradable.length > 0) {
|
|
console.log(`$ yarn upgrade ${upgradable.map(({name, version}) => `${name}@${version}`.cyan)}`);
|
|
|
|
if (!dryRun) {
|
|
|
|
let args = [
|
|
'upgrade',
|
|
...upgradable.map(({name, version}) => `${name}@${version}`)
|
|
];
|
|
|
|
let output = spawn.sync('yarn', args, {
|
|
stdio: ['ignore', 'pipe', 'inherit']
|
|
});
|
|
|
|
if (output.status) {
|
|
throw new Error('Could not install external plugins, errors occured during install');
|
|
}
|
|
|
|
console.log(output.stdout.toString());
|
|
}
|
|
}
|
|
|
|
return {upgradable, fetchable};
|
|
}
|
|
|
|
async function reconcileLocalPlugins({skipRemote, dryRun}) {
|
|
console.log(`\n[${skipRemote ? '1/1' : '1/3'}] ${emoji.get('pick')} Installing local plugin dependencies...\n`.yellow);
|
|
const {local} = reconcilePackages({quiet: true});
|
|
|
|
for (let i in local) {
|
|
let {name} = local[i];
|
|
|
|
if (!fs.existsSync(path.join(dir, 'plugins', name, 'package.json'))) {
|
|
continue;
|
|
}
|
|
|
|
let wd = path.join(dir, 'plugins', name);
|
|
|
|
console.log(`$ cd ${wd.cyan} && yarn`);
|
|
|
|
if (!dryRun) {
|
|
let args = [];
|
|
|
|
let output = spawn.sync('yarn', args, {
|
|
stdio: ['ignore', 'pipe', 'inherit'],
|
|
cwd: wd
|
|
});
|
|
|
|
if (output.status) {
|
|
throw new Error('Could not install local plugin dependencies, errors occured during install');
|
|
}
|
|
|
|
console.log(output.stdout.toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
// This traverses the local plugins and installs any dependencies listed there,
|
|
// this only is really needed for plugins that are installed via docker because
|
|
// core plugins will have their dependencies already included in core.
|
|
async function reconcilePluginDeps({skipLocal, skipRemote, dryRun, upgradeRemote}) {
|
|
let startTime = new Date();
|
|
|
|
// We don't need to do anything if we skip everything....
|
|
if (skipLocal && skipRemote) {
|
|
return;
|
|
}
|
|
|
|
// Traverse local plugins and install dependencies if enabled.
|
|
if (!skipLocal) {
|
|
await reconcileLocalPlugins({skipRemote, dryRun});
|
|
}
|
|
|
|
// Locate any external plugins and install them.
|
|
if (!skipRemote) {
|
|
let results = [];
|
|
try {
|
|
results = await reconcileRemotePlugins({skipLocal, skipRemote, dryRun, upgradeRemote});
|
|
} catch (e) {
|
|
throw e;
|
|
}
|
|
|
|
let status;
|
|
if (dryRun) {
|
|
status = '[dry-run] success'.green;
|
|
} else {
|
|
status = 'success'.green;
|
|
}
|
|
|
|
let message;
|
|
if (results.upgradable.length === 0 && results.fetchable.length === 0) {
|
|
message = 'Already up-to-date.';
|
|
} else if (results.upgradable.length === 0) {
|
|
message = `Fetched ${results.fetchable.length} new plugins.`;
|
|
} else if (results.fetchable.length === 0) {
|
|
message = `Upgraded ${results.upgradable.length} new plugins.`;
|
|
} else {
|
|
message = `Fetched ${results.fetchable.length} new plugins, upgraded ${results.upgradable.length} plugins.`;
|
|
}
|
|
|
|
console.log(`\n${status} ${message}`);
|
|
}
|
|
|
|
let endTime = new Date();
|
|
|
|
let totalTime = ((endTime.getTime() - startTime.getTime()) / 1000).toFixed(2);
|
|
console.log(`✨ Done in ${totalTime}s.`);
|
|
}
|
|
|
|
//==============================================================================
|
|
// Setting up the program command line arguments.
|
|
//==============================================================================
|
|
|
|
program
|
|
.command('list')
|
|
.description('')
|
|
.action(reconcilePackages);
|
|
|
|
program
|
|
.command('reconcile')
|
|
.description('reconciles local plugin dependencies and downloads external plugins')
|
|
.option('-u, --upgrade-remote', 'upgrades remote dependencies')
|
|
.option('-d, --dry-run', 'does not actually change anything on the filesystem acts only as a simulation')
|
|
.option('--skip-local', 'skips the local dependancy reconciliation')
|
|
.option('--skip-remote', 'skips the remote plugin reconciliation')
|
|
.action(reconcilePluginDeps);
|
|
|
|
program.parse(process.argv);
|
|
|
|
// If there is no command listed, output help.
|
|
if (!process.argv.slice(2).length) {
|
|
program.outputHelp();
|
|
}
|