Skip to content

Commit

Permalink
feat(pluginsdk): Add support for checking if an existing plugin is us…
Browse files Browse the repository at this point in the history
…ing the latest opinions.

This adds a new script `check-plugin`.
A plugin should run `check-plugin` in its postinstall script in package.json.
  • Loading branch information
christopherthielen authored and mergify[bot] committed Apr 28, 2020
1 parent 12c60d8 commit b728b15
Show file tree
Hide file tree
Showing 15 changed files with 313 additions and 5 deletions.
7 changes: 5 additions & 2 deletions packages/pluginsdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
"prepublishOnly": "npm run build"
},
"bin": {
"scaffold": "scripts/scaffold.js"
"scaffold": "scripts/scaffold.js",
"check-plugin": "scripts/check-plugin.js"
},
"dependencies": {
"check-peer-dependencies": "^2.0.1",
"readline-sync": "^1.4.10"
"readline-sync": "^1.4.10",
"strip-json-comments": "^3.1.0",
"yargs": "^15.3.1"
},
"devDependencies": {
"@rollup/plugin-commonjs": "11.0.2",
Expand Down
4 changes: 2 additions & 2 deletions packages/pluginsdk/scaffold/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"private": true,
"module": "build/dist/index.js",
"scripts": {
"clean": "npx rm -rf build",
"clean": "npx shx rm -rf build",
"build": "rollup -c",
"watch": "rollup -c -w",
"postinstall": "check-peer-dependencies"
"postinstall": "check-plugin && check-peer-dependencies"
},
"files": [
"build/dist"
Expand Down
98 changes: 98 additions & 0 deletions packages/pluginsdk/scripts/check-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env node
const yargs = require('yargs');
const { linters } = require('./check-plugin/linters');

yargs
.scriptName('check-plugin')
.option('verbose', {
alias: 'v',
type: 'boolean',
default: false,
})
.option('fix', {
type: 'boolean',
alias: 'f',
describe: 'When enabled, fixes are automatically applied',
default: false,
})
.option('fix-warnings', {
type: 'boolean',
alias: 'w',
describe: 'When enabled, fixes are automatically applied even for warnings',
default: false,
});

const { verbose, fix, fixWarnings } = yargs.argv;
checkPlugin({ verbose, fixWarnings, fix: fix || fixWarnings });

function checkPlugin(options) {
const { verbose, fix, fixWarnings } = options;

const errorFixers = [];
const warningFixers = [];

function reporter(message, ok, resolution, fixer) {
if (ok === true) {
if (verbose) {
console.log(` ✅ ${message}`);
}
} else if (ok === false) {
console.log(` ❌ ${message}`);
if (fixer) {
errorFixers.push(fixer);
if (resolution) {
console.log();
console.log(' ' + resolution);
console.log();
}
}
} else {
console.log(` ☑️ ${message}`);

if (fixer) {
warningFixers.push(fixer);
if (resolution) {
console.log();
console.log(' ' + resolution);
console.log();
}
}
}
}

linters.forEach(linter => linter(reporter));

const fixingErrors = fix && errorFixers.length;
const fixingWarnings = fixWarnings && warningFixers.length;

console.log();
console.log();
if (fixingErrors || fixingWarnings) {
if (fixingWarnings) {
console.log('Fixing errors and warnings...');
} else {
console.log('Fixing errors...');
}
} else if (warningFixers.length) {
console.log(`Run this command to fix the errors and warnings:`);
console.log();
console.log(`npx check-plugin --fix-warnings`);
} else if (errorFixers.length) {
console.log(`Run this command to fix the errors:`);
console.log();
console.log(`npx check-plugin --fix`);
}

console.log();
console.log();

if (fix) {
const commits = errorFixers.map(fixer => fixer());
commits.filter(x => typeof x === 'function').forEach(commit => commit());
}

if (fixWarnings) {
const commits = warningFixers.map(fixer => fixer());
commits.filter(x => typeof x === 'function').forEach(commit => commit());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const fs = require('fs');
const { restoreFile } = require('../util/restoreFile');

const assertFileExists = (report, filename, pristineFilename = filename) => {
const exists = fs.existsSync(filename);
const resolution = `restore ${filename} from scaffold defaults`;
const restore = () => restoreFile(filename, pristineFilename);
report(`${filename} exists`, exists, resolution, restore);
return exists;
};

module.exports = { assertFileExists };
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const fs = require('fs');
const path = require('path');
const { assertFileExists } = require('./assertFileExists');
const { restoreFile } = require('../util/restoreFile');

function assertJavascriptFile(report, filename, pristineFilename, name, requireString) {
const exists = assertFileExists(report, filename, pristineFilename);

if (exists) {
// packages/pluginsdk/scaffold/${pristineFilename}
const pristinePath = path.resolve(__dirname, '..', '..', '..', 'scaffold', pristineFilename);
const pristineFile = fs.readFileSync(pristinePath).toString();
const pluginFile = fs.readFileSync(filename).toString();
const hasRequire = pluginFile.includes(`require('${requireString}')`);
const resolution = `restore ${filename} from scaffold defaults`;
const restore = () => restoreFile(filename, pristineFilename);
report(`${name} extends @spinnaker/pluginsdk`, hasRequire, `--fix: ${resolution}`, restore);
report(
filename,
pristineFile.trim() === pluginFile.trim() || null,
`--fix-warnings: ${resolution} (${filename} has been customized)`,
restore,
);
}
}

module.exports = { assertJavascriptFile };
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { readJson, writeJson } = require('../util/readWriteJson');

const get = (path, obj) => {
return path.split('.').reduce((acc, key) => (acc !== undefined ? acc[key] : undefined), obj);
};

const writeJsonField = (filename, field, val) => {
const json = readJson(filename);
const segments = field.split('.');
const tail = segments.pop();
const parent = get(segments.join('.'), json);
parent[tail] = val;
writeJson(filename, json);
};

const assertJsonFile = (report, filename, json) => {
return function assertJsonFile(field, expectedValue) {
const currentValue = get(field, json);
const resolution = `--fix: change ${field} in ${filename} from "${currentValue}" to "${expectedValue}"`;
const fixer = () => {
writeJsonField(filename, field, expectedValue);
console.log(`fixed: changed ${field} in ${filename} from "${currentValue}" to "${expectedValue}"`);
};
report(`${filename}: ${field} field`, currentValue === expectedValue, resolution, fixer);
};
};

module.exports = { assertJsonFile };
8 changes: 8 additions & 0 deletions packages/pluginsdk/scripts/check-plugin/linters/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { checkPackageJson } = require('./lint.package.json');
const { checkTsconfig } = require('./lint.tsconfig.json');
const { checkRollupConfig } = require('./lint.rollup.config');
const { checkPrettierRc } = require('./lint.prettierrc');
const { checkEslintRc } = require('./lint.eslintrc');

const linters = [checkPackageJson, checkTsconfig, checkRollupConfig, checkPrettierRc, checkEslintRc];
module.exports = { linters };
13 changes: 13 additions & 0 deletions packages/pluginsdk/scripts/check-plugin/linters/lint.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { assertJavascriptFile } = require('../asserters/assertJavascriptFile');

function checkEslintRc(report) {
assertJavascriptFile(
report,
'.eslintrc.js',
'.eslintrc.js',
'Eslint config',
'@spinnaker/pluginsdk/pluginconfig/eslintrc',
);
}

module.exports = { checkEslintRc };
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const { execSync } = require('child_process');
const { assertJsonFile } = require('../asserters/assertJsonFile');
const { readJson, writeJson } = require('../util/readWriteJson');

function getLatestSdkVersion() {
const versionsString = execSync('npm info @spinnaker/pluginsdk versions').toString();
return JSON.parse(versionsString.replace(/'/g, '"')).pop();
}

function checkPackageJson(report) {
const pkgJson = readJson('package.json');
const deps = pkgJson.dependencies || {};
const sdk = deps['@spinnaker/pluginsdk'];
const latest = getLatestSdkVersion();

const latestVersionFixer = () => {
execSync(`yarn add @spinnaker/pluginsdk@${latest}`, { stdio: 'inherit' });
console.log(`fixed: installed @spinnaker/pluginsdk@${latest}`);
};
const latestVersionResolution = '--fix: install latest @spinnaker/pluginsdk';
report('Uses latest @spinnaker/pluginsdk', sdk === latest, latestVersionResolution, latestVersionFixer);

const checkPackageJsonField = assertJsonFile(report, 'package.json', pkgJson);

checkPackageJsonField('module', 'build/dist/index.js');
checkPackageJsonField('scripts.clean', 'npx shx rm -rf build');
checkPackageJsonField('scripts.build', 'rollup -c');
checkPackageJsonField('scripts.watch', 'rollup -c -w');
checkPackageJsonField('scripts.postinstall', 'lint-plugin && check-peer-dependencies');

const bundlesFiles = pkgJson.files && pkgJson.files.includes('build/dist');
const bundlesFilesFixer = () => {
const json = readJson('package.json');
if (!json.files) {
json.files = [];
}
json.files.push('build/dist');
writeJson('package.json', json);

console.log(`fixed: added "build/dist" to "files" in package.json`);
};
const resolution = `--fix: Add "build/dist" to files array in package.json`;
report('package.json: files includes "build/dist"', bundlesFiles, resolution, bundlesFilesFixer);
}

module.exports = { checkPackageJson };
13 changes: 13 additions & 0 deletions packages/pluginsdk/scripts/check-plugin/linters/lint.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { assertJavascriptFile } = require('../asserters/assertJavascriptFile');

function checkPrettierRc(report) {
assertJavascriptFile(
report,
'.prettierrc.js',
'scaffold.prettierrc.js',
'Prettier config',
'@spinnaker/pluginsdk/pluginconfig/prettierrc.js',
);
}

module.exports = { checkPrettierRc };
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { assertJavascriptFile } = require('../asserters/assertJavascriptFile');

function checkRollupConfig(report) {
assertJavascriptFile(
report,
'rollup.config.js',
'rollup.config.js',
'Rollup config',
'@spinnaker/pluginsdk/pluginconfig/rollup.config',
);
}

module.exports = { checkRollupConfig };
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const { assertJsonFile } = require('../asserters/assertJsonFile');
const { assertFileExists } = require('../asserters/assertFileExists');
const { readJson } = require('../util/readWriteJson');

function checkTsconfig(report) {
const exists = assertFileExists(report, 'tsconfig.json');

if (exists) {
const tsConfigJson = readJson('tsconfig.json');

const checkTsconfigField = assertJsonFile(report, 'tsconfig.json', tsConfigJson);

checkTsconfigField('extends', '@spinnaker/pluginsdk/pluginconfig/tsconfig.json');
checkTsconfigField('compilerOptions.outDir', 'build/dist');
checkTsconfigField('compilerOptions.rootDir', 'src');
}
}

module.exports = { checkTsconfig };
13 changes: 13 additions & 0 deletions packages/pluginsdk/scripts/check-plugin/util/readWriteJson.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const fs = require('fs');
const stripJsonComments = require('strip-json-comments');

function readJson(pathname) {
const string = fs.readFileSync(pathname).toString();
return JSON.parse(stripJsonComments(string));
}

function writeJson(pathname, json) {
fs.writeFileSync(pathname, JSON.stringify(json, null, 2));
}

module.exports = { readJson, writeJson };
10 changes: 10 additions & 0 deletions packages/pluginsdk/scripts/check-plugin/util/restoreFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const fs = require('fs');
const path = require('path');

function restoreFile(file, pristineFilename = file) {
const srcFile = path.resolve(__dirname, '..', '..', '..', 'scaffold', pristineFilename);
fs.writeFileSync(file, fs.readFileSync(srcFile));
console.log(`fixed: restored ${file} to default from scaffold`);
}

module.exports = { restoreFile };
7 changes: 6 additions & 1 deletion packages/pluginsdk/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2144,6 +2144,11 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"

strip-json-comments@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180"
integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==

style-inject@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/style-inject/-/style-inject-0.3.0.tgz#d21c477affec91811cc82355832a700d22bf8dd3"
Expand Down Expand Up @@ -2351,7 +2356,7 @@ yargs-parser@^18.1.1:
camelcase "^5.0.0"
decamelize "^1.2.0"

yargs@^15.0.2:
yargs@^15.0.2, yargs@^15.3.1:
version "15.3.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b"
integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==
Expand Down

0 comments on commit b728b15

Please sign in to comment.