diff --git a/.eslintignore b/.eslintignore index 9495ed7c48c5..d745b4b718d3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -38,3 +38,6 @@ packages/components/node_modules # React **/storybook-static/** + +# Templates +packages/cli/src/component/templates/** diff --git a/.prettierignore b/.prettierignore index d973e3d317c4..4c9d4a9c5a40 100644 --- a/.prettierignore +++ b/.prettierignore @@ -51,3 +51,6 @@ packages/components/docs/js # Generated files **/generated/** + +# Templates +**/*.template.* diff --git a/.yarn/offline-mirror/ansi-colors-4.1.1.tgz b/.yarn/offline-mirror/ansi-colors-4.1.1.tgz new file mode 100644 index 000000000000..d18e121b0fec Binary files /dev/null and b/.yarn/offline-mirror/ansi-colors-4.1.1.tgz differ diff --git a/.yarn/offline-mirror/enquirer-2.3.6.tgz b/.yarn/offline-mirror/enquirer-2.3.6.tgz new file mode 100644 index 000000000000..920d6e31f503 Binary files /dev/null and b/.yarn/offline-mirror/enquirer-2.3.6.tgz differ diff --git a/packages/cli/package.json b/packages/cli/package.json index 8a1b459e5c1c..cd7fb4dc9a2f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -30,9 +30,11 @@ "chalk": "^2.4.2", "child-process-promise": "^2.2.1", "clipboardy": "^2.1.0", + "enquirer": "^2.3.6", "fast-glob": "^3.2.2", "fs-extra": "^8.0.1", "inquirer": "^6.4.1", + "lodash.template": "^4.5.0", "prettier": "^2.1.0", "prettier-config-carbon": "^0.5.0", "progress-estimator": "^0.2.2", diff --git a/packages/cli/src/cli.js b/packages/cli/src/cli.js index 3c4ebfaba776..5db408955de8 100644 --- a/packages/cli/src/cli.js +++ b/packages/cli/src/cli.js @@ -25,7 +25,9 @@ async function main({ argv }) { console.error(error.stderr); process.exit(1); } - throw error; + console.error(error); + process.exit(1); + return; } console.log(message); console.log(yargs.help()); diff --git a/packages/cli/src/commands/component.js b/packages/cli/src/commands/component.js new file mode 100644 index 000000000000..0aa179569e5f --- /dev/null +++ b/packages/cli/src/commands/component.js @@ -0,0 +1,130 @@ +/** + * Copyright IBM Corp. 2019, 2019 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const { paramCase } = require('change-case'); +const fs = require('fs-extra'); +const { prompt } = require('enquirer'); +const path = require('path'); +const { loadTemplates } = require('../component'); +const { createLogger } = require('../logger'); + +const logger = createLogger('component'); + +function clearConsole() { + process.stdout.write( + process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H' + ); +} + +async function component() { + const templates = await loadTemplates(); + const questions = [ + { + type: 'input', + name: 'name', + message: 'What is the name of this component?', + validate(value) { + if (value === '') { + return 'A name is required for the component'; + } + return true; + }, + }, + { + type: 'input', + name: 'directory', + message: 'Specify the path for this component', + initial: '.', + }, + { + type: 'multiselect', + name: 'options', + message: 'What else should we scaffold out for you?', + initial: ['tests', 'stories'], + choices: [ + { + name: 'tests', + value: true, + }, + { + name: 'stories', + value: true, + }, + ], + result(names) { + return this.map(names); + }, + }, + ]; + + clearConsole(); + const answers = await prompt(questions); + + logger.start('Generating component...'); + + const directory = path.resolve( + process.cwd(), + answers.directory, + answers.name + ); + + logger.info(`Writing component directory to ${directory}`); + + if (await fs.exists(directory)) { + throw new Error(`A directory already exists at ${directory}`); + } + + logger.info('Scaffolding out default files...'); + + await fs.ensureDir(directory); + await fs.writeFile( + path.join(directory, 'index.js'), + templates.index.compile({ name: answers.name }) + ); + await fs.writeFile( + path.join(directory, `${answers.name}.js`), + templates.component.compile({ name: answers.name }) + ); + + if (answers.options.tests) { + logger.start('Scaffolding out test files...'); + await fs.ensureDir(path.join(directory, '__tests__')); + await fs.writeFile( + path.join(directory, '__tests__', `${answers.name}-test.js`), + templates.test.compile({ name: answers.name }) + ); + logger.stop(); + } + + if (answers.options.stories) { + logger.start('Scaffolding out story files...'); + await fs.writeFile( + path.join(directory, `${answers.name}-story.js`), + templates.story.compile({ + name: answers.name, + }) + ); + await fs.writeFile( + path.join(directory, `${answers.name}.mdx`), + templates.mdx.compile({ + name: answers.name, + url: paramCase(answers.name), + }) + ); + logger.stop(); + } + + logger.stop(); +} + +module.exports = { + command: 'component', + desc: '[EXPERIMENTAL] Scaffold a component in React', + handler: component, +}; diff --git a/packages/cli/src/component/index.js b/packages/cli/src/component/index.js new file mode 100644 index 000000000000..1cb35359d9f1 --- /dev/null +++ b/packages/cli/src/component/index.js @@ -0,0 +1,47 @@ +/** + * Copyright IBM Corp. 2019, 2019 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const fs = require('fs-extra'); +const path = require('path'); +const template = require('lodash.template'); + +const TEMPLATES_DIR = path.join(__dirname, 'templates'); +const blocklist = new Set(['.DS_Store']); + +async function loadTemplates() { + const files = await fs.readdir(TEMPLATES_DIR).then((names) => { + return names + .filter((name) => { + return !blocklist.has(name); + }) + .map((name) => { + const extension = path.extname(name); + return { + name: path.basename(name, `.template${extension}`), + filepath: path.join(TEMPLATES_DIR, name), + }; + }); + }); + + const templates = {}; + + for (const { name, filepath } of files) { + const contents = await fs.readFile(filepath, 'utf8'); + const compile = template(contents); + templates[name] = { + compile, + }; + } + + return templates; +} + +module.exports = { + loadTemplates, +}; diff --git a/packages/cli/src/component/templates/component.template.js b/packages/cli/src/component/templates/component.template.js new file mode 100644 index 000000000000..efcd9231556e --- /dev/null +++ b/packages/cli/src/component/templates/component.template.js @@ -0,0 +1,19 @@ +/** + * Copyright IBM Corp. 2016, 2020 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import PropTypes from 'prop-types'; +import React from 'react'; + +function <%= name %>({ children, ...rest }) { + return