Skip to content

Commit 05bb269

Browse files
committed
Update docs
1 parent 0024a33 commit 05bb269

File tree

2 files changed

+124
-61
lines changed

2 files changed

+124
-61
lines changed

CONTRIBUTING.md

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,75 @@
44

55
1. Fork the repository on GitHub
66
2. Create a named feature branch (like `support_x`)
7-
3. Add your plugin new .ts file in `src/main/plugins`. Look at the other files for examples
8-
4. Add new flags to the CLI in `src/main/index.ts`
9-
5. Add the plugin to the `plugins` array in `src/main/setup/setup.ts`.
10-
6. Add potential plugin steps to the `steps` array in `src/main/setup/setup.ts`. Steps are run top-to-bottom.
7+
3. Add a new .ts file for your plugin in `src/main/plugins`.
8+
9+
- See the [Writing a plugin section](#writing-a-plugin) below to learn how to write a Create Next Stack plugin.
10+
11+
4. Add new flags to the CLI in [`src/main/index.ts`](src/main/index.ts)
12+
5. Add the plugin to the `plugins` array in [`src/main/setup/setup.ts`](src/main/setup/setup.ts).
13+
6. Add potential plugin steps to the `steps` array in [`src/main/setup/setup.ts`](src/main/setup/setup.ts). Steps are run top-to-bottom.
1114
7. Update the `README.md`:
1215
- Add the technology to the technology list
1316
- Update the `Usage` section to include the new technology
1417
8. Consider expanding some of the e2e tests to include the new technology.
1518
9. Run tests using `yarn test` to ensure they all pass
1619
10. Submit a Pull Request on GitHub
20+
21+
## Writing a plugin
22+
23+
Plugins aren't too scary. The simple Framer Motion plugin that simply adds the `framer-motion` dependency looks like this:
24+
25+
```typescript
26+
export const framerMotionPlugin = createPlugin({
27+
name: "Framer Motion",
28+
description: "Adds support for Framer Motion",
29+
active: ({ flags }) => Boolean(flags["framer-motion"]),
30+
dependencies: {
31+
"framer-motion": {
32+
name: "framer-motion",
33+
version: "^9.0.0",
34+
},
35+
},
36+
technologies: [
37+
{
38+
name: "Framer Motion",
39+
description:
40+
"Framer Motion is a popular React animation library. It allows users to create both simple animations and complex gesture-based interactions. The library implements a declarative API, otherwise known as spring animations, which lets the developer define the animation's end state, letting the library handle the rest.",
41+
links: [
42+
{ title: "Website", url: "https://www.framer.com/motion/" },
43+
{ title: "Docs", url: "https://www.framer.com/docs/" },
44+
{ title: "GitHub", url: "https://github.com/framer/motion" },
45+
],
46+
},
47+
],
48+
} as const)
49+
```
50+
51+
and the [Emotion plugin](src/main/plugins/emotion.ts) easily sets Next.js compiler options using
52+
53+
```typescript
54+
compilerOptions: {
55+
emotion: true,
56+
}
57+
```
58+
59+
and the [Prettier plugin](src/main/plugins/prettier.ts) easily adds formatting scripts to the `package.json` file using
60+
61+
```typescript
62+
scripts: [
63+
{
64+
name: "format",
65+
description: "Formats all source code in the project.",
66+
command: "prettier --write --ignore-path=.gitignore .",
67+
},
68+
{
69+
name: "format:check",
70+
description: "Checks the formatting of all code in the project.",
71+
command: "prettier --check --ignore-path=.gitignore .",
72+
},
73+
],
74+
```
75+
76+
Take a look at the `Plugin` type for documentation on each available property in [`src/main/plugin.ts`](src/main/plugin.ts).
77+
78+
Also take a look at the [other plugins](src/main/plugins) for more advanced examples.

src/main/plugin.ts

Lines changed: 58 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,61 +2,6 @@ import { NextConfig } from "next"
22
import { ValidCNSInputs } from "./create-next-stack-types"
33
import { DeeplyReadonly } from "./helpers/deeply-readonly"
44

5-
export const createPlugin = <TPluginConfig extends PluginConfig>(
6-
pluginConfig: TPluginConfig
7-
): Plugin<TPluginConfig> => {
8-
const plugin = {
9-
...pluginConfig,
10-
}
11-
const enhancements = {
12-
steps:
13-
pluginConfig.steps != null
14-
? Object.entries(pluginConfig.steps).reduce(
15-
(acc, [key, value]) => ({
16-
...acc,
17-
[key]: createStep(value, plugin as Plugin<TPluginConfig>),
18-
}),
19-
{} as Record<string, Step>
20-
)
21-
: undefined,
22-
}
23-
for (const [key, value] of Object.entries(enhancements)) {
24-
Object.defineProperty(plugin, key, {
25-
value,
26-
enumerable: true,
27-
})
28-
}
29-
return plugin as Plugin<TPluginConfig>
30-
}
31-
32-
export const createStep = <TRawStep extends RawStep = RawStep>(
33-
step: TRawStep,
34-
plugin: Plugin
35-
): Step<TRawStep> => {
36-
return {
37-
// defaults
38-
shouldRun: true,
39-
40-
// step
41-
...step,
42-
43-
// enhancements
44-
plugin,
45-
}
46-
}
47-
48-
export type Plugin<TPluginConfig extends PluginConfig = PluginConfig> =
49-
TPluginConfig & {
50-
steps?: {
51-
[key in keyof TPluginConfig["steps"]]: Step<RawStep> // TODO: Fix type. This should be Step<TPluginConfig["steps"][key]>, but that doesn't work.
52-
}
53-
}
54-
55-
export type Step<TStep extends RawStep = RawStep> = TStep & {
56-
shouldRun: NonNullable<RawStep["shouldRun"]>
57-
plugin: Plugin
58-
}
59-
605
type PluginConfig = DeeplyReadonly<{
616
/** Name of the plugin */
627
name: string
@@ -70,7 +15,7 @@ type PluginConfig = DeeplyReadonly<{
7015
devDependencies?: Record<string, Package>
7116
/** Temporary dependencies uninstalled when Create Next Stack is done. */
7217
tmpDependencies?: Record<string, Package>
73-
/** Technology descriptions */
18+
/** Descriptions of the technologies supported by the plugin. */
7419
technologies?: Technology[]
7520
/** Scripts that are added to the package.json file. */
7621
scripts?: Script[]
@@ -153,7 +98,6 @@ type RawStep = {
15398
*/
15499
description: string
155100

156-
// TODO: Consider memoizing shouldRun, as it is sometimes called multiple times. See the lint-staged setup step.
157101
/**
158102
* A boolean or function that determines whether the custom run function should run.
159103
*
@@ -165,6 +109,63 @@ type RawStep = {
165109
run: (inputs: ValidCNSInputs) => Promise<void>
166110
}
167111

112+
export const createPlugin = <TPluginConfig extends PluginConfig>(
113+
pluginConfig: TPluginConfig
114+
): Plugin<TPluginConfig> => {
115+
const plugin = {
116+
...pluginConfig,
117+
}
118+
const enhancements = {
119+
steps:
120+
pluginConfig.steps != null
121+
? Object.entries(pluginConfig.steps).reduce(
122+
(acc, [key, value]) => ({
123+
...acc,
124+
[key]: createStep(value, plugin as Plugin<TPluginConfig>),
125+
}),
126+
{} as Record<string, Step>
127+
)
128+
: undefined,
129+
}
130+
for (const [key, value] of Object.entries(enhancements)) {
131+
Object.defineProperty(plugin, key, {
132+
value,
133+
enumerable: true,
134+
})
135+
}
136+
return plugin as Plugin<TPluginConfig>
137+
}
138+
139+
export const createStep = <TRawStep extends RawStep = RawStep>(
140+
step: TRawStep,
141+
plugin: Plugin
142+
): Step<TRawStep> => {
143+
return {
144+
// defaults
145+
shouldRun: true,
146+
147+
// TODO: Consider memoizing shouldRun, as it is sometimes called multiple times. See the lint-staged setup step.
148+
149+
// step
150+
...step,
151+
152+
// enhancements
153+
plugin,
154+
}
155+
}
156+
157+
export type Plugin<TPluginConfig extends PluginConfig = PluginConfig> =
158+
TPluginConfig & {
159+
steps?: {
160+
[key in keyof TPluginConfig["steps"]]: Step<RawStep> // TODO: Fix type. This should be Step<TPluginConfig["steps"][key]>, but that doesn't work.
161+
}
162+
}
163+
164+
export type Step<TStep extends RawStep = RawStep> = TStep & {
165+
shouldRun: NonNullable<RawStep["shouldRun"]>
166+
plugin: Plugin
167+
}
168+
168169
export const evalActive = (
169170
active: PluginConfig["active"],
170171
inputs: ValidCNSInputs

0 commit comments

Comments
 (0)