Commit f6130db0 by Steven Vachon Committed by GitHub

@grafana/toolkit: cleanup (#21441)

* Clarify readme for extended webpack config

* Add support for ES2019 features

* Build task: path.resolve → resolvePath

* Build task: avoid fs race conditions

* Build task: use async fs functions

* Build task: use rimraf directly

... because depending on the workspace parent to have it installed is fragile, and a child process is always slower.

* Build task: misc
parent 29e9b1f7
......@@ -128,21 +128,18 @@ Currently we support following Jest configuration properties:
- [`moduleNameMapper`](https://jestjs.io/docs/en/configuration#modulenamemapper-object-string-string)
### How can I customize Webpack rules or plugins?
You can provide your own webpack configuration.
Provide a function implementing `CustomWebpackConfigurationGetter` in a file named `webpack.config.js`.
You can import the correct interface and Options from `@grafana/toolkit/src/config`.
Example
You can provide your own `webpack.config.js` file that exports a `getWebpackConfig` function. We recommend that you extend the standard configuration, but you are free to create your own:
```js
import CustomPlugin from 'custom-plugin';
export const getWebpackConfig = (defaultConfig, options) => {
console.log('Custom config');
defaultConfig.plugins.push(new CustomPlugin())
return defaultConfig;
};
const CustomPlugin = require('custom-plugin');
module.exports.getWebpackConfig = (config, options) => ({
...config,
plugins: [
...config.plugins,
new CustomPlugin()
]
});
```
### How can I style my plugin?
......
......@@ -37,8 +37,10 @@
"@types/jest": "24.0.13",
"@types/jest-cli": "^23.6.0",
"@types/node": "^12.0.4",
"@types/prettier": "^1.16.4",
"@types/puppeteer-core": "1.9.0",
"@types/react-dev-utils": "^9.0.1",
"@types/rimraf": "^2.0.3",
"@types/semver": "^6.0.0",
"@types/tmp": "^0.1.0",
"@types/webpack": "4.4.34",
......@@ -55,7 +57,7 @@
"execa": "^1.0.0",
"expect-puppeteer": "4.1.1",
"file-loader": "^4.0.0",
"glob": "^7.1.4",
"globby": "^10.0.1",
"html-loader": "0.5.5",
"html-webpack-plugin": "^3.2.0",
"inquirer": "^6.3.1",
......@@ -79,6 +81,7 @@
"react-dev-utils": "^9.0.1",
"replace-in-file": "^4.1.0",
"replace-in-file-webpack-plugin": "^1.0.6",
"rimraf": "^3.0.0",
"sass-loader": "7.1.0",
"semver": "^6.1.1",
"simple-git": "^1.112.0",
......@@ -94,10 +97,6 @@
"url-loader": "^2.0.1",
"webpack": "4.35.0"
},
"devDependencies": {
"@types/glob": "^7.1.1",
"@types/prettier": "^1.16.4"
},
"_moduleAliases": {
"puppeteer": "node_modules/puppeteer-core"
}
......
import { Task, TaskRunner } from './task';
import fs from 'fs';
// @ts-ignore
import execa = require('execa');
import path = require('path');
import glob = require('glob');
import { Linter, Configuration, RuleFailure } from 'tslint';
import * as prettier from 'prettier';
import { useSpinner } from '../utils/useSpinner';
import { testPlugin } from './plugin/tests';
import { Task, TaskRunner } from './task';
import rimrafCallback from 'rimraf';
import { resolve as resolvePath } from 'path';
import { promisify } from 'util';
import globby from 'globby';
import execa from 'execa';
import { constants as fsConstants, promises as fs } from 'fs';
import { bundlePlugin as bundleFn, PluginBundleOptions } from './plugin/bundle';
import { Configuration, Linter, LintResult, RuleFailure } from 'tslint';
const { access, copyFile, readFile, writeFile } = fs;
const { COPYFILE_EXCL, F_OK } = fsConstants;
const rimraf = promisify(rimrafCallback);
interface PluginBuildOptions {
coverage: boolean;
}
interface Fixable {
fix?: boolean;
}
export const bundlePlugin = useSpinner<PluginBundleOptions>('Compiling...', async options => await bundleFn(options));
// @ts-ignore
export const clean = useSpinner<void>('Cleaning', async () => await execa('rimraf', [`${process.cwd()}/dist`]));
export const clean = useSpinner<void>('Cleaning', async () => await rimraf(`${process.cwd()}/dist`));
export const prepare = useSpinner<void>('Preparing', async () => {
// Make sure a local tsconfig exists. Otherwise this will work, but have odd behavior
let filePath = path.resolve(process.cwd(), 'tsconfig.json');
if (!fs.existsSync(filePath)) {
const srcFile = path.resolve(__dirname, '../../config/tsconfig.plugin.local.json');
fs.copyFile(srcFile, filePath, err => {
if (err) {
throw err;
const copyIfNonExistent = (srcPath: string, destPath: string) =>
copyFile(srcPath, destPath, COPYFILE_EXCL)
.then(() => console.log(`Created: ${destPath}`))
.catch(error => {
if (error.code !== 'EEXIST') {
throw error;
}
console.log(`Created: ${filePath}`);
});
}
// Make sure a local .prettierrc.js exists. Otherwise this will work, but have odd behavior
filePath = path.resolve(process.cwd(), '.prettierrc.js');
if (!fs.existsSync(filePath)) {
const srcFile = path.resolve(__dirname, '../../config/prettier.plugin.rc.js');
fs.copyFile(srcFile, filePath, err => {
if (err) {
throw err;
}
console.log(`Created: ${filePath}`);
});
}
return Promise.resolve();
export const prepare = useSpinner<void>('Preparing', async () => {
await Promise.all([
// Copy only if local tsconfig does not exist. Otherwise this will work, but have odd behavior
copyIfNonExistent(
resolvePath(process.cwd(), 'tsconfig.json'),
resolvePath(__dirname, '../../config/tsconfig.plugin.local.json')
),
// Copy only if local prettierrc does not exist. Otherwise this will work, but have odd behavior
copyIfNonExistent(
resolvePath(process.cwd(), '.prettierrc.js'),
resolvePath(__dirname, '../../config/prettier.plugin.rc.js')
),
]);
// Nothing is returned
});
// @ts-ignore
......@@ -54,101 +58,97 @@ const typecheckPlugin = useSpinner<void>('Typechecking', async () => {
await execa('tsc', ['--noEmit']);
});
const getTypescriptSources = () => {
const globPattern = path.resolve(process.cwd(), 'src/**/*.+(ts|tsx)');
return glob.sync(globPattern);
};
const getTypescriptSources = () => globby(resolvePath(process.cwd(), 'src/**/*.+(ts|tsx)'));
const getStylesSources = () => {
const globPattern = path.resolve(process.cwd(), 'src/**/*.+(scss|css)');
return glob.sync(globPattern);
};
const getStylesSources = () => globby(resolvePath(process.cwd(), 'src/**/*.+(scss|css)'));
export const prettierCheckPlugin = useSpinner<Fixable>('Prettier check', async ({ fix }) => {
const prettierConfig = require(path.resolve(__dirname, '../../config/prettier.plugin.config.json'));
const sources = [...getStylesSources(), ...getTypescriptSources()];
const promises = sources.map((s, i) => {
return new Promise<{ path: string; failed: boolean }>((resolve, reject) => {
fs.readFile(s, (err, data) => {
let failed = false;
if (err) {
throw new Error(err.message);
}
const opts = {
// @todo remove explicit params when possible -- https://github.com/microsoft/TypeScript/issues/35626
const [prettierConfig, paths] = await Promise.all<object, string[]>([
readFile(resolvePath(__dirname, '../../config/prettier.plugin.config.json'), 'utf8').then(
contents => JSON.parse(contents) as object
),
Promise.all([getStylesSources(), getTypescriptSources()]).then(results => results.flat()),
]);
const promises: Array<Promise<{ path: string; success: boolean }>> = paths.map(path =>
readFile(path, 'utf8')
.then(contents => {
const config = {
...prettierConfig,
filepath: s,
filepath: path,
};
if (!prettier.check(data.toString(), opts)) {
if (fix) {
const fixed = prettier.format(data.toString(), opts);
if (fixed && fixed.length > 10) {
fs.writeFile(s, fixed, err => {
if (err) {
console.log('Error fixing ' + s, err);
failed = true;
} else {
console.log('Fixed: ' + s);
}
});
} else {
console.log('No automatic fix for: ' + s);
failed = true;
}
} else {
failed = true;
}
if (fix && !prettier.check(contents, config)) {
return prettier.format(contents, config);
} else {
return undefined;
}
})
.then(newContents => {
if (fix && newContents && newContents.length > 10) {
return writeFile(path, newContents)
.then(() => {
console.log(`Fixed: ${path}`);
return true;
})
.catch(error => {
console.log(`Error fixing ${path}`, error);
return false;
});
} else if (fix) {
console.log(`No automatic fix for: ${path}`);
return false;
} else {
return false;
}
})
.then(success => ({ path, success }))
);
resolve({
path: s,
failed,
});
});
});
});
const failures = (await Promise.all(promises)).filter(({ success }) => !success);
const results = await Promise.all(promises);
const failures = results.filter(r => r.failed);
if (failures.length) {
if (failures.length > 0) {
console.log('\nFix Prettier issues in following files:');
failures.forEach(f => console.log(f.path));
failures.forEach(({ path }) => console.log(path));
console.log('\nRun toolkit:dev to fix errors');
throw new Error('Prettier failed');
}
});
// @ts-ignore
export const lintPlugin = useSpinner<Fixable>('Linting', async ({ fix }) => {
let tsLintConfigPath = path.resolve(process.cwd(), 'tslint.json');
if (!fs.existsSync(tsLintConfigPath)) {
tsLintConfigPath = path.resolve(__dirname, '../../config/tslint.plugin.json');
let tsLintConfigPath = resolvePath(process.cwd(), 'tslint.json');
try {
await access(tsLintConfigPath, F_OK);
} catch (error) {
tsLintConfigPath = resolvePath(__dirname, '../../config/tslint.plugin.json');
}
const options = {
fix: fix === true,
formatter: 'json',
};
const configuration = Configuration.findConfiguration(tsLintConfigPath).results;
const sourcesToLint = getTypescriptSources();
const sourcesToLint = await getTypescriptSources();
const lintResults = sourcesToLint
.map(fileName => {
const lintPromises = sourcesToLint.map(fileName =>
readFile(fileName, 'utf8').then(contents => {
const linter = new Linter(options);
const fileContents = fs.readFileSync(fileName, 'utf8');
linter.lint(fileName, fileContents, configuration);
linter.lint(fileName, contents, configuration);
return linter.getResult();
})
.filter(result => {
return result.errorCount > 0 || result.warningCount > 0;
});
);
const lintResults: LintResult[] = (await Promise.all(lintPromises)).filter(
({ errorCount, warningCount }) => errorCount > 0 || warningCount > 0
);
if (lintResults.length > 0) {
console.log('\n');
const failures = lintResults.reduce<RuleFailure[]>((failures, result) => {
return [...failures, ...result.failures];
}, []);
const failures: RuleFailure[] = lintResults.flat();
failures.forEach(f => {
// tslint:disable-next-line
console.log(
......
......@@ -9,7 +9,6 @@
"declaration": true,
"declarationDir": "dist/src",
"typeRoots": ["./node_modules/@types"],
"esModuleInterop": true,
"lib": ["es2015", "es2017.string", "dom"]
"esModuleInterop": true
}
}
......@@ -3,7 +3,7 @@
"moduleResolution": "node",
"outDir": "public/dist",
"target": "es5",
"lib": ["es6", "dom"],
"lib": ["es2019", "dom"],
"rootDirs": ["public/"],
"jsx": "react",
"module": "esnext",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment