mirror of
https://github.com/grafana/grafana.git
synced 2026-07-30 08:18:10 -05:00
Toolkit: moved front end cli scripts to separate package and introduced very early version of plugin tools
* Move cli to grafana-toolkit * Moving packages, fixing ts * Add basics of plugin build task * Add toolkit build task * Circle - use node 10 for test-frontend * Prettier fix * First attempt for having shared tsconfig for plugins * Add enzyme as peer depencency * Do not expose internal commands when using toolkit from npm package * Introduce plugin linting * Fix missing file * Fix shim extenstion * Remove rollup typings * Add tslint as dependency * Toolkit - use the same versions of enzyme and tslint as core does * Remove include property from plugin tsconfig * Take failed suites into consideration when tests failed * Set ts-jest preset for jest * Cleanup tsconfig.plugins * Add plugin:test task * Rename file causing build failute * Fixing those missed renames * Add ts as peer dependency * Remove enzyme dependency and tweak test plugin task * Allow jest options overrides via package.json config * Improvements * Remove rollup node packages * TMP : Fix ts errors when linked * use local tslint if it exists * support coverage commands * Fix merge * fix build * Some minors * Make jest pass when no tests discovered
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import fs from 'fs';
|
||||
import * as fs from 'fs';
|
||||
import darkTheme from '@grafana/ui/src/themes/dark';
|
||||
import lightTheme from '@grafana/ui/src/themes/light';
|
||||
import defaultTheme from '@grafana/ui/src/themes/default';
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
export type Task<T> = (options: T) => Promise<void>;
|
||||
@@ -1,108 +0,0 @@
|
||||
import program from 'commander';
|
||||
import { execTask } from './utils/execTask';
|
||||
import chalk from 'chalk';
|
||||
import { startTask } from './tasks/core.start';
|
||||
import { buildTask } from './tasks/grafanaui.build';
|
||||
import { releaseTask } from './tasks/grafanaui.release';
|
||||
import { changelogTask } from './tasks/changelog';
|
||||
import { cherryPickTask } from './tasks/cherrypick';
|
||||
import { closeMilestoneTask } from './tasks/closeMilestone';
|
||||
import { precommitTask } from './tasks/precommit';
|
||||
import { searchTestDataSetupTask } from './tasks/searchTestDataSetup';
|
||||
|
||||
program.option('-d, --depreciate <scripts>', 'Inform about npm script deprecation', v => v.split(','));
|
||||
|
||||
program
|
||||
.command('core:start')
|
||||
.option('-h, --hot', 'Run front-end with HRM enabled')
|
||||
.option('-t, --watchTheme', 'Watch for theme changes and regenerate variables.scss files')
|
||||
.description('Starts Grafana front-end in development mode with watch enabled')
|
||||
.action(async cmd => {
|
||||
await execTask(startTask)({
|
||||
watchThemes: cmd.watchTheme,
|
||||
hot: cmd.hot,
|
||||
});
|
||||
});
|
||||
|
||||
program
|
||||
.command('gui:build')
|
||||
.description('Builds @grafana/ui package to packages/grafana-ui/dist')
|
||||
.action(async cmd => {
|
||||
await execTask(buildTask)();
|
||||
});
|
||||
|
||||
program
|
||||
.command('gui:release')
|
||||
.description('Prepares @grafana/ui release (and publishes to npm on demand)')
|
||||
.option('-p, --publish', 'Publish @grafana/ui to npm registry')
|
||||
.option('-u, --usePackageJsonVersion', 'Use version specified in package.json')
|
||||
.option('--createVersionCommit', 'Create and push version commit')
|
||||
.action(async cmd => {
|
||||
await execTask(releaseTask)({
|
||||
publishToNpm: !!cmd.publish,
|
||||
usePackageJsonVersion: !!cmd.usePackageJsonVersion,
|
||||
createVersionCommit: !!cmd.createVersionCommit,
|
||||
});
|
||||
});
|
||||
|
||||
program
|
||||
.command('changelog')
|
||||
.option('-m, --milestone <milestone>', 'Specify milestone')
|
||||
.description('Builds changelog markdown')
|
||||
.action(async cmd => {
|
||||
if (!cmd.milestone) {
|
||||
console.log('Please specify milestone, example: -m <milestone id from github milestone URL>');
|
||||
return;
|
||||
}
|
||||
|
||||
await execTask(changelogTask)({
|
||||
milestone: cmd.milestone,
|
||||
});
|
||||
});
|
||||
|
||||
program
|
||||
.command('cherrypick')
|
||||
.description('Helps find commits to cherry pick')
|
||||
.action(async cmd => {
|
||||
await execTask(cherryPickTask)({});
|
||||
});
|
||||
|
||||
program
|
||||
.command('close-milestone')
|
||||
.option('-m, --milestone <milestone>', 'Specify milestone')
|
||||
.description('Helps ends a milestone by removing the cherry-pick label and closing it')
|
||||
.action(async cmd => {
|
||||
if (!cmd.milestone) {
|
||||
console.log('Please specify milestone, example: -m <milestone id from github milestone URL>');
|
||||
return;
|
||||
}
|
||||
|
||||
await execTask(closeMilestoneTask)({
|
||||
milestone: cmd.milestone,
|
||||
});
|
||||
});
|
||||
|
||||
program
|
||||
.command('precommit')
|
||||
.description('Executes checks')
|
||||
.action(async cmd => {
|
||||
await execTask(precommitTask)({});
|
||||
});
|
||||
|
||||
program
|
||||
.command('searchTestData')
|
||||
.option('-c, --count <number_of_dashboards>', 'Specify number of dashboards')
|
||||
.description('Setup test data for search')
|
||||
.action(async cmd => {
|
||||
await execTask(searchTestDataSetupTask)({ count: cmd.count });
|
||||
});
|
||||
|
||||
program.parse(process.argv);
|
||||
|
||||
if (program.depreciate && program.depreciate.length === 2) {
|
||||
console.log(
|
||||
chalk.yellow.bold(
|
||||
`[NPM script depreciation] ${program.depreciate[0]} is deprecated! Use ${program.depreciate[1]} instead!`
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import { Task, TaskRunner } from './task';
|
||||
import GithubClient from '../utils/githubClient';
|
||||
|
||||
interface ChangelogOptions {
|
||||
milestone: string;
|
||||
}
|
||||
|
||||
const changelogTaskRunner: TaskRunner<ChangelogOptions> = async ({ milestone }) => {
|
||||
const githubClient = new GithubClient();
|
||||
const client = githubClient.client;
|
||||
|
||||
if (!/^\d+$/.test(milestone)) {
|
||||
console.log('Use milestone number not title, find number in milestone url');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await client.get('/issues', {
|
||||
params: {
|
||||
state: 'closed',
|
||||
per_page: 100,
|
||||
labels: 'add to changelog',
|
||||
milestone: milestone,
|
||||
},
|
||||
});
|
||||
|
||||
const issues = res.data;
|
||||
|
||||
const bugs = _.sortBy(
|
||||
issues.filter(item => {
|
||||
if (item.title.match(/fix|fixes/i)) {
|
||||
return true;
|
||||
}
|
||||
if (item.labels.find(label => label.name === 'type/bug')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}),
|
||||
'title'
|
||||
);
|
||||
|
||||
const notBugs = _.sortBy(issues.filter(item => !bugs.find(bug => bug === item)), 'title');
|
||||
|
||||
let markdown = '';
|
||||
|
||||
if (notBugs.length > 0) {
|
||||
markdown = '### Features / Enhancements\n';
|
||||
}
|
||||
|
||||
for (const item of notBugs) {
|
||||
markdown += getMarkdownLineForIssue(item);
|
||||
}
|
||||
|
||||
if (bugs.length > 0) {
|
||||
markdown += '\n### Bug Fixes\n';
|
||||
}
|
||||
|
||||
for (const item of bugs) {
|
||||
markdown += getMarkdownLineForIssue(item);
|
||||
}
|
||||
|
||||
console.log(markdown);
|
||||
};
|
||||
|
||||
function getMarkdownLineForIssue(item: any) {
|
||||
const githubGrafanaUrl = 'https://github.com/grafana/grafana';
|
||||
let markdown = '';
|
||||
const title = item.title.replace(/^([^:]*)/, (match, g1) => {
|
||||
return `**${g1}**`;
|
||||
});
|
||||
|
||||
markdown += '* ' + title + '.';
|
||||
markdown += ` [#${item.number}](${githubGrafanaUrl}/pull/${item.number})`;
|
||||
markdown += `, [@${item.user.login}](${item.user.html_url})`;
|
||||
|
||||
markdown += '\n';
|
||||
|
||||
return markdown;
|
||||
}
|
||||
|
||||
export const changelogTask = new Task<ChangelogOptions>();
|
||||
changelogTask.setName('Changelog generator task');
|
||||
changelogTask.setRunner(changelogTaskRunner);
|
||||
@@ -1,47 +0,0 @@
|
||||
import { Task, TaskRunner } from './task';
|
||||
import GithubClient from '../utils/githubClient';
|
||||
|
||||
interface CherryPickOptions {}
|
||||
|
||||
const cherryPickRunner: TaskRunner<CherryPickOptions> = async () => {
|
||||
const githubClient = new GithubClient();
|
||||
const client = githubClient.client;
|
||||
|
||||
const res = await client.get('/issues', {
|
||||
params: {
|
||||
state: 'closed',
|
||||
labels: 'cherry-pick needed',
|
||||
},
|
||||
});
|
||||
|
||||
// sort by closed date ASC
|
||||
res.data.sort(function(a, b) {
|
||||
return new Date(a.closed_at).getTime() - new Date(b.closed_at).getTime();
|
||||
});
|
||||
|
||||
let commands = '';
|
||||
|
||||
console.log('--------------------------------------------------------------------');
|
||||
console.log('Printing PRs with cherry-pick-needed, in ASC merge date order');
|
||||
console.log('--------------------------------------------------------------------');
|
||||
|
||||
for (const item of res.data) {
|
||||
if (!item.milestone) {
|
||||
console.log(item.number + ' missing milestone!');
|
||||
continue;
|
||||
}
|
||||
|
||||
const issueDetails = await client.get(item.pull_request.url);
|
||||
console.log(`* ${item.title}, (#${item.number}), merge-sha: ${issueDetails.data.merge_commit_sha}`);
|
||||
commands += `git cherry-pick -x ${issueDetails.data.merge_commit_sha}\n`;
|
||||
}
|
||||
|
||||
console.log('--------------------------------------------------------------------');
|
||||
console.log('Commands (in order of how they should be executed)');
|
||||
console.log('--------------------------------------------------------------------');
|
||||
console.log(commands);
|
||||
};
|
||||
|
||||
export const cherryPickTask = new Task<CherryPickOptions>();
|
||||
cherryPickTask.setName('Cherry pick task');
|
||||
cherryPickTask.setRunner(cherryPickRunner);
|
||||
@@ -1,75 +0,0 @@
|
||||
import { Task, TaskRunner } from './task';
|
||||
import GithubClient from '../utils/githubClient';
|
||||
|
||||
interface CloseMilestoneOptions {
|
||||
milestone: string;
|
||||
}
|
||||
|
||||
const closeMilestoneTaskRunner: TaskRunner<CloseMilestoneOptions> = async ({ milestone }) => {
|
||||
const githubClient = new GithubClient(true);
|
||||
|
||||
const cherryPickLabel = 'cherry-pick needed';
|
||||
const client = githubClient.client;
|
||||
|
||||
if (!/^\d+$/.test(milestone)) {
|
||||
console.log('Use milestone number not title, find number in milestone url');
|
||||
return;
|
||||
}
|
||||
|
||||
const milestoneRes = await client.get(`/milestones/${milestone}`, {});
|
||||
|
||||
const milestoneState = milestoneRes.data.state;
|
||||
|
||||
if (milestoneState === 'closed') {
|
||||
console.log('milestone already closed. ✅');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('fetching issues/PRs of the milestone ⏬');
|
||||
|
||||
// Get all the issues/PRs with the label cherry-pick
|
||||
// Every pull request is actually an issue
|
||||
const issuesRes = await client.get('/issues', {
|
||||
params: {
|
||||
state: 'closed',
|
||||
labels: cherryPickLabel,
|
||||
per_page: 100,
|
||||
milestone: milestone,
|
||||
},
|
||||
});
|
||||
|
||||
if (issuesRes.data.length < 1) {
|
||||
console.log('no issues to remove label from');
|
||||
} else {
|
||||
console.log(`found ${issuesRes.data.length} issues to remove the cherry-pick label from 🔎`);
|
||||
}
|
||||
|
||||
for (const issue of issuesRes.data) {
|
||||
// the reason for using stdout.write is for achieving 'action -> result' on
|
||||
// the same line
|
||||
process.stdout.write(`🔧removing label from issue #${issue.number} 🗑...`);
|
||||
const resDelete = await client.delete(`/issues/${issue.number}/labels/${cherryPickLabel}`, {});
|
||||
if (resDelete.status === 200) {
|
||||
process.stdout.write('done ✅\n');
|
||||
} else {
|
||||
console.log('failed ❌');
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`cleaned up ${issuesRes.data.length} issues/prs ⚡️`);
|
||||
|
||||
const resClose = await client.patch(`/milestones/${milestone}`, {
|
||||
state: 'closed',
|
||||
});
|
||||
|
||||
if (resClose.status === 200) {
|
||||
console.log('milestone closed 🙌');
|
||||
} else {
|
||||
console.log('failed to close the milestone, response:');
|
||||
console.log(resClose);
|
||||
}
|
||||
};
|
||||
|
||||
export const closeMilestoneTask = new Task<CloseMilestoneOptions>();
|
||||
closeMilestoneTask.setName('Close Milestone generator task');
|
||||
closeMilestoneTask.setRunner(closeMilestoneTaskRunner);
|
||||
@@ -1,38 +0,0 @@
|
||||
import concurrently from 'concurrently';
|
||||
import { Task, TaskRunner } from './task';
|
||||
|
||||
interface StartTaskOptions {
|
||||
watchThemes: boolean;
|
||||
hot: boolean;
|
||||
}
|
||||
|
||||
const startTaskRunner: TaskRunner<StartTaskOptions> = async ({ watchThemes, hot }) => {
|
||||
const jobs = [
|
||||
watchThemes && {
|
||||
command: 'nodemon -e ts -w ./packages/grafana-ui/src/themes -x yarn run themes:generate',
|
||||
name: 'SASS variables generator',
|
||||
},
|
||||
hot
|
||||
? {
|
||||
command: 'webpack-dev-server --progress --colors --mode development --config scripts/webpack/webpack.hot.js',
|
||||
name: 'Dev server',
|
||||
}
|
||||
: {
|
||||
command: 'webpack --progress --colors --watch --mode development --config scripts/webpack/webpack.dev.js',
|
||||
name: 'Webpack',
|
||||
},
|
||||
];
|
||||
|
||||
try {
|
||||
await concurrently(jobs.filter(job => !!job), {
|
||||
killOthers: ['failure', 'failure'],
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
export const startTask = new Task<StartTaskOptions>();
|
||||
startTask.setName('Core startTask');
|
||||
startTask.setRunner(startTaskRunner);
|
||||
@@ -1,76 +0,0 @@
|
||||
import execa from 'execa';
|
||||
import fs from 'fs';
|
||||
import { changeCwdToGrafanaUi, restoreCwd } from '../utils/cwd';
|
||||
import chalk from 'chalk';
|
||||
import { useSpinner } from '../utils/useSpinner';
|
||||
import { Task, TaskRunner } from './task';
|
||||
|
||||
let distDir, cwd;
|
||||
|
||||
export const clean = useSpinner<void>('Cleaning', async () => await execa('npm', ['run', 'clean']));
|
||||
|
||||
const compile = useSpinner<void>('Compiling sources', () => execa('tsc', ['-p', './tsconfig.build.json']));
|
||||
|
||||
const rollup = useSpinner<void>('Bundling', () => execa('npm', ['run', 'build']));
|
||||
|
||||
export const savePackage = useSpinner<{
|
||||
path: string;
|
||||
pkg: {};
|
||||
}>('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 preparePackage = async pkg => {
|
||||
pkg.main = 'index.js';
|
||||
pkg.types = 'index.d.ts';
|
||||
await savePackage({
|
||||
path: `${cwd}/dist/package.json`,
|
||||
pkg,
|
||||
});
|
||||
};
|
||||
|
||||
const moveFiles = () => {
|
||||
const files = ['README.md', 'CHANGELOG.md', 'index.js'];
|
||||
return useSpinner<void>(`Moving ${files.join(', ')} files`, async () => {
|
||||
const promises = files.map(file => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.copyFile(`${cwd}/${file}`, `${distDir}/${file}`, err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
})();
|
||||
};
|
||||
|
||||
const buildTaskRunner: TaskRunner<void> = async () => {
|
||||
cwd = changeCwdToGrafanaUi();
|
||||
distDir = `${cwd}/dist`;
|
||||
const pkg = require(`${cwd}/package.json`);
|
||||
console.log(chalk.yellow(`Building ${pkg.name} (package.json version: ${pkg.version})`));
|
||||
|
||||
await clean();
|
||||
await compile();
|
||||
await rollup();
|
||||
await preparePackage(pkg);
|
||||
await moveFiles();
|
||||
|
||||
restoreCwd();
|
||||
};
|
||||
|
||||
export const buildTask = new Task<void>();
|
||||
buildTask.setName('@grafana/ui build');
|
||||
buildTask.setRunner(buildTaskRunner);
|
||||
@@ -1,195 +0,0 @@
|
||||
import execa from 'execa';
|
||||
import { execTask } from '../utils/execTask';
|
||||
import { changeCwdToGrafanaUiDist, changeCwdToGrafanaUi, restoreCwd } from '../utils/cwd';
|
||||
import semver from 'semver';
|
||||
import inquirer from 'inquirer';
|
||||
import chalk from 'chalk';
|
||||
import { useSpinner } from '../utils/useSpinner';
|
||||
import { savePackage, buildTask, clean } from './grafanaui.build';
|
||||
import { TaskRunner, Task } from './task';
|
||||
|
||||
type VersionBumpType = 'prerelease' | 'patch' | 'minor' | 'major';
|
||||
interface ReleaseTaskOptions {
|
||||
publishToNpm: boolean;
|
||||
usePackageJsonVersion: boolean;
|
||||
createVersionCommit: boolean;
|
||||
}
|
||||
|
||||
const promptBumpType = async () => {
|
||||
return inquirer.prompt<{ type: VersionBumpType }>([
|
||||
{
|
||||
type: 'list',
|
||||
message: 'Select version bump',
|
||||
name: 'type',
|
||||
choices: ['prerelease', 'patch', 'minor', 'major'],
|
||||
validate: answer => {
|
||||
if (answer.length < 1) {
|
||||
return 'You must choose something';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const promptPrereleaseId = async (message = 'Is this a prerelease?', allowNo = true) => {
|
||||
return inquirer.prompt<{ id: string }>([
|
||||
{
|
||||
type: 'list',
|
||||
message: message,
|
||||
name: 'id',
|
||||
choices: allowNo ? ['no', 'alpha', 'beta'] : ['alpha', 'beta'],
|
||||
validate: answer => {
|
||||
if (answer.length < 1) {
|
||||
return 'You must choose something';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
const promptConfirm = async (message?: string) => {
|
||||
return inquirer.prompt<{ confirmed: boolean }>([
|
||||
{
|
||||
type: 'confirm',
|
||||
message: message || 'Is that correct?',
|
||||
name: 'confirmed',
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
// Since Grafana core depends on @grafana/ui highly, we run full check before release
|
||||
const runChecksAndTests = async () =>
|
||||
useSpinner<void>(`Running checks and tests`, async () => {
|
||||
await execa('npm', ['run', 'test']);
|
||||
})();
|
||||
|
||||
const bumpVersion = (version: string) =>
|
||||
useSpinner<void>(`Saving version ${version} to package.json`, async () => {
|
||||
changeCwdToGrafanaUi();
|
||||
await execa('npm', ['version', version]);
|
||||
changeCwdToGrafanaUiDist();
|
||||
const pkg = require(`${process.cwd()}/package.json`);
|
||||
pkg.version = version;
|
||||
await savePackage({ path: `${process.cwd()}/package.json`, pkg });
|
||||
})();
|
||||
|
||||
const publishPackage = (name: string, version: string) =>
|
||||
useSpinner<void>(`Publishing ${name} @ ${version} to npm registry...`, async () => {
|
||||
changeCwdToGrafanaUiDist();
|
||||
await execa('npm', ['publish', '--access', 'public']);
|
||||
})();
|
||||
|
||||
const ensureMasterBranch = async () => {
|
||||
const currentBranch = await execa.stdout('git', ['symbolic-ref', '--short', 'HEAD']);
|
||||
const status = await execa.stdout('git', ['status', '--porcelain']);
|
||||
|
||||
if (currentBranch !== 'master' && status !== '') {
|
||||
console.error(chalk.red.bold('You need to be on clean master branch to release @grafana/ui'));
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
const prepareVersionCommitAndPush = async (version: string) =>
|
||||
useSpinner<void>('Commiting and pushing @grafana/ui version update', async () => {
|
||||
await execa.stdout('git', ['commit', '-a', '-m', `Upgrade @grafana/ui version to v${version}`]);
|
||||
await execa.stdout('git', ['push']);
|
||||
})();
|
||||
|
||||
const releaseTaskRunner: TaskRunner<ReleaseTaskOptions> = async ({
|
||||
publishToNpm,
|
||||
usePackageJsonVersion,
|
||||
createVersionCommit,
|
||||
}) => {
|
||||
changeCwdToGrafanaUi();
|
||||
await clean(); // Clean previous build if exists
|
||||
restoreCwd();
|
||||
|
||||
if (publishToNpm) {
|
||||
// TODO: Ensure release branch
|
||||
// When need to update this when we star keeping @grafana/ui releases in sync with core
|
||||
await ensureMasterBranch();
|
||||
}
|
||||
|
||||
await runChecksAndTests();
|
||||
|
||||
await execTask(buildTask)();
|
||||
|
||||
let releaseConfirmed = false;
|
||||
let nextVersion;
|
||||
changeCwdToGrafanaUiDist();
|
||||
|
||||
const pkg = require(`${process.cwd()}/package.json`);
|
||||
|
||||
console.log(`Current version: ${pkg.version}`);
|
||||
|
||||
do {
|
||||
if (!usePackageJsonVersion) {
|
||||
const { type } = await promptBumpType();
|
||||
console.log(type);
|
||||
if (type === 'prerelease') {
|
||||
const { id } = await promptPrereleaseId('What kind of prerelease?', false);
|
||||
nextVersion = semver.inc(pkg.version, type, id);
|
||||
} else {
|
||||
const { id } = await promptPrereleaseId();
|
||||
if (id !== 'no') {
|
||||
nextVersion = semver.inc(pkg.version, `pre${type}`, id);
|
||||
} else {
|
||||
nextVersion = semver.inc(pkg.version, type);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nextVersion = pkg.version;
|
||||
}
|
||||
|
||||
console.log(chalk.yellowBright.bold(`You are going to release a new version of ${pkg.name}`));
|
||||
|
||||
if (usePackageJsonVersion) {
|
||||
console.log(chalk.green(`Version based on package.json: `), chalk.bold.yellowBright(`${nextVersion}`));
|
||||
} else {
|
||||
console.log(chalk.green(`Version bump: ${pkg.version} ->`), chalk.bold.yellowBright(`${nextVersion}`));
|
||||
}
|
||||
|
||||
const { confirmed } = await promptConfirm();
|
||||
|
||||
releaseConfirmed = confirmed;
|
||||
} while (!releaseConfirmed);
|
||||
|
||||
if (!usePackageJsonVersion) {
|
||||
await bumpVersion(nextVersion);
|
||||
}
|
||||
|
||||
if (createVersionCommit) {
|
||||
await prepareVersionCommitAndPush(nextVersion);
|
||||
}
|
||||
|
||||
if (publishToNpm) {
|
||||
console.log(chalk.yellowBright.bold(`\nReview dist package.json before proceeding!\n`));
|
||||
const { confirmed } = await promptConfirm('Are you ready to publish to npm?');
|
||||
|
||||
if (!confirmed) {
|
||||
process.exit();
|
||||
}
|
||||
|
||||
await publishPackage(pkg.name, nextVersion);
|
||||
console.log(chalk.green(`\nVersion ${nextVersion} of ${pkg.name} succesfully released!`));
|
||||
console.log(chalk.yellow(`\nUpdated @grafana/ui/package.json with version bump created.`));
|
||||
|
||||
process.exit();
|
||||
} else {
|
||||
console.log(
|
||||
chalk.green(
|
||||
`\nVersion ${nextVersion} of ${pkg.name} succesfully prepared for release. See packages/grafana-ui/dist`
|
||||
)
|
||||
);
|
||||
console.log(chalk.green(`\nTo publish to npm registry run`), chalk.bold.blue(`npm run gui:publish`));
|
||||
}
|
||||
};
|
||||
|
||||
export const releaseTask = new Task<ReleaseTaskOptions>();
|
||||
releaseTask.setName('@grafana/ui release');
|
||||
releaseTask.setRunner(releaseTaskRunner);
|
||||
@@ -1,81 +0,0 @@
|
||||
import { Task, TaskRunner } from './task';
|
||||
import chalk from 'chalk';
|
||||
import get from 'lodash/get';
|
||||
import flatten from 'lodash/flatten';
|
||||
import execa = require('execa');
|
||||
const simpleGit = require('simple-git/promise')(process.cwd());
|
||||
|
||||
interface PrecommitOptions {}
|
||||
|
||||
const tasks = {
|
||||
lint: {
|
||||
sass: ['newer:sasslint'],
|
||||
core: ['newer:exec:tslintRoot'],
|
||||
gui: ['newer:exec:tslintPackages'],
|
||||
},
|
||||
typecheck: {
|
||||
core: ['newer:exec:typecheckRoot'],
|
||||
gui: ['newer:exec:typecheckPackages'],
|
||||
},
|
||||
test: {
|
||||
lint: {
|
||||
ts: ['no-only-tests'],
|
||||
go: ['no-focus-convey-tests'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const precommitRunner: TaskRunner<PrecommitOptions> = async () => {
|
||||
const status = await simpleGit.status();
|
||||
const sassFiles = status.files.filter(
|
||||
file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.scss)$/g) || file.path.indexOf('.sass-lint.yml') > -1
|
||||
);
|
||||
|
||||
const tsFiles = status.files.filter(file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.(ts|tsx))$/g));
|
||||
const testFiles = status.files.filter(file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\.test.(ts|tsx))$/g));
|
||||
const goTestFiles = status.files.filter(file => (file.path as string).match(/^[a-zA-Z0-9\_\-\/]+(\_test.go)$/g));
|
||||
const grafanaUiFiles = tsFiles.filter(file => (file.path as string).indexOf('grafana-ui') > -1);
|
||||
|
||||
const grafanaUIFilesChangedOnly = tsFiles.length > 0 && tsFiles.length - grafanaUiFiles.length === 0;
|
||||
const coreFilesChangedOnly = tsFiles.length > 0 && grafanaUiFiles.length === 0;
|
||||
|
||||
const taskPaths = [];
|
||||
|
||||
if (sassFiles.length > 0) {
|
||||
taskPaths.push('lint.sass');
|
||||
}
|
||||
|
||||
if (testFiles.length) {
|
||||
taskPaths.push('test.lint.ts');
|
||||
}
|
||||
|
||||
if (goTestFiles.length) {
|
||||
taskPaths.push('test.lint.go');
|
||||
}
|
||||
|
||||
if (tsFiles.length > 0) {
|
||||
if (grafanaUIFilesChangedOnly) {
|
||||
taskPaths.push('lint.gui', 'typecheck.core', 'typecheck.gui');
|
||||
} else if (coreFilesChangedOnly) {
|
||||
taskPaths.push('lint.core', 'typecheck.core');
|
||||
} else {
|
||||
taskPaths.push('lint.core', 'lint.gui', 'typecheck.core', 'typecheck.gui');
|
||||
}
|
||||
}
|
||||
|
||||
const gruntTasks = flatten(taskPaths.map(path => get(tasks, path)));
|
||||
if (gruntTasks.length > 0) {
|
||||
console.log(chalk.yellow(`Precommit checks: ${taskPaths.join(', ')}`));
|
||||
const task = execa('grunt', gruntTasks);
|
||||
// @ts-ignore
|
||||
const stream = task.stdout;
|
||||
stream.pipe(process.stdout);
|
||||
return task;
|
||||
}
|
||||
console.log(chalk.yellow('Skipping precommit checks, not front-end changes detected'));
|
||||
return;
|
||||
};
|
||||
|
||||
export const precommitTask = new Task<PrecommitOptions>();
|
||||
precommitTask.setName('Precommit task');
|
||||
precommitTask.setRunner(precommitRunner);
|
||||
@@ -1,117 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import _ from 'lodash';
|
||||
import { Task, TaskRunner } from './task';
|
||||
|
||||
interface SearchTestDataSetupOptions {
|
||||
count: number;
|
||||
}
|
||||
|
||||
const client = axios.create({
|
||||
baseURL: 'http://localhost:3000/api',
|
||||
auth: {
|
||||
username: 'admin',
|
||||
password: 'admin2',
|
||||
},
|
||||
});
|
||||
|
||||
export async function getUser(user): Promise<any> {
|
||||
console.log('Creating user ' + user.name);
|
||||
const search = await client.get('/users/search', {
|
||||
params: { query: user.login },
|
||||
});
|
||||
|
||||
if (search.data.totalCount === 1) {
|
||||
user.id = search.data.users[0].id;
|
||||
return user;
|
||||
}
|
||||
|
||||
const rsp = await client.post('/admin/users', user);
|
||||
user.id = rsp.data.id;
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function getTeam(team: any): Promise<any> {
|
||||
// delete if exists
|
||||
const teams = await client.get('/teams/search');
|
||||
for (const existing of teams.data.teams) {
|
||||
if (existing.name === team.name) {
|
||||
console.log('Team exists, deleting');
|
||||
await client.delete('/teams/' + existing.id);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Creating team ' + team.name);
|
||||
const teamRsp = await client.post(`/teams`, team);
|
||||
team.id = teamRsp.data.teamId;
|
||||
return team;
|
||||
}
|
||||
|
||||
export async function addToTeam(team: any, user: any): Promise<any> {
|
||||
const members = await client.get(`/teams/${team.id}/members`);
|
||||
console.log(`Adding user ${user.name} to team ${team.name}`);
|
||||
await client.post(`/teams/${team.id}/members`, { userId: user.id });
|
||||
}
|
||||
|
||||
export async function setDashboardAcl(dashboardId: any, aclList: any) {
|
||||
console.log('Setting Dashboard ACL ' + dashboardId);
|
||||
await client.post(`/dashboards/id/${dashboardId}/permissions`, { items: aclList });
|
||||
}
|
||||
|
||||
const searchTestDataSetupRunnner: TaskRunner<SearchTestDataSetupOptions> = async ({ count }) => {
|
||||
const user1 = await getUser({
|
||||
name: 'searchTestUser1',
|
||||
email: 'searchTestUser@team.com',
|
||||
login: 'searchTestUser1',
|
||||
password: '12345',
|
||||
});
|
||||
|
||||
const team1 = await getTeam({ name: 'searchTestTeam1', email: 'searchtestdata@team.com' });
|
||||
addToTeam(team1, user1);
|
||||
|
||||
// create or update folder
|
||||
const folder: any = {
|
||||
uid: 'search-test-data',
|
||||
title: 'Search test data folder',
|
||||
version: 1,
|
||||
};
|
||||
|
||||
try {
|
||||
await client.delete(`/folders/${folder.uid}`);
|
||||
} catch (err) {}
|
||||
|
||||
console.log('Creating folder');
|
||||
|
||||
const rsp = await client.post(`/folders`, folder);
|
||||
folder.id = rsp.data.id;
|
||||
folder.url = rsp.data.url;
|
||||
|
||||
await setDashboardAcl(folder.id, []);
|
||||
|
||||
console.log('Creating dashboards');
|
||||
|
||||
const dashboards: any = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const dashboard: any = {
|
||||
uid: 'search-test-dash-' + i.toString().padStart(5, '0'),
|
||||
title: 'Search test dash ' + i.toString().padStart(5, '0'),
|
||||
};
|
||||
|
||||
const rsp = await client.post(`/dashboards/db`, {
|
||||
dashboard: dashboard,
|
||||
folderId: folder.id,
|
||||
overwrite: true,
|
||||
});
|
||||
|
||||
dashboard.id = rsp.data.id;
|
||||
dashboard.url = rsp.data.url;
|
||||
|
||||
console.log('Created dashboard ' + dashboard.title);
|
||||
dashboards.push(dashboard);
|
||||
await setDashboardAcl(dashboard.id, [{ userId: 0, teamId: team1.id, permission: 4 }]);
|
||||
}
|
||||
};
|
||||
|
||||
export const searchTestDataSetupTask = new Task<SearchTestDataSetupOptions>();
|
||||
searchTestDataSetupTask.setName('Search test data setup');
|
||||
searchTestDataSetupTask.setRunner(searchTestDataSetupRunnner);
|
||||
@@ -1,23 +0,0 @@
|
||||
export type TaskRunner<T> = (options: T) => Promise<any>;
|
||||
|
||||
export class Task<TOptions> {
|
||||
name: string;
|
||||
runner: (options: TOptions) => Promise<any>;
|
||||
options: TOptions;
|
||||
|
||||
setName = name => {
|
||||
this.name = name;
|
||||
};
|
||||
|
||||
setRunner = (runner: TaskRunner<TOptions>) => {
|
||||
this.runner = runner;
|
||||
};
|
||||
|
||||
setOptions = options => {
|
||||
this.options = options;
|
||||
};
|
||||
|
||||
exec = () => {
|
||||
return this.runner(this.options);
|
||||
};
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
const cwd = process.cwd();
|
||||
|
||||
export const changeCwdToGrafanaUi = () => {
|
||||
process.chdir(`${cwd}/packages/grafana-ui`);
|
||||
return process.cwd();
|
||||
};
|
||||
|
||||
export const changeCwdToGrafanaUiDist = () => {
|
||||
process.chdir(`${cwd}/packages/grafana-ui/dist`);
|
||||
};
|
||||
|
||||
export const restoreCwd = () => {
|
||||
process.chdir(cwd);
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Task } from '../tasks/task';
|
||||
import chalk from 'chalk';
|
||||
|
||||
export const execTask = <TOptions>(task: Task<TOptions>) => async (options: TOptions) => {
|
||||
console.log(chalk.yellow(`Running ${chalk.bold(task.name)} task`));
|
||||
task.setOptions(options);
|
||||
try {
|
||||
console.group();
|
||||
await task.exec();
|
||||
console.groupEnd();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
import GithubClient from './githubClient';
|
||||
|
||||
const fakeClient = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
delete process.env.GITHUB_USERNAME;
|
||||
delete process.env.GITHUB_ACCESS_TOKEN;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.GITHUB_USERNAME;
|
||||
delete process.env.GITHUB_ACCESS_TOKEN;
|
||||
});
|
||||
|
||||
describe('GithubClient', () => {
|
||||
it('should initialise a GithubClient', () => {
|
||||
const github = new GithubClient();
|
||||
expect(github).toBeInstanceOf(GithubClient);
|
||||
});
|
||||
|
||||
describe('#client', () => {
|
||||
it('it should contain a client', () => {
|
||||
const spy = jest.spyOn(GithubClient.prototype, 'createClient').mockImplementation(() => fakeClient);
|
||||
|
||||
const github = new GithubClient();
|
||||
const client = github.client;
|
||||
|
||||
expect(spy).toHaveBeenCalledWith({
|
||||
baseURL: 'https://api.github.com/repos/grafana/grafana',
|
||||
timeout: 10000,
|
||||
});
|
||||
expect(client).toEqual(fakeClient);
|
||||
});
|
||||
|
||||
describe('when the credentials are required', () => {
|
||||
it('should create the client when the credentials are defined', () => {
|
||||
const username = 'grafana';
|
||||
const token = 'averysecureaccesstoken';
|
||||
|
||||
process.env.GITHUB_USERNAME = username;
|
||||
process.env.GITHUB_ACCESS_TOKEN = token;
|
||||
|
||||
const spy = jest.spyOn(GithubClient.prototype, 'createClient').mockImplementation(() => fakeClient);
|
||||
|
||||
const github = new GithubClient(true);
|
||||
const client = github.client;
|
||||
|
||||
expect(spy).toHaveBeenCalledWith({
|
||||
baseURL: 'https://api.github.com/repos/grafana/grafana',
|
||||
timeout: 10000,
|
||||
auth: { username, password: token },
|
||||
});
|
||||
|
||||
expect(client).toEqual(fakeClient);
|
||||
});
|
||||
|
||||
describe('when the credentials are not defined', () => {
|
||||
it('should throw an error', () => {
|
||||
expect(() => {
|
||||
new GithubClient(true);
|
||||
}).toThrow(/operation needs a GITHUB_USERNAME and GITHUB_ACCESS_TOKEN environment variables/);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||
|
||||
const baseURL = 'https://api.github.com/repos/grafana/grafana';
|
||||
|
||||
// Encapsulates the creation of a client for the Github API
|
||||
//
|
||||
// Two key things:
|
||||
// 1. You can specify whenever you want the credentials to be required or not when imported.
|
||||
// 2. If the the credentials are available as part of the environment, even if
|
||||
// they're not required - the library will use them. This allows us to overcome
|
||||
// any API rate limiting imposed without authentication.
|
||||
|
||||
class GithubClient {
|
||||
client: AxiosInstance;
|
||||
|
||||
constructor(required = false) {
|
||||
const username = process.env.GITHUB_USERNAME;
|
||||
const token = process.env.GITHUB_ACCESS_TOKEN;
|
||||
|
||||
const clientConfig: AxiosRequestConfig = {
|
||||
baseURL: baseURL,
|
||||
timeout: 10000,
|
||||
};
|
||||
|
||||
if (required && !username && !token) {
|
||||
throw new Error('operation needs a GITHUB_USERNAME and GITHUB_ACCESS_TOKEN environment variables');
|
||||
}
|
||||
|
||||
if (username && token) {
|
||||
clientConfig.auth = { username: username, password: token };
|
||||
}
|
||||
|
||||
this.client = this.createClient(clientConfig);
|
||||
}
|
||||
|
||||
private createClient(clientConfig: AxiosRequestConfig) {
|
||||
return axios.create(clientConfig);
|
||||
}
|
||||
}
|
||||
|
||||
export default GithubClient;
|
||||
@@ -1,19 +0,0 @@
|
||||
import ora from 'ora';
|
||||
|
||||
type FnToSpin<T> = (options: T) => Promise<void>;
|
||||
|
||||
export const useSpinner = <T>(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) {
|
||||
spinner.fail(e);
|
||||
if (killProcess) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user