diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7aef8ec9..fbbc77d1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -106,22 +106,17 @@ Make sure you are set up locally by following the [Getting Started](#getting-sta ## Writing a Plugin -Plugins aren't too scary. A Create Next Stack plugin consists of a simple TypeScript file that calls a `createPlugin()` function with JSON object. +Plugins aren't too scary. A Create Next Stack plugin consists of a simple TypeScript file that exports an object of type [`Plugin`](./packages/create-next-stack/src/main/plugin.ts). See the [Framer Motion plugin](packages/create-next-stack/src/main/plugins/framer-motion.ts) for example. This plugin adds the `framer-motion` npm dependency to the generated Next.js project, as well as adding some documentation about the technology. ```typescript -export const framerMotionPlugin = createPlugin({ +export const framerMotionPlugin: Plugin = { id: "framer-motion", name: "Framer Motion", description: "Adds support for Framer Motion", active: ({ flags }) => Boolean(flags["framer-motion"]), - dependencies: { - "framer-motion": { - name: "framer-motion", - version: "^9.0.0", - }, - }, + dependencies: [{ name: "framer-motion", version: "^9.0.0" }], technologies: [ { id: "framerMotion", @@ -135,19 +130,20 @@ export const framerMotionPlugin = createPlugin({ ], }, ], -} as const) +} as const ``` -Below is a breakdown of the `createPlugin()` function's JSON object: +Below is a small breakdown of the properties of the above plugin object: +- The `id` property is a unique identifier for the plugin. - The `name` property is the name of the plugin. - The `description` property is a short description of the plugin. -- The `active` property is a function that returns a boolean indicating whether the plugin should be active. This function is passed the `flags` object, which contains all the flags passed to the `create-next-stack` command. -- The `dependencies` property is an object containing the npm dependencies that should be added to the generated Next.js project. The key and `name` property is the name of the dependency, and the `version` property is version of the dependency. -- The `technologies` property is an array of objects containing information about the technology. The `name` property is the name of the technology. The `description` property is a short description of the technology. The `links` property is an array of objects containing links to the technology's website, documentation, and GitHub repository. +- The `active` property is a function that returns a boolean indicating whether the plugin should be active. This function is passed the flags and arguments passed by users to the `create-next-stack` command. +- The `dependencies` property is an array of npm dependencies that should be added to the generated Next.js project. +- The `technologies` property is an array of objects containing documentation about the technologies supported by the plugin. -Some of these properties are optional, and some are required. Some properties are used by the CLI, some are used by the website, and some both. It's not too important to know everywhere these properties are used. As long as we specify as many properties as possible, the CLI and website is going to find out how to use it. +Some of these properties are optional, and some are required. Some properties are used by the CLI, some are used by the website, and some both. It's not too important to know exactly where these properties are used. As long as we specify all relevant properties, the CLI and website is going to find out how to use it. -For a complete list of properties that can be passed to the `createPlugin()` function, their explanations, and usage, see the [`Plugin` type definition](packages/create-next-stack/src/main/plugin.ts). You should find all the documentation you need there. If not, please [open an issue](https://github.com/akd-io/create-next-stack/issues/new). +For a complete list of properties of the `Plugin` type, their explanations, and usage, see the [`Plugin` type definition](packages/create-next-stack/src/main/plugin.ts). You should find all the documentation you need there. If not, please [open an issue](https://github.com/akd-io/create-next-stack/issues/new). For more examples, please take a look at the [existing plugins](packages/create-next-stack/src/main/plugins). diff --git a/apps/website/templates/LandingPage/components/TechnologiesForm.tsx b/apps/website/templates/LandingPage/components/TechnologiesForm.tsx index 1921eae1..be6dabf1 100644 --- a/apps/website/templates/LandingPage/components/TechnologiesForm.tsx +++ b/apps/website/templates/LandingPage/components/TechnologiesForm.tsx @@ -47,6 +47,7 @@ type OptionKey = | "plausible" | "vercel" | "netlify" + | "prisma" const options = { pnpm: { key: "pnpm", value: "pnpm", label: "pnpm" }, @@ -124,6 +125,11 @@ const options = { value: "netlify", label: "Netlify", }, + prisma: { + key: "prisma", + value: "prisma", + label: "Prisma", + }, } satisfies { [Key in OptionKey]: { key: Key @@ -173,6 +179,7 @@ const deploymentOptionKeys = [ optionKeys.vercel, optionKeys.netlify, ] satisfies OptionKey[] +const ormOptionKeys = [optionKeys.prisma] satisfies OptionKey[] type ProjectName = string type PackageManager = (typeof packageManagerOptionKeys)[number] @@ -187,6 +194,7 @@ type ServerStateManagementLibrary = (typeof serverStateManagementLibraryOptionKeys)[number] type Analytics = (typeof analyticsOptionKeys)[number] type Deployment = (typeof deploymentOptionKeys)[number] +type ORM = (typeof ormOptionKeys)[number] type TechnologiesFormData = { projectName: ProjectName @@ -201,6 +209,7 @@ type TechnologiesFormData = { serverStateManagementLibraries: ServerStateManagementLibrary[] analytics: Analytics[] deployment: Deployment[] + orm: ORM[] } const defaultFormData: TechnologiesFormData = { projectName: "my-app", @@ -215,6 +224,7 @@ const defaultFormData: TechnologiesFormData = { serverStateManagementLibraries: [optionKeys.reactQuery], analytics: [], deployment: [optionKeys.vercel], + orm: [], } const formDataKeys = objectToKeyToKeyMap(defaultFormData) @@ -233,6 +243,7 @@ const categoryLabels = { serverStateManagementLibraries: "Server State Management", analytics: "Analytics", deployment: "Deployment", + orm: "ORMs", } as const export const TechnologiesForm: React.FC = () => { @@ -271,6 +282,7 @@ export const TechnologiesForm: React.FC = () => { pushArgs(formData.serverStateManagementLibraries) pushArgs(formData.analytics) pushArgs(formData.deployment) + pushArgs(formData.orm) const projectNameSegments = formData.projectName.split("/") const lastPartOfProjectName = projectNameSegments.pop()! @@ -293,7 +305,8 @@ export const TechnologiesForm: React.FC = () => { | "continuousIntegration" | "serverStateManagementLibraries" | "analytics" - | "deployment", + | "deployment" + | "orm", optionKeys: Array, validators?: { [key in keyof typeof options]?: Array<{ @@ -455,6 +468,13 @@ export const TechnologiesForm: React.FC = () => { analyticsOptionKeys )} + + + + {categoryLabels.orm} + + {CheckboxesOfOptionKeys(formDataKeys.orm, ormOptionKeys)} + diff --git a/packages/create-next-stack/README.md b/packages/create-next-stack/README.md index ff5a718d..148c80d2 100644 --- a/packages/create-next-stack/README.md +++ b/packages/create-next-stack/README.md @@ -64,6 +64,7 @@ The table below provides an overview of the technologies currently supported by | next-plausible | [Website](https://next-plausible.vercel.app/) - [GitHub](https://github.com/4lejandrito/next-plausible) | | Vercel | [Website](https://vercel.com/) - [Docs](https://vercel.com/docs) - [CLI Docs](https://vercel.com/docs/cli) | | Netlify | [Website](https://www.netlify.com/) - [Docs](https://docs.netlify.com/) - [CLI Docs](https://cli.netlify.com/) | +| Prisma | [Website](https://www.prisma.io/) - [Docs](https://www.prisma.io/docs) - [GitHub](https://github.com/prisma/prisma) | @@ -104,6 +105,7 @@ FLAGS --plausible Adds Plausible. (Analytics) --prettier Adds Prettier. (Code formatting) + --prisma Adds Prisma. (ORM) --react-hook-form Adds React Hook Form. (Form library) --react-icons Adds React Icons. (Icon library) --react-query Adds React Query. (Server state management diff --git a/packages/create-next-stack/src/main/commands/create-next-stack.ts b/packages/create-next-stack/src/main/commands/create-next-stack.ts index 5a0ae1ce..6b6419a1 100644 --- a/packages/create-next-stack/src/main/commands/create-next-stack.ts +++ b/packages/create-next-stack/src/main/commands/create-next-stack.ts @@ -112,6 +112,11 @@ export default class CreateNextStack extends Command { vercel: Flags.boolean({ description: "Adds Vercel. (Hosting)", }), + + // ORMs + prisma: Flags.boolean({ + description: "Adds Prisma. (ORM)", + }), } async run(): Promise { diff --git a/packages/create-next-stack/src/main/helpers/deeply-readonly.ts b/packages/create-next-stack/src/main/helpers/deeply-readonly.ts deleted file mode 100644 index bed2015f..00000000 --- a/packages/create-next-stack/src/main/helpers/deeply-readonly.ts +++ /dev/null @@ -1,11 +0,0 @@ -export type DeeplyReadonly = T extends unknown[] - ? DeeplyReadonlyArray - : T extends Record - ? DeeplyReadonlyObject - : T - -export type DeeplyReadonlyArray = ReadonlyArray> - -export type DeeplyReadonlyObject = { - readonly [P in keyof T]: DeeplyReadonly -} diff --git a/packages/create-next-stack/src/main/helpers/filterAsync.ts b/packages/create-next-stack/src/main/helpers/filterAsync.ts new file mode 100644 index 00000000..4e30de2b --- /dev/null +++ b/packages/create-next-stack/src/main/helpers/filterAsync.ts @@ -0,0 +1,7 @@ +export const filterAsync = async ( + array: T[], + predicate: (item: T) => Promise +): Promise => { + const verdicts = await Promise.all(array.map(predicate)) + return array.filter((_, index) => verdicts[index]) +} diff --git a/packages/create-next-stack/src/main/helpers/io.ts b/packages/create-next-stack/src/main/helpers/io.ts index 3ae70fc3..4df65e8a 100644 --- a/packages/create-next-stack/src/main/helpers/io.ts +++ b/packages/create-next-stack/src/main/helpers/io.ts @@ -1,5 +1,6 @@ import { existsSync } from "fs" import fs from "fs/promises" +import path from "path" import { logDebug, logError } from "../logging" import { isUnknownArray } from "./is-unknown-array" import { isUnknownObject } from "./is-unknown-object" @@ -13,7 +14,10 @@ export const makeDirectory = async (path: string): Promise => { } export const writeFile: typeof fs.writeFile = async (file, data, options) => { - logDebug("Writing file:", file.toString()) + const fileString = file.toString() + const directory = path.dirname(fileString) + await makeDirectory(directory) + logDebug("Writing file:", fileString) return fs.writeFile(file, data, options) } diff --git a/packages/create-next-stack/src/main/plugin.ts b/packages/create-next-stack/src/main/plugin.ts index 71d2fe74..bdf6f7a6 100644 --- a/packages/create-next-stack/src/main/plugin.ts +++ b/packages/create-next-stack/src/main/plugin.ts @@ -1,8 +1,7 @@ import { NextConfig } from "next" import { ValidCNSInputs } from "./create-next-stack-types" -import { DeeplyReadonly } from "./helpers/deeply-readonly" -type PluginConfig = DeeplyReadonly<{ +export type Plugin = { /** ID that uniquely identifies the plugin */ id: string /** Name of the plugin */ @@ -10,19 +9,19 @@ type PluginConfig = DeeplyReadonly<{ /** Description of the plugin */ description: string /** Whether the plugin is active or not. This determines if dependencies are installed, technologies and scripts added, steps run, and more. */ - active: boolean | ((inputs: ValidCNSInputs) => boolean) + active: boolean | ((inputs: ValidCNSInputs) => boolean | Promise) /** Dependencies that are added to the package.json file. */ - dependencies?: Record + dependencies?: Package[] /** Dev dependencies that are added to the package.json file. */ - devDependencies?: Record + devDependencies?: Package[] /** Temporary dependencies uninstalled when Create Next Stack is done. */ - tmpDependencies?: Record + tmpDependencies?: Package[] /** Descriptions of the technologies supported by the plugin. */ technologies?: Technology[] /** Scripts that are added to the package.json file. */ scripts?: Script[] /** A series of functions that are run by Create Next Stack. */ - steps?: Record + steps?: Step[] /** * Environment variables needed by the plugin. * These variables are added to the generated .env and README.md files. @@ -40,6 +39,20 @@ type PluginConfig = DeeplyReadonly<{ * The list will be added to the generated landing page, the README.md file and written to the console. */ todos?: string[] + /** Files to be added by the plugin. */ + addFiles?: Array<{ + /** Destination of the file to add. */ + destination: string + /** Content of the file. */ + content: string | ((inputs: ValidCNSInputs) => string | Promise) + /** + * Condition to determine if the file should be added. + * + */ + condition?: + | boolean + | ((inputs: ValidCNSInputs) => boolean | Promise) + }> /** Slots to fill in the generated files. */ slots?: { /** Slots to fill in the _app.tsx file. The file is generated using the following template: @@ -150,7 +163,7 @@ type PluginConfig = DeeplyReadonly<{ wrappersEnd?: string } } -}> +} export type Package = { /** Name of the package. */ @@ -196,97 +209,30 @@ type Script = { command: string } -type RawStep = { +export type Step = { /** ID that uniquely identified the technology across all plugins' steps. */ id: string - - /** - * `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. - * - * Eg. "setting up Prettier" or "adding ESLint" - */ + /** A description of the step. It should be written in present continuous tense, without punctuation, and with a lowercase first letter unless the description starts with a name or similar. */ description: string - - /** - * A boolean or function that determines whether the custom run function should run. - * - * Default is true - */ + /** A boolean or function that determines whether the custom run function should run. Default is true. */ shouldRun?: boolean | ((inputs: ValidCNSInputs) => Promise | boolean) - /** Custom run function. */ run: (inputs: ValidCNSInputs) => Promise } -export const createPlugin = ( - pluginConfig: TPluginConfig -): Plugin => { - const plugin = { - ...pluginConfig, - } - const enhancements = { - steps: - pluginConfig.steps != null - ? Object.entries(pluginConfig.steps).reduce( - (acc, [key, value]) => ({ - ...acc, - [key]: createStep(value, plugin as Plugin), - }), - {} as Record - ) - : undefined, - } - for (const [key, value] of Object.entries(enhancements)) { - Object.defineProperty(plugin, key, { - value, - enumerable: true, - }) - } - return plugin as Plugin -} - -export const createStep = ( - step: TRawStep, - plugin: Plugin -): Step => { - return { - // defaults - shouldRun: true, - - // TODO: Consider memoizing shouldRun, as it is sometimes called multiple times. See the lint-staged setup step. - - // step - ...step, - - // enhancements - plugin, - } -} - -export type Plugin = - TPluginConfig & { - steps?: { - [key in keyof TPluginConfig["steps"]]: Step // TODO: Fix type. This should be Step, but that doesn't work. - } - } - -export type Step = TStep & { - shouldRun: NonNullable - plugin: Plugin -} - -export const evalActive = ( - active: PluginConfig["active"], +export const evalProperty = async ( + value: T | ((inputs: ValidCNSInputs) => T | Promise), inputs: ValidCNSInputs -): boolean => { - if (typeof active === "function") return active(inputs) - return active +): Promise => { + if (typeof value === "function") return await value(inputs) + return value } -export const evalShouldRun = async ( - shouldRun: Step["shouldRun"], - inputs: ValidCNSInputs -): Promise => { - if (typeof shouldRun === "function") return await shouldRun(inputs) - return shouldRun +export const evalOptionalProperty = async ( + value: T | ((inputs: ValidCNSInputs) => T | Promise) | undefined, + inputs: ValidCNSInputs, + defaultValue: Exclude +): Promise => { + if (typeof value === "undefined") return defaultValue + return await evalProperty(value, inputs) } diff --git a/packages/create-next-stack/src/main/plugins/chakra-ui/chakra-ui.ts b/packages/create-next-stack/src/main/plugins/chakra-ui.ts similarity index 69% rename from packages/create-next-stack/src/main/plugins/chakra-ui/chakra-ui.ts rename to packages/create-next-stack/src/main/plugins/chakra-ui.ts index 72ee7260..3f5fc0d3 100644 --- a/packages/create-next-stack/src/main/plugins/chakra-ui/chakra-ui.ts +++ b/packages/create-next-stack/src/main/plugins/chakra-ui.ts @@ -1,23 +1,15 @@ import endent from "endent" -import { writeFile } from "../../helpers/io" -import { createPlugin } from "../../plugin" -import { chakraTheme } from "./setup/chakra-theme" +import { Plugin } from "../plugin" -export const chakraUIPlugin = createPlugin({ +export const chakraUIPlugin: Plugin = { id: "chakra-ui", name: "Chakra UI", description: "Adds support for Chakra UI", active: ({ flags }) => Boolean(flags.chakra), - dependencies: { - "@chakra-ui/icons": { - name: "@chakra-ui/icons", - version: "^2.0.0", - }, - "@chakra-ui/react": { - name: "@chakra-ui/react", - version: "^2.0.0", - }, - }, + dependencies: [ + { name: "@chakra-ui/react", version: "^2.0.0" }, + { name: "@chakra-ui/icons", version: "^2.0.0" }, + ], technologies: [ { id: "chakraUI", @@ -31,15 +23,6 @@ export const chakraUIPlugin = createPlugin({ ], }, ], - steps: { - setUpChakraUI: { - id: "setUpChakraUI", - description: "setting up Chakra UI", - run: async () => { - await writeFile("chakra-theme.ts", chakraTheme) - }, - }, - }, slots: { app: { imports: endent` @@ -61,4 +44,19 @@ export const chakraUIPlugin = createPlugin({ body: ``, }, }, -} as const) + addFiles: [ + { + destination: "chakra-theme.ts", + content: endent` + import { extendTheme, ThemeConfig } from "@chakra-ui/react"; + + const config: ThemeConfig = { + initialColorMode: "light", + useSystemColorMode: false, + }; + + export const chakraTheme = extendTheme({ config }); + `, + }, + ], +} diff --git a/packages/create-next-stack/src/main/plugins/chakra-ui/setup/chakra-theme.ts b/packages/create-next-stack/src/main/plugins/chakra-ui/setup/chakra-theme.ts deleted file mode 100644 index 50754007..00000000 --- a/packages/create-next-stack/src/main/plugins/chakra-ui/setup/chakra-theme.ts +++ /dev/null @@ -1,12 +0,0 @@ -import endent from "endent" - -export const chakraTheme = endent` - import { extendTheme, ThemeConfig } from "@chakra-ui/react"; - - const config: ThemeConfig = { - initialColorMode: "light", - useSystemColorMode: false, - }; - - export const chakraTheme = extendTheme({ config }); -` diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/generate-env.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/generate-env.ts index c88862bb..a9262850 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/generate-env.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/generate-env.ts @@ -1,9 +1,9 @@ import endent from "endent" import { ValidCNSInputs } from "../../../create-next-stack-types" -import { getSortedFilteredEnvironmentVariables } from "../sort-orders/environment-variables" +import { getEnvironmentVariables } from "../sort-orders/environment-variables" -export const generateEnv = (inputs: ValidCNSInputs): string => { - const environmentVariables = getSortedFilteredEnvironmentVariables(inputs) +export const generateEnv = async (inputs: ValidCNSInputs): Promise => { + const environmentVariables = (await getEnvironmentVariables(inputs)) .map((environmentVariable) => { const { name, description, defaultValue } = environmentVariable diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/pages/generate-app.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/pages/generate-app.ts index 5d2e0b29..d8da670d 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/pages/generate-app.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/pages/generate-app.ts @@ -3,28 +3,28 @@ import { ValidCNSInputs } from "../../../../create-next-stack-types" import { nonNull } from "../../../../helpers/non-null" import { filterPlugins } from "../../../../setup/setup" -export const generateApp = (inputs: ValidCNSInputs): string => { - const imports = filterPlugins(inputs) +export const generateApp = async (inputs: ValidCNSInputs): Promise => { + const imports = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.app?.imports) .filter(nonNull) .join("\n") - const postImports = filterPlugins(inputs) + const postImports = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.app?.postImports) .filter(nonNull) .join("\n") - const logic = filterPlugins(inputs) + const logic = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.app?.logic) .filter(nonNull) .join("\n\n") // Double new line to separate plugin logic - const componentsStart = filterPlugins(inputs) + const componentsStart = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.app?.componentsStart) .filter(nonNull) .join("\n") - const componentsEnd = filterPlugins(inputs) + const componentsEnd = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.app?.componentsEnd) .filter(nonNull) .reverse() diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/pages/generate-document.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/pages/generate-document.ts index 531d224a..f69e867a 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/pages/generate-document.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/pages/generate-document.ts @@ -3,32 +3,34 @@ import type { ValidCNSInputs } from "../../../../create-next-stack-types" import { nonNull } from "../../../../helpers/non-null" import { filterPlugins } from "../../../../setup/setup" -export const generateDocument = (inputs: ValidCNSInputs): string => { - const imports = filterPlugins(inputs) +export const generateDocument = async ( + inputs: ValidCNSInputs +): Promise => { + const imports = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.document?.imports) .filter(nonNull) .join("\n") - const afterImports = filterPlugins(inputs) + const afterImports = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.document?.afterImports) .filter(nonNull) .join("\n") - const classMembers = filterPlugins(inputs) + const classMembers = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.document?.classMembers) .filter(nonNull) .join("\n") - const renderLogic = filterPlugins(inputs) + const renderLogic = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.document?.renderLogic) .filter(nonNull) .join("\n") - const htmlAttributes = filterPlugins(inputs) + const htmlAttributes = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.document?.htmlAttributes) .filter(nonNull) .join(" ") - const headTags = filterPlugins(inputs) + const headTags = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.document?.headTags) .filter(nonNull) .join("\n") - const body = filterPlugins(inputs) + const body = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.document?.body) .filter(nonNull) .join("\n") diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/templates/LandingPage/generate-LandingPageTemplate.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/templates/LandingPage/generate-LandingPageTemplate.ts index 072afb59..9f0137e2 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/templates/LandingPage/generate-LandingPageTemplate.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/templates/LandingPage/generate-LandingPageTemplate.ts @@ -4,8 +4,10 @@ import { getProjectNameOfPath } from "../../../../../helpers/get-project-name-of import { nonNull } from "../../../../../helpers/non-null" import { filterPlugins } from "../../../../../setup/setup" -export const generateLandingPageTemplate = (inputs: ValidCNSInputs): string => { - const todos = filterPlugins(inputs) +export const generateLandingPageTemplate = async ( + inputs: ValidCNSInputs +): Promise => { + const todos = (await filterPlugins(inputs)) .flatMap((plugin) => plugin.todos) .filter(nonNull) const hasTodos = todos.length > 0 diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/templates/LandingPage/generate-technologies.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/templates/LandingPage/generate-technologies.ts index 454f4a8e..6e920ba2 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/templates/LandingPage/generate-technologies.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/add-content/templates/LandingPage/generate-technologies.ts @@ -1,21 +1,22 @@ import endent from "endent" import { ValidCNSInputs } from "../../../../../create-next-stack-types" -import { DeeplyReadonly } from "../../../../../helpers/deeply-readonly" import { stringify } from "../../../../../helpers/stringify" import { getTechnologies } from "../../../sort-orders/technologies" // This type should match the one in the template below. -export type Technology = DeeplyReadonly<{ +export type Technology = { name: string description: string links: Array<{ title: string url: string }> -}> +} -export const generateTechnologies = (inputs: ValidCNSInputs): string => { - const technologies: Technology[] = getTechnologies(inputs) +export const generateTechnologies = async ( + inputs: ValidCNSInputs +): Promise => { + const technologies: Technology[] = await getTechnologies(inputs) return endent` export type Technology = { diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/add-next-config/generate-next-config.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/add-next-config/generate-next-config.ts index 3ae4b75d..86ed6df2 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/add-next-config/generate-next-config.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/add-next-config/generate-next-config.ts @@ -12,21 +12,21 @@ export const generateNextConfig = async ( const defaultNextConfig: NextConfig = { reactStrictMode: true, } - const nextConfigs = filterPlugins(inputs) + const nextConfigs = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.nextConfigJs?.nextConfig) .filter(nonNull) const mergedNextConfig = merge(defaultNextConfig, ...nextConfigs) - const imports = filterPlugins(inputs) + const imports = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.nextConfigJs?.imports) .filter(nonNull) .join("\n") - const wrappersStart = filterPlugins(inputs) + const wrappersStart = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.nextConfigJs?.wrappersStart) .filter(nonNull) - const wrappersEnd = filterPlugins(inputs) + const wrappersEnd = (await filterPlugins(inputs)) .map((plugin) => plugin.slots?.nextConfigJs?.wrappersEnd) .filter(nonNull) .reverse() diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-env-table-rows copy.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-env-table-rows copy.ts index d751c016..a094da56 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-env-table-rows copy.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-env-table-rows copy.ts @@ -1,10 +1,10 @@ import { ValidCNSInputs } from "../../../create-next-stack-types" -import { getSortedFilteredEnvironmentVariables } from "../sort-orders/environment-variables" +import { getEnvironmentVariables } from "../sort-orders/environment-variables" export const generateEnvironmentVariableTableRows = async ( inputs: ValidCNSInputs ): Promise => { - const environmentVariables = getSortedFilteredEnvironmentVariables(inputs) + const environmentVariables = await getEnvironmentVariables(inputs) if (environmentVariables.length === 0) { return null } diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-readme.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-readme.ts index 0484e5c9..a059c1d0 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-readme.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-readme.ts @@ -14,13 +14,13 @@ export const generateReadme = async ( ): Promise => { const { args, flags } = inputs - const todos = filterPlugins(inputs) + const todos = (await filterPlugins(inputs)) .flatMap((plugin) => plugin.todos) .filter(nonNull) const runCommand = runCommandMap[flags["package-manager"]] - const technologies = getTechnologies(inputs) + const technologies = await getTechnologies(inputs) const scriptTableRows = await generateScriptTableRows(inputs) const environmentVariableTableRows = diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-script-table-rows.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-script-table-rows.ts index 1c5efb7c..9183e449 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-script-table-rows.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-script-table-rows.ts @@ -1,10 +1,10 @@ import { ValidCNSInputs } from "../../../create-next-stack-types" -import { getSortedFilteredScripts } from "../sort-orders/scripts" +import { getScripts } from "../sort-orders/scripts" export const generateScriptTableRows = async ( inputs: ValidCNSInputs ): Promise => { - const scripts = getSortedFilteredScripts(inputs) + const scripts = await getScripts(inputs) if (scripts.length === 0) { return null } diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-technology-table-rows.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-technology-table-rows.ts index d8353673..d9abe937 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-technology-table-rows.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/add-readme/generate-technology-table-rows.ts @@ -1,8 +1,7 @@ -import { DeeplyReadonly } from "../../../helpers/deeply-readonly" import { Technology } from "../../../plugin" export const generateTechnologyTableRows = async ( - technologies: Array, "id">> + technologies: Array> ): Promise => { if (technologies.length === 0) { return null diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/create-next-stack.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/create-next-stack.ts index 0a523da4..9ed761cc 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/create-next-stack.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/create-next-stack.ts @@ -2,44 +2,76 @@ import endent from "endent" import path from "path" import { copyDirectory } from "../../helpers/copy-directory" import { getCreateNextStackDir } from "../../helpers/get-create-next-stack-dir" -import { - makeDirectory, - modifyJsonFile, - toObject, - writeFile, -} from "../../helpers/io" +import { modifyJsonFile, toObject, writeFile } from "../../helpers/io" import { isGitInitialized } from "../../helpers/is-git-initialized" -import { remove } from "../../helpers/remove" +import { nonNull } from "../../helpers/non-null" import { runCommand } from "../../helpers/run-command" import { logWarning } from "../../logging" -import { createPlugin } from "../../plugin" +import { evalProperty, Plugin } from "../../plugin" import { getNameVersionCombo, install, uninstall } from "../../setup/packages" import { filterPlugins } from "../../setup/setup" -import { prettierPlugin } from "../prettier" +import { prettierPackage } from "../prettier" import { generateEnv } from "./add-content/generate-env" import { generateApp } from "./add-content/pages/generate-app" import { generateDocument } from "./add-content/pages/generate-document" import { generateIndexPage } from "./add-content/pages/generate-index" import { generateLandingPageTemplate } from "./add-content/templates/LandingPage/generate-LandingPageTemplate" import { generateTechnologies } from "./add-content/templates/LandingPage/generate-technologies" +import { generateNextConfig } from "./add-next-config/generate-next-config" import { generateReadme } from "./add-readme/generate-readme" -import { getSortedFilteredEnvironmentVariables } from "./sort-orders/environment-variables" -import { getSortedFilteredScripts } from "./sort-orders/scripts" +import { getEnvironmentVariables } from "./sort-orders/environment-variables" +import { getScripts } from "./sort-orders/scripts" const gitAttributesFilename = ".gitattributes" -export const createNextStackPlugin = createPlugin({ +export const createNextStackPlugin: Plugin = { id: "create-next-stack", name: "Create Next Stack", description: "Adds various miscellaneous steps. Some necessities, some niceties.", active: true, - steps: { - addScripts: { + addFiles: [ + { + destination: ".env", + condition: async (inputs) => + (await getEnvironmentVariables(inputs)).length > 0, + content: (inputs) => generateEnv(inputs), + }, + { + destination: "next.config.js", + content: (inputs) => generateNextConfig(inputs), + }, + { + destination: "pages/index.tsx", + content: (inputs) => generateIndexPage(inputs), + }, + { + destination: "pages/_app.tsx", + content: (inputs) => generateApp(inputs), + }, + { + destination: "pages/_document.tsx", + content: (inputs) => generateDocument(inputs), + }, + { + destination: "templates/LandingPage/technologies.ts", + content: (inputs) => generateTechnologies(inputs), + }, + { + destination: "templates/LandingPage/LandingPageTemplate.tsx", + content: (inputs) => generateLandingPageTemplate(inputs), + }, + { + destination: "README.md", + content: (inputs) => generateReadme(inputs), + }, + ], + steps: [ + { id: "addScripts", description: "adding scripts to package.json", run: async (inputs) => { - const scripts = getSortedFilteredScripts(inputs) + const scripts = await getScripts(inputs) await modifyJsonFile("package.json", (packageJson) => ({ ...packageJson, @@ -56,7 +88,7 @@ export const createNextStackPlugin = createPlugin({ })) }, }, - copyAssets: { + { id: "copyAssets", description: "copying static assets", run: async (): Promise => { @@ -66,43 +98,22 @@ export const createNextStackPlugin = createPlugin({ await copyDirectory(source, destination) }, }, - addContent: { + { id: "addContent", description: "adding content", run: async (inputs) => { - await makeDirectory("components") - const environmentVariables = - getSortedFilteredEnvironmentVariables(inputs) - if (environmentVariables.length > 0) { - await writeFile(".env", generateEnv(inputs)) - } - await Promise.all([ - writeFile("pages/index.tsx", generateIndexPage(inputs)), - writeFile("pages/_app.tsx", generateApp(inputs)), - writeFile("pages/_document.tsx", generateDocument(inputs)), + const pluginFilesToWrite = (await filterPlugins(inputs)) + .flatMap((plugin) => plugin.addFiles) + .filter(nonNull) - writeFile( - "templates/LandingPage/technologies.ts", - generateTechnologies(inputs) - ), - writeFile( - "templates/LandingPage/LandingPageTemplate.tsx", - generateLandingPageTemplate(inputs) - ), - ]) - }, - }, - addReadme: { - id: "addReadme", - description: "adding Readme", - run: async (inputs) => { - const readmeFileName = "README.md" - await remove(readmeFileName) - const readmeString = await generateReadme(inputs) - await writeFile(readmeFileName, readmeString) + await Promise.all( + pluginFilesToWrite.map(async ({ destination, content }) => + writeFile(destination, await evalProperty(content, inputs)) + ) + ) }, }, - initialCommit: { + { id: "initialCommit", description: "adding initial commit", shouldRun: async () => { @@ -123,24 +134,26 @@ export const createNextStackPlugin = createPlugin({ ]) }, }, - installDependencies: { + { id: "installDependencies", description: "installing dependencies", run: async (inputs) => { const { flags } = inputs - const depsAndTmpDeps = filterPlugins(inputs).flatMap((plugin) => { - return [ - ...(plugin.dependencies != null - ? Object.values(plugin.dependencies) - : []), - ...(plugin.tmpDependencies != null - ? Object.values(plugin.tmpDependencies) - : []), - ] - }) + const depsAndTmpDeps = (await filterPlugins(inputs)).flatMap( + (plugin) => { + return [ + ...(plugin.dependencies != null + ? Object.values(plugin.dependencies) + : []), + ...(plugin.tmpDependencies != null + ? Object.values(plugin.tmpDependencies) + : []), + ] + } + ) - const devDeps = filterPlugins(inputs).flatMap((plugin) => + const devDeps = (await filterPlugins(inputs)).flatMap((plugin) => plugin.devDependencies != null ? Object.values(plugin.devDependencies) : [] @@ -154,11 +167,11 @@ export const createNextStackPlugin = createPlugin({ } }, }, - uninstallTemporaryDependencies: { + { id: "uninstallTemporaryDependencies", description: "uninstalling temporary dependencies", run: async (inputs) => { - const tmpDeps = filterPlugins(inputs).flatMap((plugin) => + const tmpDeps = (await filterPlugins(inputs)).flatMap((plugin) => plugin.tmpDependencies != null ? Object.values(plugin.tmpDependencies) : [] @@ -169,18 +182,18 @@ export const createNextStackPlugin = createPlugin({ } }, }, - formatProject: { + { id: "formatProject", description: "formatting project", run: async () => { await runCommand("npx", [ - getNameVersionCombo(prettierPlugin.devDependencies.prettier), + getNameVersionCombo(prettierPackage), "--write", ".", ]) }, }, - addGitAttributes: { + { id: "addGitAttributes", description: `adding ${gitAttributesFilename}`, shouldRun: async () => { @@ -204,5 +217,5 @@ export const createNextStackPlugin = createPlugin({ ) }, }, - }, -} as const) + ], +} diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/environment-variables.test.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/environment-variables.test.ts index 649ef8c3..ffc99087 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/environment-variables.test.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/environment-variables.test.ts @@ -1,4 +1,5 @@ import { test } from "@jest/globals" +import endent from "endent" import { plugins } from "../../../setup/setup" import { environmentVariablesSortOrder } from "./environment-variables" @@ -26,7 +27,11 @@ test("`environmentVariablesSortOrder` includes all plugins' environment variable for (const requiredEnvironmentVariable of requiredEnvironmentVariables) { if (!actualEnvironmentVariables.has(requiredEnvironmentVariable)) { throw new Error( - `Missing environment variable with name "${requiredEnvironmentVariable}" in environment-variables.ts` + endent` + Missing environment variable with name "${requiredEnvironmentVariable}" in environment-variables.ts + environment-variables.ts can be found here: + src/main/plugins/create-next-stack/sort-orders/environment-variables.ts + ` ) } } diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/environment-variables.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/environment-variables.ts index b0ee8539..ee010a35 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/environment-variables.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/environment-variables.ts @@ -7,10 +7,8 @@ export const environmentVariablesSortOrder: string[] = [ "NEXT_PUBLIC_WEBSITE_DOMAIN", ] -export const getSortedFilteredEnvironmentVariables = ( - inputs: ValidCNSInputs -) => { - const pluginEnvironmentVariables = filterPlugins(inputs) +export const getEnvironmentVariables = async (inputs: ValidCNSInputs) => { + const pluginEnvironmentVariables = (await filterPlugins(inputs)) .flatMap((plugin) => plugin.environmentVariables) .filter(nonNull) return pluginEnvironmentVariables.sort((a, b) => diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/scripts.test.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/scripts.test.ts index e5099431..ce696880 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/scripts.test.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/scripts.test.ts @@ -1,4 +1,5 @@ import { test } from "@jest/globals" +import endent from "endent" import { plugins } from "../../../setup/setup" import { scriptsSortOrder } from "./scripts" @@ -24,7 +25,11 @@ test("`scriptsSortOrder` includes all plugins' scripts", () => { for (const requiredScript of requiredScripts) { if (!actualScripts.has(requiredScript)) { throw new Error( - `Missing script with name "${requiredScript}" in scripts.ts` + endent` + Missing script with name "${requiredScript}" in scripts.ts + scripts.ts can be found here: + src/main/plugins/create-next-stack/sort-orders/scripts.ts + ` ) } } diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/scripts.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/scripts.ts index ba5d49ba..346c1b6f 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/scripts.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/scripts.ts @@ -16,8 +16,8 @@ export const scriptsSortOrder: string[] = [ "deploy:netlify", ] -export const getSortedFilteredScripts = (inputs: ValidCNSInputs) => { - const pluginScripts = filterPlugins(inputs) +export const getScripts = async (inputs: ValidCNSInputs) => { + const pluginScripts = (await filterPlugins(inputs)) .flatMap((plugin) => plugin.scripts) .filter(nonNull) return pluginScripts.sort((a, b) => diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/technologies.test.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/technologies.test.ts index 3730f382..be6d8b03 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/technologies.test.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/technologies.test.ts @@ -1,4 +1,5 @@ import { test } from "@jest/globals" +import endent from "endent" import { plugins } from "../../../setup/setup" import { technologiesSortOrder } from "./technologies" @@ -24,7 +25,11 @@ test("`technologiesSortOrder` includes all plugins' technologies", () => { for (const requiredTechnologyID of requiredTechnologyIDs) { if (!actualTechnologyIDs.has(requiredTechnologyID)) { throw new Error( - `Missing technology with ID "${requiredTechnologyID}" in technologies.ts` + endent` + Missing technology with ID "${requiredTechnologyID}" in technologies.ts + technologies.ts can be found here: + src/main/plugins/create-next-stack/sort-orders/technologies.ts + ` ) } } diff --git a/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/technologies.ts b/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/technologies.ts index 8dfc4bfd..bd3a34fa 100644 --- a/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/technologies.ts +++ b/packages/create-next-stack/src/main/plugins/create-next-stack/sort-orders/technologies.ts @@ -1,5 +1,4 @@ import { ValidCNSInputs } from "../../../create-next-stack-types" -import { DeeplyReadonly } from "../../../helpers/deeply-readonly" import { nonNull } from "../../../helpers/non-null" import { compareByOrder } from "../../../helpers/sort-by-order" import { Technology } from "../../../plugin" @@ -34,12 +33,13 @@ export const technologiesSortOrder: string[] = [ "nextPlausible", "vercel", "netlify", + "prisma", ] -export const getTechnologies = ( +export const getTechnologies = async ( inputs: ValidCNSInputs -): Array, "id">> => { - return filterPlugins(inputs) +): Promise>> => { + return (await filterPlugins(inputs)) .flatMap((plugin) => plugin.technologies) .filter(nonNull) .sort((a, b) => compareByOrder(a.id, b.id, technologiesSortOrder)) @@ -48,9 +48,7 @@ export const getTechnologies = ( })) } -export const getAllTechnologies = (): Array< - Omit, "id"> -> => { +export const getAllTechnologies = (): Array> => { return plugins .flatMap((plugin) => plugin.technologies ?? []) .sort((a, b) => compareByOrder(a.id, b.id, technologiesSortOrder)) diff --git a/packages/create-next-stack/src/main/plugins/css-modules.ts b/packages/create-next-stack/src/main/plugins/css-modules.ts new file mode 100644 index 00000000..fa97a5e2 --- /dev/null +++ b/packages/create-next-stack/src/main/plugins/css-modules.ts @@ -0,0 +1,62 @@ +import endent from "endent" +import { Plugin } from "../plugin" + +const globalStyles = endent` + * { + box-sizing: border-box; + } + + html, + body { + padding: 0; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + line-height: 1.5; + } + + code { + font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, + Bitstream Vera Sans Mono, Courier New, monospace; + } + + a { + color: inherit; + text-decoration: none; + font-weight: bold; + } +` + +export const cssModuleTechnology = { + id: "cssModules", + name: "CSS Modules", + description: + "CSS Modules are CSS files in which all class names are scoped locally to the component importing them. This means that developers can use the same CSS class name in different files without worrying about naming conflicts. Gone are the days of writing BEM class names!", + links: [ + { title: "Website", url: "https://github.com/css-modules/css-modules" }, + { title: "Docs", url: "https://github.com/css-modules/css-modules" }, + { + title: "Next.js-specific docs", + url: "https://nextjs.org/docs/basic-features/built-in-css-support#adding-component-level-css", + }, + ], +} + +export const cssModulesPlugin: Plugin = { + id: "css-modules", + name: "CSS Modules", + description: "Adds relevant CSS Modules boilerplate and documentation", + active: ({ flags }) => Boolean(flags.styling === "css-modules"), + technologies: [cssModuleTechnology], + slots: { + app: { + imports: `import "../styles/global-styles.css";`, + }, + }, + addFiles: [ + { + destination: "styles/global-styles.css", + content: globalStyles, + }, + ], +} diff --git a/packages/create-next-stack/src/main/plugins/css-modules/add-content/styles/global-styles.ts b/packages/create-next-stack/src/main/plugins/css-modules/add-content/styles/global-styles.ts deleted file mode 100644 index 4a2d368a..00000000 --- a/packages/create-next-stack/src/main/plugins/css-modules/add-content/styles/global-styles.ts +++ /dev/null @@ -1,27 +0,0 @@ -import endent from "endent" - -export const generateGlobalStyles = (): string => endent` - * { - box-sizing: border-box; - } - - html, - body { - padding: 0; - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, - Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; - line-height: 1.5; - } - - code { - font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, - Bitstream Vera Sans Mono, Courier New, monospace; - } - - a { - color: inherit; - text-decoration: none; - font-weight: bold; - } -` diff --git a/packages/create-next-stack/src/main/plugins/css-modules/css-modules.ts b/packages/create-next-stack/src/main/plugins/css-modules/css-modules.ts deleted file mode 100644 index f76c0668..00000000 --- a/packages/create-next-stack/src/main/plugins/css-modules/css-modules.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { makeDirectory, writeFile } from "../../helpers/io" -import { createPlugin } from "../../plugin" -import { generateGlobalStyles } from "./add-content/styles/global-styles" - -export const cssModulesPlugin = createPlugin({ - id: "css-modules", - name: "CSS Modules", - description: "Adds relevant CSS Modules boilerplate and documentation", - active: ({ flags }) => Boolean(flags.styling === "css-modules"), - technologies: [ - { - id: "cssModules", - name: "CSS Modules", - description: - "CSS Modules are CSS files in which all class names are scoped locally to the component importing them. This means that developers can use the same CSS class name in different files without worrying about naming conflicts. Gone are the days of writing BEM class names!", - links: [ - { title: "Website", url: "https://github.com/css-modules/css-modules" }, - { title: "Docs", url: "https://github.com/css-modules/css-modules" }, - { - title: "Next.js-specific docs", - url: "https://nextjs.org/docs/basic-features/built-in-css-support#adding-component-level-css", - }, - ], - }, - ], - steps: { - setUpCssModules: { - id: "setUpCssModules", - description: "setting up CSS Modules", - run: async () => { - await makeDirectory("styles") - await writeFile("styles/global-styles.css", generateGlobalStyles()) - }, - }, - }, - slots: { - app: { - imports: `import "../styles/global-styles.css";`, - }, - }, -} as const) diff --git a/packages/create-next-stack/src/main/plugins/emotion.ts b/packages/create-next-stack/src/main/plugins/emotion.ts index d85834fb..b146bfc5 100644 --- a/packages/create-next-stack/src/main/plugins/emotion.ts +++ b/packages/create-next-stack/src/main/plugins/emotion.ts @@ -1,15 +1,15 @@ import { modifyJsonFile, toObject } from "../helpers/io" -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" -export const emotionPlugin = createPlugin({ +export const emotionPlugin: Plugin = { id: "emotion", name: "Emotion", description: "Adds support for Emotion", active: ({ flags }) => flags.styling === "emotion", - dependencies: { - "@emotion/react": { name: "@emotion/react", version: "^11.0.0" }, - "@emotion/styled": { name: "@emotion/styled", version: "^11.0.0" }, - }, + dependencies: [ + { name: "@emotion/react", version: "^11.0.0" }, + { name: "@emotion/styled", version: "^11.0.0" }, + ], technologies: [ { id: "emotion", @@ -23,8 +23,8 @@ export const emotionPlugin = createPlugin({ ], }, ], - steps: { - setUpEmotion: { + steps: [ + { id: "setUpEmotion", description: "setting up Emotion", run: async () => { @@ -40,7 +40,7 @@ export const emotionPlugin = createPlugin({ })) }, }, - }, + ], slots: { nextConfigJs: { nextConfig: { @@ -50,4 +50,4 @@ export const emotionPlugin = createPlugin({ }, }, }, -} as const) +} diff --git a/packages/create-next-stack/src/main/plugins/eslint.ts b/packages/create-next-stack/src/main/plugins/eslint.ts index 30e741c1..6b375009 100644 --- a/packages/create-next-stack/src/main/plugins/eslint.ts +++ b/packages/create-next-stack/src/main/plugins/eslint.ts @@ -1,6 +1,6 @@ -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" -export const eslintPlugin = createPlugin({ +export const eslintPlugin: Plugin = { id: "eslint", name: "ESLint", description: "Adds relevant documentation for ESLint", @@ -22,4 +22,4 @@ export const eslintPlugin = createPlugin({ ], }, ], -} as const) +} diff --git a/packages/create-next-stack/src/main/plugins/formatting-pre-commit-hook.ts b/packages/create-next-stack/src/main/plugins/formatting-pre-commit-hook.ts index f102f5a0..b8bb5b0d 100644 --- a/packages/create-next-stack/src/main/plugins/formatting-pre-commit-hook.ts +++ b/packages/create-next-stack/src/main/plugins/formatting-pre-commit-hook.ts @@ -3,35 +3,23 @@ import { isGitInitialized } from "../helpers/is-git-initialized" import { remove } from "../helpers/remove" import { runCommand } from "../helpers/run-command" import { logWarning } from "../logging" -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" -export const formattingPreCommitHookPlugin = createPlugin({ +export const formattingPreCommitHookPlugin: Plugin = { id: "formatting-pre-commit-hook", name: "formatting-pre-commit-hook", description: "Adds support for a formatting pre-commit hook by setting up Husky and lint-staged using mrm", active: ({ flags }) => Boolean(flags.prettier && flags["formatting-pre-commit-hook"]), - tmpDependencies: { - mrm: { - name: "mrm", - version: "^4.0.0", - }, - "mrm-task-lint-staged": { - name: "mrm-task-lint-staged", - version: "^7.0.0", - }, - }, - devDependencies: { - "lint-staged": { - name: "lint-staged", - version: ">=10", - }, - husky: { - name: "husky", - version: ">=7", - }, - }, + tmpDependencies: [ + { name: "mrm", version: "^4.0.0" }, + { name: "mrm-task-lint-staged", version: "^7.0.0" }, + ], + devDependencies: [ + { name: "lint-staged", version: ">=10" }, + { name: "husky", version: ">=7" }, + ], technologies: [ { id: "husky", @@ -63,8 +51,8 @@ export const formattingPreCommitHookPlugin = createPlugin({ command: "husky install", }, ], - steps: { - setUpFormattingPreCommitHook: { + steps: [ + { id: "setUpFormattingPreCommitHook", description: "setting up formatting pre-commit hook", shouldRun: async () => { @@ -87,5 +75,5 @@ export const formattingPreCommitHookPlugin = createPlugin({ })) }, }, - }, -} as const) + ], +} diff --git a/packages/create-next-stack/src/main/plugins/formik.ts b/packages/create-next-stack/src/main/plugins/formik.ts index 0f18a351..affb36aa 100644 --- a/packages/create-next-stack/src/main/plugins/formik.ts +++ b/packages/create-next-stack/src/main/plugins/formik.ts @@ -1,16 +1,11 @@ -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" -export const formikPlugin = createPlugin({ +export const formikPlugin: Plugin = { id: "formik", name: "Formik", description: "Adds support for Formik", active: ({ flags }) => Boolean(flags["formik"]), - dependencies: { - formik: { - name: "formik", - version: "^2.0.0", - }, - }, + dependencies: [{ name: "formik", version: "^2.0.0" }], technologies: [ { id: "formik", @@ -24,4 +19,4 @@ export const formikPlugin = createPlugin({ ], }, ], -} as const) +} diff --git a/packages/create-next-stack/src/main/plugins/framer-motion.ts b/packages/create-next-stack/src/main/plugins/framer-motion.ts index 49f207e2..fdf30691 100644 --- a/packages/create-next-stack/src/main/plugins/framer-motion.ts +++ b/packages/create-next-stack/src/main/plugins/framer-motion.ts @@ -1,16 +1,11 @@ -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" -export const framerMotionPlugin = createPlugin({ +export const framerMotionPlugin: Plugin = { id: "framer-motion", name: "Framer Motion", description: "Adds support for Framer Motion", active: ({ flags }) => Boolean(flags["framer-motion"]), - dependencies: { - "framer-motion": { - name: "framer-motion", - version: "^9.0.0", - }, - }, + dependencies: [{ name: "framer-motion", version: "^9.0.0" }], technologies: [ { id: "framerMotion", @@ -24,4 +19,4 @@ export const framerMotionPlugin = createPlugin({ ], }, ], -} as const) +} diff --git a/packages/create-next-stack/src/main/plugins/github-actions.ts b/packages/create-next-stack/src/main/plugins/github-actions.ts index 32a027ee..fb2371de 100644 --- a/packages/create-next-stack/src/main/plugins/github-actions.ts +++ b/packages/create-next-stack/src/main/plugins/github-actions.ts @@ -1,15 +1,13 @@ import endent from "endent" -import path from "path" import { ValidCNSInputs } from "../create-next-stack-types" -import { makeDirectory, writeFile } from "../helpers/io" import { cleanInstallCommandMap, runCommandMap, } from "../helpers/package-manager-utils" -import { createPlugin, evalActive } from "../plugin" +import { evalProperty, Plugin } from "../plugin" import { prettierPlugin } from "./prettier" -export const githubActionsPlugin = createPlugin({ +export const githubActionsPlugin: Plugin = { id: "github-actions", name: "GitHub Actions", description: "Adds support for GitHub Actions", @@ -21,14 +19,8 @@ export const githubActionsPlugin = createPlugin({ description: "GitHub Actions is a tool for automating software development workflows. It is integrated with GitHub repositories and enables developers to automate tasks such as building, testing, and deploying their applications.", links: [ - { - title: "Website", - url: "https://github.com/features/actions", - }, - { - title: "Docs", - url: "https://docs.github.com/en/actions", - }, + { title: "Website", url: "https://github.com/features/actions" }, + { title: "Docs", url: "https://docs.github.com/en/actions" }, { title: "Workflow syntax", url: "https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions", @@ -44,25 +36,23 @@ export const githubActionsPlugin = createPlugin({ command: "echo No tests found.", }, ], - steps: { - addGithubWorkflowStep: { - id: "addGithubWorkflowStep", - description: "adding GitHub workflow", - run: async (inputs) => { - const directory = ".github/workflows" - const filename = "ci.yml" - await makeDirectory(directory) - const filePath = path.resolve(`${directory}/${filename}`) - await writeFile(filePath, generateCiYml(inputs)) - }, + addFiles: [ + { + destination: ".github/workflows/ci.yml", + content: (inputs) => generateCiYml(inputs), }, - }, -} as const) + ], +} -const generateCiYml = (inputs: ValidCNSInputs): string => { +const generateCiYml = async (inputs: ValidCNSInputs): Promise => { const { flags } = inputs const packageManager = flags["package-manager"] + const isPrettierPluginActive = await evalProperty( + prettierPlugin.active, + inputs + ) + return endent` name: "CI" @@ -99,7 +89,7 @@ const generateCiYml = (inputs: ValidCNSInputs): string => { run: ${cleanInstallCommandMap[packageManager]} ${ - evalActive(prettierPlugin.active, inputs) + isPrettierPluginActive ? endent` - name: "Check format" run: ${runCommandMap[packageManager]} format:check diff --git a/packages/create-next-stack/src/main/plugins/mantine/mantine.ts b/packages/create-next-stack/src/main/plugins/mantine.ts similarity index 66% rename from packages/create-next-stack/src/main/plugins/mantine/mantine.ts rename to packages/create-next-stack/src/main/plugins/mantine.ts index 49659e86..244dc1ea 100644 --- a/packages/create-next-stack/src/main/plugins/mantine/mantine.ts +++ b/packages/create-next-stack/src/main/plugins/mantine.ts @@ -1,31 +1,17 @@ import endent from "endent" -import { writeFile } from "../../helpers/io" -import { createPlugin } from "../../plugin" -import { mantineTheme } from "./setup/mantine-theme" +import { Plugin } from "../plugin" -export const mantinePlugin = createPlugin({ +export const mantinePlugin: Plugin = { id: "mantine", name: "Mantine", description: "Adds support for Mantine", active: ({ flags }) => Boolean(flags.mantine), - dependencies: { - "@mantine/core": { - name: "@mantine/core", - version: "^6.0.0", - }, - "@mantine/hooks": { - name: "@mantine/hooks", - version: "^6.0.0", - }, - "@mantine/next": { - name: "@mantine/next", - version: "^6.0.0", - }, - "@emotion/server": { - name: "@emotion/server", - version: "^11.0.0", - }, - }, + dependencies: [ + { name: "@mantine/core", version: "^6.0.0" }, + { name: "@mantine/hooks", version: "^6.0.0" }, + { name: "@mantine/next", version: "^6.0.0" }, + { name: "@emotion/server", version: "^11.0.0" }, + ], technologies: [ { id: "mantine", @@ -39,15 +25,6 @@ export const mantinePlugin = createPlugin({ ], }, ], - steps: { - setUpMantine: { - id: "setUpMantine", - description: "setting up Mantine", - run: async () => { - await writeFile("mantine-theme.ts", mantineTheme) - }, - }, - }, slots: { app: { imports: endent` @@ -77,4 +54,16 @@ export const mantinePlugin = createPlugin({ `, }, }, -} as const) + addFiles: [ + { + destination: "mantine-theme.ts", + content: endent` + import { MantineThemeOverride } from "@mantine/core"; + + export const mantineTheme: MantineThemeOverride = { + colorScheme: "light", + }; + `, + }, + ], +} diff --git a/packages/create-next-stack/src/main/plugins/mantine/setup/mantine-theme.ts b/packages/create-next-stack/src/main/plugins/mantine/setup/mantine-theme.ts deleted file mode 100644 index 71aac987..00000000 --- a/packages/create-next-stack/src/main/plugins/mantine/setup/mantine-theme.ts +++ /dev/null @@ -1,9 +0,0 @@ -import endent from "endent" - -export const mantineTheme = endent` - import { MantineThemeOverride } from "@mantine/core"; - - export const mantineTheme: MantineThemeOverride = { - colorScheme: "light", - }; -` diff --git a/packages/create-next-stack/src/main/plugins/material-ui/material-ui.ts b/packages/create-next-stack/src/main/plugins/material-ui.ts similarity index 61% rename from packages/create-next-stack/src/main/plugins/material-ui/material-ui.ts rename to packages/create-next-stack/src/main/plugins/material-ui.ts index 1d931838..d8ae15a1 100644 --- a/packages/create-next-stack/src/main/plugins/material-ui/material-ui.ts +++ b/packages/create-next-stack/src/main/plugins/material-ui.ts @@ -1,19 +1,43 @@ import endent from "endent" -import { writeFile } from "../../helpers/io" -import { createPlugin } from "../../plugin" -import { materialTheme } from "./setup/material-theme" +import { Plugin } from "../plugin" -export const materialUIPlugin = createPlugin({ +const materialTheme = endent` + import { Roboto } from 'next/font/google'; + import { createTheme } from '@mui/material/styles'; + import { red } from '@mui/material/colors'; + + export const roboto = Roboto({ + weight: ['300', '400', '500', '700'], + subsets: ['latin'], + display: 'swap', + fallback: ['Helvetica', 'Arial', 'sans-serif'], + }); + + // Create a theme instance. + export default createTheme({ + palette: { + primary: { + main: '#556cd6', + }, + secondary: { + main: '#19857b', + }, + error: { + main: red.A400, + }, + }, + typography: { + fontFamily: roboto.style.fontFamily, + }, + }); +` + +export const materialUIPlugin: Plugin = { id: "material-ui", name: "Material UI", description: "Adds support for Material UI", active: ({ flags }) => Boolean(flags["material-ui"]), - dependencies: { - "@mui/material": { - name: "@mui/material", - version: "^5.0.0", - }, - }, + dependencies: [{ name: "@mui/material", version: "^5.0.0" }], technologies: [ { id: "materialUI", @@ -30,15 +54,6 @@ export const materialUIPlugin = createPlugin({ ], }, ], - steps: { - setUpMaterialUI: { - id: "setUpMaterialUI", - description: "setting up Material UI", - run: async () => { - await writeFile("material-theme.ts", materialTheme) - }, - }, - }, slots: { app: { imports: endent` @@ -58,4 +73,10 @@ export const materialUIPlugin = createPlugin({ headTags: ``, }, }, -} as const) + addFiles: [ + { + destination: "material-theme.ts", + content: materialTheme, + }, + ], +} diff --git a/packages/create-next-stack/src/main/plugins/material-ui/setup/material-theme.ts b/packages/create-next-stack/src/main/plugins/material-ui/setup/material-theme.ts deleted file mode 100644 index ad3109c4..00000000 --- a/packages/create-next-stack/src/main/plugins/material-ui/setup/material-theme.ts +++ /dev/null @@ -1,32 +0,0 @@ -import endent from "endent" - -export const materialTheme = endent` - import { Roboto } from 'next/font/google'; - import { createTheme } from '@mui/material/styles'; - import { red } from '@mui/material/colors'; - - export const roboto = Roboto({ - weight: ['300', '400', '500', '700'], - subsets: ['latin'], - display: 'swap', - fallback: ['Helvetica', 'Arial', 'sans-serif'], - }); - - // Create a theme instance. - export default createTheme({ - palette: { - primary: { - main: '#556cd6', - }, - secondary: { - main: '#19857b', - }, - error: { - main: red.A400, - }, - }, - typography: { - fontFamily: roboto.style.fontFamily, - }, - }); -` diff --git a/packages/create-next-stack/src/main/plugins/netlify.ts b/packages/create-next-stack/src/main/plugins/netlify.ts index 50d8e9d1..bb43cdf5 100644 --- a/packages/create-next-stack/src/main/plugins/netlify.ts +++ b/packages/create-next-stack/src/main/plugins/netlify.ts @@ -1,16 +1,11 @@ -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" -export const netlifyPlugin = createPlugin({ +export const netlifyPlugin: Plugin = { id: "netlify", name: "Netlify", description: "Adds support for Netlify", active: ({ flags }) => Boolean(flags["netlify"]), - devDependencies: { - "netlify-cli": { - name: "netlify-cli", - version: "^15.6.0", - }, - }, + devDependencies: [{ name: "netlify-cli", version: "^15.6.0" }], scripts: [ { name: "deploy:netlify", @@ -34,4 +29,4 @@ export const netlifyPlugin = createPlugin({ todos: [ "Integrate Netlify with your repository host for continuous deployments at https://app.netlify.com/start. The Netlify CLI, mainly used for preview deployments, won't auto-detect Next.js until you do.", ], -} as const) +} diff --git a/packages/create-next-stack/src/main/plugins/next.ts b/packages/create-next-stack/src/main/plugins/next.ts index bd42251c..7372502b 100644 --- a/packages/create-next-stack/src/main/plugins/next.ts +++ b/packages/create-next-stack/src/main/plugins/next.ts @@ -1,20 +1,19 @@ import chalk from "chalk" import endent from "endent" import path from "path" -import { makeDirectory, writeFile } from "../helpers/io" +import { makeDirectory } from "../helpers/io" import { remove } from "../helpers/remove" import { runCommand } from "../helpers/run-command" import { logDebug } from "../logging" -import { createPlugin, Package } from "../plugin" +import { Package, Plugin } from "../plugin" import { getNameVersionCombo } from "../setup/packages" -import { generateNextConfig } from "./create-next-stack/add-next-config/generate-next-config" const createNextAppPackage: Package = { name: "create-next-app", version: "13.2.3", } -export const nextPlugin = createPlugin({ +export const nextPlugin: Plugin = { id: "next", name: "Next.js", description: "Adds Next.js foundation", @@ -58,8 +57,8 @@ export const nextPlugin = createPlugin({ command: "next lint", }, ], - steps: { - createNextApp: { + steps: [ + { id: "createNextApp", description: "running Create Next App", @@ -129,7 +128,7 @@ export const nextPlugin = createPlugin({ process.chdir(args.app_name) }, }, - removeOfficialCNAContent: { + { id: "removeOfficialCNAContent", description: "removing content added by Create Next App", run: async () => { @@ -139,19 +138,10 @@ export const nextPlugin = createPlugin({ remove("public/next.svg"), remove("public/thirteen.svg"), remove("public/vercel.svg"), + remove("README.md"), + remove("next.config.js"), ]) - await makeDirectory("pages") - }, - }, - addNextConfig: { - id: "addNextConfig", - description: "adding next.config.js", - run: async (inputs) => { - const nextConfigFileName = "next.config.js" - await remove(nextConfigFileName) - const nextConfigString = await generateNextConfig(inputs) - await writeFile(nextConfigFileName, nextConfigString) }, }, - }, -} as const) + ], +} diff --git a/packages/create-next-stack/src/main/plugins/npm.ts b/packages/create-next-stack/src/main/plugins/npm.ts index 8fc9d478..bde42014 100644 --- a/packages/create-next-stack/src/main/plugins/npm.ts +++ b/packages/create-next-stack/src/main/plugins/npm.ts @@ -1,6 +1,6 @@ -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" -export const npmPlugin = createPlugin({ +export const npmPlugin: Plugin = { id: "npm", name: "npm", description: "Adds relevant npm documentation", @@ -18,4 +18,4 @@ export const npmPlugin = createPlugin({ ], }, ], -} as const) +} diff --git a/packages/create-next-stack/src/main/plugins/plausible.ts b/packages/create-next-stack/src/main/plugins/plausible.ts index 0244dfd5..39894b99 100644 --- a/packages/create-next-stack/src/main/plugins/plausible.ts +++ b/packages/create-next-stack/src/main/plugins/plausible.ts @@ -1,19 +1,14 @@ import endent from "endent" -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" const websiteDomainEnvVar = "NEXT_PUBLIC_WEBSITE_DOMAIN" -export const plausiblePlugin = createPlugin({ +export const plausiblePlugin: Plugin = { id: "plausible", name: "Plausible", description: "Adds support for Plausible Analytics", active: ({ flags }) => flags["plausible"], - dependencies: { - "next-plausible": { - name: "next-plausible", - version: "^3.0.0", - }, - }, + dependencies: [{ name: "next-plausible", version: "^3.0.0" }], technologies: [ { id: "plausible", @@ -77,4 +72,4 @@ export const plausiblePlugin = createPlugin({ `Set up an account in Plausible Analytics, and add your website in their dashboard.`, `Update the \`${websiteDomainEnvVar}\` environment variable to your website's domain to connect Plausible Analytics to your app.`, ], -} as const) +} diff --git a/packages/create-next-stack/src/main/plugins/pnpm.ts b/packages/create-next-stack/src/main/plugins/pnpm.ts index ff601676..ffa67ea0 100644 --- a/packages/create-next-stack/src/main/plugins/pnpm.ts +++ b/packages/create-next-stack/src/main/plugins/pnpm.ts @@ -1,8 +1,8 @@ import { runCommand } from "../helpers/run-command" -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" import { getNameVersionCombo } from "../setup/packages" -export const pnpmPlugin = createPlugin({ +export const pnpmPlugin: Plugin = { id: "pnpm", name: "pnpm", description: "Adds support for pnpm", @@ -20,8 +20,8 @@ export const pnpmPlugin = createPlugin({ ], }, ], - steps: { - updatePnpm: { + steps: [ + { id: "updatePnpm", description: "updating pnpm", run: async () => { @@ -32,5 +32,5 @@ export const pnpmPlugin = createPlugin({ ]) }, }, - }, -} as const) + ], +} diff --git a/packages/create-next-stack/src/main/plugins/prettier.ts b/packages/create-next-stack/src/main/plugins/prettier.ts index b134a35b..1438d5e6 100644 --- a/packages/create-next-stack/src/main/plugins/prettier.ts +++ b/packages/create-next-stack/src/main/plugins/prettier.ts @@ -1,18 +1,20 @@ -import { modifyJsonFile, toArray, writeJsonFile } from "../helpers/io" -import { createPlugin } from "../plugin" +import { modifyJsonFile, toArray } from "../helpers/io" +import { Package, Plugin } from "../plugin" -export const prettierPlugin = createPlugin({ +export const prettierPackage = { + name: "prettier", + version: "^2.0.0", +} satisfies Package + +export const prettierPlugin: Plugin = { id: "prettier", name: "Prettier", description: "Adds support for Prettier", active: ({ flags }) => Boolean(flags.prettier), - devDependencies: { - prettier: { name: "prettier", version: "^2.0.0" }, - "eslint-config-prettier": { - name: "eslint-config-prettier", - version: "^8.0.0", - }, - }, + devDependencies: [ + prettierPackage, + { name: "eslint-config-prettier", version: "^8.0.0" }, + ], technologies: [ { id: "prettier", @@ -39,30 +41,26 @@ export const prettierPlugin = createPlugin({ command: "prettier --check --ignore-path=.gitignore .", }, ], - steps: { - setUpPrettier: { + addFiles: [ + { + destination: ".prettierrc", + content: `{}`, + }, + ], + steps: [ + { id: "setUpPrettier", description: "setting up Prettier", run: async () => { - await Promise.all([addPrettierConfig(), setUpEslintConfigPrettier()]) + await modifyJsonFile(".eslintrc.json", (eslintrc) => ({ + ...eslintrc, + extends: [ + // + ...toArray(eslintrc["extends"]), + "eslint-config-prettier", + ], + })) }, }, - }, -} as const) - -const addPrettierConfig = async () => { - const prettierConfig = {} // Only provide overrides in this config. Not setting Prettier's defaults explicitly is preferred, so our rules will follow Prettier's defaults as much as possible. - - await writeJsonFile(".prettierrc", prettierConfig) -} - -const setUpEslintConfigPrettier = async () => { - await modifyJsonFile(".eslintrc.json", (eslintrc) => ({ - ...eslintrc, - extends: [ - // - ...toArray(eslintrc["extends"]), - "eslint-config-prettier", - ], - })) + ], } diff --git a/packages/create-next-stack/src/main/plugins/prisma.ts b/packages/create-next-stack/src/main/plugins/prisma.ts new file mode 100644 index 00000000..38f26c7f --- /dev/null +++ b/packages/create-next-stack/src/main/plugins/prisma.ts @@ -0,0 +1,89 @@ +import endent from "endent" +import { runCommand } from "../helpers/run-command" +import { Plugin } from "../plugin" + +export const prismaPlugin: Plugin = { + id: "prisma", + name: "Prisma", + description: "Adds support for Prisma", + active: ({ flags }) => Boolean(flags["prisma"]), + dependencies: [{ name: "@prisma/client", version: "^4.16.0" }], + devDependencies: [{ name: "prisma", version: "^4.16.0" }], + technologies: [ + { + id: "prisma", + name: "Prisma", + description: + "Prisma is a next-generation ORM for Node.js. It is designed to simplify database access and declaratively define the schema for your database. Prisma replaces traditional ORMs and can be used to build GraphQL servers, REST APIs, microservices & more.", + links: [ + { title: "Website", url: "https://www.prisma.io/" }, + { title: "Docs", url: "https://www.prisma.io/docs" }, + { title: "GitHub", url: "https://github.com/prisma/prisma" }, + ], + }, + ], + steps: [ + { + id: "setUpPrisma", + description: "setting up Prisma", + run: async () => { + await runCommand("npx", ["prisma", "generate"]) + }, + }, + ], + addFiles: [ + { + destination: "prisma/schema.prisma", + content: endent` + generator client { + provider = "prisma-client-js" + } + + datasource db { + provider = "sqlite" + url = "file:./dev.db" + } + + model Todo { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + content String + } + `, + }, + { + destination: "prisma/seed.ts", + content: endent` + import { Prisma, PrismaClient } from "@prisma/client"; + + const prisma = new PrismaClient(); + + const todoCreateInputs: Prisma.TodoCreateInput[] = [ + { content: "This is a todo" }, + { content: "This is another todo" }, + ]; + + async function main() { + console.log(\`Start seeding ...\`); + for (const todoCreateInput of todoCreateInputs) { + const todo = await prisma.todo.create({ + data: todoCreateInput, + }); + console.log(\`Created todo with id: \${todo.id}\`); + } + console.log(\`Seeding finished.\`); + } + + main() + .then(async () => { + await prisma.$disconnect(); + }) + .catch(async (e) => { + console.error(e); + await prisma.$disconnect(); + process.exit(1); + }); + `, + }, + ], +} diff --git a/packages/create-next-stack/src/main/plugins/react-hook-form.ts b/packages/create-next-stack/src/main/plugins/react-hook-form.ts index 63405cde..4accf0f7 100644 --- a/packages/create-next-stack/src/main/plugins/react-hook-form.ts +++ b/packages/create-next-stack/src/main/plugins/react-hook-form.ts @@ -1,16 +1,11 @@ -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" -export const reactHookFormPlugin = createPlugin({ +export const reactHookFormPlugin: Plugin = { id: "react-hook-form", name: "React Hook Form", description: "Adds support for React Hook Form", active: ({ flags }) => Boolean(flags["react-hook-form"]), - dependencies: { - "react-hook-form": { - name: "react-hook-form", - version: "^7.0.0", - }, - }, + dependencies: [{ name: "react-hook-form", version: "^7.0.0" }], technologies: [ { id: "reactHookForm", @@ -27,4 +22,4 @@ export const reactHookFormPlugin = createPlugin({ ], }, ], -} as const) +} diff --git a/packages/create-next-stack/src/main/plugins/react-icons.ts b/packages/create-next-stack/src/main/plugins/react-icons.ts index a0b4dfd2..81cb5da2 100644 --- a/packages/create-next-stack/src/main/plugins/react-icons.ts +++ b/packages/create-next-stack/src/main/plugins/react-icons.ts @@ -1,13 +1,11 @@ -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" -export const reactIconsPlugin = createPlugin({ +export const reactIconsPlugin: Plugin = { id: "react-icons", name: "React Icons", description: "Adds support for React Icons", active: ({ flags }) => Boolean(flags["react-icons"]), - devDependencies: { - "react-icons": { name: "react-icons", version: "^4.8.0" }, - }, + devDependencies: [{ name: "react-icons", version: "^4.8.0" }], technologies: [ { id: "reactIcons", @@ -20,4 +18,4 @@ export const reactIconsPlugin = createPlugin({ ], }, ], -} as const) +} diff --git a/packages/create-next-stack/src/main/plugins/react-query.ts b/packages/create-next-stack/src/main/plugins/react-query.ts index fcb9c33e..1a898525 100644 --- a/packages/create-next-stack/src/main/plugins/react-query.ts +++ b/packages/create-next-stack/src/main/plugins/react-query.ts @@ -1,21 +1,15 @@ import endent from "endent" -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" -export const reactQueryPlugin = createPlugin({ +export const reactQueryPlugin: Plugin = { id: "react-query", name: "React Query", description: "Adds support for React Query", active: ({ flags }) => Boolean(flags["react-query"]), - devDependencies: { - "@tanstack/react-query": { - name: "@tanstack/react-query", - version: "^4.0.0", - }, - "@tanstack/react-query-devtools": { - name: "@tanstack/react-query-devtools", - version: "^4.0.0", - }, - }, + devDependencies: [ + { name: "@tanstack/react-query", version: "^4.0.0" }, + { name: "@tanstack/react-query-devtools", version: "^4.0.0" }, + ], technologies: [ { id: "reactQuery", @@ -54,4 +48,4 @@ export const reactQueryPlugin = createPlugin({ `, }, }, -} as const) +} diff --git a/packages/create-next-stack/src/main/plugins/react.ts b/packages/create-next-stack/src/main/plugins/react.ts index 933bd71e..cb10cea5 100644 --- a/packages/create-next-stack/src/main/plugins/react.ts +++ b/packages/create-next-stack/src/main/plugins/react.ts @@ -1,6 +1,6 @@ -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" -export const reactPlugin = createPlugin({ +export const reactPlugin: Plugin = { id: "react", name: "React", description: "Adds relevant React documentation", @@ -22,4 +22,4 @@ export const reactPlugin = createPlugin({ ], }, ], -} as const) +} diff --git a/packages/create-next-stack/src/main/plugins/sass.ts b/packages/create-next-stack/src/main/plugins/sass.ts new file mode 100644 index 00000000..f6ba57a5 --- /dev/null +++ b/packages/create-next-stack/src/main/plugins/sass.ts @@ -0,0 +1,65 @@ +import endent from "endent" +import { Plugin } from "../plugin" +import { cssModuleTechnology } from "./css-modules" + +const globalStyles = endent` + * { + box-sizing: border-box; + } + + html, + body { + padding: 0; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + line-height: 1.5; + } + + code { + font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, + Bitstream Vera Sans Mono, Courier New, monospace; + } + + a { + color: inherit; + text-decoration: none; + font-weight: bold; + } +` + +export const sassPlugin: Plugin = { + id: "sass", + name: "Sass", + description: "Adds support for Sass", + active: ({ flags }) => flags.styling === "css-modules-with-sass", + dependencies: [{ name: "sass", version: "^1.0.0" }], + technologies: [ + cssModuleTechnology, + { + id: "sass", + name: "Sass", + description: + "Sass is a stylesheet language that is compiled to CSS. It is an extension of CSS that adds extra powers to the basic language. It allows developers to use variables, nested rules, mixins, inline imports, and more.", + links: [ + { title: "Website", url: "https://sass-lang.com/" }, + { title: "Docs", url: "https://sass-lang.com/documentation" }, + { + title: "Next.js-specific docs", + url: "https://nextjs.org/docs/basic-features/built-in-css-support#sass-support", + }, + ], + }, + ], + slots: { + app: { + imports: `import "../styles/global-styles.scss";`, + }, + }, + addFiles: [ + { + destination: "styles/global-styles.scss", + content: globalStyles, + }, + ], +} diff --git a/packages/create-next-stack/src/main/plugins/sass/add-content/styles/global-styles.ts b/packages/create-next-stack/src/main/plugins/sass/add-content/styles/global-styles.ts deleted file mode 100644 index 4a2d368a..00000000 --- a/packages/create-next-stack/src/main/plugins/sass/add-content/styles/global-styles.ts +++ /dev/null @@ -1,27 +0,0 @@ -import endent from "endent" - -export const generateGlobalStyles = (): string => endent` - * { - box-sizing: border-box; - } - - html, - body { - padding: 0; - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, - Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; - line-height: 1.5; - } - - code { - font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, - Bitstream Vera Sans Mono, Courier New, monospace; - } - - a { - color: inherit; - text-decoration: none; - font-weight: bold; - } -` diff --git a/packages/create-next-stack/src/main/plugins/sass/sass.ts b/packages/create-next-stack/src/main/plugins/sass/sass.ts deleted file mode 100644 index e3c3a142..00000000 --- a/packages/create-next-stack/src/main/plugins/sass/sass.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { makeDirectory, writeFile } from "../../helpers/io" -import { createPlugin } from "../../plugin" -import { cssModulesPlugin } from "../css-modules/css-modules" -import { generateGlobalStyles } from "./add-content/styles/global-styles" - -export const sassPlugin = createPlugin({ - id: "sass", - name: "Sass", - description: "Adds support for Sass", - active: ({ flags }) => flags.styling === "css-modules-with-sass", - dependencies: { sass: { name: "sass", version: "^1.0.0" } }, - technologies: [ - cssModulesPlugin.technologies[0], - { - id: "sass", - name: "Sass", - description: - "Sass is a stylesheet language that is compiled to CSS. It is an extension of CSS that adds extra powers to the basic language. It allows developers to use variables, nested rules, mixins, inline imports, and more.", - links: [ - { title: "Website", url: "https://sass-lang.com/" }, - { title: "Docs", url: "https://sass-lang.com/documentation" }, - { - title: "Next.js-specific docs", - url: "https://nextjs.org/docs/basic-features/built-in-css-support#sass-support", - }, - ], - }, - ], - steps: { - setUpSass: { - id: "setUpSass", - description: "setting up Sass", - run: async () => { - await makeDirectory("styles") - await writeFile("styles/global-styles.scss", generateGlobalStyles()) - }, - }, - }, - slots: { - app: { - imports: `import "../styles/global-styles.scss";`, - }, - }, -} as const) diff --git a/packages/create-next-stack/src/main/plugins/styled-components.ts b/packages/create-next-stack/src/main/plugins/styled-components.ts index c58f1423..65e76f94 100644 --- a/packages/create-next-stack/src/main/plugins/styled-components.ts +++ b/packages/create-next-stack/src/main/plugins/styled-components.ts @@ -1,19 +1,12 @@ -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" -export const styledComponentsPlugin = createPlugin({ +export const styledComponentsPlugin: Plugin = { id: "styled-components", name: "Styled Components", description: "Adds support for Styled Components", active: ({ flags }) => Boolean(flags.styling === "styled-components"), - dependencies: { - "styled-components": { name: "styled-components", version: "^5.0.0" }, - }, - devDependencies: { - "@types/styled-components": { - name: "@types/styled-components", - version: "^5.0.0", - }, - }, + dependencies: [{ name: "styled-components", version: "^5.0.0" }], + devDependencies: [{ name: "@types/styled-components", version: "^5.0.0" }], technologies: [ { id: "styledComponents", @@ -39,4 +32,4 @@ export const styledComponentsPlugin = createPlugin({ }, }, }, -} as const) +} diff --git a/packages/create-next-stack/src/main/plugins/tailwind-css.ts b/packages/create-next-stack/src/main/plugins/tailwind-css.ts index ef4b4e8c..8257d914 100644 --- a/packages/create-next-stack/src/main/plugins/tailwind-css.ts +++ b/packages/create-next-stack/src/main/plugins/tailwind-css.ts @@ -1,6 +1,5 @@ import endent from "endent" -import { makeDirectory, writeFile } from "../helpers/io" -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" /** * Follows a combination of the official Next.js template: @@ -9,25 +8,16 @@ import { createPlugin } from "../plugin" * https://tailwindcss.com/docs/guides/nextjs */ -export const tailwindCSSPlugin = createPlugin({ +export const tailwindCSSPlugin: Plugin = { id: "tailwind-css", name: "Tailwind CSS", description: "Adds support for Tailwind CSS", active: ({ flags }) => flags["styling"] === "tailwind-css", - devDependencies: { - tailwindcss: { - name: "tailwindcss", - version: "^3.0.0", - }, - autoprefixer: { - name: "autoprefixer", - version: "^10.0.0", - }, - postcss: { - name: "postcss", - version: "^8.0.0", - }, - }, + devDependencies: [ + { name: "tailwindcss", version: "^3.0.0" }, + { name: "autoprefixer", version: "^10.0.0" }, + { name: "postcss", version: "^8.0.0" }, + ], technologies: [ { id: "tailwindCSS", @@ -41,66 +31,51 @@ export const tailwindCSSPlugin = createPlugin({ ], }, ], - steps: { - setUpTailwindCss: { - id: "setUpTailwindCss", - description: "setting up Tailwind CSS", - run: async () => { - await Promise.all([ - addTailwindConfig(), - addPostcssConfig(), - addStylesGlobalsCss(), - ]) - }, - }, - }, slots: { app: { imports: `import "../styles/globals.css";`, }, }, -} as const) - -const addTailwindConfig = async () => { - // From running `npx tailwind init -p --types` and adding globs to the content array according to https://tailwindcss.com/docs/guides/nextjs - const tailwindConfigString = endent` - /** @type {import('tailwindcss/types').Config} */ - const config = { - content: [ - './pages/**/*.{js,ts,jsx,tsx}', - './components/**/*.{js,ts,jsx,tsx}', - ], - theme: { - extend: {}, - }, - plugins: [], - } - - module.exports = config; - ` - await writeFile("tailwind.config.js", tailwindConfigString) -} - -const addPostcssConfig = async () => { - // From https://github.com/vercel/next.js/blob/canary/examples/with-tailwindcss/postcss.config.js - const postcssConfigString = endent` - module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, - } - ` - await writeFile("postcss.config.js", postcssConfigString) -} + addFiles: [ + { + // From https://github.com/vercel/next.js/blob/canary/examples/with-tailwindcss/styles/globals.css` + destination: "styles/globals.css", + content: endent` + @tailwind base; + @tailwind components; + @tailwind utilities; + `, + }, + { + // From running `npx tailwind init -p --types` and adding globs to the content array according to https://tailwindcss.com/docs/guides/nextjs + destination: "tailwind.config.js", + content: endent` + /** @type {import('tailwindcss/types').Config} */ + const config = { + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], + theme: { + extend: {}, + }, + plugins: [], + } -const addStylesGlobalsCss = async () => { - // From https://github.com/vercel/next.js/blob/canary/examples/with-tailwindcss/styles/globals.css - const stylesGlobalsCssString = endent` - @tailwind base; - @tailwind components; - @tailwind utilities; - ` - await makeDirectory("styles") - await writeFile("styles/globals.css", stylesGlobalsCssString) + module.exports = config; + `, + }, + { + // From https://github.com/vercel/next.js/blob/canary/examples/with-tailwindcss/postcss.config.js + destination: "postcss.config.js", + content: endent` + module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, + } + `, + }, + ], } diff --git a/packages/create-next-stack/src/main/plugins/typescript.ts b/packages/create-next-stack/src/main/plugins/typescript.ts index 76fc9577..96e55695 100644 --- a/packages/create-next-stack/src/main/plugins/typescript.ts +++ b/packages/create-next-stack/src/main/plugins/typescript.ts @@ -1,6 +1,6 @@ -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" -export const typescriptPlugin = createPlugin({ +export const typescriptPlugin: Plugin = { id: "typescript", name: "Typescript", description: "Adds relevant Typescript documentation", @@ -19,4 +19,4 @@ export const typescriptPlugin = createPlugin({ ], }, ], -} as const) +} diff --git a/packages/create-next-stack/src/main/plugins/vercel.ts b/packages/create-next-stack/src/main/plugins/vercel.ts index 0d725051..9b16f6eb 100644 --- a/packages/create-next-stack/src/main/plugins/vercel.ts +++ b/packages/create-next-stack/src/main/plugins/vercel.ts @@ -1,16 +1,11 @@ -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" -export const vercelPlugin = createPlugin({ +export const vercelPlugin: Plugin = { id: "vercel", name: "Vercel", description: "Adds support for Vercel", active: ({ flags }) => Boolean(flags["vercel"]), - devDependencies: { - vercel: { - name: "vercel", - version: "^30.2.2", - }, - }, + devDependencies: [{ name: "vercel", version: "^30.2.2" }], scripts: [ { name: "deploy:vercel", @@ -34,4 +29,4 @@ export const vercelPlugin = createPlugin({ todos: [ "Integrate Vercel with your repository host for continuous deployments at https://vercel.com/new", ], -} as const) +} diff --git a/packages/create-next-stack/src/main/plugins/yarn.ts b/packages/create-next-stack/src/main/plugins/yarn.ts index 6701f3a1..f2309e2a 100644 --- a/packages/create-next-stack/src/main/plugins/yarn.ts +++ b/packages/create-next-stack/src/main/plugins/yarn.ts @@ -1,8 +1,8 @@ import { runCommand } from "../helpers/run-command" -import { createPlugin } from "../plugin" +import { Plugin } from "../plugin" import { getNameVersionCombo } from "../setup/packages" -export const yarnPlugin = createPlugin({ +export const yarnPlugin: Plugin = { id: "yarn", name: "Yarn", description: "Adds support for Yarn", @@ -20,8 +20,8 @@ export const yarnPlugin = createPlugin({ ], }, ], - steps: { - updateYarn: { + steps: [ + { id: "updateYarn", description: "updating Yarn", run: async () => { @@ -32,5 +32,5 @@ export const yarnPlugin = createPlugin({ ]) }, }, - }, -} as const) + ], +} diff --git a/packages/create-next-stack/src/main/setup/setup.ts b/packages/create-next-stack/src/main/setup/setup.ts index 40254025..02921021 100644 --- a/packages/create-next-stack/src/main/setup/setup.ts +++ b/packages/create-next-stack/src/main/setup/setup.ts @@ -2,38 +2,40 @@ import chalk from "chalk" import { ValidCNSInputs } from "../create-next-stack-types" import { capitalizeFirstLetter } from "../helpers/capitalize-first-letter" import { getDiffString } from "../helpers/diff-string" +import { filterAsync } from "../helpers/filterAsync" import { inDebugMode } from "../helpers/in-debug-mode" import { time } from "../helpers/time" import { logDebug, logInfo } from "../logging" -import { evalActive, evalShouldRun, Plugin } from "../plugin" -import { chakraUIPlugin } from "../plugins/chakra-ui/chakra-ui" +import { evalOptionalProperty, evalProperty, Plugin } from "../plugin" +import { chakraUIPlugin } from "../plugins/chakra-ui" import { createNextStackPlugin } from "../plugins/create-next-stack/create-next-stack" -import { cssModulesPlugin } from "../plugins/css-modules/css-modules" +import { cssModulesPlugin } from "../plugins/css-modules" import { emotionPlugin } from "../plugins/emotion" import { eslintPlugin } from "../plugins/eslint" import { formattingPreCommitHookPlugin } from "../plugins/formatting-pre-commit-hook" import { formikPlugin } from "../plugins/formik" import { framerMotionPlugin } from "../plugins/framer-motion" import { githubActionsPlugin } from "../plugins/github-actions" -import { mantinePlugin } from "../plugins/mantine/mantine" -import { materialUIPlugin } from "../plugins/material-ui/material-ui" +import { mantinePlugin } from "../plugins/mantine" +import { materialUIPlugin } from "../plugins/material-ui" import { netlifyPlugin } from "../plugins/netlify" import { nextPlugin } from "../plugins/next" import { npmPlugin } from "../plugins/npm" import { plausiblePlugin } from "../plugins/plausible" import { pnpmPlugin } from "../plugins/pnpm" import { prettierPlugin } from "../plugins/prettier" +import { prismaPlugin } from "../plugins/prisma" import { reactPlugin } from "../plugins/react" import { reactHookFormPlugin } from "../plugins/react-hook-form" import { reactIconsPlugin } from "../plugins/react-icons" import { reactQueryPlugin } from "../plugins/react-query" -import { sassPlugin } from "../plugins/sass/sass" +import { sassPlugin } from "../plugins/sass" import { styledComponentsPlugin } from "../plugins/styled-components" import { tailwindCSSPlugin } from "../plugins/tailwind-css" import { typescriptPlugin } from "../plugins/typescript" import { vercelPlugin } from "../plugins/vercel" import { yarnPlugin } from "../plugins/yarn" -import { steps } from "../steps" +import { getSteps } from "../steps" import { printFinalMessages } from "./print-final-messages" export const plugins: Plugin[] = [ @@ -64,19 +66,31 @@ export const plugins: Plugin[] = [ plausiblePlugin, vercelPlugin, netlifyPlugin, + prismaPlugin, ] -export const filterPlugins = (inputs: ValidCNSInputs): Plugin[] => - plugins.filter((plugin) => evalActive(plugin.active, inputs)) +export const filterPlugins = async ( + inputs: ValidCNSInputs +): Promise => { + return await filterAsync( + plugins, + async (plugin) => await evalProperty(plugin.active, inputs) + ) +} export const performSetupSteps = async ( inputs: ValidCNSInputs ): Promise => { + const steps = await getSteps(inputs) + const allStepsDiff = await time(async () => { for (const step of steps) { - const pluginActive = evalActive(step.plugin.active, inputs) - const stepShouldRun = await evalShouldRun(step.shouldRun, inputs) - if (!pluginActive || !stepShouldRun) { + const stepShouldRun = await evalOptionalProperty( + step.shouldRun, + inputs, + true + ) + if (!stepShouldRun) { continue } diff --git a/packages/create-next-stack/src/main/steps.test.ts b/packages/create-next-stack/src/main/steps.test.ts index f15f7b42..bf9ae71b 100644 --- a/packages/create-next-stack/src/main/steps.test.ts +++ b/packages/create-next-stack/src/main/steps.test.ts @@ -1,25 +1,24 @@ import { test } from "@jest/globals" +import { nonNull } from "./helpers/non-null" import { plugins } from "./setup/setup" -import { steps } from "./steps" +import { stepsOrder } from "./steps" test("`steps` contains no duplicates", () => { const seenSteps = new Set() - for (const step of steps) { - const { id } = step - if (seenSteps.has(id)) { - throw new Error(`Duplicate step with ID "${id}" found in steps.ts`) + for (const stepId of stepsOrder) { + if (seenSteps.has(stepId)) { + throw new Error(`Duplicate step with ID "${stepId}" found in steps.ts`) } - seenSteps.add(id) + seenSteps.add(stepId) } }) test("`steps` includes all plugins' steps", () => { - const requiredStepIDs = plugins.flatMap((plugin) => - plugin.steps // - ? Object.values(plugin.steps).map((step) => step.id) - : [] - ) - const actualStepIDs = new Set(steps.map((step) => step.id)) + const requiredStepIDs = plugins + .flatMap((plugin) => plugin.steps) + .filter(nonNull) + .map((step) => step.id) + const actualStepIDs = new Set(stepsOrder) for (const requiredStepID of requiredStepIDs) { if (!actualStepIDs.has(requiredStepID)) { throw new Error(`Missing step with ID "${requiredStepID}" in steps.ts`) diff --git a/packages/create-next-stack/src/main/steps.ts b/packages/create-next-stack/src/main/steps.ts index 52230922..d853adec 100644 --- a/packages/create-next-stack/src/main/steps.ts +++ b/packages/create-next-stack/src/main/steps.ts @@ -1,63 +1,41 @@ +import { ValidCNSInputs } from "./create-next-stack-types" +import { nonNull } from "./helpers/non-null" +import { compareByOrder } from "./helpers/sort-by-order" import { Step } from "./plugin" -import { chakraUIPlugin } from "./plugins/chakra-ui/chakra-ui" -import { createNextStackPlugin } from "./plugins/create-next-stack/create-next-stack" -import { cssModulesPlugin } from "./plugins/css-modules/css-modules" -import { emotionPlugin } from "./plugins/emotion" -import { formattingPreCommitHookPlugin } from "./plugins/formatting-pre-commit-hook" -import { githubActionsPlugin } from "./plugins/github-actions" -import { mantinePlugin } from "./plugins/mantine/mantine" -import { materialUIPlugin } from "./plugins/material-ui/material-ui" -import { nextPlugin } from "./plugins/next" -import { pnpmPlugin } from "./plugins/pnpm" -import { prettierPlugin } from "./plugins/prettier" -import { sassPlugin } from "./plugins/sass/sass" -import { tailwindCSSPlugin } from "./plugins/tailwind-css" -import { yarnPlugin } from "./plugins/yarn" +import { filterPlugins } from "./setup/setup" -export const steps: Step[] = [ +export const stepsOrder: string[] = [ // Update package manager - pnpmPlugin.steps.updatePnpm, - yarnPlugin.steps.updateYarn, - + "updatePnpm", + "updateYarn", // Create Next App - nextPlugin.steps.createNextApp, - nextPlugin.steps.removeOfficialCNAContent, - + "createNextApp", + "removeOfficialCNAContent", // Install dependencies - createNextStackPlugin.steps.installDependencies, - + "installDependencies", // Configuration - createNextStackPlugin.steps.addScripts, - createNextStackPlugin.steps.addGitAttributes, - nextPlugin.steps.addNextConfig, - + "addScripts", + "addGitAttributes", // Styling - tailwindCSSPlugin.steps.setUpTailwindCss, - cssModulesPlugin.steps.setUpCssModules, - sassPlugin.steps.setUpSass, - emotionPlugin.steps.setUpEmotion, - + "setUpEmotion", // Formatting - prettierPlugin.steps.setUpPrettier, - formattingPreCommitHookPlugin.steps.setUpFormattingPreCommitHook, - - // Continuous integration - githubActionsPlugin.steps.addGithubWorkflowStep, - + "setUpPrettier", + "setUpFormattingPreCommitHook", // Add/generate content - createNextStackPlugin.steps.copyAssets, - createNextStackPlugin.steps.addContent, - createNextStackPlugin.steps.addReadme, - - // Component libraries - mantinePlugin.steps.setUpMantine, - chakraUIPlugin.steps.setUpChakraUI, - materialUIPlugin.steps.setUpMaterialUI, - + "copyAssets", + "addContent", // Uninstall temporary dependencies - createNextStackPlugin.steps.uninstallTemporaryDependencies, - + "uninstallTemporaryDependencies", + // ORMs + "setUpPrisma", // Format & initial commit - createNextStackPlugin.steps.formatProject, - createNextStackPlugin.steps.initialCommit, + "formatProject", + "initialCommit", ] + +export const getSteps = async (inputs: ValidCNSInputs): Promise => { + return (await filterPlugins(inputs)) + .flatMap((plugin) => plugin.steps) + .filter(nonNull) + .sort((a, b) => compareByOrder(a.id, b.id, stepsOrder)) +}