@grafana/toolkit: integrate latest improvements (#18168)

* @grafana/toolkit: make ts-loader ignore files that are not bundled

* @grafana/toolkit: improve the circleci task (#18133)

This PR makes some minor improvements to the circle task

Adds build info to plugin.json
adds dependencies to deployed artifacts
Makes sure prettier has content before writing (avoid writing empty files)
renames 'bundle' to 'package' and saves stuff in a 'packages' file

* @grafana/toolkit: enable plugin themes to work with common stylesheet (#18160)

This makes it possible to use themes styleshheet files and stylesheet imports at the same time. The problem occurred when trying to migrate polystat panel to toolkit: grafana/grafana-polystat-panel#62
This commit is contained in:
Dominik Prokop 2019-07-18 13:48:35 +02:00 committed by GitHub
parent 8202fa2fde
commit 2c8809d3cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 437 additions and 220 deletions

View File

@ -2,5 +2,5 @@
"npmClient": "yarn", "npmClient": "yarn",
"useWorkspaces": true, "useWorkspaces": true,
"packages": ["packages/*"], "packages": ["packages/*"],
"version": "6.4.0-alpha.12" "version": "6.4.0-alpha.22"
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@grafana/data", "name": "@grafana/data",
"version": "6.4.0-alpha.12", "version": "6.4.0-alpha.22",
"description": "Grafana Data Library", "description": "Grafana Data Library",
"keywords": [ "keywords": [
"typescript" "typescript"

View File

@ -1,6 +1,6 @@
{ {
"name": "@grafana/runtime", "name": "@grafana/runtime",
"version": "6.4.0-alpha.12", "version": "6.4.0-alpha.22",
"description": "Grafana Runtime Library", "description": "Grafana Runtime Library",
"keywords": [ "keywords": [
"grafana" "grafana"

View File

@ -90,6 +90,7 @@ Adidtionaly, you can also provide additional Jest config via package.json file.
We support pure css, SASS and CSS in JS approach (via Emotion). We support pure css, SASS and CSS in JS approach (via Emotion).
1. Single css/sass file 1. Single css/sass file
Create your css/sass file and import it in your plugin entry point (typically module.ts): Create your css/sass file and import it in your plugin entry point (typically module.ts):
```ts ```ts
@ -100,6 +101,7 @@ The styles will be injected via `style` tag during runtime.
Note, that imported static assets will be inlined as base64 URIs. *This can be a subject of change in the future!* Note, that imported static assets will be inlined as base64 URIs. *This can be a subject of change in the future!*
2. Theme specific css/sass files 2. Theme specific css/sass files
If you want to provide different stylesheets for dark/light theme, create `dark.[css|scss]` and `light.[css|scss]` files in `src/styles` directory of your plugin. Based on that we will generate stylesheets that will end up in `dist/styles` directory. If you want to provide different stylesheets for dark/light theme, create `dark.[css|scss]` and `light.[css|scss]` files in `src/styles` directory of your plugin. Based on that we will generate stylesheets that will end up in `dist/styles` directory.
TODO: add note about loadPluginCss TODO: add note about loadPluginCss
@ -107,6 +109,7 @@ TODO: add note about loadPluginCss
Note that static files (png, svg, json, html) are all copied to dist directory when the plugin is bundled. Relative paths to those files does not change. Note that static files (png, svg, json, html) are all copied to dist directory when the plugin is bundled. Relative paths to those files does not change.
3. Emotion 3. Emotion
Starting from Grafana 6.2 our suggested way of styling plugins is by using [Emotion](https://emotion.sh). It's a css-in-js library that we use internaly at Grafana. The biggest advantage of using Emotion is that you will get access to Grafana Theme variables. Starting from Grafana 6.2 our suggested way of styling plugins is by using [Emotion](https://emotion.sh). It's a css-in-js library that we use internaly at Grafana. The biggest advantage of using Emotion is that you will get access to Grafana Theme variables.
To use start using Emotion you first need to add it to your plugin dependencies: To use start using Emotion you first need to add it to your plugin dependencies:

View File

@ -1,6 +1,6 @@
{ {
"name": "@grafana/toolkit", "name": "@grafana/toolkit",
"version": "6.4.0-alpha.12", "version": "6.4.0-alpha.22",
"description": "Grafana Toolkit", "description": "Grafana Toolkit",
"keywords": [ "keywords": [
"grafana", "grafana",

View File

@ -16,10 +16,9 @@ import { pluginDevTask } from './tasks/plugin.dev';
import { import {
ciBuildPluginTask, ciBuildPluginTask,
ciBuildPluginDocsTask, ciBuildPluginDocsTask,
ciBundlePluginTask, ciPackagePluginTask,
ciTestPluginTask, ciTestPluginTask,
ciPluginReportTask, ciPluginReportTask,
ciDeployPluginTask,
} from './tasks/plugin.ci'; } from './tasks/plugin.ci';
import { buildPackageTask } from './tasks/package.build'; import { buildPackageTask } from './tasks/package.build';
@ -171,10 +170,10 @@ export const run = (includeInternalScripts = false) => {
}); });
program program
.command('plugin:ci-bundle') .command('plugin:ci-package')
.description('Create a zip artifact for the plugin') .description('Create a zip packages for the plugin')
.action(async cmd => { .action(async cmd => {
await execTask(ciBundlePluginTask)({}); await execTask(ciPackagePluginTask)({});
}); });
program program
@ -194,13 +193,6 @@ export const run = (includeInternalScripts = false) => {
await execTask(ciPluginReportTask)({}); await execTask(ciPluginReportTask)({});
}); });
program
.command('plugin:ci-deploy')
.description('Publish plugin CI results')
.action(async cmd => {
await execTask(ciDeployPluginTask)({});
});
program.on('command:*', () => { program.on('command:*', () => {
console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' ')); console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
process.exit(1); process.exit(1);

View File

@ -43,6 +43,23 @@ export const savePackage = useSpinner<SavePackageOptions>(
const preparePackage = async (pkg: any) => { const preparePackage = async (pkg: any) => {
pkg.main = 'index.js'; pkg.main = 'index.js';
pkg.types = 'index.d.ts'; pkg.types = 'index.d.ts';
const version: string = pkg.version;
const name: string = pkg.name;
const deps: any = pkg.dependencies;
// Below we are adding cross-dependencies to Grafana's packages
// with the version being published
if (name.endsWith('/ui')) {
deps['@grafana/data'] = version;
} else if (name.endsWith('/runtime')) {
deps['@grafana/data'] = version;
deps['@grafana/ui'] = version;
} else if (name.endsWith('/toolkit')) {
deps['@grafana/data'] = version;
deps['@grafana/ui'] = version;
}
await savePackage({ await savePackage({
path: `${cwd}/dist/package.json`, path: `${cwd}/dist/package.json`,
pkg, pkg,

View File

@ -82,12 +82,19 @@ export const prettierCheckPlugin = useSpinner<Fixable>('Prettier check', async (
if (!prettier.check(data.toString(), opts)) { if (!prettier.check(data.toString(), opts)) {
if (fix) { if (fix) {
const fixed = prettier.format(data.toString(), opts); const fixed = prettier.format(data.toString(), opts);
fs.writeFile(s, fixed, err => { if (fixed && fixed.length > 10) {
if (err) { fs.writeFile(s, fixed, err => {
console.log('Error fixing ' + s, err); if (err) {
failed = true; console.log('Error fixing ' + s, err);
} failed = true;
}); } else {
console.log('Fixed: ' + s);
}
});
} else {
console.log('No automatic fix for: ' + s);
failed = true;
}
} else { } else {
failed = true; failed = true;
} }

View File

@ -7,89 +7,34 @@ import { getPluginJson } from '../../config/utils/pluginValidation';
import execa = require('execa'); import execa = require('execa');
import path = require('path'); import path = require('path');
import fs = require('fs'); import fs = require('fs');
import { getPackageDetails } from '../utils/fileHelper';
import {
job,
getJobFolder,
writeJobStats,
getCiFolder,
agregateWorkflowInfo,
agregateCoverageInfo,
getPluginSourceInfo,
TestResultInfo,
agregateTestInfo,
} from './plugin/ci';
export interface PluginCIOptions { export interface PluginCIOptions {
backend?: string; backend?: string;
full?: boolean; full?: boolean;
} }
const calcJavascriptSize = (base: string, files?: string[]): number => {
files = files || fs.readdirSync(base);
let size = 0;
if (files) {
files.forEach(file => {
const newbase = path.join(base, file);
const stat = fs.statSync(newbase);
if (stat.isDirectory()) {
size += calcJavascriptSize(newbase, fs.readdirSync(newbase));
} else {
if (file.endsWith('.js')) {
size += stat.size;
}
}
});
}
return size;
};
const getJobFromProcessArgv = () => {
const arg = process.argv[2];
if (arg && arg.startsWith('plugin:ci-')) {
const task = arg.substring('plugin:ci-'.length);
if ('build' === task) {
if ('--platform' === process.argv[3] && process.argv[4]) {
return task + '_' + process.argv[4];
}
return 'build_nodejs';
}
return task;
}
return 'unknown_job';
};
const job = process.env.CIRCLE_JOB || getJobFromProcessArgv();
const getJobFolder = () => {
const dir = path.resolve(process.cwd(), 'ci', 'jobs', job);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
return dir;
};
const getCiFolder = () => {
const dir = path.resolve(process.cwd(), 'ci');
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
return dir;
};
const writeJobStats = (startTime: number, workDir: string) => {
const stats = {
job,
startTime,
endTime: Date.now(),
};
const f = path.resolve(workDir, 'stats.json');
fs.writeFile(f, JSON.stringify(stats, null, 2), err => {
if (err) {
throw new Error('Unable to stats: ' + f);
}
});
};
/** /**
* 1. BUILD * 1. BUILD
* *
* when platform exists it is building backend, otherwise frontend * when platform exists it is building backend, otherwise frontend
* *
* Each build writes data: * Each build writes data:
* ~/work/build_xxx/ * ~/ci/jobs/build_xxx/
* *
* Anything that should be put into the final zip file should be put in: * Anything that should be put into the final zip file should be put in:
* ~/work/build_xxx/dist * ~/ci/jobs/build_xxx/dist
*/ */
const buildPluginRunner: TaskRunner<PluginCIOptions> = async ({ backend }) => { const buildPluginRunner: TaskRunner<PluginCIOptions> = async ({ backend }) => {
const start = Date.now(); const start = Date.now();
@ -158,23 +103,22 @@ const buildPluginDocsRunner: TaskRunner<PluginCIOptions> = async () => {
export const ciBuildPluginDocsTask = new Task<PluginCIOptions>('Build Plugin Docs', buildPluginDocsRunner); export const ciBuildPluginDocsTask = new Task<PluginCIOptions>('Build Plugin Docs', buildPluginDocsRunner);
/** /**
* 2. BUNDLE * 2. Package
* *
* Take everything from `~/ci/job/{any}/dist` and * Take everything from `~/ci/job/{any}/dist` and
* 1. merge it into: `~/ci/dist` * 1. merge it into: `~/ci/dist`
* 2. zip it into artifacts in `~/ci/artifacts` * 2. zip it into packages in `~/ci/packages`
* 3. prepare grafana environment in: `~/ci/grafana-test-env` * 3. prepare grafana environment in: `~/ci/grafana-test-env`
*
*/ */
const bundlePluginRunner: TaskRunner<PluginCIOptions> = async () => { const packagePluginRunner: TaskRunner<PluginCIOptions> = async () => {
const start = Date.now(); const start = Date.now();
const ciDir = getCiFolder(); const ciDir = getCiFolder();
const artifactsDir = path.resolve(ciDir, 'artifacts'); const packagesDir = path.resolve(ciDir, 'packages');
const distDir = path.resolve(ciDir, 'dist'); const distDir = path.resolve(ciDir, 'dist');
const docsDir = path.resolve(ciDir, 'docs'); const docsDir = path.resolve(ciDir, 'docs');
const grafanaEnvDir = path.resolve(ciDir, 'grafana-test-env'); const grafanaEnvDir = path.resolve(ciDir, 'grafana-test-env');
await execa('rimraf', [artifactsDir, distDir, grafanaEnvDir]); await execa('rimraf', [packagesDir, distDir, grafanaEnvDir]);
fs.mkdirSync(artifactsDir); fs.mkdirSync(packagesDir);
fs.mkdirSync(distDir); fs.mkdirSync(distDir);
fs.mkdirSync(grafanaEnvDir); fs.mkdirSync(grafanaEnvDir);
@ -199,10 +143,19 @@ const bundlePluginRunner: TaskRunner<PluginCIOptions> = async () => {
} }
} }
console.log('Save the source info in plugin.json');
const pluginJsonFile = path.resolve(distDir, 'plugin.json');
const pluginInfo = getPluginJson(pluginJsonFile);
(pluginInfo.info as any).source = await getPluginSourceInfo();
fs.writeFile(pluginJsonFile, JSON.stringify(pluginInfo, null, 2), err => {
if (err) {
throw new Error('Error writing: ' + pluginJsonFile);
}
});
console.log('Building ZIP'); console.log('Building ZIP');
const pluginInfo = getPluginJson(`${distDir}/plugin.json`);
let zipName = pluginInfo.id + '-' + pluginInfo.info.version + '.zip'; let zipName = pluginInfo.id + '-' + pluginInfo.info.version + '.zip';
let zipFile = path.resolve(artifactsDir, zipName); let zipFile = path.resolve(packagesDir, zipName);
process.chdir(distDir); process.chdir(distDir);
await execa('zip', ['-r', zipFile, '.']); await execa('zip', ['-r', zipFile, '.']);
restoreCwd(); restoreCwd();
@ -212,58 +165,31 @@ const bundlePluginRunner: TaskRunner<PluginCIOptions> = async () => {
throw new Error('Invalid zip file: ' + zipFile); throw new Error('Invalid zip file: ' + zipFile);
} }
const zipInfo: any = {
name: zipName,
size: zipStats.size,
};
const info: any = { const info: any = {
plugin: zipInfo, plugin: await getPackageDetails(zipFile, distDir),
}; };
try {
const exe = await execa('shasum', [zipFile]);
const idx = exe.stdout.indexOf(' ');
const sha1 = exe.stdout.substring(0, idx);
fs.writeFile(zipFile + '.sha1', sha1, err => {});
zipInfo.sha1 = sha1;
} catch {
console.warn('Unable to read SHA1 Checksum');
}
console.log('Setup Grafan Environment'); console.log('Setup Grafan Environment');
let p = path.resolve(grafanaEnvDir, 'plugins', pluginInfo.id); let p = path.resolve(grafanaEnvDir, 'plugins', pluginInfo.id);
fs.mkdirSync(p, { recursive: true }); fs.mkdirSync(p, { recursive: true });
await execa('unzip', [zipFile, '-d', p]); await execa('unzip', [zipFile, '-d', p]);
// If docs exist, zip them into artifacts // If docs exist, zip them into packages
if (fs.existsSync(docsDir)) { if (fs.existsSync(docsDir)) {
console.log('Creating documentation zip'); console.log('Creating documentation zip');
zipName = pluginInfo.id + '-' + pluginInfo.info.version + '-docs.zip'; zipName = pluginInfo.id + '-' + pluginInfo.info.version + '-docs.zip';
zipFile = path.resolve(artifactsDir, zipName); zipFile = path.resolve(packagesDir, zipName);
process.chdir(docsDir); process.chdir(docsDir);
await execa('zip', ['-r', zipFile, '.']); await execa('zip', ['-r', zipFile, '.']);
restoreCwd(); restoreCwd();
const zipStats = fs.statSync(zipFile); info.docs = await getPackageDetails(zipFile, docsDir);
const zipInfo: any = {
name: zipName,
size: zipStats.size,
};
try {
const exe = await execa('shasum', [zipFile]);
const idx = exe.stdout.indexOf(' ');
const sha1 = exe.stdout.substring(0, idx);
fs.writeFile(zipFile + '.sha1', sha1, err => {});
zipInfo.sha1 = sha1;
} catch {
console.warn('Unable to read SHA1 Checksum');
}
info.docs = zipInfo;
} }
p = path.resolve(artifactsDir, 'info.json'); p = path.resolve(packagesDir, 'info.json');
fs.writeFile(p, JSON.stringify(info, null, 2), err => { fs.writeFile(p, JSON.stringify(info, null, 2), err => {
if (err) { if (err) {
throw new Error('Error writing artifact info: ' + p); throw new Error('Error writing package info: ' + p);
} }
}); });
@ -283,7 +209,7 @@ const bundlePluginRunner: TaskRunner<PluginCIOptions> = async () => {
writeJobStats(start, getJobFolder()); writeJobStats(start, getJobFolder());
}; };
export const ciBundlePluginTask = new Task<PluginCIOptions>('Bundle Plugin', bundlePluginRunner); export const ciPackagePluginTask = new Task<PluginCIOptions>('Bundle Plugin', packagePluginRunner);
/** /**
* 3. Test (end-to-end) * 3. Test (end-to-end)
@ -294,11 +220,11 @@ export const ciBundlePluginTask = new Task<PluginCIOptions>('Bundle Plugin', bun
const testPluginRunner: TaskRunner<PluginCIOptions> = async ({ full }) => { const testPluginRunner: TaskRunner<PluginCIOptions> = async ({ full }) => {
const start = Date.now(); const start = Date.now();
const workDir = getJobFolder(); const workDir = getJobFolder();
const pluginInfo = getPluginJson(`${process.cwd()}/src/plugin.json`); const pluginInfo = getPluginJson(`${process.cwd()}/ci/dist/plugin.json`);
const results: TestResultInfo = { job };
const args = { const args = {
withCredentials: true, withCredentials: true,
baseURL: process.env.GRAFANA_URL || 'http://localhost:3000/', baseURL: process.env.BASE_URL || 'http://localhost:3000/',
responseType: 'json', responseType: 'json',
auth: { auth: {
username: 'admin', username: 'admin',
@ -306,40 +232,32 @@ const testPluginRunner: TaskRunner<PluginCIOptions> = async ({ full }) => {
}, },
}; };
const axios = require('axios'); try {
const frontendSettings = await axios.get('api/frontend/settings', args); const axios = require('axios');
const frontendSettings = await axios.get('api/frontend/settings', args);
results.grafana = frontendSettings.data.buildInfo;
console.log('Grafana Version: ' + JSON.stringify(frontendSettings.data.buildInfo, null, 2)); console.log('Grafana: ' + JSON.stringify(results.grafana, null, 2));
const allPlugins: any[] = await axios.get('api/plugins', args).data;
// for (const plugin of allPlugins) {
// if (plugin.id === pluginInfo.id) {
// console.log('------------');
// console.log(plugin);
// console.log('------------');
// } else {
// console.log('Plugin:', plugin.id, plugin.latestVersion);
// }
// }
console.log('PLUGINS:', allPlugins);
if (full) {
const pluginSettings = await axios.get(`api/plugins/${pluginInfo.id}/settings`, args); const pluginSettings = await axios.get(`api/plugins/${pluginInfo.id}/settings`, args);
console.log('Plugin Info: ' + JSON.stringify(pluginSettings.data, null, 2)); console.log('Plugin Info: ' + JSON.stringify(pluginSettings.data, null, 2));
console.log('TODO Puppeteer Tests', workDir);
results.status = 'TODO... puppeteer';
} catch (err) {
results.error = err;
results.status = 'EXCEPTION Thrown';
console.log('Test Error', err);
} }
console.log('TODO puppeteer'); const f = path.resolve(workDir, 'results.json');
fs.writeFile(f, JSON.stringify(results, null, 2), err => {
if (err) {
throw new Error('Error saving: ' + f);
}
});
const elapsed = Date.now() - start;
const stats = {
job,
sha1: `${process.env.CIRCLE_SHA1}`,
startTime: start,
buildTime: elapsed,
endTime: Date.now(),
};
console.log('TODO Puppeteer Tests', stats);
writeJobStats(start, workDir); writeJobStats(start, workDir);
}; };
@ -352,35 +270,28 @@ export const ciTestPluginTask = new Task<PluginCIOptions>('Test Plugin (e2e)', t
* *
*/ */
const pluginReportRunner: TaskRunner<PluginCIOptions> = async () => { const pluginReportRunner: TaskRunner<PluginCIOptions> = async () => {
const start = Date.now(); const ciDir = path.resolve(process.cwd(), 'ci');
const workDir = getJobFolder(); const packageInfo = require(path.resolve(ciDir, 'packages', 'info.json'));
const reportDir = path.resolve(process.cwd(), 'ci', 'report');
await execa('rimraf', [reportDir]);
fs.mkdirSync(reportDir);
const file = path.resolve(reportDir, `report.txt`); console.log('Save the source info in plugin.json');
fs.writeFile(file, `TODO... actually make a report (csv etc)`, err => { const pluginJsonFile = path.resolve(ciDir, 'dist', 'plugin.json');
const report = {
plugin: getPluginJson(pluginJsonFile),
packages: packageInfo,
workflow: agregateWorkflowInfo(),
coverage: agregateCoverageInfo(),
tests: agregateTestInfo(),
};
console.log('REPORT', report);
const file = path.resolve(ciDir, 'report.json');
fs.writeFile(file, JSON.stringify(report, null, 2), err => {
if (err) { if (err) {
throw new Error('Unable to write: ' + file); throw new Error('Unable to write: ' + file);
} }
}); });
console.log('TODO... notify some service');
console.log('TODO... real report');
writeJobStats(start, workDir);
}; };
export const ciPluginReportTask = new Task<PluginCIOptions>('Deploy plugin', pluginReportRunner); export const ciPluginReportTask = new Task<PluginCIOptions>('Generate Plugin Report', pluginReportRunner);
/**
* 5. Deploy
*
* deploy the zip to a running grafana instance
*
*/
const deployPluginRunner: TaskRunner<PluginCIOptions> = async () => {
console.log('TODO DEPLOY??');
console.log(' if PR => write a comment to github with difference ');
console.log(' if master | vXYZ ==> upload artifacts to some repo ');
};
export const ciDeployPluginTask = new Task<PluginCIOptions>('Deploy plugin', deployPluginRunner);

View File

@ -0,0 +1,214 @@
import execa = require('execa');
import path = require('path');
import fs = require('fs');
export interface PluginSourceInfo {
time?: number;
repo?: string;
branch?: string;
hash?: string;
}
export interface JobInfo {
job?: string;
startTime: number;
endTime: number;
elapsed: number;
status?: string;
buildNumber?: number;
}
export interface WorkflowInfo extends JobInfo {
workflowId?: string;
jobs: JobInfo[];
user?: string;
repo?: string;
}
const getJobFromProcessArgv = () => {
const arg = process.argv[2];
if (arg && arg.startsWith('plugin:ci-')) {
const task = arg.substring('plugin:ci-'.length);
if ('build' === task) {
if ('--backend' === process.argv[3] && process.argv[4]) {
return task + '_' + process.argv[4];
}
return 'build_plugin';
}
return task;
}
return 'unknown_job';
};
export const job = process.env.CIRCLE_JOB || getJobFromProcessArgv();
export const getPluginSourceInfo = async (): Promise<PluginSourceInfo> => {
if (process.env.CIRCLE_SHA1) {
return Promise.resolve({
time: Date.now(),
repo: process.env.CIRCLE_REPOSITORY_URL,
branch: process.env.CIRCLE_BRANCH,
hash: process.env.CIRCLE_SHA1,
});
}
const exe = await execa('git', ['rev-parse', 'HEAD']);
return {
time: Date.now(),
hash: exe.stdout,
};
};
const getBuildNumber = (): number | undefined => {
if (process.env.CIRCLE_BUILD_NUM) {
return parseInt(process.env.CIRCLE_BUILD_NUM, 10);
}
return undefined;
};
export const getJobFolder = () => {
const dir = path.resolve(process.cwd(), 'ci', 'jobs', job);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
return dir;
};
export const getCiFolder = () => {
const dir = path.resolve(process.cwd(), 'ci');
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
return dir;
};
export const writeJobStats = (startTime: number, workDir: string) => {
const endTime = Date.now();
const stats: JobInfo = {
job,
startTime,
endTime,
elapsed: endTime - startTime,
buildNumber: getBuildNumber(),
};
const f = path.resolve(workDir, 'job.json');
fs.writeFile(f, JSON.stringify(stats, null, 2), err => {
if (err) {
throw new Error('Unable to stats: ' + f);
}
});
};
export const agregateWorkflowInfo = (): WorkflowInfo => {
const now = Date.now();
const workflow: WorkflowInfo = {
jobs: [],
startTime: now,
endTime: now,
workflowId: process.env.CIRCLE_WORKFLOW_ID,
repo: process.env.CIRCLE_PROJECT_REPONAME,
user: process.env.CIRCLE_PROJECT_USERNAME,
buildNumber: getBuildNumber(),
elapsed: 0,
};
const jobsFolder = path.resolve(getCiFolder(), 'jobs');
if (fs.existsSync(jobsFolder)) {
const files = fs.readdirSync(jobsFolder);
if (files && files.length) {
files.forEach(file => {
const p = path.resolve(jobsFolder, file, 'job.json');
if (fs.existsSync(p)) {
const job = require(p) as JobInfo;
workflow.jobs.push(job);
if (job.startTime < workflow.startTime) {
workflow.startTime = job.startTime;
}
if (job.endTime > workflow.endTime) {
workflow.endTime = job.endTime;
}
} else {
console.log('Missing Job info: ', p);
}
});
} else {
console.log('NO JOBS IN: ', jobsFolder);
}
}
workflow.elapsed = workflow.endTime - workflow.startTime;
return workflow;
};
export interface CoverageDetails {
total: number;
covered: number;
skipped: number;
pct: number;
}
export interface CoverageInfo {
job: string;
summary: { [key: string]: CoverageDetails };
report?: string; // path to report
}
export const agregateCoverageInfo = (): CoverageInfo[] => {
const coverage: CoverageInfo[] = [];
const ciDir = getCiFolder();
const jobsFolder = path.resolve(ciDir, 'jobs');
if (fs.existsSync(jobsFolder)) {
const files = fs.readdirSync(jobsFolder);
if (files && files.length) {
files.forEach(file => {
const dir = path.resolve(jobsFolder, file, 'coverage');
if (fs.existsSync(dir)) {
const s = path.resolve(dir, 'coverage-summary.json');
const r = path.resolve(dir, 'lcov-report', 'index.html');
if (fs.existsSync(s)) {
const raw = require(s);
const info: CoverageInfo = {
job: file,
summary: raw.total,
};
if (fs.existsSync(r)) {
info.report = r.substring(ciDir.length);
}
coverage.push(info);
}
}
});
} else {
console.log('NO JOBS IN: ', jobsFolder);
}
}
return coverage;
};
export interface TestResultInfo {
job: string;
grafana?: any;
status?: string;
error?: string;
}
export const agregateTestInfo = (): TestResultInfo[] => {
const tests: TestResultInfo[] = [];
const ciDir = getCiFolder();
const jobsFolder = path.resolve(ciDir, 'jobs');
if (fs.existsSync(jobsFolder)) {
const files = fs.readdirSync(jobsFolder);
if (files && files.length) {
files.forEach(file => {
if (file.startsWith('test')) {
const summary = path.resolve(jobsFolder, file, 'results.json');
if (fs.existsSync(summary)) {
tests.push(require(summary) as TestResultInfo);
}
}
});
} else {
console.log('NO Jobs IN: ', jobsFolder);
}
}
return tests;
};

View File

@ -0,0 +1,72 @@
import execa = require('execa');
import path = require('path');
import fs = require('fs');
interface ExtensionBytes {
[key: string]: number;
}
export function getFileSizeReportInFolder(dir: string, info?: ExtensionBytes): ExtensionBytes {
if (!info) {
info = {};
}
const files = fs.readdirSync(dir);
if (files) {
files.forEach(file => {
const newbase = path.join(dir, file);
const stat = fs.statSync(newbase);
if (stat.isDirectory()) {
getFileSizeReportInFolder(newbase, info);
} else {
let ext = '<other>';
const idx = file.lastIndexOf('.');
if (idx > 0) {
ext = file.substring(idx + 1).toLowerCase();
}
const current = info![ext] || 0;
info![ext] = current + stat.size;
}
});
}
return info;
}
interface ZipFileInfo {
name: string;
size: number;
contents: ExtensionBytes;
sha1?: string;
md5?: string;
}
export async function getPackageDetails(zipFile: string, zipSrc: string, writeChecksum = true): Promise<ZipFileInfo> {
const zipStats = fs.statSync(zipFile);
if (zipStats.size < 100) {
throw new Error('Invalid zip file: ' + zipFile);
}
const info: ZipFileInfo = {
name: path.basename(zipFile),
size: zipStats.size,
contents: getFileSizeReportInFolder(zipSrc),
};
try {
const exe = await execa('shasum', [zipFile]);
const idx = exe.stdout.indexOf(' ');
const sha1 = exe.stdout.substring(0, idx);
if (writeChecksum) {
fs.writeFile(zipFile + '.sha1', sha1, err => {});
}
info.sha1 = sha1;
} catch {
console.warn('Unable to read SHA1 Checksum');
}
try {
const exe = await execa('md5sum', [zipFile]);
const idx = exe.stdout.indexOf(' ');
info.md5 = exe.stdout.substring(0, idx);
} catch {
console.warn('Unable to read MD5 Checksum');
}
return info;
}

View File

@ -1,12 +1,4 @@
// See: packages/grafana-ui/src/types/plugin.ts import { PluginMeta } from '@grafana/ui';
interface PluginJSONSchema {
id: string;
info: PluginMetaInfo;
}
interface PluginMetaInfo {
version: string;
}
export const validatePluginJson = (pluginJson: any) => { export const validatePluginJson = (pluginJson: any) => {
if (!pluginJson.id) { if (!pluginJson.id) {
@ -32,7 +24,7 @@ export const validatePluginJson = (pluginJson: any) => {
} }
}; };
export const getPluginJson = (path: string): PluginJSONSchema => { export const getPluginJson = (path: string): PluginMeta => {
let pluginJson; let pluginJson;
try { try {
pluginJson = require(path); pluginJson = require(path);
@ -42,5 +34,5 @@ export const getPluginJson = (path: string): PluginJSONSchema => {
validatePluginJson(pluginJson); validatePluginJson(pluginJson);
return pluginJson as PluginJSONSchema; return pluginJson as PluginMeta;
}; };

View File

@ -185,8 +185,10 @@ export const getWebpackConfig: WebpackConfigurationGetter = options => {
plugins: ['angularjs-annotate'], plugins: ['angularjs-annotate'],
}, },
}, },
{
'ts-loader', loader: 'ts-loader',
options: { onlyCompileBundledFiles: true },
},
], ],
exclude: /(node_modules)/, exclude: /(node_modules)/,
}, },

View File

@ -63,16 +63,12 @@ export const hasThemeStylesheets = (root: string = process.cwd()) => {
}; };
export const getStyleLoaders = () => { export const getStyleLoaders = () => {
const shouldExtractCss = hasThemeStylesheets(); const extractionLoader = {
loader: MiniCssExtractPlugin.loader,
const executiveLoader = shouldExtractCss options: {
? { publicPath: '../',
loader: MiniCssExtractPlugin.loader, },
options: { };
publicPath: '../',
},
}
: 'style-loader';
const cssLoaders = [ const cssLoaders = [
{ {
@ -95,21 +91,32 @@ export const getStyleLoaders = () => {
}, },
]; ];
return [ const rules = [
{
test: /(dark|light)\.css$/,
use: [extractionLoader, ...cssLoaders],
},
{
test: /(dark|light)\.scss$/,
use: [extractionLoader, ...cssLoaders, 'sass-loader'],
},
{ {
test: /\.css$/, test: /\.css$/,
use: [executiveLoader, ...cssLoaders], use: ['style-loader', ...cssLoaders, 'sass-loader'],
exclude: [`${process.cwd()}/src/styles/light.css`, `${process.cwd()}/src/styles/dark.css`],
}, },
{ {
test: /\.scss$/, test: /\.scss$/,
use: [executiveLoader, ...cssLoaders, 'sass-loader'], use: ['style-loader', ...cssLoaders, 'sass-loader'],
exclude: [`${process.cwd()}/src/styles/light.scss`, `${process.cwd()}/src/styles/dark.scss`],
}, },
]; ];
return rules;
}; };
export const getFileLoaders = () => { export const getFileLoaders = () => {
const shouldExtractCss = hasThemeStylesheets(); const shouldExtractCss = hasThemeStylesheets();
// const pluginJson = getPluginJson();
return [ return [
{ {

View File

@ -1,6 +1,6 @@
{ {
"extends": "../tsconfig.json", "extends": "../tsconfig.json",
"include": ["src/**/*.ts"], "include": ["src/**/*.ts", "../../public/app/types/jquery/*.ts"],
"exclude": ["dist", "node_modules"], "exclude": ["dist", "node_modules"],
"compilerOptions": { "compilerOptions": {
"module": "commonjs", "module": "commonjs",
@ -9,6 +9,6 @@
"declaration": false, "declaration": false,
"typeRoots": ["./node_modules/@types"], "typeRoots": ["./node_modules/@types"],
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["es2015", "es2017.string"] "lib": ["es2015", "es2017.string", "dom"]
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@grafana/ui", "name": "@grafana/ui",
"version": "6.4.0-alpha.12", "version": "6.4.0-alpha.22",
"description": "Grafana Components Library", "description": "Grafana Components Library",
"keywords": [ "keywords": [
"grafana", "grafana",
@ -20,7 +20,7 @@
"author": "Grafana Labs", "author": "Grafana Labs",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@grafana/data": "^6.4.0-alpha.8", "@grafana/data": "^6.4.0-alpha.22",
"@torkelo/react-select": "2.1.1", "@torkelo/react-select": "2.1.1",
"@types/react-color": "2.17.0", "@types/react-color": "2.17.0",
"classnames": "2.2.6", "classnames": "2.2.6",