UNPKG

4.75 kBPlain TextView Raw
1#!/usr/bin/env node
2
3/* Auxiliary script that helps to install and upgrade Topcoder libraries
4 * published to NPM. */
5/* eslint-disable import/no-extraneous-dependencies, no-console */
6
7const commandLineArgs = require('command-line-args');
8const commandLineUsage = require('command-line-usage');
9const fs = require('fs');
10const path = require('path');
11const { spawnSync } = require('child_process');
12
13/* Definition of command-line arguments. */
14const OPTIONS = [{
15 name: 'help',
16 alias: 'h',
17 description: 'Shows usage instructions',
18 type: Boolean,
19}, {
20 name: 'just-fix-deps',
21 description: 'Skips installation of libraries, just fixes dependencies',
22 type: Boolean,
23}, {
24 name: 'libraries',
25 defaultOption: true,
26 multiple: true,
27 type: String,
28}];
29
30/* Definition of help information. */
31const HELP = [{
32 content: 'Usage: {cyanBright topcoder-lib-setup [OPTION]... LIBRARY...}',
33}, {
34 header: 'Description',
35 content:
36 `Installs or upgrades Topcoder's ReactJS libraries released to NPM.
37 {cyanBright [OPTION]...} is the list of options (see below);
38 {cyanBright LIBRARY...} is the list of libraries to install/upgrade`,
39}, {
40 header: 'Options',
41 optionList: OPTIONS.filter(x => !x.defaultOption),
42}];
43
44/**
45 * Generates a string containing name and version of the package to be
46 * installed.
47 * @param {Array} entry Array with package name as the first element, and
48 * corresponding version or URI given in a `package.json`.
49 * @return {String} Package name and version as a string that can be passed
50 * into NPM's install command.
51 */
52function generateTargetPackage(entry) {
53 if (entry[1].match(/^(git\+)?(file|https)?:\/\//)) return entry[1];
54 if (entry[1].match(/^[\^~]/)) return `${entry[0]}@${entry[1].slice(1)}`;
55 return `${entry[0]}@${entry[1]}`;
56}
57
58/**
59 * Adopts dev dependencies of the `donor` package into the `host` one.
60 * @param {Object} donorData Data from donor's `package.json`.
61 * @param {Object} hostData Data from host's `package.json`.
62 */
63function adoptDevDependencies(donorData) {
64 let deps = Object.entries(donorData.devDependencies || {});
65 deps = deps.map(generateTargetPackage);
66 spawnSync('npm', ['install', '--save-dev'].concat(deps), {
67 stdio: 'inherit',
68 });
69}
70
71/**
72 * Locates and loads `package.json` of the host package (assumed to be inside
73 * the current working directory).
74 * @return {Object} Data from `package.json` parsed into JSON.
75 */
76function getHostPackageJson() {
77 const url = path.resolve(process.cwd(), 'package.json');
78 return JSON.parse(fs.readFileSync(url));
79}
80
81/**
82 * Locates and loads `package.json` file of the specified package.
83 * @param {String} package Package name.
84 * @return {Object} Data from `package.json` parsed into JSON.
85 */
86function getPackageJson(packageName = 'topcoder-react-utils') {
87 let url = packageName === 'topcoder-react-utils' ? '..' : packageName;
88 url = path.dirname(require.resolve(url));
89 for (;;) {
90 const files = fs.readdirSync(url);
91 if (files.includes('package.json')) {
92 url = path.resolve(url, 'package.json');
93 break;
94 }
95 const up = path.resolve(url, '..');
96 if (url === up) throw new Error(`Cannot find the package ${packageName}`);
97 url = up;
98 }
99 return JSON.parse(fs.readFileSync(url));
100}
101
102/**
103 * Installs specified library.
104 * @param {String} library Library name.
105 */
106function install(library) {
107 let name = library;
108 if (name.indexOf('@') <= 0) name += '@latest';
109 spawnSync('npm', ['install', '--save', name], { stdio: 'inherit' });
110}
111
112/**
113 * Outputs help information to console.
114 */
115function showHelp() {
116 console.log(commandLineUsage(HELP));
117}
118
119/**
120 * Updates prod dependencies of `host` package that are also prod dependencies
121 * of `donor` to the same versions specified in the donor's `package.json`.
122 * @param {Object} donorData Data from donor's `package.json`.
123 * @param {Object} hostData Data from host's `package.json`.
124 */
125function updateProdDependencies(donorData, hostData) {
126 let deps = Object.entries(donorData.dependencies || {});
127 const hostDeps = hostData.dependencies || {};
128 deps = deps.filter(x => hostDeps[x[0]]);
129 deps = deps.map(generateTargetPackage);
130 spawnSync('npm', ['install', '--save'].concat(deps), { stdio: 'inherit' });
131}
132
133/* Entry point. */
134
135const options = commandLineArgs(OPTIONS);
136if (!options.libraries) {
137 options.libraries = ['topcoder-react-utils'];
138}
139
140if (options.help) showHelp();
141else {
142 const hostData = getHostPackageJson();
143 options.libraries.forEach((library) => {
144 if (!options['just-fix-deps']) install(library);
145 const libData = getPackageJson(library);
146 adoptDevDependencies(libData, hostData);
147 updateProdDependencies(libData, hostData);
148 });
149 spawnSync('npm', ['install'], { stdio: 'inherit' });
150}