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 |
|
7 | const commandLineArgs = require('command-line-args');
|
8 | const commandLineUsage = require('command-line-usage');
|
9 | const fs = require('fs');
|
10 | const path = require('path');
|
11 | const { spawnSync } = require('child_process');
|
12 |
|
13 | /* Definition of command-line arguments. */
|
14 | const 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. */
|
31 | const 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 | */
|
52 | function 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 | */
|
63 | function 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 | */
|
76 | function 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 | */
|
86 | function 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 | */
|
106 | function 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 | */
|
115 | function 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 | */
|
125 | function 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 |
|
135 | const options = commandLineArgs(OPTIONS);
|
136 | if (!options.libraries) {
|
137 | options.libraries = ['topcoder-react-utils'];
|
138 | }
|
139 |
|
140 | if (options.help) showHelp();
|
141 | else {
|
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 | }
|