Skip to content

Commit c7d2d82

Browse files
authored
Merge pull request #180 from akd-io/feature/add-jest-and-sort-orders
Add Jest and sort orders
2 parents da914ef + 2d6406f commit c7d2d82

38 files changed

+2569
-142
lines changed

CONTRIBUTING.md

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,33 @@ Make sure you are set up locally by following the [Getting Started](#getting-sta
7070

7171
- `git checkout -b feature/support-my-favorite-technology`
7272

73-
2. Add a new .ts file for your plugin in the plugins directory at `packages\create-next-stack\src\main\plugins`
73+
2. Useful npm scripts of `packages/create-next-stack` to run during development:
74+
75+
- `check-types:watch` - Runs TypeScript in watch mode to check types as you make changes. You can run this instead of `build:watch` while working on the CLI, as the e2e tests do just-in-time compilation via `ts-node`.
76+
- `jest:watch` - Runs Jest in watch mode to run unit tests as you make changes.
77+
- These tests were specifically made to ease the plugin authoring process, so don't forget this one.
78+
- `test` - Runs e2e tests. Note that this will run all e2e tests, which can take quite a while.
79+
- `lint` - Runs ESLint to lint the project.
80+
- `test:manual`
81+
- For example, `pnpm run test:manual --package-manager=pnpm --styling=emotion`.
82+
- Sets up a new directory for a test run of the CLI, runs the CLI with the specified flags, and builds the generated Next app, and checks formatting and linting.
83+
- This is useful for manually testing the CLI. Pass whatever flags to the CLI that you want to test. The `app_name` argument will be set automatically.
84+
- `test:raw` - Runs the binary directly. Rarely used, but can be useful for manual tests where you want to be able to specify the `app_name` argument yourself.
85+
- `clean` - Removes all generated files, including build files and the `create-next-stack-tests` directory created by the e2e tests.
86+
87+
3. Add a new .ts file for your plugin in the plugins directory at `packages\create-next-stack\src\main\plugins`
7488

7589
- See the [Writing a plugin section](#writing-a-plugin) below to learn how to write a Create Next Stack plugin.
7690

77-
3. Add new flags to the `create-next-stack` command in [`create-next-stack.ts`](packages\create-next-stack\src\main\commands\create-next-stack.ts).
78-
4. Add the plugin to the `plugins` array in [`setup.ts`](packages/create-next-stack/src/main/setup/setup.ts).
79-
5. Add potential plugin steps to the `steps` array in [`setup.ts`](packages/create-next-stack/src/main/setup/setup.ts). Steps are run top-to-bottom.
80-
6. Update the [`README.md`](README.md):
91+
4. Add new flags to the `create-next-stack` command in [`create-next-stack.ts`](packages\create-next-stack\src\main\commands\create-next-stack.ts).
92+
5. Add the plugin to the `plugins` array in [`setup.ts`](packages/create-next-stack/src/main/setup/setup.ts).
93+
6. Add potential plugin steps to the `steps` array in [`setup.ts`](packages/create-next-stack/src/main/setup/setup.ts). Steps are run top-to-bottom.
94+
7. Update the [`README.md`](README.md):
8195
- Add the technology to the technology list
8296
- Update the `Usage` section by copy pasting the output of running `yarn print:help`
83-
7. Consider expanding some of the e2e tests to include the new technology. See [`test.ts`](packages\create-next-stack\src\tests\e2e\test.ts) for current tests.
84-
8. Run tests using `yarn test` to ensure they all pass.
85-
9. Submit a Pull Request on GitHub.
97+
8. Consider expanding some of the e2e tests to include the new technology. See [`test.ts`](packages\create-next-stack\src\tests\e2e\test.ts) for current tests.
98+
9. Run tests using `yarn test` to ensure they all pass.
99+
10. Submit a Pull Request on GitHub.
86100

87101
## Writing a Plugin
88102

@@ -92,6 +106,7 @@ See the [Framer Motion plugin](packages/create-next-stack/src/main/plugins/emoti
92106

93107
```typescript
94108
export const framerMotionPlugin = createPlugin({
109+
id: "framer-motion",
95110
name: "Framer Motion",
96111
description: "Adds support for Framer Motion",
97112
active: ({ flags }) => Boolean(flags["framer-motion"]),
@@ -103,6 +118,7 @@ export const framerMotionPlugin = createPlugin({
103118
},
104119
technologies: [
105120
{
121+
id: "framerMotion",
106122
name: "Framer Motion",
107123
description:
108124
"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.",
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/** @type {import('ts-jest').JestConfigWithTsJest} */
2+
// eslint-disable-next-line no-undef
3+
module.exports = {
4+
preset: "ts-jest",
5+
testEnvironment: "node",
6+
testPathIgnorePatterns: ["/node_modules/", "/lib/", "/src/tests/"],
7+
}

packages/create-next-stack/package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,14 @@
4848
"scripts": {
4949
"prepack": "pnpm build",
5050
"check-types": "tsc --noEmit",
51+
"check-types:watch": "tsc --noEmit --watch",
5152
"build": "rimraf lib && tsc --build",
53+
"build:watch": "rimraf lib && tsc --build --watch",
5254
"clean": "rimraf lib && pnpm run clean-tests-dir",
5355
"clean-tests-dir": "ts-node src/tests/e2e/clean-tests-dir.ts",
54-
"test": "pnpm build && ts-node src/tests/e2e/test.ts",
56+
"test": "pnpm build && pnpm run jest && ts-node src/tests/e2e/test.ts",
57+
"jest": "jest",
58+
"jest:watch": "jest --watch",
5559
"test:manual": "pnpm build && ts-node src/tests/e2e/test-manual.ts --debug",
5660
"test:small": "pnpm build && ts-node src/tests/e2e/test-manual.ts --debug --package-manager=pnpm --styling=css-modules",
5761
"test:large": "pnpm build && ts-node src/tests/e2e/test-manual.ts --debug --package-manager=pnpm --styling=emotion --react-hook-form --formik --prettier --formatting-pre-commit-hook --chakra --framer-motion --github-actions",
@@ -74,6 +78,7 @@
7478
"validate-npm-package-name": "^5.0.0"
7579
},
7680
"devDependencies": {
81+
"@jest/globals": "^29.5.0",
7782
"@types/inquirer": "^9.0.3",
7883
"@types/node": "^18.15.13",
7984
"@types/uuid": "^9.0.1",
@@ -82,7 +87,9 @@
8287
"@typescript-eslint/parser": "^5.59.0",
8388
"eslint": "^8.39.0",
8489
"eslint-config-prettier": "^8.8.0",
90+
"jest": "^29.5.0",
8591
"next": "^13.3.1",
92+
"ts-jest": "^29.1.0",
8693
"ts-node": "^10.9.1",
8794
"uuid": "^9.0.0"
8895
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { logDebug } from "../logging"
2+
3+
/**
4+
* Compares two string values by their order in an array.
5+
* @param a a string value
6+
* @param b a string value
7+
* @param order an array of string values specifying the order
8+
* @returns a negative number if `a` comes before `b` in `order`, a positive number if `b` comes before `a` in `order`, or `0` if `a` and `b` are equal in `order` or if either `a` or `b` are not in `order`
9+
*/
10+
export const compareByOrder = (a: string, b: string, order: string[]) => {
11+
const aIndex = order.indexOf(a)
12+
const bIndex = order.indexOf(b)
13+
if (aIndex === -1 || bIndex === -1) {
14+
logDebug(
15+
`WARNING: One or both of the values being compared are not in the order array. This should be caught by a test.`
16+
)
17+
return 0
18+
}
19+
return aIndex - bIndex
20+
}

packages/create-next-stack/src/main/plugin.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { ValidCNSInputs } from "./create-next-stack-types"
33
import { DeeplyReadonly } from "./helpers/deeply-readonly"
44

55
type PluginConfig = DeeplyReadonly<{
6+
/** ID that uniquely identifies the plugin */
7+
id: string
68
/** Name of the plugin */
79
name: string
810
/** Description of the plugin */
@@ -55,7 +57,9 @@ export type Package = {
5557
version: string
5658
}
5759

58-
type Technology = {
60+
export type Technology = {
61+
/** ID that uniquely identified the technology across all plugins' technologies. */
62+
id: string
5963
/** The name of the technology. */
6064
name: string
6165
/** Description of a technology. This is displayed in the generated README.md file, as well as in the landing page's list of technologies. */
@@ -91,6 +95,9 @@ type Script = {
9195
}
9296

9397
type RawStep = {
98+
/** ID that uniquely identified the technology across all plugins' steps. */
99+
id: string
100+
94101
/**
95102
* `description` should be written in present continuous tense, without punctuation, and with a lowercase first letter unless the description starts with a name or similar.
96103
*

packages/create-next-stack/src/main/plugins/chakra-ui/chakra-ui.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { createPlugin } from "../../plugin"
44
import { chakraTheme } from "./setup/chakra-theme"
55

66
export const chakraUIPlugin = createPlugin({
7+
id: "chakra-ui",
78
name: "Chakra UI",
89
description: "Adds support for Chakra UI",
910
active: ({ flags }) => Boolean(flags.chakra),
@@ -19,6 +20,7 @@ export const chakraUIPlugin = createPlugin({
1920
},
2021
technologies: [
2122
{
23+
id: "chakraUI",
2224
name: "Chakra UI",
2325
description:
2426
"Chakra UI is a simple, modular, and accessible React component library that provides all the building blocks needed to build React user interfaces. It uses Emotion under the hood and includes components ranging from basic buttons and form input fields to tooltips and modals.",
@@ -30,7 +32,8 @@ export const chakraUIPlugin = createPlugin({
3032
},
3133
],
3234
steps: {
33-
setup: {
35+
setUpChakraUI: {
36+
id: "setUpChakraUI",
3437
description: "setting up Chakra UI",
3538
run: async () => {
3639
await writeFile("chakra-theme.ts", chakraTheme)

packages/create-next-stack/src/main/plugins/create-next-stack/add-content/templates/LandingPage/generate-technologies.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,20 @@ import endent from "endent"
22
import { ValidCNSInputs } from "../../../../../create-next-stack-types"
33
import { DeeplyReadonly } from "../../../../../helpers/deeply-readonly"
44
import { stringify } from "../../../../../helpers/stringify"
5-
import { filterPlugins } from "../../../../../setup/setup"
6-
7-
// TODO: Add a technologies sort order here. Use TypeScript to force setting all plugin technologies.
5+
import { getTechnologies } from "../../../sort-orders/technologies"
86

97
// This type should match the one in the template below.
10-
export type Technology = {
8+
export type Technology = DeeplyReadonly<{
119
name: string
1210
description: string
1311
links: Array<{
1412
title: string
1513
url: string
1614
}>
17-
}
15+
}>
1816

1917
export const generateTechnologies = (inputs: ValidCNSInputs): string => {
20-
const pluginTechnologies = filterPlugins(inputs).flatMap(
21-
(plugin): DeeplyReadonly<Technology[]> => plugin.technologies ?? []
22-
)
18+
const technologies: Technology[] = getTechnologies(inputs)
2319

2420
return endent`
2521
export type Technology = {
@@ -30,6 +26,6 @@ export const generateTechnologies = (inputs: ValidCNSInputs): string => {
3026
url: string;
3127
}>;
3228
};
33-
export const technologies: Technology[] = ${stringify(pluginTechnologies)};
29+
export const technologies: Technology[] = ${stringify(technologies)};
3430
`
3531
}
Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
import { ValidCNSInputs } from "../../../create-next-stack-types"
2-
import { filterPlugins } from "../../../setup/setup"
2+
import { getSortedFilteredScripts } from "../sort-orders/scripts"
33

44
export const generateScriptTableRows = async (
55
inputs: ValidCNSInputs
66
): Promise<string> => {
7-
const pluginScripts = filterPlugins(inputs).flatMap(
8-
(plugin) => plugin.scripts ?? []
9-
)
10-
11-
const scriptRowsString = pluginScripts
7+
const scripts = getSortedFilteredScripts(inputs)
8+
const scriptRowsString = scripts
129
.map((script) => `|\`${script.name}\`|${script.description}|`)
1310
.join("\n")
14-
1511
return scriptRowsString
1612
}

packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-technology-table-rows.ts

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,13 @@
11
import { ValidCNSInputs } from "../../../create-next-stack-types"
2-
import { filterPlugins } from "../../../setup/setup"
2+
import { getTechnologies } from "../sort-orders/technologies"
33

44
export const generateTechnologyTableRows = async (
55
inputs: ValidCNSInputs
66
): Promise<string> => {
7-
type TechnologyTableRow = {
8-
name: string
9-
links: string
10-
}
11-
12-
const technologies: TechnologyTableRow[] = filterPlugins(inputs).flatMap(
13-
({ technologies }) => {
14-
if (!technologies) return []
15-
return technologies.map((technology) => ({
16-
name: technology.name,
17-
links: technology.links
18-
.map((l) => `[${l.title}](${l.url})`)
19-
.join(" - "),
20-
}))
21-
}
22-
)
23-
7+
const technologies = getTechnologies(inputs).map((technology) => ({
8+
name: technology.name,
9+
links: technology.links.map((l) => `[${l.title}](${l.url})`).join(" - "),
10+
}))
2411
return technologies
2512
.map((technology) => `| ${technology.name} | ${technology.links} |`)
2613
.join("\n")

packages/create-next-stack/src/main/plugins/create-next-stack/create-next-stack.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,22 @@ import { generateIndexPage } from "./add-content/pages/generate-index"
2222
import { generateLandingPageTemplate } from "./add-content/templates/LandingPage/generate-LandingPageTemplate"
2323
import { generateTechnologies } from "./add-content/templates/LandingPage/generate-technologies"
2424
import { generateReadme } from "./add-readme/generate-readme"
25+
import { getSortedFilteredScripts } from "./sort-orders/scripts"
2526

2627
const gitAttributesFilename = ".gitattributes"
2728

2829
export const createNextStackPlugin = createPlugin({
30+
id: "create-next-stack",
2931
name: "Create Next Stack",
3032
description:
3133
"Adds various miscellaneous steps. Some necessities, some niceties.",
3234
active: true,
3335
steps: {
3436
addScripts: {
37+
id: "addScripts",
3538
description: "adding scripts to package.json",
3639
run: async (inputs) => {
37-
const scripts = filterPlugins(inputs).flatMap(
38-
(plugin) => plugin.scripts ?? []
39-
)
40-
41-
// TODO: Add a scripts sort order here. Use TypeScript to force setting all plugin scripts.
40+
const scripts = getSortedFilteredScripts(inputs)
4241

4342
await modifyJsonFile("package.json", (packageJson) => ({
4443
...packageJson,
@@ -56,6 +55,7 @@ export const createNextStackPlugin = createPlugin({
5655
},
5756
},
5857
copyAssets: {
58+
id: "copyAssets",
5959
description: "copying static assets",
6060
run: async (): Promise<void> => {
6161
const createNextStackDir = getCreateNextStackDir()
@@ -65,6 +65,7 @@ export const createNextStackPlugin = createPlugin({
6565
},
6666
},
6767
addContent: {
68+
id: "addContent",
6869
description: "adding content",
6970
run: async (inputs) => {
7071
await makeDirectory("components")
@@ -84,6 +85,7 @@ export const createNextStackPlugin = createPlugin({
8485
},
8586
},
8687
addReadme: {
88+
id: "addReadme",
8789
description: "adding Readme",
8890
run: async (inputs) => {
8991
const readmeFileName = "README.md"
@@ -93,6 +95,7 @@ export const createNextStackPlugin = createPlugin({
9395
},
9496
},
9597
initialCommit: {
98+
id: "initialCommit",
9699
description: "adding initial commit",
97100
shouldRun: async () => {
98101
if (!(await isGitInitialized())) {
@@ -113,6 +116,7 @@ export const createNextStackPlugin = createPlugin({
113116
},
114117
},
115118
installDependencies: {
119+
id: "installDependencies",
116120
description: "installing dependencies",
117121
run: async (inputs) => {
118122
const { flags } = inputs
@@ -143,6 +147,7 @@ export const createNextStackPlugin = createPlugin({
143147
},
144148
},
145149
uninstallTemporaryDependencies: {
150+
id: "uninstallTemporaryDependencies",
146151
description: "uninstalling temporary dependencies",
147152
run: async (inputs) => {
148153
const tmpDeps = filterPlugins(inputs).flatMap((plugin) => {
@@ -157,6 +162,7 @@ export const createNextStackPlugin = createPlugin({
157162
},
158163
},
159164
formatProject: {
165+
id: "formatProject",
160166
description: "formatting project",
161167
run: async () => {
162168
await runCommand("npx", [
@@ -167,6 +173,7 @@ export const createNextStackPlugin = createPlugin({
167173
},
168174
},
169175
addGitAttributes: {
176+
id: "addGitAttributes",
170177
description: `adding ${gitAttributesFilename}`,
171178
shouldRun: async () => {
172179
if (!(await isGitInitialized())) {

0 commit comments

Comments
 (0)