@salesforce/dev-scripts
Version:
Standardize package.json scripts and config files for Salesforce projects.
170 lines (148 loc) • 6.14 kB
JavaScript
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
const { join } = require('path');
const PackageJson = require('./package-json');
const { resolveConfig } = require('./sf-config');
/**
* These are not part of dev-scripts pjson because they depend on dev-scripts and would create a circular dependency
* But, if the target repo has the dep, we want to make sure it meets the minimum version.
*/
const nonPjsonDependencyMinimums = new Map([
['@salesforce/sf-plugins-core', '^11.2.4'],
['@salesforce/core', '^8.2.7'],
['@salesforce/kit', '^3.1.6'],
['@salesforce/ts-types', '^2.0.11'],
['@oclif/core', '^4'],
['@salesforce/cli-plugins-testkit', '^5.3.20'],
['@salesforce/source-deploy-retrieve', '^12.0.1'],
['@salesforce/source-tracking', '^7.0.1'],
['@salesforce/plugin-command-reference', '^3.1.5'],
['@oclif/plugin-command-snapshot', '^5.2.3'],
['eslint-plugin-sf-plugin', '^1.18.8'],
['@salesforce/telemetry', '^6.0.14'],
['oclif', '^4'],
]);
const getVersionNum = (ver) => (ver.startsWith('^') || ver.startsWith('~') ? ver.slice(1) : ver);
const meetsMinimumVersion = (pjsonDepVersion, devScriptsDepVersion) => {
// First remove any carets and tildes
const pVersion = getVersionNum(pjsonDepVersion);
const dsVersion =
getVersionNum(devScriptsDepVersion) ?? getVersionNum(nonPjsonDependencyMinimums.get(pjsonDepVersion));
// Compare the version in package.json with the dev scripts version.
// result === -1 means the version in package.json < dev scripts version
// result === 0 means they match
// result === 1 means the version in package.json > dev scripts version
return pVersion.localeCompare(dsVersion, 'en-u-kn-true') > -1;
};
module.exports = (projectPath) => {
const pjson = new PackageJson(projectPath);
const config = resolveConfig(projectPath);
const devDependencies = pjson.get('devDependencies');
const added = [];
const removed = [];
const devScriptsVersion = (name) =>
devScriptsPjson.dependencies?.[name] ??
devScriptsPjson.devDependencies?.[name] ??
nonPjsonDependencyMinimums.get(name);
const devScriptsPjson = require(join(__dirname, '..', 'package.json'));
const add = (name, version) => {
version = version ?? devScriptsVersion(name);
if (!version) {
throw new Error(
// eslint-disable-next-line max-len
`Version empty for ${name}. Make sure it is in the devDependencies in dev-scripts since it is being added to the actual projects devDependencies.`
);
}
// If the dependency min version has been met, ignore it.
if (!devDependencies[name] || !meetsMinimumVersion(devDependencies[name], version)) {
devDependencies[name] = version;
added.push(name);
}
};
const remove = (name) => {
if (devDependencies[name]) {
delete devDependencies[name];
removed.push(name);
}
};
const scripts = config.scripts;
const eslintConfigSfTsPjson = require('eslint-config-salesforce-typescript/package.json');
/** devDeps that *should* be in every repo dev-scripts touches. */
const requiredDeps = [];
/** These packages are provided to orgs via devScripts. They should not be in the pjson of the consumer
* If you don't like the devScripts version, you can specify your own in sf-dev-rc.json in the dependencies section.
*/
const providedByDevScripts = [
'@commitlint/cli',
'@commitlint/config-conventional',
'typedoc-plugin-missing-exports',
'source-map-support',
'typedoc',
'husky',
'pretty-quick',
'prettier',
'nyc',
'mocha',
'sinon',
'chai',
'wireit',
'@types/chai',
'@types/mocha',
'@types/node',
'@types/sinon',
'@salesforce/dev-config',
'@salesforce/prettier-config',
// this repo manages all things eslint. Its dependencies are in dev-scripts and therefore should be omitted
'eslint-config-salesforce-typescript',
...Object.keys(eslintConfigSfTsPjson.dependencies),
// leave these alone if the project has them
].filter((dep) => !new Set(config.devDepOverrides).has(dep));
/**
* We don't want these in any repo. This is a good way to clean up things en masse
*/
const bannedDeps = ['cz-conventional-changelog', 'lint-staged', 'tslint', 'eslint-plugin-prettier'].concat(
scripts.format ? [] : ['prettier', '@salesforce/prettier-config']
);
// removes go before adds because some are "added back"
providedByDevScripts.forEach((dep) => remove(dep));
bannedDeps.forEach((dep) => remove(dep));
// calling add will force it to exist
requiredDeps.forEach((dep) => add(dep));
// look through the target's devDeps and, if devScripts has a min version, apply that
Object.keys(devDependencies).forEach((dep) => {
const version = devScriptsVersion(dep);
if (!bannedDeps.includes(dep) && !requiredDeps.includes(dep) && version) {
add(dep, version);
}
});
// update any non-devDeps to their minimum versions if devScripts specifies one
const dependencies = pjson.get('dependencies');
Object.entries(dependencies).forEach(([dep, depVersion]) => {
if (nonPjsonDependencyMinimums.has(dep)) {
const minVersion = nonPjsonDependencyMinimums.get(dep);
if (!meetsMinimumVersion(depVersion, minVersion)) {
pjson.actions.push(`updating ${dep} to ${minVersion}`);
dependencies[dep] = minVersion;
}
}
});
// Check to see if these deps are used by yarn scripts. If not, remove them.
const possiblyUnnecessaryDeps = ['shx'];
possiblyUnnecessaryDeps.forEach((dep) => {
if (!Object.values(scripts).some((script) => script?.includes(dep))) {
remove(dep);
}
});
if (added.length > 0) {
pjson.actions.push(`added/updated devDependencies ${added.join(', ')}`);
}
if (removed.length > 0) {
pjson.actions.push(`removed devDependencies controlled by dev-scripts, ${removed.join(', ')}`);
}
pjson.write();
return added.length > 0;
};