Skip to content

Commit 56d4d89

Browse files
authored
Merge pull request #334 from primer/shawnbot/module-generator
Add new module generator
2 parents 1908ce5 + 2abace0 commit 56d4d89

File tree

51 files changed

+982
-91
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+982
-91
lines changed

.travis.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ env:
99
# github
1010
- secure: "J+1oWjvvXjyrwkY/4IFWKdN/weFmQcPwlRuFG4R0Gb3rYe4nqtC9l68sJvmS8asc8dQMhOhcUZCH6sjvo7l2WD4NuK4umPSbs+rJNUsfbvH4pZjStQIj/3ll1OfQelGDWAYQWhIfciYY4F3Bp0ZWTfKOppLQ2AVIYu1fPVXDdlo="
1111

12-
before_script:
13-
- lerna bootstrap
14-
1512
script:
1613
- npm test
1714

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Primer CSS Module Generator
2+
3+
[![npm version](http://img.shields.io/npm/v/generator-primer-module.svg)](https://www.npmjs.org/package/generator-primer-module)
4+
[![Build Status](https://travis-ci.org/primer/primer-css.svg?branch=master)](https://travis-ci.org/primer/primer-css)
5+
6+
Primer is the CSS framework that powers GitHub's front-end design. This is a
7+
[Yeoman] generator that we use to scaffold new Primer modules.
8+
9+
## Usage
10+
11+
### In the Monorepo
12+
13+
1. `cd` to the top level directory of the `primer-css` repository
14+
1. Run:
15+
16+
```sh
17+
npm run new-module
18+
```
19+
20+
You can also pass the module name as a positional argument like this:
21+
22+
```sh
23+
npm run new-module -- primer-module-name
24+
```
25+
26+
1. Answer the interactive prompts.
27+
28+
> If you don't know some of the answers (aside from the module name, which
29+
> is required), it's okay to press <kbd>enter</kbd> or <kbd>return</kbd>.
30+
31+
1. If all goes well, the new module will be bootstrapped and ready to use. You
32+
should see a directory with this structure:
33+
34+
```
35+
modules/primer-module-name/
36+
├── LICENSE
37+
├── README.md
38+
├── index.scss
39+
├── lib
40+
│   └── module-name.scss
41+
└── package.json
42+
```
43+
44+
1. If you have any TODOs left from unanswered prompts, fill them out! You can
45+
list them again with:
46+
47+
```sh
48+
ack TODO modules/primer-module-name
49+
```
50+
51+
(Note: you can use `grep` if you don't have `ack` installed.)
52+
53+
54+
### Standalone installation
55+
56+
This repository is distributed with [npm][npm]. After [installing
57+
npm][install-npm], you can install `generator-primer-module` with this command.
58+
59+
```sh
60+
npm install --save generator-primer-module
61+
```
62+
63+
You'll also need to install the [`yo` CLI](https://github.com/yeoman/yo):
64+
65+
```sh
66+
npm install -g yo
67+
```
68+
69+
### Standalone usage
70+
71+
It's possible to use this generator to create "standalone" Primer modules that
72+
live outside of the Primer CSS monorepo, with the following caveats:
73+
74+
* When prompted to add the new module to existing meta-packages, you will need
75+
to un-select them all.
76+
* You will also need to manually install all of the monorepo's top-level dev
77+
dependencies to get tools like `primer-module-build` and `ava`.
78+
* The `npm test` command will not work, because it references a test spec in
79+
the monorepo.
80+
81+
To run the generator, just pass `primer-module` to the `yo` CLI:
82+
83+
```sh
84+
yo primer-module
85+
```
86+
87+
Then answer the interactiv prompts. **Note that, unlike most other generators,
88+
this one creates a new directory with the provided module name in the current
89+
working directory.**
90+
91+
You can also pass the module name as a positional argument, as in:
92+
93+
```sh
94+
yo primer-module primer-foo-bar
95+
```
96+
97+
98+
## License
99+
100+
[MIT](./LICENSE) &copy; [GitHub](https://github.com/)
101+
102+
[primer]: https://github.com/primer/primer
103+
[docs]: http://primercss.io/
104+
[npm]: https://www.npmjs.com/
105+
[install-npm]: https://docs.npmjs.com/getting-started/installing-node
106+
[sass]: http://sass-lang.com/
107+
[yeoman]: http://yeoman.io
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
const chalk = require("chalk")
2+
const fse = require("fs-extra")
3+
const path = require("path")
4+
const Generator = require("yeoman-generator")
5+
6+
const stripPrimerPrefix = require("./lib/strip-prefix")
7+
8+
const OPTIONS = require("./options")
9+
10+
module.exports = class PrimerModule extends Generator {
11+
12+
constructor(args, opts) {
13+
super(args, opts)
14+
15+
// initialize positional arguments and option flags
16+
Object.entries(OPTIONS).forEach(([name, val]) => {
17+
if (val.argument) {
18+
this.argument(name, Object.assign(val.argument, {name}))
19+
} else if (val.option) {
20+
const option = Object.assign(val.option, {name})
21+
if (name.includes("_")) {
22+
const alias = name.replace(/_/g, "-")
23+
option.alias = option.alias
24+
? [alias].concat(option.alias)
25+
: alias
26+
}
27+
this.option(name, option)
28+
}
29+
})
30+
}
31+
32+
initializing() {
33+
// this.log("It looks like you are adding a new Primer module!")
34+
}
35+
36+
prompting() {
37+
if (this.options.module) {
38+
this.log(
39+
"Okay, let's get you started with %s...",
40+
chalk.green(this.options.module)
41+
)
42+
}
43+
44+
// filter out options without prompts, and which already
45+
// have options set, then add back the "name" key to each
46+
const prompts = Object.entries(OPTIONS)
47+
.filter(([name, {prompt}]) => {
48+
return prompt && (
49+
prompt.when === true || typeof this.options[name] === "undefined"
50+
)
51+
})
52+
.map(([name, {prompt}]) => {
53+
// bind functions to the generator as `this`
54+
Object.keys(prompt).forEach(key => {
55+
if (typeof prompt[key] === "function") {
56+
prompt[key] = prompt[key].bind(this)
57+
}
58+
})
59+
return Object.assign(prompt, {name})
60+
})
61+
62+
// remove prompts for which arguments were already provided
63+
return this.prompt(prompts)
64+
.then(answers => {
65+
Object.assign(this.options, answers)
66+
})
67+
}
68+
69+
configuring() {
70+
this.dependencies = this._getDependencies()
71+
if (this.options.docs) {
72+
return fse.readFile(this.options.docs, "utf8")
73+
.then(docs => this.options.docs = docs)
74+
}
75+
}
76+
77+
paths() {
78+
this.basePath = this.destinationPath(this.options.module)
79+
}
80+
81+
writing() {
82+
this.log("creating: %s", chalk.green(this.basePath))
83+
84+
const data = {
85+
"dependencies": this.dependencies,
86+
}
87+
88+
Object.assign(
89+
data,
90+
Object.entries(OPTIONS)
91+
.map(([name, value]) => [name, this.options[name]])
92+
.reduce((acc, [name, value]) => {
93+
acc[name] = value
94+
return acc
95+
}, {})
96+
)
97+
98+
if (this.options.verbose) {
99+
const debugData = Object.assign({}, data, {
100+
"dependencies": Object.keys(data.dependencies),
101+
})
102+
console.warn(chalk.green("data:"), JSON.stringify(debugData, null, " "))
103+
}
104+
105+
// for the index.scss import
106+
data.lib = stripPrimerPrefix(data.module)
107+
108+
// copy the whole directory with each file treated as
109+
// an EJS template
110+
this.fs.copyTpl(
111+
this.templatePath(),
112+
this.basePath,
113+
data
114+
)
115+
116+
const src = path.join(this.basePath, "lib/module.scss")
117+
const dest = src.replace("module.scss", `${data.lib}.scss`)
118+
this.fs.move(src, dest)
119+
}
120+
121+
install() {
122+
const pkg = this.fs.readJSON(
123+
path.join(this.basePath, "package.json")
124+
)
125+
// this.log("package:", pkg.name, "@", pkg.version)
126+
127+
const dependents = this.options.dependents
128+
if (Array.isArray(dependents)) {
129+
dependents.forEach(dependent => {
130+
this._addAsDependencyTo(pkg, dependent)
131+
})
132+
} else {
133+
this.log(chalk.red("No dependents!"), dependents)
134+
}
135+
}
136+
137+
end() {
138+
if (this.options.todo === true) {
139+
this.log(
140+
"\n📝 ",
141+
chalk.bold("Remember to fill in any remaining TODOs below:"),
142+
"\n"
143+
)
144+
this.spawnCommandSync("ack", ["TODO", this.basePath], {
145+
stdio: "inherit",
146+
})
147+
}
148+
}
149+
150+
_getDependencies() {
151+
return [
152+
"primer-support",
153+
].reduce((deps, module) => {
154+
deps[module] = require(`${module}/package.json`)
155+
return deps
156+
}, {})
157+
}
158+
159+
_addAsDependencyTo(pkg, dest) {
160+
this.log(
161+
"adding %s@%s as a dependency to %s...",
162+
pkg.name, pkg.version, dest
163+
)
164+
165+
const destPath = require.resolve(
166+
path.join("../../", dest, "package.json")
167+
)
168+
169+
this.fs.extendJSON(destPath, {
170+
dependencies: {
171+
[pkg.name]: pkg.version,
172+
},
173+
})
174+
}
175+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = str => {
2+
return str.split("-")
3+
.map(word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase())
4+
.join(" ")
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = str => str.replace(/^primer-/, "")

0 commit comments

Comments
 (0)