From 7ec87ee76b312a80ea149878e57aab8645052f61 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 11 Jul 2019 03:47:58 -0700 Subject: [PATCH] grafana/toolkit: improve CircleCI stubs (#17995) * validate type and id * copy all svg and png, useful if people don't use the img folder * update comments * add stubs for each ci task * use ci-work folder rather than build * use axios for basic testing * Packages: publish packages@6.3.0-alpha.39 * bump version * add download task * Packages: publish packages@6.3.0-alpha.40 * merge all dist folders into one * fix folder paths * Fix ts error * Packages: publish packages@6.3.0-beta.0 * Packages: publish packages@6.3.0-beta.1 * bump next to 6.4 * Packages: publish packages@6.4.0-alpha.2 * better build and bundle tasks * fix lint * Packages: publish packages@6.4.0-alpha.3 * copy the file to start grafana * Packages: publish packages@6.4.0-alpha.4 * use sudo for copy * Packages: publish packages@6.4.0-alpha.5 * add missing service * add service and homepath * Packages: publish packages@6.4.0-alpha.6 * make the folder * Update packages/grafana-toolkit/src/cli/tasks/plugin.ci.ts * Update packages/grafana-toolkit/src/cli/tasks/plugin.ci.ts --- lerna.json | 2 +- package.json | 2 +- packages/grafana-data/README.md | 2 +- packages/grafana-data/package.json | 2 +- packages/grafana-runtime/README.md | 2 +- packages/grafana-runtime/package.json | 6 +- packages/grafana-toolkit/package.json | 9 +- packages/grafana-toolkit/src/cli/index.ts | 50 ++- .../src/cli/tasks/plugin.ci.ts | 356 ++++++++++++++++-- .../src/config/utils/pluginValidation.test.ts | 2 +- .../src/config/utils/pluginValidation.ts | 19 +- packages/grafana-ui/package.json | 4 +- yarn.lock | 12 + 13 files changed, 414 insertions(+), 54 deletions(-) diff --git a/lerna.json b/lerna.json index 83a941b5539..1fa5994e34f 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "npmClient": "yarn", "useWorkspaces": true, "packages": ["packages/*"], - "version": "6.3.0-alpha.36" + "version": "6.4.0-alpha.6" } diff --git a/package.json b/package.json index 7ca741d04eb..f11453499d9 100644 --- a/package.json +++ b/package.json @@ -148,7 +148,7 @@ "themes:generate": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/generateSassVariableFiles.ts", "packages:prepare": "lerna run clean && npm run test && lerna version --tag-version-prefix=\"packages@\" -m \"Packages: publish %s\" --no-push", "packages:build": "lerna run clean && lerna run build", - "packages:publish": "lerna publish from-package --contents dist --tag-version-prefix=\"packages@\" --dist-tag next" + "packages:publish": "lerna publish from-package --contents dist --dist-tag next --tag-version-prefix=\"packages@\"" }, "husky": { "hooks": { diff --git a/packages/grafana-data/README.md b/packages/grafana-data/README.md index 3176bb45b5d..76401653d06 100644 --- a/packages/grafana-data/README.md +++ b/packages/grafana-data/README.md @@ -1,3 +1,3 @@ # Grafana Data Library -The core data components \ No newline at end of file +This package holds the root data types and functions used within Grafana. \ No newline at end of file diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index 34d6509bfde..73bbc12c020 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -1,6 +1,6 @@ { "name": "@grafana/data", - "version": "6.3.0-alpha.36", + "version": "6.4.0-alpha.2", "description": "Grafana Data Library", "keywords": [ "typescript" diff --git a/packages/grafana-runtime/README.md b/packages/grafana-runtime/README.md index f01cd35537c..852cc3327e9 100644 --- a/packages/grafana-runtime/README.md +++ b/packages/grafana-runtime/README.md @@ -1,3 +1,3 @@ # Grafana Runtime library -Interfaces that let you use the runtime... \ No newline at end of file +This package allows access to grafana services. It requires Grafana to be running already and the functions to be imported as externals. \ No newline at end of file diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index 94a0e758d3a..7905da52922 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -1,11 +1,9 @@ { "name": "@grafana/runtime", - "version": "6.3.0-alpha.36", + "version": "6.4.0-alpha.2", "description": "Grafana Runtime Library", "keywords": [ - "typescript", - "react", - "react-component" + "grafana" ], "main": "src/index.ts", "scripts": { diff --git a/packages/grafana-toolkit/package.json b/packages/grafana-toolkit/package.json index 184c72e94a2..78f8438ba56 100644 --- a/packages/grafana-toolkit/package.json +++ b/packages/grafana-toolkit/package.json @@ -1,11 +1,11 @@ { "name": "@grafana/toolkit", - "version": "6.3.0-alpha.36", + "version": "6.4.0-alpha.6", "description": "Grafana Toolkit", "keywords": [ - "typescript", - "react", - "react-component" + "grafana", + "cli", + "plugins" ], "bin": { "grafana-toolkit": "./bin/grafana-toolkit.js" @@ -30,6 +30,7 @@ "@types/node": "^12.0.4", "@types/react-dev-utils": "^9.0.1", "@types/semver": "^6.0.0", + "@types/tmp": "^0.1.0", "@types/webpack": "4.4.34", "axios": "0.19.0", "babel-loader": "8.0.6", diff --git a/packages/grafana-toolkit/src/cli/index.ts b/packages/grafana-toolkit/src/cli/index.ts index 17277a4b4bf..ab9efadae40 100644 --- a/packages/grafana-toolkit/src/cli/index.ts +++ b/packages/grafana-toolkit/src/cli/index.ts @@ -13,7 +13,13 @@ import { pluginTestTask } from './tasks/plugin.tests'; import { searchTestDataSetupTask } from './tasks/searchTestDataSetup'; import { closeMilestoneTask } from './tasks/closeMilestone'; import { pluginDevTask } from './tasks/plugin.dev'; -import { pluginCITask } from './tasks/plugin.ci'; +import { + ciBuildPluginTask, + ciBundlePluginTask, + ciTestPluginTask, + ciDeployPluginTask, + ciSetupPluginTask, +} from './tasks/plugin.ci'; import { buildPackageTask } from './tasks/package.build'; export const run = (includeInternalScripts = false) => { @@ -141,15 +147,47 @@ export const run = (includeInternalScripts = false) => { }); program - .command('plugin:ci') - .option('--dryRun', "Dry run (don't post results)") - .description('Run Plugin CI task') + .command('plugin:ci-build') + .option('--platform ', 'For backend task, which backend to run') + .description('Build the plugin, leaving artifacts in /dist') .action(async cmd => { - await execTask(pluginCITask)({ - dryRun: cmd.dryRun, + await execTask(ciBuildPluginTask)({ + platform: cmd.platform, }); }); + program + .command('plugin:ci-bundle') + .description('Create a zip artifact for the plugin') + .action(async cmd => { + await execTask(ciBundlePluginTask)({}); + }); + + program + .command('plugin:ci-setup') + .option('--installer ', 'Name of installer to download and run') + .description('Install and configure grafana') + .action(async cmd => { + await execTask(ciSetupPluginTask)({ + installer: cmd.installer, + }); + }); + program + .command('plugin:ci-test') + .description('end-to-end test using bundle in /artifacts') + .action(async cmd => { + await execTask(ciTestPluginTask)({ + platform: cmd.platform, + }); + }); + + program + .command('plugin:ci-deploy') + .description('Publish plugin CI results') + .action(async cmd => { + await execTask(ciDeployPluginTask)({}); + }); + program.on('command:*', () => { console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' ')); process.exit(1); diff --git a/packages/grafana-toolkit/src/cli/tasks/plugin.ci.ts b/packages/grafana-toolkit/src/cli/tasks/plugin.ci.ts index c9ce6db4e65..71cbe4eb20d 100644 --- a/packages/grafana-toolkit/src/cli/tasks/plugin.ci.ts +++ b/packages/grafana-toolkit/src/cli/tasks/plugin.ci.ts @@ -9,7 +9,8 @@ import path = require('path'); import fs = require('fs'); export interface PluginCIOptions { - dryRun?: boolean; + platform?: string; + installer?: string; } const calcJavascriptSize = (base: string, files?: string[]): number => { @@ -32,22 +33,164 @@ const calcJavascriptSize = (base: string, files?: string[]): number => { return size; }; -const pluginCIRunner: TaskRunner = async ({ dryRun }) => { +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'; +}; + +// /** +// * Like cp -rn... BUT error if an destination file exists +// */ +// async function copyDirErrorIfExists(src:string,dest:string) { +// const entries = await fs.readdirSync(src,{withFileTypes:true}); +// if(!fs.existsSync(dest)) { +// fs.mkdirSync(dest); +// } +// console.log( 'DIR', src ); +// for(let entry of entries) { +// const srcPath = path.join(src,entry.name); +// const destPath = path.join(dest,entry.name); +// if(entry.isDirectory()) { +// await copyDirErrorIfExists(srcPath,destPath); +// } else if(fs.existsSync(destPath)) { +// console.log( 'XXXXXXXXXXXXXXX', destPath ); +// console.log( 'XXXXXXXXXXXXXXX', destPath ); +// throw new Error('Duplicate entry: '+destPath); +// } +// else { +// // console.log( 'COPY', destPath ); +// await fs.copyFileSync(srcPath,destPath); +// } +// } +// } + +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 + * + * when platform exists it is building backend, otherwise frontend + * + * Each build writes data: + * ~/work/build_xxx/ + * + * Anything that should be put into the final zip file should be put in: + * ~/work/build_xxx/dist + */ +const buildPluginRunner: TaskRunner = async ({ platform }) => { const start = Date.now(); - const distDir = `${process.cwd()}/dist`; - const artifactsDir = `${process.cwd()}/artifacts`; - await execa('rimraf', [`${process.cwd()}/coverage`]); - await execa('rimraf', [artifactsDir]); + const workDir = getJobFolder(); + await execa('rimraf', [workDir]); + fs.mkdirSync(workDir); - // Do regular build process - await pluginBuildRunner({ coverage: true }); - const elapsed = Date.now() - start; - - if (!fs.existsSync(artifactsDir)) { - fs.mkdirSync(artifactsDir); + if (platform) { + console.log('TODO, backend support?'); + fs.mkdirSync(path.resolve(process.cwd(), 'dist')); + const file = path.resolve(process.cwd(), 'dist', `README_${platform}.txt`); + fs.writeFile(file, `TODO... build ${platform}!`, err => { + if (err) { + throw new Error('Unable to write: ' + file); + } + }); + } else { + // Do regular build process with coverage + await pluginBuildRunner({ coverage: true }); } - // TODO? can this typed from @grafana/ui? + // Move local folders to the scoped job folder + for (const name of ['dist', 'coverage']) { + const dir = path.resolve(process.cwd(), name); + if (fs.existsSync(dir)) { + fs.renameSync(dir, path.resolve(workDir, name)); + } + } + writeJobStats(start, workDir); +}; + +export const ciBuildPluginTask = new Task('Build Plugin', buildPluginRunner); + +/** + * 2. BUNDLE + * + * Take everything from `~/ci/job/{any}/dist` and + * 1. merge it into: `~/ci/dist` + * 2. zip it into artifacts in `~/ci/artifacts` + * 3. prepare grafana environment in: `~/ci/grafana-test-env` + * + */ +const bundlePluginRunner: TaskRunner = async () => { + const start = Date.now(); + const ciDir = getCiFolder(); + const artifactsDir = path.resolve(ciDir, 'artifacts'); + const distDir = path.resolve(ciDir, 'dist'); + const grafanaEnvDir = path.resolve(ciDir, 'grafana-test-env'); + await execa('rimraf', [artifactsDir, distDir, grafanaEnvDir]); + fs.mkdirSync(artifactsDir); + fs.mkdirSync(distDir); + fs.mkdirSync(grafanaEnvDir); + + console.log('Build Dist Folder'); + + // 1. Check for a local 'dist' folder + const d = path.resolve(process.cwd(), 'dist'); + if (fs.existsSync(d)) { + await execa('cp', ['-rn', d + '/.', distDir]); + } + + // 2. Look for any 'dist' folders under ci/job/XXX/dist + const dirs = fs.readdirSync(path.resolve(ciDir, 'jobs')); + for (const j of dirs) { + const contents = path.resolve(ciDir, 'jobs', j, 'dist'); + if (fs.existsSync(contents)) { + try { + await execa('cp', ['-rn', contents + '/.', distDir]); + } catch (er) { + throw new Error('Duplicate files found in dist folders'); + } + } + } + + console.log('Building ZIP'); const pluginInfo = getPluginJson(`${distDir}/plugin.json`); const zipName = pluginInfo.id + '-' + pluginInfo.info.version + '.zip'; const zipFile = path.resolve(artifactsDir, zipName); @@ -55,23 +198,184 @@ const pluginCIRunner: TaskRunner = async ({ dryRun }) => { await execa('zip', ['-r', zipFile, '.']); restoreCwd(); - const stats = { - startTime: start, - buildTime: elapsed, - jsSize: calcJavascriptSize(distDir), - zipSize: fs.statSync(zipFile).size, - endTime: Date.now(), + const zipStats = fs.statSync(zipFile); + if (zipStats.size < 100) { + throw new Error('Invalid zip file: ' + zipFile); + } + let sha1 = undefined; + try { + const exe = await execa('shasum', [zipFile]); + const idx = exe.stdout.indexOf(' '); + sha1 = exe.stdout.substring(0, idx); + fs.writeFile(zipFile + '.sha1', sha1, err => {}); + } catch { + console.warn('Unable to read SHA1 Checksum'); + } + + const info = { + name: zipName, + sha1, + size: zipStats.size, }; - fs.writeFile(artifactsDir + '/stats.json', JSON.stringify(stats, null, 2), err => { + let p = path.resolve(artifactsDir, 'info.json'); + fs.writeFile(p, JSON.stringify(info, null, 2), err => { if (err) { - throw new Error('Unable to write stats'); + throw new Error('Error writing artifact info: ' + p); } - console.log('Stats', stats); }); - if (!dryRun) { - console.log('TODO send info to github?'); - } + console.log('Setup Grafan Environment'); + p = path.resolve(grafanaEnvDir, 'plugins', pluginInfo.id); + fs.mkdirSync(p, { recursive: true }); + await execa('unzip', [zipFile, '-d', p]); + + // Write the custom settings + p = path.resolve(grafanaEnvDir, 'custom.ini'); + const customIniBody = + `# Autogenerated by @grafana/toolkit \n` + + `[paths] \n` + + `plugins = ${path.resolve(grafanaEnvDir, 'plugins')}\n` + + `\n`; // empty line + fs.writeFile(p, customIniBody, err => { + if (err) { + throw new Error('Unable to write: ' + p); + } + }); + + writeJobStats(start, getJobFolder()); }; -export const pluginCITask = new Task('Plugin CI', pluginCIRunner); +export const ciBundlePluginTask = new Task('Bundle Plugin', bundlePluginRunner); + +/** + * 3. Setup (install grafana and setup provisioning) + * + * deploy the zip to a running grafana instance + * + */ +const setupPluginRunner: TaskRunner = async ({ installer }) => { + const start = Date.now(); + + if (!installer) { + throw new Error('Missing installer path'); + } + + // Download the grafana installer + const installDir = path.resolve(process.cwd(), '.installer'); + const installFile = path.resolve(installDir, installer); + if (!fs.existsSync(installFile)) { + if (!fs.existsSync(installDir)) { + fs.mkdirSync(installDir); + } + console.log('download', installer); + const exe = await execa('wget', ['-O', installFile, 'https://dl.grafana.com/oss/release/' + installer]); + console.log(exe.stdout); + } + + console.log('Install Grafana'); + let exe = await execa('sudo', ['apt-get', 'install', '-y', 'adduser', 'libfontconfig1']); + exe = await execa('sudo', ['dpkg', '-i', installFile]); + console.log(exe.stdout); + + const customIniFile = path.resolve(getCiFolder(), 'grafana-test-env', 'custom.ini'); + const configDir = '/usr/share/grafana/conf/'; + exe = await execa('sudo', ['cp', '-f', customIniFile, configDir]); + console.log(exe.stdout); + + // sudo service grafana-server start + console.log('Starting Grafana'); + exe = await execa('sudo', ['service', 'grafana-server', 'start']); + console.log(exe.stdout); + // exe = await execa('grafana-cli', ['--version', '--homepath', '/usr/share/grafana']); + // console.log(exe.stdout); + // exe = await execa('grafana-cli', ['plugins', 'ls', '--homepath', '/usr/share/grafana']); + // console.log(exe.stdout); + + const dir = getJobFolder() + '_setup'; + await execa('rimraf', [dir]); + fs.mkdirSync(dir); + writeJobStats(start, dir); +}; + +export const ciSetupPluginTask = new Task('Setup Grafana', setupPluginRunner); + +/** + * 4. Test (end-to-end) + * + * deploy the zip to a running grafana instance + * + */ +const testPluginRunner: TaskRunner = async ({ platform }) => { + const start = Date.now(); + const workDir = getJobFolder(); + + const args = { + withCredentials: true, + baseURL: process.env.GRAFANA_URL || 'http://localhost:3000/', + responseType: 'json', + auth: { + username: 'admin', + password: 'admin', + }, + }; + + const axios = require('axios'); + const frontendSettings = await axios.get('api/frontend/settings', args); + + console.log('Grafana Version: ' + JSON.stringify(frontendSettings.data.buildInfo, null, 2)); + + const pluginInfo = getPluginJson(`${process.cwd()}/src/plugin.json`); + const pluginSettings = await axios.get(`api/plugins/${pluginInfo.id}/settings`, args); + + console.log('Plugin Info: ' + JSON.stringify(pluginSettings.data, null, 2)); + + console.log('TODO puppeteer'); + + 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); +}; + +export const ciTestPluginTask = new Task('Test Plugin (e2e)', testPluginRunner); + +/** + * 4. Deploy + * + * deploy the zip to a running grafana instance + * + */ +const deployPluginRunner: TaskRunner = async () => { + const start = Date.now(); + + // TASK Time + if (process.env.CIRCLE_INTERNAL_TASK_DATA) { + const timingInfo = fs.readdirSync(`${process.env.CIRCLE_INTERNAL_TASK_DATA}`); + if (timingInfo) { + timingInfo.forEach(file => { + console.log('TIMING INFO: ', file); + }); + } + } + + const elapsed = Date.now() - start; + const stats = { + job, + sha1: `${process.env.CIRCLE_SHA1}`, + startTime: start, + buildTime: elapsed, + endTime: Date.now(), + }; + console.log('TODO DEPLOY??', stats); + 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('Deploy plugin', deployPluginRunner); diff --git a/packages/grafana-toolkit/src/config/utils/pluginValidation.test.ts b/packages/grafana-toolkit/src/config/utils/pluginValidation.test.ts index f971f5b3f78..6a898bc0dc4 100644 --- a/packages/grafana-toolkit/src/config/utils/pluginValidation.test.ts +++ b/packages/grafana-toolkit/src/config/utils/pluginValidation.test.ts @@ -3,7 +3,7 @@ import { getPluginJson, validatePluginJson } from './pluginValidation'; describe('pluginValdation', () => { describe('plugin.json', () => { test('missing plugin.json file', () => { - expect(() => getPluginJson(`${__dirname}/mocks/missing-plugin-json`)).toThrow('plugin.json file is missing!'); + expect(() => getPluginJson(`${__dirname}/mocks/missing-plugin.json`)).toThrowError(); }); }); diff --git a/packages/grafana-toolkit/src/config/utils/pluginValidation.ts b/packages/grafana-toolkit/src/config/utils/pluginValidation.ts index 217b60ccafd..1947d74a190 100644 --- a/packages/grafana-toolkit/src/config/utils/pluginValidation.ts +++ b/packages/grafana-toolkit/src/config/utils/pluginValidation.ts @@ -1,5 +1,3 @@ -import path = require('path'); - // See: packages/grafana-ui/src/types/plugin.ts interface PluginJSONSchema { id: string; @@ -22,15 +20,24 @@ export const validatePluginJson = (pluginJson: any) => { if (!pluginJson.info.version) { throw new Error('Plugin info.version is missing in plugin.json'); } + + const types = ['panel', 'datasource', 'app']; + const type = pluginJson.type; + if (!types.includes(type)) { + throw new Error('Invalid plugin type in plugin.json: ' + type); + } + + if (!pluginJson.id.endsWith('-' + type)) { + throw new Error('[plugin.json] id should end with: -' + type); + } }; -export const getPluginJson = (root: string = process.cwd()): PluginJSONSchema => { +export const getPluginJson = (path: string): PluginJSONSchema => { let pluginJson; - try { - pluginJson = require(path.resolve(root, 'src/plugin.json')); + pluginJson = require(path); } catch (e) { - throw new Error('plugin.json file is missing!'); + throw new Error('Unable to find: ' + path); } validatePluginJson(pluginJson); diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 54f65770120..6b27f4e3bbd 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -1,9 +1,9 @@ { "name": "@grafana/ui", - "version": "6.3.0-alpha.36", + "version": "6.4.0-alpha.2", "description": "Grafana Components Library", "keywords": [ - "typescript", + "grafana", "react", "react-component" ], diff --git a/yarn.lock b/yarn.lock index 365e2b48968..a08a92ec27f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3371,6 +3371,11 @@ resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.2.tgz#721ca5c5d1a2988b4a886e35c2ffc5735b6afbdf" integrity sha512-PeHg/AtdW6aaIO2a+98Xj7rWY4KC1E6yOy7AFknJQ7VXUGNrMlyxDFxJo7HqLtjQms/ZhhQX52mLVW/EX3JGOw== +"@types/tmp@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.1.0.tgz#19cf73a7bcf641965485119726397a096f0049bd" + integrity sha512-6IwZ9HzWbCq6XoQWhxLpDjuADodH/MKXRUIDFudvgjcVdjFknvmR+DNsoUeer4XPrEnrZs04Jj+kfV9pFsrhmA== + "@types/uglify-js@*": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082" @@ -17202,6 +17207,13 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.1.0.tgz#ee434a4e22543082e294ba6201dcc6eafefa2877" + integrity sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw== + dependencies: + rimraf "^2.6.3" + tmpl@1.0.x: version "1.0.4" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"