197 lines
4.9 KiB
TypeScript
197 lines
4.9 KiB
TypeScript
/*
|
|
* This script checks if new versions of node modules are available.
|
|
* It uses naming conventions to transform constants to matching node module name.
|
|
*
|
|
* Usage:
|
|
* yarn check-versions [file|package]
|
|
*
|
|
* Positional arg:
|
|
* - [file]: relative or absolute file path to the versions file.
|
|
*
|
|
* Example:
|
|
* yarn check-versions react
|
|
*/
|
|
|
|
import { join, relative } from 'path';
|
|
import { gt } from 'semver';
|
|
import { readJsonSync, writeJsonSync } from 'fs-extra';
|
|
import * as chalk from 'chalk';
|
|
import { dasherize } from '../packages/workspace/src/utils/strings';
|
|
import * as glob from 'glob';
|
|
import { execSync } from 'child_process';
|
|
import { readFileSync, writeFileSync } from 'fs';
|
|
|
|
const root = join(__dirname, '..');
|
|
const excluded = ['nxVersion'];
|
|
const scoped = [
|
|
'babel',
|
|
'emotion',
|
|
'reduxjs',
|
|
'swc',
|
|
'testing-library',
|
|
'types',
|
|
];
|
|
|
|
try {
|
|
const files = process.argv[2]
|
|
? [normalize(process.argv[2])]
|
|
: glob.sync('packages/**/*/versions.ts').map((x) => relative(root, x));
|
|
checkFiles(files);
|
|
} catch (e) {
|
|
console.log(chalk.red(e.message));
|
|
process.exitCode = 1;
|
|
}
|
|
|
|
function normalize(x: string) {
|
|
if (x.endsWith('.ts')) {
|
|
return x;
|
|
} else {
|
|
return join('packages', x, 'src/utils/versions.ts');
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
function checkFiles(files: string[]) {
|
|
console.log(chalk.blue(`Checking versions in the following files...\n`));
|
|
console.log(` - ${files.join('\n - ')}\n`);
|
|
const maxFileNameLength = Math.max(...files.map((f) => f.length));
|
|
|
|
let hasError = false;
|
|
|
|
files.forEach((f) => {
|
|
const projectRoot = f.split('/src/')[0];
|
|
const migrationsPath = join(projectRoot, 'migrations.json');
|
|
const migrationsJson = readJsonSync(migrationsPath);
|
|
let versionsContent = readFileSync(f).toString();
|
|
const versions = getVersions(f);
|
|
const npmPackages = getPackages(versions);
|
|
const results = npmPackages.map(([p, v, o]) => getVersionData(p, v, o));
|
|
const logContext = `${f.padEnd(maxFileNameLength)}`;
|
|
const packageUpdates = {};
|
|
|
|
results.forEach((r) => {
|
|
if (r.outdated) {
|
|
console.log(
|
|
`${logContext} ❗ ${chalk.bold(
|
|
r.package
|
|
)} has new version ${chalk.bold(r.latest)} (current: ${r.prev})`
|
|
);
|
|
versionsContent = versionsContent.replace(
|
|
`${r.variable} = '${r.prev}'`,
|
|
`${r.variable} = '${r.latest}'`
|
|
);
|
|
packageUpdates[r.package] = {
|
|
version: r.latest,
|
|
alwaysAddToPackageJson: false,
|
|
};
|
|
}
|
|
if (r.invalid) {
|
|
hasError = true;
|
|
console.log(
|
|
`${logContext} ⚠️ ${chalk.bold(r.package)} has an invalid version (${
|
|
r.prev
|
|
}) specified. Latest is ${r.latest}.`
|
|
);
|
|
}
|
|
});
|
|
|
|
if (Object.keys(packageUpdates).length > 0) {
|
|
migrationsJson.packageJsonUpdates['x.y.z'] = {
|
|
version: 'x.y.z',
|
|
packages: packageUpdates,
|
|
};
|
|
writeFileSync(f, versionsContent);
|
|
writeJsonSync(migrationsPath, migrationsJson, { spaces: 2 });
|
|
}
|
|
});
|
|
|
|
if (hasError) {
|
|
throw new Error('Invalid versions of packages found (please see above).');
|
|
}
|
|
}
|
|
|
|
function getVersions(path: string) {
|
|
const versionsPath =
|
|
path.startsWith('.') || path.startsWith('packages')
|
|
? join(__dirname, '..', path)
|
|
: path;
|
|
try {
|
|
return require(versionsPath);
|
|
} catch {
|
|
throw new Error(`Could not load ${path}. Please make sure it is valid.`);
|
|
}
|
|
}
|
|
|
|
function getPackages(versions: Record<string, string>): string[][] {
|
|
return Object.entries(versions).reduce((acc, [name, version]) => {
|
|
if (!excluded.includes(name)) {
|
|
const npmName = getNpmName(name);
|
|
acc.push([npmName, version, name]);
|
|
}
|
|
return acc;
|
|
}, [] as string[][]);
|
|
}
|
|
|
|
function getNpmName(name: string): string {
|
|
const dashedName = dasherize(name.replace(/Version$/, ''));
|
|
const scope = scoped.find((s) => dashedName.startsWith(`${s}-`));
|
|
|
|
if (scope) {
|
|
const rest = dashedName.split(`${scope}-`)[1];
|
|
return `@${scope}/${rest}`;
|
|
} else {
|
|
return dashedName;
|
|
}
|
|
}
|
|
|
|
function getVersionData(
|
|
p: string,
|
|
v: string,
|
|
o: string
|
|
): {
|
|
variable: string;
|
|
package: string;
|
|
outdated: boolean;
|
|
invalid: boolean;
|
|
latest: string;
|
|
prev?: string;
|
|
} {
|
|
try {
|
|
const latest = JSON.parse(
|
|
execSync(`npm view ${p} version --json --silent`, {
|
|
stdio: ['ignore'],
|
|
}).toString('utf-8')
|
|
);
|
|
if (gt(latest, v)) {
|
|
return {
|
|
variable: o,
|
|
package: p,
|
|
outdated: true,
|
|
invalid: false,
|
|
latest,
|
|
prev: v,
|
|
};
|
|
}
|
|
if (gt(v, latest)) {
|
|
return {
|
|
variable: o,
|
|
package: p,
|
|
outdated: false,
|
|
invalid: true,
|
|
latest,
|
|
prev: v,
|
|
};
|
|
}
|
|
} catch {
|
|
// ignored
|
|
}
|
|
return {
|
|
variable: o,
|
|
package: p,
|
|
outdated: false,
|
|
invalid: false,
|
|
latest: v,
|
|
};
|
|
}
|