Implemented scripts for building and releasing grafana/ui

This commit is contained in:
Dominik Prokop
2019-02-21 10:58:28 +01:00
parent 72e269c8f3
commit 529c1ea53d
17 changed files with 684 additions and 44 deletions

1
scripts/cli/index.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export type Task<T> = (options: T) => Promise<void>;

View File

@@ -1,22 +1,33 @@
import program from 'commander';
import { startTask } from './start';
import chalk from 'chalk';
import { execTask } from './utils/execTask';
export type Task<T> = (options: T) => Promise<void>;
// TODO: Refactor to commander commands
// This will enable us to have command scoped options and limit the ifs below
program
.option('-h, --hot', 'Runs front-end with hot reload enabled')
.option('-t, --theme', 'Watches for theme changes and regenerates variables.scss files')
.option('-d, --depreciate <scripts>', 'Inform about npm script deprecation', v => v.split(','))
.option('-b, --build', 'Created @grafana/ui build')
.option('-r, --release', 'Releases @grafana/ui to npm')
.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!`
)
);
if (program.build) {
execTask('grafanaui.build');
} else if (program.release) {
execTask('grafanaui.release');
} else {
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!`
)
);
}
execTask('core.start', {
watchThemes: !!program.theme,
hot: !!program.hot,
});
}
startTask({
watchThemes: !!program.theme,
hot: !!program.hot,
});

View File

@@ -1,7 +1,14 @@
import concurrently from 'concurrently';
import { Task } from '..';
export const startTask = async ({ watchThemes, hot }: { watchThemes: boolean; hot: boolean }) => {
interface StartTaskOptions {
watchThemes: boolean;
hot: boolean;
}
const startTask: Task<StartTaskOptions> = async ({ watchThemes, hot }) => {
const jobs = [];
if (watchThemes) {
jobs.push({
command: 'nodemon -e ts -w ./packages/grafana-ui/src/themes -x yarn run themes:generate',
@@ -30,3 +37,5 @@ export const startTask = async ({ watchThemes, hot }: { watchThemes: boolean; ho
process.exit(1);
}
};
export default startTask;

View File

@@ -0,0 +1,103 @@
import execa from 'execa';
import fs from 'fs';
import { Task } from '..';
import { changeCwdToGrafanaUi, restoreCwd } from '../utils/cwd';
import chalk from 'chalk';
import { startSpinner } from '../utils/startSpinner';
let distDir, cwd;
const clean = async () => {
const spinner = startSpinner('Cleaning');
try {
await execa('npm', ['run', 'clean']);
spinner.succeed();
} catch (e) {
spinner.fail();
throw e;
}
};
const compile = async () => {
const spinner = startSpinner('Compiling sources');
try {
await execa('tsc', ['-p', './tsconfig.build.json']);
spinner.succeed();
} catch (e) {
console.log(e);
spinner.fail();
}
};
const rollup = async () => {
const spinner = startSpinner('Bundling');
try {
await execa('npm', ['run', 'build']);
spinner.succeed();
} catch (e) {
spinner.fail();
}
};
export const savePackage = async (path, pkg) => {
const spinner = startSpinner('Updating package.json');
return new Promise((resolve, reject) => {
fs.writeFile(path, JSON.stringify(pkg, null, 2), err => {
if (err) {
spinner.fail();
console.error(err);
reject(err);
return;
}
spinner.succeed();
resolve();
});
});
};
const preparePackage = async pkg => {
pkg.main = 'index.js';
pkg.types = 'index.d.ts';
await savePackage(`${cwd}/dist/package.json`, pkg);
};
const moveFiles = async () => {
const files = ['README.md', 'CHANGELOG.md', 'index.js'];
const spinner = startSpinner(`Moving ${files.join(', ')} files`);
const promises = files.map(file => {
return fs.copyFile(`${cwd}/${file}`, `${distDir}/${file}`, err => {
if (err) {
console.error(err);
return;
}
});
});
try {
await Promise.all(promises);
spinner.succeed();
} catch (e) {
spinner.fail();
}
};
const buildTask: Task<void> = async () => {
cwd = changeCwdToGrafanaUi();
distDir = `${cwd}/dist`;
const pkg = require(`${cwd}/package.json`);
console.log(chalk.yellow(`Building ${pkg.name} @ ${pkg.version}`));
await clean();
await compile();
await rollup();
await preparePackage(pkg);
await moveFiles();
restoreCwd();
};
export default buildTask;

View File

@@ -0,0 +1,133 @@
import execa from 'execa';
import { Task } from '..';
import { execTask } from '../utils/execTask';
import { changeCwdToGrafanaUiDist, changeCwdToGrafanaUi } from '../utils/cwd';
import semver from 'semver';
import inquirer from 'inquirer';
import chalk from 'chalk';
import { startSpinner } from '../utils/startSpinner';
import { savePackage } from './grafanaui.build';
type VersionBumpType = 'patch' | 'minor' | 'major';
const promptBumpType = async () => {
return inquirer.prompt<{ type: VersionBumpType }>([
{
type: 'list',
message: 'Select version bump',
name: 'type',
choices: ['patch', 'minor', 'major'],
validate: answer => {
if (answer.length < 1) {
return 'You must choose something';
}
return true;
},
},
]);
};
const promptPrereleaseId = async () => {
return inquirer.prompt<{ id: string }>([
{
type: 'list',
message: 'Is this a prerelease?',
name: 'id',
choices: ['no', '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,
},
]);
};
const bumpVersion = async (version: string) => {
const spinner = startSpinner(`Saving version ${version} to package.json`);
changeCwdToGrafanaUi();
try {
await execa('npm', ['version', version]);
spinner.succeed();
} catch (e) {
console.log(e);
spinner.fail();
}
changeCwdToGrafanaUiDist();
const pkg = require(`${process.cwd()}/package.json`);
pkg.version = version;
await savePackage(`${process.cwd()}/package.json`, pkg);
};
const publishPackage = async (name: string, version: string) => {
changeCwdToGrafanaUiDist();
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();
}
const spinner = startSpinner(`Publishing ${name} @ ${version} to npm registry...`);
try {
await execa('npm', ['publish']);
spinner.succeed();
} catch (e) {
console.log(e);
spinner.fail();
process.exit(1);
}
};
const releaseTask: Task<void> = async () => {
await execTask('grafanaui.build');
let releaseConfirmed = false;
let nextVersion;
changeCwdToGrafanaUiDist();
const pkg = require(`${process.cwd()}/package.json`);
console.log(`Current version: ${pkg.version}`);
do {
const { type } = await promptBumpType();
const { id } = await promptPrereleaseId();
if (id !== 'no') {
nextVersion = semver.inc(pkg.version, `pre${type}`, id);
} else {
nextVersion = semver.inc(pkg.version, type);
}
console.log(chalk.yellowBright.bold(`You are going to release a new version of ${pkg.name}`));
console.log(chalk.green(`Version bump: ${pkg.version} ->`), chalk.bold.yellowBright(`${nextVersion}`));
const { confirmed } = await promptConfirm();
releaseConfirmed = confirmed;
} while (!releaseConfirmed);
await bumpVersion(nextVersion);
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 - COMMIT THIS FILE!`));
};
export default releaseTask;

14
scripts/cli/utils/cwd.ts Normal file
View File

@@ -0,0 +1,14 @@
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);
};

View File

@@ -0,0 +1,6 @@
import { Task } from '..';
export const execTask = async <T>(taskName, options?: T) => {
const task = await import(`${__dirname}/../tasks/${taskName}.ts`);
return task.default(options) as Task<T>;
};

View File

@@ -0,0 +1,7 @@
import ora from 'ora';
export const startSpinner = (label: string) => {
const spinner = new ora(label);
spinner.start();
return spinner;
};