Skip to content

Commit

Permalink
feat(cli): add component command (#7384)
Browse files Browse the repository at this point in the history
* feat(cli): add component command

* chore(react): remove test files

* chore(cli): fix eslint violations

Co-authored-by: DAK <[email protected]>
Co-authored-by: TJ Egan <[email protected]>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Dec 5, 2020
1 parent e11fdf2 commit 74b0850
Show file tree
Hide file tree
Showing 15 changed files with 353 additions and 10 deletions.
3 changes: 3 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ packages/components/node_modules

# React
**/storybook-static/**

# Templates
packages/cli/src/component/templates/**
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,6 @@ packages/components/docs/js

# Generated files
**/generated/**

# Templates
**/*.template.*
Binary file added .yarn/offline-mirror/ansi-colors-4.1.1.tgz
Binary file not shown.
Binary file added .yarn/offline-mirror/enquirer-2.3.6.tgz
Binary file not shown.
2 changes: 2 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
130 changes: 130 additions & 0 deletions packages/cli/src/commands/component.js
Original file line number Diff line number Diff line change
@@ -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,
};
47 changes: 47 additions & 0 deletions packages/cli/src/component/index.js
Original file line number Diff line number Diff line change
@@ -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,
};
19 changes: 19 additions & 0 deletions packages/cli/src/component/templates/component.template.js
Original file line number Diff line number Diff line change
@@ -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 <div {...rest}>{children}</div>;
}

<%= name %>.propTypes = {
children: PropTypes.node,
};

export default <%= name %>;
9 changes: 9 additions & 0 deletions packages/cli/src/component/templates/index.template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* 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 <%= name %> from './<%= name %>';
export { <%= name %> };
37 changes: 37 additions & 0 deletions packages/cli/src/component/templates/mdx.template.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Props } from '@storybook/addon-docs/blocks';
import { <%= name %> } from './';

# <%= name %>

[Source
code](https://github.com/carbon-design-system/carbon/tree/master/packages/react/src/components/<%=
name %>) &nbsp;|&nbsp; [Usage
guidelines](https://www.carbondesignsystem.com/components/<%= name %>/usage)
&nbsp;|&nbsp; [Accessibility](https://www.carbondesignsystem.com/components/<%=
url %>/accessibility)

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->

## Table of Contents

- [Overview](#overview)
- [Component API](#component-api)
- [Feedback](#feedback)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Overview

TODO

## Component API

<Props />

## Feedback

Help us improve this component by providing feedback, asking questions on Slack,
or updating this file on
[GitHub](https://github.com/carbon-design-system/carbon/edit/master/packages/react/src/components/<%=
name %>/<%= name %>.mdx).
22 changes: 22 additions & 0 deletions packages/cli/src/component/templates/story.template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* 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 React from 'react';
import { <%= name %> } from './';
import mdx from './<%= name %>.mdx';

export default {
title: '<%= name %>',
component: <%= name %>,
parameters: {
docs: {
page: mdx,
},
},
};

export const example = () => <<%= name %>>Story Example</<%= name %>>;
35 changes: 35 additions & 0 deletions packages/cli/src/component/templates/test.template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* 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 { cleanup, render, screen } from '@testing-library/react';
import React from 'react';
import { <%= name %> } from '../';

describe('<%= name %>', () => {
afterEach(cleanup);

it('should work', () => {
render(<<%= name %>>test</<%= name %>>);
// TODO
});

describe('automated accessibility testing', () => {
it('should have no axe violations', async () => {
render(<<%= name %>>test</<%= name %>>);
await expect(screen.getByText('test')).toHaveNoAxeViolations();
});

it('should have no accessibility checker violations', async () => {
render(<<%= name %>>test</<%= name %>>);
await expect(screen.getByText('test')).toHaveNoACViolations('<%= name %>');
});
});

describe('Component API', () => {
// TODO
});
});
Loading

0 comments on commit 74b0850

Please sign in to comment.