1 | #!/usr/bin/env node
|
2 | "use strict";
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | var __importStar = (this && this.__importStar) || function (mod) {
|
8 | if (mod && mod.__esModule) return mod;
|
9 | var result = {};
|
10 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
11 | result["default"] = mod;
|
12 | return result;
|
13 | };
|
14 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
15 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
16 | };
|
17 | Object.defineProperty(exports, "__esModule", { value: true });
|
18 | const path = __importStar(require("path"));
|
19 | const utils = __importStar(require("./utils"));
|
20 | const package_json_1 = __importDefault(require("package-json"));
|
21 | const commander_1 = __importDefault(require("commander"));
|
22 | const semver_1 = __importDefault(require("semver"));
|
23 | const versionCache = new Map();
|
24 |
|
25 |
|
26 |
|
27 | const SEMVER_RANGE = /^(~|\^|=|<|>|<=|>=)?([\w\-.]*)$/;
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | async function getSpecifier(currentSpecifier, suggestedSpecifier) {
|
41 | var _a;
|
42 | if (semver_1.default.validRange(suggestedSpecifier)) {
|
43 | return suggestedSpecifier;
|
44 | }
|
45 |
|
46 |
|
47 | let [, suggestedSigil, suggestedTag] = (_a = suggestedSpecifier.match(SEMVER_RANGE), (_a !== null && _a !== void 0 ? _a : []));
|
48 | if (!suggestedTag) {
|
49 | throw Error(`Invalid version specifier: ${suggestedSpecifier}`);
|
50 | }
|
51 |
|
52 | if (!suggestedSigil) {
|
53 | const match = currentSpecifier.match(SEMVER_RANGE);
|
54 | if (match === null) {
|
55 | throw Error(`Current version range is not recognized: ${currentSpecifier}`);
|
56 | }
|
57 | const [, currentSigil] = match;
|
58 | if (currentSigil) {
|
59 | suggestedSigil = currentSigil;
|
60 | }
|
61 | }
|
62 | return `${suggestedSigil}${suggestedTag}`;
|
63 | }
|
64 | async function getVersion(pkg, specifier) {
|
65 | const key = JSON.stringify([pkg, specifier]);
|
66 | if (versionCache.has(key)) {
|
67 | return versionCache.get(key);
|
68 | }
|
69 | if (semver_1.default.validRange(specifier) === null) {
|
70 |
|
71 | const match = specifier.match(SEMVER_RANGE);
|
72 | if (match === null) {
|
73 | throw Error(`Invalid version specifier: ${specifier}`);
|
74 | }
|
75 |
|
76 | const { version } = await package_json_1.default(pkg, { version: match[2] });
|
77 | specifier = match[1] + version;
|
78 | }
|
79 | versionCache.set(key, specifier);
|
80 | return specifier;
|
81 | }
|
82 |
|
83 |
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 | function subset(range1, range2) {
|
91 | var _a, _b;
|
92 | try {
|
93 | const [, r1, version1] = (_a = range1.match(SEMVER_RANGE), (_a !== null && _a !== void 0 ? _a : []));
|
94 | const [, r2] = (_b = range2.match(SEMVER_RANGE), (_b !== null && _b !== void 0 ? _b : []));
|
95 | return (['', '>=', '=', '~', '^'].includes(r1) &&
|
96 | r1 === r2 &&
|
97 | !!semver_1.default.valid(version1) &&
|
98 | semver_1.default.satisfies(version1, range2));
|
99 | }
|
100 | catch (e) {
|
101 | return false;
|
102 | }
|
103 | }
|
104 | async function handleDependency(dependencies, dep, suggestedSpecifier, minimal) {
|
105 | const log = [];
|
106 | let updated = false;
|
107 | const oldRange = dependencies[dep];
|
108 | const specifier = await getSpecifier(oldRange, suggestedSpecifier);
|
109 | const newRange = await getVersion(dep, specifier);
|
110 | if (minimal && subset(newRange, oldRange)) {
|
111 | log.push(`SKIPPING ${dep} ${oldRange} -> ${newRange}`);
|
112 | }
|
113 | else {
|
114 | log.push(`${dep} ${oldRange} -> ${newRange}`);
|
115 | dependencies[dep] = newRange;
|
116 | updated = true;
|
117 | }
|
118 | return { updated, log };
|
119 | }
|
120 |
|
121 |
|
122 |
|
123 | async function handlePackage(name, specifier, packagePath, dryRun = false, minimal = false) {
|
124 | let fileUpdated = false;
|
125 | const fileLog = [];
|
126 |
|
127 | packagePath = path.join(packagePath, 'package.json');
|
128 | let data;
|
129 | try {
|
130 | data = utils.readJSONFile(packagePath);
|
131 | }
|
132 | catch (e) {
|
133 | console.debug('Skipping package ' + packagePath);
|
134 | return;
|
135 | }
|
136 |
|
137 | for (const dtype of ['dependencies', 'devDependencies']) {
|
138 | const deps = data[dtype] || {};
|
139 | if (typeof name === 'string') {
|
140 | const dep = name;
|
141 | if (dep in deps) {
|
142 | const { updated, log } = await handleDependency(deps, dep, specifier, minimal);
|
143 | if (updated) {
|
144 | fileUpdated = true;
|
145 | }
|
146 | fileLog.push(...log);
|
147 | }
|
148 | }
|
149 | else {
|
150 | const keys = Object.keys(deps);
|
151 | keys.sort();
|
152 | for (const dep of keys) {
|
153 | if (dep.match(name)) {
|
154 | const { updated, log } = await handleDependency(deps, dep, specifier, minimal);
|
155 | if (updated) {
|
156 | fileUpdated = true;
|
157 | }
|
158 | fileLog.push(...log);
|
159 | }
|
160 | }
|
161 | }
|
162 | }
|
163 | if (fileLog.length > 0) {
|
164 | console.debug(packagePath);
|
165 | console.debug(fileLog.join('\n'));
|
166 | console.debug();
|
167 | }
|
168 |
|
169 | if (!dryRun && fileUpdated) {
|
170 | utils.writePackageData(packagePath, data);
|
171 | }
|
172 | }
|
173 | commander_1.default
|
174 | .description('Update dependency versions')
|
175 | .usage('[options] <package> [versionspec], versionspec defaults to ^latest')
|
176 | .option('--dry-run', 'Do not perform actions, just print output')
|
177 | .option('--regex', 'Package is a regular expression')
|
178 | .option('--lerna', 'Update dependencies in all lerna packages')
|
179 | .option('--path <path>', 'Path to package or monorepo to update')
|
180 | .option('--minimal', 'only update if the change is substantial')
|
181 | .arguments('<package> [versionspec]')
|
182 | .action(async (name, version = '^latest', args) => {
|
183 | const basePath = path.resolve(args.path || '.');
|
184 | const pkg = args.regex ? new RegExp(name) : name;
|
185 | if (args.lerna) {
|
186 | const paths = utils.getLernaPaths(basePath).sort();
|
187 |
|
188 |
|
189 | for (const pkgPath of paths) {
|
190 | await handlePackage(pkg, version, pkgPath, args.dryRun, args.minimal);
|
191 | }
|
192 | }
|
193 | await handlePackage(pkg, version, basePath, args.dryRun, args.minimal);
|
194 | });
|
195 | commander_1.default.on('--help', function () {
|
196 | console.debug(`
|
197 | Examples
|
198 | --------
|
199 |
|
200 | Update the package 'webpack' to a specific version range:
|
201 |
|
202 | update-dependency webpack ^4.0.0
|
203 |
|
204 | Update all packages to the latest version, with a caret.
|
205 | Only update if the update is substantial:
|
206 |
|
207 | update-dependency --minimal --regex '.*' ^latest
|
208 |
|
209 | Update all packages, that does not start with '@jupyterlab',
|
210 | to the latest version and use the same version specifier currently
|
211 | being used
|
212 |
|
213 | update:dependency --regex '^(?!@jupyterlab).*' latest --dry-run
|
214 |
|
215 | Print the log of the above without actually making any changes.
|
216 |
|
217 | update-dependency --dry-run --minimal --regex '.*' ^latest
|
218 |
|
219 | Update all packages starting with '@jupyterlab/' to the version
|
220 | the 'latest' tag currently points to, with a caret range:
|
221 |
|
222 | update-dependency --regex '^@jupyterlab/' ^latest
|
223 |
|
224 | Update all packages starting with '@jupyterlab/' in all lerna
|
225 | workspaces and the root package.json to whatever version the 'next'
|
226 | tag for each package currently points to (with a caret tag).
|
227 | Update the version range only if the change is substantial.
|
228 |
|
229 | update-dependency --lerna --regex --minimal '^@jupyterlab/' ^next
|
230 | `);
|
231 | });
|
232 | commander_1.default.parse(process.argv);
|
233 |
|
234 | if (!process.argv.slice(2).length) {
|
235 | commander_1.default.outputHelp();
|
236 | process.exit(1);
|
237 | }
|
238 |
|
\ | No newline at end of file |