Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hammer cli. #1

Merged
merged 22 commits into from
Jun 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.DS_Store
node_modules
dist
lerna-debug.log
23 changes: 23 additions & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = {
presets: [
'@babel/preset-react',
[
'@babel/preset-env',
// It is important to note that @babel/preset-env does not support stage-x plugins
{
targets: {
node: '10',
},
useBuiltIns: 'usage',
corejs: 3,
},
],
],
plugins: [
['@babel/plugin-proposal-decorators', { legacy: true }],
['@babel/plugin-proposal-class-properties', { loose: true }],
['@babel/plugin-proposal-export-default-from'],
['@babel/plugin-proposal-object-rest-spread'],
],
ignore: ['**/*.test.js'],
};
3 changes: 3 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
roots: ['packages/'],
};
11 changes: 11 additions & 0 deletions lerna.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"packages": ["packages/*"],
"version": "0.0.0-alpha.4",
"npmClient": "yarn",
"useWorkspaces": true,
"command": {
"publish": {
"verifyAccess": false
}
}
}
27 changes: 27 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "hammer",
"private": true,
"license": "MIT",
"workspaces": [
"packages/*"
],
"devDependencies": {
"@babel/cli": "^7.4.4",
"@babel/core": "^7.4.5",
"@babel/node": "^7.4.5",
"@babel/plugin-proposal-class-properties": "^7.4.4",
"@babel/plugin-proposal-decorators": "^7.4.4",
"@babel/plugin-proposal-export-default-from": "^7.2.0",
"@babel/preset-env": "^7.4.5",
"@babel/preset-react": "^7.0.0",
"core-js": "^3.1.4",
"jest": "^24.8.0",
"lerna": "^3.15.0",
"nodemon": "1.19.1",
"prettier": "^1.18.2"
},
"scripts": {
"build": "lerna run build",
"test": "lerna run test -- --colors"
}
}
13 changes: 13 additions & 0 deletions packages/hammer-cli/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": "../../babel.config.js",
"plugins": [
[
"babel-plugin-module-resolver",
{
"alias": {
"src": "./src"
}
}
]
]
}
43 changes: 43 additions & 0 deletions packages/hammer-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Hammer-CLI

**WARNING:** This document is aspirational (see [Readme Driven
Development](https://tom.preston-werner.com/2010/08/23/readme-driven-development.html))
and not everything contained within it is true yet.

## What is this?

By installing `hammer` you'll be able to run a bunch of commands that you might
find useful during development.

## Installation

With Yarn `yarn add -D @hammerframework/hammer-cli`, or NPM
`npm install --save-dev @hammerframework/hammer-cli`

## Usage

```terminal
yarn hammer [command]
```

## Development

Run `yarn dev` to automatically reload the

Add a new command by creating `CommandName/CommandName.js` file in the
`./src/commands` directory.

A command should export the following:

```js
export default ({ args }) => {}; // The react-ink component.
export const commandProps = {
name: 'generate',
alias: 'g', // invoke with hammer s instead of hammer scaffold,
description: 'This command does a, b, but not c.',
};
```

## Publishing

This is a monorepo and is published via LearnaJS. See the root README for instructions.
71 changes: 71 additions & 0 deletions packages/hammer-cli/generators
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import fs from 'fs';
import path from 'path';
import camelcase from 'camelcase';

const pascalCase = string => camelcase(string, { pascalCase: true });

const component = componentName => {
return `import React from 'react'
import PropTypes from 'prop-types'

/**
* This amazing component does...
*/
const ${componentName} = ({ as: Element = div, ...rest }) => {
return <Element {...rest}>I am ${componentName}.</Element>;
};

ComponentName.propTypes = {}

export const queryProps = (args = {}) => ({
query: gql\`query ${componentName}View {}\`,
skeleton: () => null,
...args
});

export default ${componentName};
`;
};

const test = componentName => {
return `
import React from 'react';
import { fireEvent, cleanup } from '@testing-library/react';

import ${componentName} from './';

describe('${componentName}', () => {

afterEach(() => {
cleanup()
});

it('this test will fail', () => {
const component = renderComponent(ComponentName);
component.debug();
expect(true).toBe(false);
})
})
`;
};

const mdx = componentName => {
return `
import ${componentName} from './'

# ${componentName}

- [ ] Document the props/ types
- [ ] Allow user to play with the component
`;
};

export default name => {
const componentName = pascalCase(name);

return {
[`${componentName}/${componentName}.js`]: component(componentName),
[`${componentName}/${componentName}.test.js`]: test(componentName),
[`${componentName}/${componentName}.mdx`]: mdx(componentName),
};
};
39 changes: 39 additions & 0 deletions packages/hammer-cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "@hammerframework/hammer-cli",
"description": "The Hammer CLI",
"version": "0.0.0-alpha.4",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/hammerframework/hammer.git/"
},
"bin": {
"hammer": "./dist/index.js"
},
"files": [
"dist"
],
"scripts": {
"build": "babel src --out-dir dist",
"clean": "rm -rf dist",
"prepare": "yarn clean && yarn build",
"hammer": "babel-node src/index.js",
"dev": "nodemon --exec babel-node src/index.js",
"test": "jest",
"test:watch": "yarn test --watch"
},
"dependencies": {
"camelcase": "^5.3.1",
"core-js": "^3.1.4",
"find-yarn-workspace-root": "^1.2.1",
"ink": "^2.2.0",
"react": "^16.8.6",
"require-dir": "^1.2.0",
"yargs-parser": "^13.1.1"
},
"devDependencies": {
"babel-plugin-module-resolver": "^3.2.0",
"ink-testing-library": "^1.0.2"
},
"gitHead": "f45e8e09cba6e0176396288f24a376ec19280e2f"
}
95 changes: 95 additions & 0 deletions packages/hammer-cli/src/commands/Generate/Generate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import path from 'path';
import React from 'react';
import { Box, Text, Color } from 'ink';

import { hammerWorkspaceDir, writeFile, bytes } from 'src/lib';

import component from './generators/component';

/**
* A generator is a function that takes a name and returns a list of filenames
* and contents that should be written to the disk.
*/
const DEFAULT_GENERATORS = {
component,
};

const DEFAULT_COMPONENT_DIR = () =>
path.join(hammerWorkspaceDir(), './web/src/components/');

const Generate = ({
args,
generators = DEFAULT_GENERATORS,
fileWriter = writeFile,
}) => {
if (!hammerWorkspaceDir()) {
return (
<Color red>
The `generate` command has to be run in your hammer project directory.
</Color>
);
}

const [
_commandName,
generatorName,
name,
targetDir = DEFAULT_COMPONENT_DIR(),
] = args;

const generator = generators[generatorName];

if (!generator || !name) {
return (
<>
<Box flexDirection="column" marginBottom={1}>
<Box marginBottom={1}>
<Text bold>Usage</Text>
</Box>
<Text>
hammer generate{' '}
<Color blue>{generatorName || 'generator'} name [path]</Color>
</Text>
</Box>
<Box flexDirection="column" marginBottom={1}>
<Text bold underline>
Available generators:
</Text>
<Box marginX={2} flexDirection="column">
<Text> component</Text>
<Text> Generate a React component</Text>
</Box>
</Box>
</>
);
}

const files = generator(name);
const results = Object.keys(files).map(filename => {
const contents = files[filename];
try {
fileWriter(path.join(targetDir, filename), contents);
return (
<Text key={`wrote-${filename}`}>
Wrote {filename} {bytes(contents)} bytes
</Text>
);
} catch (e) {
return (
<Text key={`error-${filename}`}>
<Color red>{e}</Color>
</Text>
);
}
});

return results;
};

export const commandProps = {
name: 'generate',
alias: 'g',
description: 'Save time by generating boilerplate code',
};

export default Generate;
28 changes: 28 additions & 0 deletions packages/hammer-cli/src/commands/Generate/Generate.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import { render } from 'ink-testing-library';

import Generate from './Generate';

describe('Command: Generate', () => {
const generators = {
testGenerator: () => ({ 'a.js': 'a', 'a.test.js': 'b' }),
};

it('command usage is shown when no or a bad generator is selected', () => {
const { lastFrame } = render(
<Generate args={['generate']} generators={generators} />
);
expect(lastFrame()).toMatch(/Usage:/g);
});

it('routes to the correct command', () => {
const { lastFrame } = render(
<Generate
args={['generate', 'testGenerator', 'NewComponent']}
generators={generators}
fileWriter={() => {}}
/>
);
expect(lastFrame()).toMatch(/Wrote a.js/g);
});
});
Loading