Commit d745a66a by Steven Vachon Committed by GitHub

@grafana/toolkit: cleanup (#27906)

* Simplify `useSpinner` to run immediately, instead of returning a function

* Removed unnecessary exports

* Avoid mutating input arguments
parent f60f1c3f
// @ts-ignore
import * as _ from 'lodash';
import { Task, TaskRunner } from './task';
import { Task } from './task';
import GithubClient from '../utils/githubClient';
import difference from 'lodash/difference';
import chalk from 'chalk';
......@@ -46,9 +46,8 @@ const getPackageChangelog = (packageName: string, issues: any[]) => {
return markdown;
};
const changelogTaskRunner: TaskRunner<ChangelogOptions> = useSpinner<ChangelogOptions>(
'Generating changelog',
async ({ milestone }) => {
const changelogTaskRunner = ({ milestone }: ChangelogOptions) =>
useSpinner('Generating changelog', async () => {
const githubClient = new GithubClient();
const client = githubClient.client;
......@@ -106,8 +105,7 @@ const changelogTaskRunner: TaskRunner<ChangelogOptions> = useSpinner<ChangelogOp
markdown += getPackageChangelog('grafana-ui', grafanaUiIssues);
console.log(markdown);
}
);
});
function getMarkdownLineForIssue(item: any) {
const githubGrafanaUrl = 'https://github.com/grafana/grafana';
......
......@@ -5,28 +5,29 @@ import * as path from 'path';
import chalk from 'chalk';
import { useSpinner } from '../utils/useSpinner';
import { Task, TaskRunner } from './task';
import { cloneDeep } from 'lodash';
import globby from 'globby';
import series from 'p-series';
let distDir: string, cwd: string;
export const clean = useSpinner('Cleaning', () => execa('npm', ['run', 'clean']));
const clean = () => useSpinner('Cleaning', () => execa('npm', ['run', 'clean']));
const compile = useSpinner('Compiling sources', () => execa('tsc', ['-p', './tsconfig.build.json']));
const compile = () => useSpinner('Compiling sources', () => execa('tsc', ['-p', './tsconfig.build.json']));
const rollup = useSpinner('Bundling', () => execa('npm', ['run', 'bundle']));
const bundle = () => useSpinner('Bundling', () => execa('npm', ['run', 'bundle']));
interface SavePackageOptions {
path: string;
pkg: {};
}
export const savePackage = useSpinner<SavePackageOptions>(
'Updating package.json',
({ path, pkg }: SavePackageOptions) => fs.writeFile(path, JSON.stringify(pkg, null, 2))
);
const savePackage = ({ path, pkg }: SavePackageOptions) =>
useSpinner('Updating package.json', () => fs.writeFile(path, JSON.stringify(pkg, null, 2)));
const preparePackage = async (pkg: any) => {
pkg = cloneDeep(pkg); // avoid mutations
pkg.main = 'index.js';
pkg.types = 'index.d.ts';
......@@ -58,7 +59,7 @@ const moveFiles = () => {
return useSpinner(`Moving ${files.join(', ')} files`, () => {
const promises = files.map(file => fs.copyFile(`${cwd}/${file}`, `${distDir}/${file}`));
return Promise.all(promises);
})();
});
};
const moveStaticFiles = async (pkg: any) => {
......@@ -67,7 +68,7 @@ const moveStaticFiles = async (pkg: any) => {
const staticFiles = await globby('src/**/*.{png,svg,gif,jpg}');
const promises = staticFiles.map(file => fs.copyFile(file, file.replace(/^src/, 'compiled')));
await Promise.all(promises);
})();
});
}
};
......@@ -93,7 +94,7 @@ const buildTaskRunner: TaskRunner<PackageBuildOptions> = async ({ scope }) => {
await clean();
await compile();
await moveStaticFiles(pkg);
await rollup();
await bundle();
await preparePackage(pkg);
await moveFiles();
};
......
......@@ -22,9 +22,10 @@ interface Fixable {
fix?: boolean;
}
export const bundlePlugin = useSpinner<PluginBundleOptions>('Compiling...', async options => await bundleFn(options));
const bundlePlugin = (options: PluginBundleOptions) => useSpinner('Compiling...', () => bundleFn(options));
export const clean = useSpinner('Cleaning', async () => await rimraf(`${process.cwd()}/dist`));
// @ts-ignore
const clean = () => useSpinner('Cleaning', () => rimraf(`${process.cwd()}/dist`));
const copyIfNonExistent = (srcPath: string, destPath: string) =>
copyFile(srcPath, destPath, COPYFILE_EXCL)
......@@ -35,82 +36,80 @@ const copyIfNonExistent = (srcPath: string, destPath: string) =>
}
});
export const prepare = useSpinner('Preparing', async () => {
await Promise.all([
// Remove local dependencies for @grafana/data/node_modules
// See: https://github.com/grafana/grafana/issues/26748
rimraf(resolvePath(__dirname, 'node_modules/@grafana/data/node_modules')),
// Copy only if local tsconfig does not exist. Otherwise this will work, but have odd behavior
copyIfNonExistent(
resolvePath(__dirname, '../../config/tsconfig.plugin.local.json'),
resolvePath(process.cwd(), 'tsconfig.json')
),
// Copy only if local prettierrc does not exist. Otherwise this will work, but have odd behavior
copyIfNonExistent(
resolvePath(__dirname, '../../config/prettier.plugin.rc.js'),
resolvePath(process.cwd(), '.prettierrc.js')
),
]);
// Nothing is returned
});
export const prepare = () =>
useSpinner('Preparing', () =>
Promise.all([
// Remove local dependencies for @grafana/data/node_modules
// See: https://github.com/grafana/grafana/issues/26748
rimraf(resolvePath(__dirname, 'node_modules/@grafana/data/node_modules')),
// Copy only if local tsconfig does not exist. Otherwise this will work, but have odd behavior
copyIfNonExistent(
resolvePath(__dirname, '../../config/tsconfig.plugin.local.json'),
resolvePath(process.cwd(), 'tsconfig.json')
),
// Copy only if local prettierrc does not exist. Otherwise this will work, but have odd behavior
copyIfNonExistent(
resolvePath(__dirname, '../../config/prettier.plugin.rc.js'),
resolvePath(process.cwd(), '.prettierrc.js')
),
])
);
// @ts-ignore
const typecheckPlugin = useSpinner('Typechecking', async () => {
await execa('tsc', ['--noEmit']);
});
const typecheckPlugin = () => useSpinner('Typechecking', () => execa('tsc', ['--noEmit']));
const getTypescriptSources = () => globby(resolvePath(process.cwd(), 'src/**/*.+(ts|tsx)'));
// @ts-ignore
const getStylesSources = () => globby(resolvePath(process.cwd(), 'src/**/*.+(scss|css)'));
export const lintPlugin = useSpinner<Fixable>('Linting', async ({ fix } = {}) => {
try {
// Show a warning if the tslint file exists
await access(resolvePath(process.cwd(), 'tslint.json'));
console.log('\n');
console.log('--------------------------------------------------------------');
console.log('NOTE: @grafana/toolkit has migrated to use eslint');
console.log('Update your configs to use .eslintrc rather than tslint.json');
console.log('--------------------------------------------------------------');
} catch {
// OK: tslint does not exist
}
// @todo should remove this because the config file could be in a parent dir or within package.json
const configFile = await globby(resolvePath(process.cwd(), '.eslintrc?(.cjs|.js|.json|.yaml|.yml)')).then(
filePaths => {
if (filePaths.length > 0) {
return filePaths[0];
} else {
return resolvePath(__dirname, '../../config/eslint.plugin.json');
}
export const lintPlugin = ({ fix }: Fixable = {}) =>
useSpinner('Linting', async () => {
try {
// Show a warning if the tslint file exists
await access(resolvePath(process.cwd(), 'tslint.json'));
console.log('\n');
console.log('--------------------------------------------------------------');
console.log('NOTE: @grafana/toolkit has migrated to use eslint');
console.log('Update your configs to use .eslintrc rather than tslint.json');
console.log('--------------------------------------------------------------');
} catch {
// OK: tslint does not exist
}
);
const cli = new CLIEngine({
configFile,
fix,
});
// @todo should remove this because the config file could be in a parent dir or within package.json
const configFile = await globby(resolvePath(process.cwd(), '.eslintrc?(.cjs|.js|.json|.yaml|.yml)')).then(
filePaths => {
if (filePaths.length > 0) {
return filePaths[0];
} else {
return resolvePath(__dirname, '../../config/eslint.plugin.json');
}
}
);
const report = cli.executeOnFiles(await getTypescriptSources());
const cli = new CLIEngine({
configFile,
fix,
});
const report = cli.executeOnFiles(await getTypescriptSources());
if (fix) {
CLIEngine.outputFixes(report);
}
if (fix) {
CLIEngine.outputFixes(report);
}
const { errorCount, results, warningCount } = report;
const { errorCount, results, warningCount } = report;
if (errorCount > 0 || warningCount > 0) {
const formatter = cli.getFormatter();
console.log('\n');
console.log(formatter(results));
console.log('\n');
throw new Error(`${errorCount + warningCount} linting errors found in ${results.length} files`);
}
});
if (errorCount > 0 || warningCount > 0) {
const formatter = cli.getFormatter();
console.log('\n');
console.log(formatter(results));
console.log('\n');
throw new Error(`${errorCount + warningCount} linting errors found in ${results.length} files`);
}
});
export const pluginBuildRunner: TaskRunner<PluginBuildOptions> = async ({ coverage }) => {
await prepare();
......
......@@ -7,31 +7,29 @@ import { lintPlugin } from './plugin.build';
import execa = require('execa');
import path = require('path');
const bundlePlugin = useSpinner<PluginBundleOptions>('Bundling plugin in dev mode', options => {
return bundleFn(options);
});
const yarnlink = useSpinner('Linking local toolkit', async () => {
try {
// Make sure we are not using package.json defined toolkit
await execa('yarn', ['remove', '@grafana/toolkit']);
} catch (e) {
console.log('\n', e.message, '\n');
}
await execa('yarn', ['link', '@grafana/toolkit']);
// Add all the same dependencies as toolkit
const args: string[] = ['add'];
const packages = require(path.resolve(__dirname, '../../../package.json'));
for (const [key, value] of Object.entries(packages.dependencies)) {
args.push(`${key}@${value}`);
}
await execa('yarn', args);
console.log('Added dependencies required by local @grafana/toolkit. Do not checkin this package.json!');
return Promise.resolve();
});
const bundlePlugin = (options: PluginBundleOptions) =>
useSpinner('Bundling plugin in dev mode', () => bundleFn(options));
const yarnlink = () =>
useSpinner('Linking local toolkit', async () => {
try {
// Make sure we are not using package.json defined toolkit
await execa('yarn', ['remove', '@grafana/toolkit']);
} catch (e) {
console.log('\n', e.message, '\n');
}
await execa('yarn', ['link', '@grafana/toolkit']);
// Add all the same dependencies as toolkit
const args: string[] = ['add'];
const packages = require(path.resolve(__dirname, '../../../package.json'));
for (const [key, value] of Object.entries(packages.dependencies)) {
args.push(`${key}@${value}`);
}
await execa('yarn', args);
console.log('Added dependencies required by local @grafana/toolkit. Do not checkin this package.json!');
});
const pluginDevRunner: TaskRunner<PluginBundleOptions> = async options => {
if (options.yarnlink) {
......
......@@ -5,19 +5,18 @@ import path = require('path');
interface UpdatePluginTask {}
const updateCiConfig = useSpinner<any>('Updating CircleCI config', async () => {
const ciConfigPath = path.join(process.cwd(), '.circleci');
if (!fs.existsSync(ciConfigPath)) {
fs.mkdirSync(ciConfigPath);
}
const updateCiConfig = () =>
useSpinner('Updating CircleCI config', async () => {
const ciConfigPath = path.join(process.cwd(), '.circleci');
if (!fs.existsSync(ciConfigPath)) {
fs.mkdirSync(ciConfigPath);
}
const sourceFile = path.join('node_modules/@grafana/toolkit/config/circleci', 'config.yml');
const destFile = path.join(ciConfigPath, 'config.yml');
fs.copyFileSync(sourceFile, destFile);
});
const sourceFile = path.join('node_modules/@grafana/toolkit/config/circleci', 'config.yml');
const destFile = path.join(ciConfigPath, 'config.yml');
fs.copyFileSync(sourceFile, destFile);
});
const pluginUpdateRunner: TaskRunner<UpdatePluginTask> = async () => {
await updateCiConfig({});
};
const pluginUpdateRunner: TaskRunner<UpdatePluginTask> = () => updateCiConfig();
export const pluginUpdateTask = new Task<UpdatePluginTask>('Update Plugin', pluginUpdateRunner);
......@@ -58,85 +58,86 @@ const gitUrlParse = (url: string): { owner: string; name: string } => {
throw `Could not find a suitable git repository. Received [${url}]`;
};
const prepareRelease = useSpinner<any>('Preparing release', async ({ dryrun, verbose }) => {
const ciDir = getCiFolder();
const distDir = path.resolve(ciDir, 'dist');
const distContentDir = path.resolve(distDir, getPluginId());
const pluginJsonFile = path.resolve(distContentDir, 'plugin.json');
const pluginJson = getPluginJson(pluginJsonFile);
const githubPublishScript: Command = [
['git', ['config', 'user.email', DEFAULT_EMAIL_ADDRESS]],
['git', ['config', 'user.name', DEFAULT_USERNAME]],
await checkoutBranch(`release-${pluginJson.info.version}`),
['/bin/rm', ['-rf', 'dist'], { dryrun }],
['mv', ['-v', distContentDir, 'dist']],
['git', ['add', '--force', 'dist'], { dryrun }],
['/bin/rm', ['-rf', 'src'], { enterprise: true }],
['git', ['rm', '-rf', 'src'], { enterprise: true }],
[
'git',
['commit', '-m', `automated release ${pluginJson.info.version} [skip ci]`],
{
dryrun,
okOnError: [/nothing to commit/g, /nothing added to commit/g, /no changes added to commit/g],
},
],
['git', ['push', '-f', 'origin', `release-${pluginJson.info.version}`], { dryrun }],
['git', ['tag', '-f', `v${pluginJson.info.version}`]],
['git', ['push', '-f', 'origin', `v${pluginJson.info.version}`]],
];
for (let line of githubPublishScript) {
const opts = line.length === 3 ? line[2] : {};
const command = line[0];
const args = line[1];
try {
if (verbose) {
console.log('executing >>', line);
}
if (line.length > 0 && line[0].length > 0) {
if (opts['dryrun']) {
line[1].push('--dry-run');
const prepareRelease = ({ dryrun, verbose }: any) =>
useSpinner('Preparing release', async () => {
const ciDir = getCiFolder();
const distDir = path.resolve(ciDir, 'dist');
const distContentDir = path.resolve(distDir, getPluginId());
const pluginJsonFile = path.resolve(distContentDir, 'plugin.json');
const pluginJson = getPluginJson(pluginJsonFile);
const githubPublishScript: Command = [
['git', ['config', 'user.email', DEFAULT_EMAIL_ADDRESS]],
['git', ['config', 'user.name', DEFAULT_USERNAME]],
await checkoutBranch(`release-${pluginJson.info.version}`),
['/bin/rm', ['-rf', 'dist'], { dryrun }],
['mv', ['-v', distContentDir, 'dist']],
['git', ['add', '--force', 'dist'], { dryrun }],
['/bin/rm', ['-rf', 'src'], { enterprise: true }],
['git', ['rm', '-rf', 'src'], { enterprise: true }],
[
'git',
['commit', '-m', `automated release ${pluginJson.info.version} [skip ci]`],
{
dryrun,
okOnError: [/nothing to commit/g, /nothing added to commit/g, /no changes added to commit/g],
},
],
['git', ['push', '-f', 'origin', `release-${pluginJson.info.version}`], { dryrun }],
['git', ['tag', '-f', `v${pluginJson.info.version}`]],
['git', ['push', '-f', 'origin', `v${pluginJson.info.version}`]],
];
for (let line of githubPublishScript) {
const opts = line.length === 3 ? line[2] : {};
const command = line[0];
const args = line[1];
try {
if (verbose) {
console.log('executing >>', line);
}
// Exit if the plugin is NOT an enterprise plugin
if (pluginJson.enterprise && !opts['enterprise']) {
continue;
}
if (line.length > 0 && line[0].length > 0) {
if (opts['dryrun']) {
line[1].push('--dry-run');
}
const { stdout } = await execa(command, args);
if (verbose) {
console.log(stdout);
}
} else {
if (verbose) {
console.log('skipping empty line');
}
}
} catch (ex) {
const err: string = ex.message;
if (opts['okOnError'] && Array.isArray(opts['okOnError'])) {
let trueError = true;
for (let regex of opts['okOnError']) {
if (err.match(regex)) {
trueError = false;
break;
// Exit if the plugin is NOT an enterprise plugin
if (pluginJson.enterprise && !opts['enterprise']) {
continue;
}
const { stdout } = await execa(command, args);
if (verbose) {
console.log(stdout);
}
} else {
if (verbose) {
console.log('skipping empty line');
}
}
} catch (ex) {
const err: string = ex.message;
if (opts['okOnError'] && Array.isArray(opts['okOnError'])) {
let trueError = true;
for (let regex of opts['okOnError']) {
if (err.match(regex)) {
trueError = false;
break;
}
}
if (!trueError) {
// This is not an error
continue;
if (!trueError) {
// This is not an error
continue;
}
}
console.error(err);
process.exit(-1);
}
console.error(err);
process.exit(-1);
}
}
});
});
interface GithubPublishReleaseOptions {
commitHash?: string;
......@@ -145,13 +146,11 @@ interface GithubPublishReleaseOptions {
gitRepoName: string;
}
const createRelease = useSpinner<GithubPublishReleaseOptions>(
'Creating release',
async ({ commitHash, githubUser, githubToken, gitRepoName }) => {
const createRelease = ({ commitHash, githubUser, githubToken, gitRepoName }: GithubPublishReleaseOptions) =>
useSpinner('Creating release', async () => {
const gitRelease = new GitHubRelease(githubToken, githubUser, gitRepoName, await releaseNotes(), commitHash);
return gitRelease.release();
}
);
});
export interface GithubPublishOptions {
dryrun?: boolean;
......
......@@ -9,7 +9,7 @@ export interface PluginBundleOptions {
yarnlink?: boolean;
}
// export const bundlePlugin = useSpinner<PluginBundleOptions>('Bundle plugin', ({ watch }) => {
// export const bundlePlugin = ({ watch, production }: PluginBundleOptions) => useSpinner('Bundle plugin', async () => {
export const bundlePlugin = async ({ watch, production }: PluginBundleOptions) => {
const compiler = webpack(
await loadWebpackConfig({
......
......@@ -97,21 +97,26 @@ export const promptPluginDetails = async (name?: string) => {
};
};
export const fetchTemplate = useSpinner<{ type: PluginType; dest: string }>(
'Fetching plugin template...',
async ({ type, dest }) => {
export const fetchTemplate = ({ type, dest }: { type: PluginType; dest: string }) =>
useSpinner('Fetching plugin template...', async () => {
const url = RepositoriesPaths[type];
if (!url) {
throw new Error('Unknown plugin type');
}
await simpleGit.clone(url, dest);
}
);
});
export const prepareJsonFiles = useSpinner<{ type: PluginType; pluginDetails: PluginDetails; pluginPath: string }>(
'Saving package.json and plugin.json files',
async ({ type, pluginDetails, pluginPath }) => {
export const prepareJsonFiles = ({
type,
pluginDetails,
pluginPath,
}: {
type: PluginType;
pluginDetails: PluginDetails;
pluginPath: string;
}) =>
useSpinner('Saving package.json and plugin.json files', async () => {
const packageJsonPath = path.resolve(pluginPath, 'package.json');
const pluginJsonPath = path.resolve(pluginPath, 'src/plugin.json');
const packageJson: any = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
......@@ -146,12 +151,10 @@ export const prepareJsonFiles = useSpinner<{ type: PluginType; pluginDetails: Pl
return fs.writeFile(filePath, JSON.stringify(f, null, 2));
})
);
}
);
});
export const removeGitFiles = useSpinner<string>('Cleaning', async pluginPath =>
rmdir(`${path.resolve(pluginPath, '.git')}`)
);
export const removeGitFiles = (pluginPath: string) =>
useSpinner('Cleaning', async () => rmdir(`${path.resolve(pluginPath, '.git')}`));
/* eslint-disable no-console */
export const formatPluginDetails = (details: PluginDetails) => {
......
......@@ -10,9 +10,8 @@ export interface PluginTestOptions {
testNamePattern?: string;
}
export const testPlugin = useSpinner<PluginTestOptions>(
'Running tests',
async ({ updateSnapshot, coverage, watch, testPathPattern, testNamePattern }) => {
export const testPlugin = ({ updateSnapshot, coverage, watch, testPathPattern, testNamePattern }: PluginTestOptions) =>
useSpinner('Running tests', async () => {
const testConfig = loadJestPluginConfig();
const cliConfig = {
......@@ -38,5 +37,4 @@ export const testPlugin = useSpinner<PluginTestOptions>(
throw new Error('Tests failed');
}
}
}
);
});
......@@ -8,35 +8,30 @@ const path = require('path');
let distDir: string, cwd: string;
// @ts-ignore
export const clean = useSpinner('Cleaning', async () => await execa('npm', ['run', 'clean']));
// @ts-ignore
const compile = useSpinner('Compiling sources', async () => {
try {
await execa('tsc', ['-p', './tsconfig.json']);
} catch (e) {
console.log(e);
throw e;
}
});
// @ts-ignore
export const savePackage = useSpinner<{
path: string;
pkg: {};
// @ts-ignore
}>('Updating package.json', async ({ path, pkg }) => {
return new Promise((resolve, reject) => {
fs.writeFile(path, JSON.stringify(pkg, null, 2), err => {
if (err) {
reject(err);
return;
}
resolve();
const clean = () => useSpinner('Cleaning', () => execa('npm', ['run', 'clean']));
const compile = () =>
useSpinner('Compiling sources', async () => {
try {
await execa('tsc', ['-p', './tsconfig.json']);
} catch (e) {
console.log(e);
throw e;
}
});
const savePackage = ({ path, pkg }: { path: string; pkg: {} }) =>
useSpinner('Updating package.json', async () => {
new Promise((resolve, reject) => {
fs.writeFile(path, JSON.stringify(pkg, null, 2), err => {
if (err) {
reject(err);
return;
}
resolve();
});
});
});
});
const preparePackage = async (pkg: any) => {
pkg.bin = {
......@@ -63,7 +58,7 @@ const copyFiles = () => {
'src/config/styles.mock.js',
'src/config/jest.plugin.config.local.js',
];
// @ts-ignore
return useSpinner(`Moving ${files.join(', ')} files`, async () => {
const promises = files.map(file => {
return new Promise((resolve, reject) => {
......@@ -82,12 +77,11 @@ const copyFiles = () => {
});
await Promise.all(promises);
})();
});
};
const copySassFiles = () => {
const files = ['_variables.generated.scss', '_variables.dark.generated.scss', '_variables.light.generated.scss'];
// @ts-ignore
return useSpinner(`Copy scss files ${files.join(', ')} files`, async () => {
const sassDir = path.resolve(cwd, '../../public/sass/');
const promises = files.map(file => {
......@@ -104,7 +98,7 @@ const copySassFiles = () => {
});
await Promise.all(promises);
})();
});
};
interface ToolkitBuildOptions {}
......
import ora = require('ora');
import ora from 'ora';
type FnToSpin<T> = (options: T) => Promise<any>;
export function useSpinner<T = void>(spinnerLabel: string, fn: FnToSpin<T>, killProcess = true) {
return async (options: T) => {
const spinner = ora(spinnerLabel);
spinner.start();
try {
await fn(options);
spinner.succeed();
} catch (e) {
console.trace(e); // eslint-disable-line no-console
spinner.fail(e.message || e);
if (killProcess) {
process.exit(1);
}
export const useSpinner = async (label: string, fn: () => Promise<any>, killProcess = true) => {
const spinner = ora(label);
spinner.start();
try {
await fn();
spinner.succeed();
} catch (e) {
console.trace(e); // eslint-disable-line no-console
spinner.fail(e.message || e);
if (killProcess) {
process.exit(1);
}
};
}
}
};
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