From 1b76e07910361a2ac770f492b57ccc2110b33b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 19 Feb 2020 14:39:20 +0100 Subject: [PATCH 1/3] WIP --- .gitignore | 1 + frontend/packages/cli/bin/backstage-cli | 2 + frontend/packages/cli/cli.js | 4 - frontend/packages/cli/package.json | 18 +- .../packages/cli/src/commands/createPlugin.ts | 187 ++++++++++++++++++ frontend/packages/cli/src/index.ts | 23 ++- .../templates/default-plugin/ScaffoldPage.hbs | 32 +++ .../default-plugin/ScaffoldPage.test.hbs | 14 ++ .../default-plugin/ScaffoldPlugin.hbs | 14 ++ .../cli/templates/default-plugin/index.hbs | 1 + .../cli/templates/default-plugin/index.js | 1 + .../templates/default-plugin/plugin-info.hbs | 10 + frontend/packages/cli/tsconfig.json | 9 + frontend/yarn.lock | 91 ++++++++- 14 files changed, 394 insertions(+), 13 deletions(-) create mode 100755 frontend/packages/cli/bin/backstage-cli delete mode 100755 frontend/packages/cli/cli.js create mode 100644 frontend/packages/cli/src/commands/createPlugin.ts create mode 100644 frontend/packages/cli/templates/default-plugin/ScaffoldPage.hbs create mode 100644 frontend/packages/cli/templates/default-plugin/ScaffoldPage.test.hbs create mode 100644 frontend/packages/cli/templates/default-plugin/ScaffoldPlugin.hbs create mode 100644 frontend/packages/cli/templates/default-plugin/index.hbs create mode 100644 frontend/packages/cli/templates/default-plugin/index.js create mode 100644 frontend/packages/cli/templates/default-plugin/plugin-info.hbs create mode 100644 frontend/packages/cli/tsconfig.json diff --git a/.gitignore b/.gitignore index bedeac419e..e0c121215f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ secrets.env .DS_Store cjs/ esm/ +types/ diff --git a/frontend/packages/cli/bin/backstage-cli b/frontend/packages/cli/bin/backstage-cli new file mode 100755 index 0000000000..7aa96ef3d4 --- /dev/null +++ b/frontend/packages/cli/bin/backstage-cli @@ -0,0 +1,2 @@ +#!/usr/bin/env node +require('../cjs'); diff --git a/frontend/packages/cli/cli.js b/frontend/packages/cli/cli.js deleted file mode 100755 index 4ad3db98a9..0000000000 --- a/frontend/packages/cli/cli.js +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env node -const hello = require('./src/index.ts'); - -console.log('output:', hello()); diff --git a/frontend/packages/cli/package.json b/frontend/packages/cli/package.json index 8f145e4ee1..af2f191343 100644 --- a/frontend/packages/cli/package.json +++ b/frontend/packages/cli/package.json @@ -8,12 +8,24 @@ "scripts": { "build": "web-scripts build", "lint": "web-scripts lint", - "test": "web-scripts test" + "test": "web-scripts test", + "start": "nodemon ." }, "devDependencies": { - "@spotify/web-scripts": "^6.0.0" + "@spotify/web-scripts": "^6.0.0", + "@types/node": "^13.7.2", + "nodemon": "^2.0.2", + "ts-node": "^8.6.2" }, "bin": { - "backstage-cli": "cli.js" + "backstage-cli": "bin/backstage-cli" + }, + "dependencies": { + "commander": "^4.1.1" + }, + "nodemonConfig": { + "watch": "./src", + "exec": "ts-node", + "ext": "ts" } } diff --git a/frontend/packages/cli/src/commands/createPlugin.ts b/frontend/packages/cli/src/commands/createPlugin.ts new file mode 100644 index 0000000000..b924655895 --- /dev/null +++ b/frontend/packages/cli/src/commands/createPlugin.ts @@ -0,0 +1,187 @@ +const path = require('path'); +const fs = require('fs'); +const fsExtra = require('fs-extra'); +const replace = require('replace-in-file'); +const dashify = require('dashify'); +const inquirer = require('inquirer'); +const handlebars = require('handlebars'); + +const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1); +const lowercaseFirstLetter = str => str.charAt(0).toLowerCase() + str.slice(1); + +const appendTextFile = (file, textToAppend) => { + const originalContents = fs.readFileSync(file, 'utf8'); + + // Make sure that there is a newline before and after the new text + if (originalContents && !originalContents.endsWith('\n')) { + textToAppend = `\n${textToAppend}`; + } + if (textToAppend && !textToAppend.endsWith('\n')) { + textToAppend = `${textToAppend}\n`; + } + + fs.appendFileSync(file, textToAppend); +}; + +const questions = [ + { + type: 'input', + name: 'id', + message: + "Enter an ID for the plugin (Please capitalize) e.g. 'MyAwesomePlugin' [required]", + validate: value => (value ? true : 'Please enter an ID for the plugin'), + }, + { + type: 'confirm', + name: 'ts', + message: 'Use Typescript?', + }, + { + type: 'input', + name: 'owner', + message: 'Which squad will own this plugin? [required]', + validate: value => + value ? true : 'Please enter a squad that will own the plugin', + }, + { + type: 'input', + name: 'title', + message: "Enter a title for the plugin e.g. 'My Awesome Plugin' [optional]", + default: '', + }, + { + type: 'input', + name: 'desc', + message: + "Enter a description for the plugin e.g. 'This Plugin does all awesome things!' [optional]", + default: '', + }, + { + type: 'input', + name: 'author', + message: "Author's slack handle e.g. alund [optional]", + default: '', + }, + { + type: 'input', + name: 'support_channel', + message: + 'Slack channel to contact for support on this plugin e.g. data-support [optional]', + default: '', + }, +]; + +const createPluginCommand = () => { + inquirer.prompt(questions).then(answers => { + const pluginId = capitalize(answers.id); + + const pluginFolderName = lowercaseFirstLetter( + pluginId.replace(/Plugin/g, ''), + ); + answers.folder = pluginFolderName; + + const pluginRoute = `/${dashify(pluginFolderName)}`; + answers.route = pluginRoute; + + const pluginRoot = path.join(__dirname, '../src/plugins/'); + const source = path.join(pluginRoot, 'scaffold'); + const destination = path.join(pluginRoot, pluginFolderName); + + const pluginImportText = `import { ${pluginId} } from 'plugins/${pluginFolderName}';\n// @@import`; + const pluginDefinitionText = `${pluginId},\n // @@definition`; + const pluginManagerBootstrap = path.join( + pluginRoot, + 'pluginManagerBootstrap.js', + ); + + if (fs.existsSync(destination)) { + return console.error( + `Uh Oh! Looks like another plugin exists with the same name.\nPlease check ${destination} directory.`, + ); + } + + try { + fs.mkdirSync(destination); + + const compileFileAndWrite = (sourceFile, destFile) => { + const hbFile = fs.readFileSync(path.join(source, sourceFile)); + const template = handlebars.compile(hbFile.toString()); + const contents = template({ name: destFile, ...answers }); + fs.writeFile(path.join(destination, destFile), contents, err => { + if (err) { + return console.log( + 'Error writing file ', + path.join(destination, destFile), + ); + } + console.log('Wrote ', path.join(destination, destFile)); + }); + }; + + ['Page', 'Plugin'].forEach(fileType => { + compileFileAndWrite( + `Scaffold${fileType}.hbs`, + `${pluginId}${fileType}${answers.ts ? '.tsx' : '.js'}`, + ); + }); + + compileFileAndWrite('ScaffoldPage.test.hbs', `${pluginId}Page.test.js`); + compileFileAndWrite('plugin-info.hbs', 'plugin-info.yaml'); + compileFileAndWrite('index.hbs', 'index.js'); + appendTextFile( + '.github/CODEOWNERS', + `/src/plugins/${pluginFolderName}/ @backstage/${answers.owner}`, + ); + + // import and add plugin to bootstrap + const options = { + files: pluginManagerBootstrap, + from: [/\/\/ @@import/g, /\/\/ @@definition/], + to: [pluginImportText, pluginDefinitionText], + }; + + replace(options, err => { + if (err) { + return console.error(err); + } + console.log(''); + console.log( + 'Added plugin to bootstrap, and the plugin directory to CODEOWNERS.', + ); + console.log( + `You are all set! Check http://localhost:5678${pluginRoute}`, + ); + console.log(''); + console.log( + 'NOTE: First thing to do now should be to create a Pull Request and wait for the', + ); + console.log( + 'current goalie of the Tools squad to review and merge it. After that is done,', + ); + console.log( + 'through the use of the CODEOWNERS feature, you should be able to continue', + ); + console.log('development inside your plugin directory on your own.'); + console.log(''); + console.log('Hack away!'); + }); + } catch (e) { + console.error(e); + fsExtra.removeSync(destination); + const options = { + files: pluginManagerBootstrap, + from: [pluginImportText, pluginImportText], + to: [/\/\/ @@import/g, /\/\/ @@definition/], + }; + + replace(options, err => { + if (err) { + return console.error(err); + } + console.log('Cleaned up'); + }); + } + }); +}; + +export default createPluginCommand; diff --git a/frontend/packages/cli/src/index.ts b/frontend/packages/cli/src/index.ts index a6e3282955..2ea6cdc04a 100644 --- a/frontend/packages/cli/src/index.ts +++ b/frontend/packages/cli/src/index.ts @@ -1,3 +1,22 @@ -const hello = () => 'Hello World'; +import program from 'commander'; +import createPluginCommand from './commands/createPlugin'; -module.exports = hello; +const main = (argv: string[]) => { + program + .command('create-plugin') + .description('Creates a new plugin in the current repository') + .action(createPluginCommand); + + program.on('command:*', () => { + console.error( + 'Invalid command: %s\nSee --help for a list of available commands.', + program.args.join(' '), + ); + process.exit(1); + }); + + program.parse(argv); +}; + +// main(process.argv); +main([process.argv[0], process.argv[1], 'create-plugin', '--bla']); diff --git a/frontend/packages/cli/templates/default-plugin/ScaffoldPage.hbs b/frontend/packages/cli/templates/default-plugin/ScaffoldPage.hbs new file mode 100644 index 0000000000..3a02ea4403 --- /dev/null +++ b/frontend/packages/cli/templates/default-plugin/ScaffoldPage.hbs @@ -0,0 +1,32 @@ +import React{{#if ts}}, { FC }{{/if}} from 'react'; +import { Typography } from '@material-ui/core'; +import { theme } from 'core/app/PageThemeProvider'; +import InfoCard from 'shared/components/InfoCard'; +import { Content, ContentHeader, Header, HeaderLabel, Navigation, NavItem, Page } from 'shared/components/layout'; + +/** + * This component demonstrates how to render a page with its own navigation. + * You typically create layouts that can be shared between multiple pages. + * Layouts can be found in {@link src/shared/components/layout/} + */ +const {{id}}Page{{#if ts}}: FC<>{{/if}} = () => { + return ( + +
+ + +
+ + + + + + + Hello world, this is your new plugin! + + +
+ ); +}; + +export default {{id}}Page; diff --git a/frontend/packages/cli/templates/default-plugin/ScaffoldPage.test.hbs b/frontend/packages/cli/templates/default-plugin/ScaffoldPage.test.hbs new file mode 100644 index 0000000000..b1f12525c9 --- /dev/null +++ b/frontend/packages/cli/templates/default-plugin/ScaffoldPage.test.hbs @@ -0,0 +1,14 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { wrapInThemedTestApp } from 'testUtils'; + +import {{id}}Page from './{{id}}Page'; + +const minProps = {}; + +describe('<{{id}}Page />', () => { + it('renders without exploding', () => { + const { getByText } = render(wrapInThemedTestApp(<{{id}}Page {...minProps} />)); + expect(getByText(/Hello world.*/)).toBeInTheDocument(); + }); +}); diff --git a/frontend/packages/cli/templates/default-plugin/ScaffoldPlugin.hbs b/frontend/packages/cli/templates/default-plugin/ScaffoldPlugin.hbs new file mode 100644 index 0000000000..770e379565 --- /dev/null +++ b/frontend/packages/cli/templates/default-plugin/ScaffoldPlugin.hbs @@ -0,0 +1,14 @@ +import React from 'react'; +import { createPlugin } from 'shared/pluginApi'; + +const {{id}}Page = React.lazy( + /* istanbul ignore next */ () => import(/* webpackChunkName: "{{folder}}" */ 'plugins/{{folder}}/{{id}}Page'), +); + +export default createPlugin({ + manifest: require('./plugin-info.yaml'), + + register({ router }) { + router.registerRoute('{{route}}', {{id}}Page); + } +}); diff --git a/frontend/packages/cli/templates/default-plugin/index.hbs b/frontend/packages/cli/templates/default-plugin/index.hbs new file mode 100644 index 0000000000..07b190dcb7 --- /dev/null +++ b/frontend/packages/cli/templates/default-plugin/index.hbs @@ -0,0 +1 @@ +export { default as {{id}} } from 'plugins/{{folder}}/{{id}}Plugin'; diff --git a/frontend/packages/cli/templates/default-plugin/index.js b/frontend/packages/cli/templates/default-plugin/index.js new file mode 100644 index 0000000000..3243cf662b --- /dev/null +++ b/frontend/packages/cli/templates/default-plugin/index.js @@ -0,0 +1 @@ +export { default as ScaffoldPlugin } from 'plugins/scaffold/ScaffoldPlugin'; diff --git a/frontend/packages/cli/templates/default-plugin/plugin-info.hbs b/frontend/packages/cli/templates/default-plugin/plugin-info.hbs new file mode 100644 index 0000000000..125d7a66f3 --- /dev/null +++ b/frontend/packages/cli/templates/default-plugin/plugin-info.hbs @@ -0,0 +1,10 @@ +--- +id: {{id}} +title: {{title}} +description: {{desc}} +owner: {{owner}} +visibility: public +facts: + support_channel: {{support_channel}} + authors: + - slack: {{author}} diff --git a/frontend/packages/cli/tsconfig.json b/frontend/packages/cli/tsconfig.json new file mode 100644 index 0000000000..d0711b5dff --- /dev/null +++ b/frontend/packages/cli/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@spotify/web-scripts/config/tsconfig.json", + "include": ["src"], + "compilerOptions": { + "paths": { + "*": ["src/*"] + } + } +} diff --git a/frontend/yarn.lock b/frontend/yarn.lock index b3c7d0d45b..37ed639f2d 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2920,6 +2920,11 @@ resolved "https://registry.npmjs.org/@types/node/-/node-12.12.26.tgz#213e153babac0ed169d44a6d919501e68f59dea9" integrity sha512-UmUm94/QZvU5xLcUlNR8hA7Ac+fGpO1EG/a8bcWVz0P0LqtxFmun9Y2bbtuckwGboWJIT70DoWq1r3hb56n3DA== +"@types/node@^13.7.2": + version "13.7.2" + resolved "https://registry.npmjs.org/@types/node/-/node-13.7.2.tgz#50375b95b5845a34efda2ffb3a087c7becbc46c6" + integrity sha512-uvilvAQbdJvnSBFcKJ2td4016urcGvsiR+N4dHGU87ml8O2Vl6l+ErOi9w0kXSPiwJ1AYlIW+0pDXDWWMOiWbw== + "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" @@ -3510,6 +3515,11 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -4506,7 +4516,7 @@ chokidar@^2.0.2, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.3.0: +chokidar@^3.2.2, chokidar@^3.3.0: version "3.3.1" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450" integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg== @@ -4823,7 +4833,7 @@ commander@^2.11.0, commander@^2.20.0, commander@~2.20.3: resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^4.0.1: +commander@^4.0.1, commander@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== @@ -5676,7 +5686,7 @@ debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "^2.1.1" -debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5: +debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: version "3.2.6" resolved "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -5884,6 +5894,11 @@ diff-sequences@^25.1.0: resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.1.0.tgz#fd29a46f1c913fd66c22645dc75bffbe43051f32" integrity sha512-nFIfVk5B/NStCsJ+zaPO4vYuLjlzQ6uFvPxzYyHlejNZ/UGa7G/n7peOXVrVNvRuyfstt+mZQYGpjxg9Z6N8Kw== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -8082,6 +8097,11 @@ iferr@^1.0.2: resolved "https://registry.npmjs.org/iferr/-/iferr-1.0.2.tgz#e9fde49a9da06dc4a4194c6c9ed6d08305037a6d" integrity sha512-9AfeLfji44r5TKInjhz3W9DyZI1zR1JAf2hVBMGhddAKPqBsupb89jGfbCTHIGZd6fGZl9WlHdn4AObygyMKwg== +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= + ignore-walk@^3.0.1: version "3.0.3" resolved "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" @@ -10611,6 +10631,11 @@ make-error@1.x: resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8" integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g== +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + make-fetch-happen@^5.0.0: version "5.0.2" resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-5.0.2.tgz#aa8387104f2687edca01c8687ee45013d02d19bd" @@ -11328,6 +11353,22 @@ node-releases@^1.1.47: dependencies: semver "^6.3.0" +nodemon@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.2.tgz#9c7efeaaf9b8259295a97e5d4585ba8f0cbe50b0" + integrity sha512-GWhYPMfde2+M0FsHnggIHXTqPDHXia32HRhh6H0d75Mt9FKUoCBvumNHr7LdrpPBTKxsWmIEOjoN+P4IU6Hcaw== + dependencies: + chokidar "^3.2.2" + debug "^3.2.6" + ignore-by-default "^1.0.1" + minimatch "^3.0.4" + pstree.remy "^1.1.7" + semver "^5.7.1" + supports-color "^5.5.0" + touch "^3.1.0" + undefsafe "^2.0.2" + update-notifier "^2.5.0" + nopt@^4.0.1, nopt@~4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" @@ -11336,6 +11377,13 @@ nopt@^4.0.1, nopt@~4.0.1: abbrev "1" osenv "^0.1.4" +nopt@~1.0.10: + version "1.0.10" + resolved "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" + integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4= + dependencies: + abbrev "1" + normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -13273,6 +13321,11 @@ psl@^1.1.24, psl@^1.1.28: resolved "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== +pstree.remy@^1.1.7: + version "1.1.7" + resolved "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.7.tgz#c76963a28047ed61542dc361aa26ee55a7fa15f3" + integrity sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A== + public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -15337,7 +15390,7 @@ supports-color@^2.0.0: resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^5.3.0: +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -15686,6 +15739,13 @@ toidentifier@1.0.0: resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +touch@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" + integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== + dependencies: + nopt "~1.0.10" + tough-cookie@^2.3.3, tough-cookie@^2.3.4, tough-cookie@^2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -15759,6 +15819,17 @@ ts-jest@^25.0.0: semver "^5.5" yargs-parser "10.x" +ts-node@^8.6.2: + version "8.6.2" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-8.6.2.tgz#7419a01391a818fbafa6f826a33c1a13e9464e35" + integrity sha512-4mZEbofxGqLL2RImpe3zMJukvEvcO1XP8bj8ozBPySdCUXEcU5cIRwR0aM3R+VoZq7iXc8N86NC0FspGRqP4gg== + dependencies: + arg "^4.1.0" + diff "^4.0.1" + make-error "^1.1.1" + source-map-support "^0.5.6" + yn "3.1.1" + ts-pnp@1.1.5, ts-pnp@^1.1.2: version "1.1.5" resolved "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.1.5.tgz#840e0739c89fce5f3abd9037bb091dbff16d9dec" @@ -15886,6 +15957,13 @@ umask@^1.1.0, umask@~1.1.0: resolved "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" integrity sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0= +undefsafe@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" + integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A== + dependencies: + debug "^2.2.0" + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -16936,6 +17014,11 @@ yargs@^8.0.2: y18n "^3.2.1" yargs-parser "^7.0.0" +yn@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + zen-observable@^0.8.15: version "0.8.15" resolved "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" From 5b539a3e1407e457731de41c25e5a34f249dfda8 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Thu, 20 Feb 2020 14:19:48 +0100 Subject: [PATCH 2/3] refactor createPlugin and add tests. cli can now create a plugin from template --- .gitignore | 1 + frontend/packages/cli/package.json | 12 +- .../cli/src/commands/createPlugin.test.ts | 50 ++++ .../packages/cli/src/commands/createPlugin.ts | 252 +++++------------- frontend/packages/cli/src/index.ts | 2 +- .../templates/default-plugin/package.json.hbs | 28 ++ frontend/packages/cli/tsconfig.json | 1 + frontend/yarn.lock | 35 ++- 8 files changed, 198 insertions(+), 183 deletions(-) create mode 100644 frontend/packages/cli/src/commands/createPlugin.test.ts create mode 100644 frontend/packages/cli/templates/default-plugin/package.json.hbs diff --git a/.gitignore b/.gitignore index e0c121215f..b23f1e0c19 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea/ secrets.env .DS_Store cjs/ diff --git a/frontend/packages/cli/package.json b/frontend/packages/cli/package.json index af2f191343..93a224d109 100644 --- a/frontend/packages/cli/package.json +++ b/frontend/packages/cli/package.json @@ -21,8 +21,18 @@ "backstage-cli": "bin/backstage-cli" }, "dependencies": { - "commander": "^4.1.1" + "@types/inquirer": "^6.5.0", + "commander": "^4.1.1", + "dashify": "^2.0.0", + "handlebars": "^4.7.3", + "inquirer": "^7.0.4", + "replace-in-file": "^5.0.2" }, + "files": [ + "templates", + "bin", + "cjs" + ], "nodemonConfig": { "watch": "./src", "exec": "ts-node", diff --git a/frontend/packages/cli/src/commands/createPlugin.test.ts b/frontend/packages/cli/src/commands/createPlugin.test.ts new file mode 100644 index 0000000000..6836e3270e --- /dev/null +++ b/frontend/packages/cli/src/commands/createPlugin.test.ts @@ -0,0 +1,50 @@ +import fs from 'fs'; +import path from 'path'; +import { createPluginFolder, createFileFromTemplate } from './createPlugin'; + +describe('createPlugin', () => { + describe('createPluginFolder', () => { + it('should create a plugin directory in the correct place', () => { + const tempDir = fs.mkdtempSync('createPluginTest'); + try { + const pluginFolder = createPluginFolder(tempDir, 'foo'); + expect(fs.existsSync(pluginFolder)).toBe(true); + } finally { + fs.rmdirSync(tempDir, { recursive: true }); + } + }); + + it('should not create a plugin directory if it already exists', () => { + const tempDir = fs.mkdtempSync('createPluginTest'); + try { + const pluginFolder = createPluginFolder(tempDir, 'foo'); + expect(fs.existsSync(pluginFolder)).toBe(true); + expect(() => createPluginFolder(tempDir, 'foo')).toThrowError( + /A plugin with the same name already exists/, + ); + } finally { + fs.rmdirSync(tempDir, { recursive: true }); + } + }); + }); + + describe('createFileFromTemplate', () => { + it('should generate a valid output with inserted values', () => { + const tempDir = fs.mkdtempSync('createFileFromTemplate'); + try { + const sourceData = '{"name": "@spotify-backstage/{{id}}"}'; + const targetData = '{"name": "@spotify-backstage/foo"}'; + const sourcePath = path.join(tempDir, 'in.hbs'); + const targetPath = path.join(tempDir, 'out.json'); + fs.writeFileSync(sourcePath, sourceData); + + createFileFromTemplate(sourcePath, targetPath, { id: 'foo' }); + + expect(fs.existsSync(targetPath)).toBe(true); + expect(fs.readFileSync(targetPath).toString()).toBe(targetData); + } finally { + fs.rmdirSync(tempDir, { recursive: true }); + } + }); + }); +}); diff --git a/frontend/packages/cli/src/commands/createPlugin.ts b/frontend/packages/cli/src/commands/createPlugin.ts index b924655895..03e4366b0b 100644 --- a/frontend/packages/cli/src/commands/createPlugin.ts +++ b/frontend/packages/cli/src/commands/createPlugin.ts @@ -1,187 +1,83 @@ -const path = require('path'); -const fs = require('fs'); -const fsExtra = require('fs-extra'); -const replace = require('replace-in-file'); -const dashify = require('dashify'); -const inquirer = require('inquirer'); -const handlebars = require('handlebars'); +import fs from 'fs'; +import path from 'path'; +import handlebars from 'handlebars'; +import inquirer from 'inquirer'; -const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1); -const lowercaseFirstLetter = str => str.charAt(0).toLowerCase() + str.slice(1); +export const createPluginFolder = (rootDir: string, id: string): string => { + const destination = path.join(rootDir, 'packages', 'plugins', id); -const appendTextFile = (file, textToAppend) => { - const originalContents = fs.readFileSync(file, 'utf8'); - - // Make sure that there is a newline before and after the new text - if (originalContents && !originalContents.endsWith('\n')) { - textToAppend = `\n${textToAppend}`; - } - if (textToAppend && !textToAppend.endsWith('\n')) { - textToAppend = `${textToAppend}\n`; + if (fs.existsSync(destination)) { + throw new Error( + `A plugin with the same name already exists: ${destination}`, + ); } - fs.appendFileSync(file, textToAppend); + try { + fs.mkdirSync(destination, { recursive: true }); + return destination; + } catch (e) { + throw new Error( + `Failed to create plugin directory: ${destination}: ${e.message}`, + ); + } }; -const questions = [ - { - type: 'input', - name: 'id', - message: - "Enter an ID for the plugin (Please capitalize) e.g. 'MyAwesomePlugin' [required]", - validate: value => (value ? true : 'Please enter an ID for the plugin'), - }, - { - type: 'confirm', - name: 'ts', - message: 'Use Typescript?', - }, - { - type: 'input', - name: 'owner', - message: 'Which squad will own this plugin? [required]', - validate: value => - value ? true : 'Please enter a squad that will own the plugin', - }, - { - type: 'input', - name: 'title', - message: "Enter a title for the plugin e.g. 'My Awesome Plugin' [optional]", - default: '', - }, - { - type: 'input', - name: 'desc', - message: - "Enter a description for the plugin e.g. 'This Plugin does all awesome things!' [optional]", - default: '', - }, - { - type: 'input', - name: 'author', - message: "Author's slack handle e.g. alund [optional]", - default: '', - }, - { - type: 'input', - name: 'support_channel', - message: - 'Slack channel to contact for support on this plugin e.g. data-support [optional]', - default: '', - }, -]; - -const createPluginCommand = () => { - inquirer.prompt(questions).then(answers => { - const pluginId = capitalize(answers.id); - - const pluginFolderName = lowercaseFirstLetter( - pluginId.replace(/Plugin/g, ''), - ); - answers.folder = pluginFolderName; - - const pluginRoute = `/${dashify(pluginFolderName)}`; - answers.route = pluginRoute; - - const pluginRoot = path.join(__dirname, '../src/plugins/'); - const source = path.join(pluginRoot, 'scaffold'); - const destination = path.join(pluginRoot, pluginFolderName); - - const pluginImportText = `import { ${pluginId} } from 'plugins/${pluginFolderName}';\n// @@import`; - const pluginDefinitionText = `${pluginId},\n // @@definition`; - const pluginManagerBootstrap = path.join( - pluginRoot, - 'pluginManagerBootstrap.js', - ); - - if (fs.existsSync(destination)) { - return console.error( - `Uh Oh! Looks like another plugin exists with the same name.\nPlease check ${destination} directory.`, - ); - } - - try { - fs.mkdirSync(destination); - - const compileFileAndWrite = (sourceFile, destFile) => { - const hbFile = fs.readFileSync(path.join(source, sourceFile)); - const template = handlebars.compile(hbFile.toString()); - const contents = template({ name: destFile, ...answers }); - fs.writeFile(path.join(destination, destFile), contents, err => { - if (err) { - return console.log( - 'Error writing file ', - path.join(destination, destFile), - ); - } - console.log('Wrote ', path.join(destination, destFile)); - }); - }; - - ['Page', 'Plugin'].forEach(fileType => { - compileFileAndWrite( - `Scaffold${fileType}.hbs`, - `${pluginId}${fileType}${answers.ts ? '.tsx' : '.js'}`, - ); - }); - - compileFileAndWrite('ScaffoldPage.test.hbs', `${pluginId}Page.test.js`); - compileFileAndWrite('plugin-info.hbs', 'plugin-info.yaml'); - compileFileAndWrite('index.hbs', 'index.js'); - appendTextFile( - '.github/CODEOWNERS', - `/src/plugins/${pluginFolderName}/ @backstage/${answers.owner}`, - ); - - // import and add plugin to bootstrap - const options = { - files: pluginManagerBootstrap, - from: [/\/\/ @@import/g, /\/\/ @@definition/], - to: [pluginImportText, pluginDefinitionText], - }; - - replace(options, err => { - if (err) { - return console.error(err); - } - console.log(''); - console.log( - 'Added plugin to bootstrap, and the plugin directory to CODEOWNERS.', - ); - console.log( - `You are all set! Check http://localhost:5678${pluginRoute}`, - ); - console.log(''); - console.log( - 'NOTE: First thing to do now should be to create a Pull Request and wait for the', - ); - console.log( - 'current goalie of the Tools squad to review and merge it. After that is done,', - ); - console.log( - 'through the use of the CODEOWNERS feature, you should be able to continue', - ); - console.log('development inside your plugin directory on your own.'); - console.log(''); - console.log('Hack away!'); - }); - } catch (e) { - console.error(e); - fsExtra.removeSync(destination); - const options = { - files: pluginManagerBootstrap, - from: [pluginImportText, pluginImportText], - to: [/\/\/ @@import/g, /\/\/ @@definition/], - }; - - replace(options, err => { - if (err) { - return console.error(err); - } - console.log('Cleaned up'); - }); - } +export const createFileFromTemplate = ( + sourcePath: string, + destinationPath: string, + answers: { [key: string]: string }, +) => { + const template = fs.readFileSync(sourcePath); + const compiled = handlebars.compile(template.toString()); + const contents = compiled({ + name: path.basename(destinationPath), + ...answers, }); + try { + fs.writeFileSync(destinationPath, contents); + } catch (e) { + throw new Error(`Failed to create file: ${destinationPath}: ${e.message}`); + } }; -export default createPluginCommand; +const createPlugin = async (): Promise => { + const currentDir = process.argv[1]; + const questions = [ + { + type: 'input', + name: 'id', + message: 'Enter an ID for the plugin [required]', + validate: (value: any) => + value ? true : 'Please enter an ID for the plugin', + }, + ]; + + const answers: { [key: string]: string } = await inquirer.prompt(questions); + const destinationFolder = createPluginFolder( + path.join(currentDir, '..', '..', '..'), + answers.id, + ); + + const templateFolder = path.join( + currentDir, + '..', + '..', + '@spotify-backstage', + 'cli', + 'templates', + 'default-plugin', + ); + const files = [{ input: 'package.json.hbs', output: 'package.json' }]; + + files.forEach(file => { + createFileFromTemplate( + path.join(templateFolder, file.input), + path.join(destinationFolder, file.output), + answers, + ); + }); + + return destinationFolder; +}; + +export default createPlugin; diff --git a/frontend/packages/cli/src/index.ts b/frontend/packages/cli/src/index.ts index 2ea6cdc04a..ba9462dc3d 100644 --- a/frontend/packages/cli/src/index.ts +++ b/frontend/packages/cli/src/index.ts @@ -19,4 +19,4 @@ const main = (argv: string[]) => { }; // main(process.argv); -main([process.argv[0], process.argv[1], 'create-plugin', '--bla']); +main([process.argv[0], process.argv[1], 'create-plugin']); diff --git a/frontend/packages/cli/templates/default-plugin/package.json.hbs b/frontend/packages/cli/templates/default-plugin/package.json.hbs new file mode 100644 index 0000000000..d091fbc1ec --- /dev/null +++ b/frontend/packages/cli/templates/default-plugin/package.json.hbs @@ -0,0 +1,28 @@ +{ + "name": "@spotify-backstage/{{id}}", + "version": "{{version}}", + "main": "src/index.ts", + "main:src": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "scripts": { + "build": "web-scripts build", + "lint": "web-scripts lint", + "test": "web-scripts test", + "start": "nodemon ." + }, + "devDependencies": { + "@spotify/web-scripts": "^6.0.0", + "@types/node": "^13.7.2", + "nodemon": "^2.0.2", + "ts-node": "^8.6.2" + }, + "bin": { + "backstage-{{id}}": "bin/backstage-{{id}}" + }, + "nodemonConfig": { + "watch": "./src", + "exec": "ts-node", + "ext": "ts" + } +} diff --git a/frontend/packages/cli/tsconfig.json b/frontend/packages/cli/tsconfig.json index d0711b5dff..84b9319b94 100644 --- a/frontend/packages/cli/tsconfig.json +++ b/frontend/packages/cli/tsconfig.json @@ -2,6 +2,7 @@ "extends": "@spotify/web-scripts/config/tsconfig.json", "include": ["src"], "compilerOptions": { + "baseUrl": "src", "paths": { "*": ["src/*"] } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 37ed639f2d..9d0342195a 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2860,6 +2860,14 @@ resolved "https://registry.npmjs.org/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860" integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw== +"@types/inquirer@^6.5.0": + version "6.5.0" + resolved "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz#b83b0bf30b88b8be7246d40e51d32fe9d10e09be" + integrity sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw== + dependencies: + "@types/through" "*" + rxjs "^6.4.0" + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.1" resolved "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" @@ -3009,6 +3017,13 @@ "@types/react-dom" "*" "@types/testing-library__dom" "*" +"@types/through@*": + version "0.0.30" + resolved "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895" + integrity sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "15.0.0" resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d" @@ -5646,6 +5661,11 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +dashify@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/dashify/-/dashify-2.0.0.tgz#fff270ca2868ca427fee571de35691d6e437a648" + integrity sha512-hpA5C/YrPjucXypHPPc0oJ1l9Hf6wWbiOL7Ik42cxnsUOhWiCB/fylKbKqqJalW9FgkNQCw16YO8uW9Hs0Iy1A== + data-urls@^1.0.0, data-urls@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe" @@ -7676,7 +7696,7 @@ handle-thing@^2.0.0: resolved "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== -handlebars@^4.4.0: +handlebars@^4.4.0, handlebars@^4.7.3: version "4.7.3" resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.7.3.tgz#8ece2797826886cf8082d1726ff21d2a022550ee" integrity sha512-SRGwSYuNfx8DwHD/6InAPzD6RgeruWLT+B8e8a7gGs8FWgHzlExpTFMEq2IA6QpAfOClpKHy6+8IqTjeBCu6Kg== @@ -8288,7 +8308,7 @@ inquirer@6.5.0: strip-ansi "^5.1.0" through "^2.3.6" -inquirer@7.0.4, inquirer@^7.0.0: +inquirer@7.0.4, inquirer@^7.0.0, inquirer@^7.0.4: version "7.0.4" resolved "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz#99af5bde47153abca23f5c7fc30db247f39da703" integrity sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ== @@ -14054,6 +14074,15 @@ repeating@^2.0.0: dependencies: is-finite "^1.0.0" +replace-in-file@^5.0.2: + version "5.0.2" + resolved "https://registry.npmjs.org/replace-in-file/-/replace-in-file-5.0.2.tgz#bd26203b66dfb5b8112ae36a2d2cf928ea4cfe12" + integrity sha512-1Vc7Sbr/rTuHgU1PZuBb7tGsFx3D4NKdhV4BpEF2MuN/6+SoXcFtx+dZ1Zz+5Dq4k5x9js87Y+gXQYPTQ9ppkA== + dependencies: + chalk "^3.0.0" + glob "^7.1.6" + yargs "^15.0.2" + request-promise-core@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" @@ -16978,7 +17007,7 @@ yargs@^14.2.2: y18n "^4.0.0" yargs-parser "^15.0.0" -yargs@^15.0.0, yargs@^15.0.1: +yargs@^15.0.0, yargs@^15.0.1, yargs@^15.0.2: version "15.1.0" resolved "https://registry.npmjs.org/yargs/-/yargs-15.1.0.tgz#e111381f5830e863a89550bd4b136bb6a5f37219" integrity sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg== From 2d27436129df840b7bd3227ccc0e194cfcff200a Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Thu, 20 Feb 2020 14:59:47 +0100 Subject: [PATCH 3/3] review comments --- frontend/packages/cli/package.json | 6 +++--- frontend/packages/cli/src/commands/createPlugin.test.ts | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/packages/cli/package.json b/frontend/packages/cli/package.json index 93a224d109..9d14a392a3 100644 --- a/frontend/packages/cli/package.json +++ b/frontend/packages/cli/package.json @@ -14,6 +14,7 @@ "devDependencies": { "@spotify/web-scripts": "^6.0.0", "@types/node": "^13.7.2", + "@types/inquirer": "^6.5.0", "nodemon": "^2.0.2", "ts-node": "^8.6.2" }, @@ -21,12 +22,11 @@ "backstage-cli": "bin/backstage-cli" }, "dependencies": { - "@types/inquirer": "^6.5.0", "commander": "^4.1.1", "dashify": "^2.0.0", "handlebars": "^4.7.3", - "inquirer": "^7.0.4", - "replace-in-file": "^5.0.2" + "replace-in-file": "^5.0.2", + "inquirer": "^7.0.4" }, "files": [ "templates", diff --git a/frontend/packages/cli/src/commands/createPlugin.test.ts b/frontend/packages/cli/src/commands/createPlugin.test.ts index 6836e3270e..46d7bc982f 100644 --- a/frontend/packages/cli/src/commands/createPlugin.test.ts +++ b/frontend/packages/cli/src/commands/createPlugin.test.ts @@ -1,21 +1,23 @@ import fs from 'fs'; import path from 'path'; +import os from 'os'; import { createPluginFolder, createFileFromTemplate } from './createPlugin'; describe('createPlugin', () => { describe('createPluginFolder', () => { it('should create a plugin directory in the correct place', () => { - const tempDir = fs.mkdtempSync('createPluginTest'); + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-')); try { const pluginFolder = createPluginFolder(tempDir, 'foo'); expect(fs.existsSync(pluginFolder)).toBe(true); + expect(pluginFolder).toMatch(/packages\/plugins\/foo/); } finally { fs.rmdirSync(tempDir, { recursive: true }); } }); it('should not create a plugin directory if it already exists', () => { - const tempDir = fs.mkdtempSync('createPluginTest'); + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-')); try { const pluginFolder = createPluginFolder(tempDir, 'foo'); expect(fs.existsSync(pluginFolder)).toBe(true); @@ -30,7 +32,7 @@ describe('createPlugin', () => { describe('createFileFromTemplate', () => { it('should generate a valid output with inserted values', () => { - const tempDir = fs.mkdtempSync('createFileFromTemplate'); + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'test-')); try { const sourceData = '{"name": "@spotify-backstage/{{id}}"}'; const targetData = '{"name": "@spotify-backstage/foo"}';