From b4c3f6ace16bdc00b288163b84d9c2e36390aeea Mon Sep 17 00:00:00 2001 From: aidansunbury Date: Fri, 25 Oct 2024 23:08:36 -0700 Subject: [PATCH 01/11] docs: added appRouter CLI flag to docs --- www/src/pages/en/installation.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/www/src/pages/en/installation.mdx b/www/src/pages/en/installation.mdx index 6b520aff59..34d183f1a5 100644 --- a/www/src/pages/en/installation.mdx +++ b/www/src/pages/en/installation.mdx @@ -58,6 +58,7 @@ For our CI, we have some experimental flags that allow you to scaffold any app w | `--nextAuth` | Include NextAuth.js in the project | | `--tailwind` | Include Tailwind CSS in the project | | `--dbProvider [provider]` | Include a configured database in the project | +| `--appRouter` | Use Next.js App Router in the project | If you don't provide the `CI` flag, the rest of these flags have no effect. From e1dfc8508fcee14e9c52c2f7e3278adf53dd8f95 Mon Sep 17 00:00:00 2001 From: aidansunbury Date: Sat, 9 Nov 2024 15:03:43 -0800 Subject: [PATCH 02/11] initial comments on edit locations --- cli/src/cli/index.ts | 1 + cli/src/installers/index.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/cli/src/cli/index.ts b/cli/src/cli/index.ts index ce5ef6ca50..9b42d69795 100644 --- a/cli/src/cli/index.ts +++ b/cli/src/cli/index.ts @@ -140,6 +140,7 @@ export const runCli = async (): Promise => { )}`, defaultOptions.flags.dbProvider ) + //* Add eslint / biome options .option( "--appRouter [boolean]", "Explicitly tell the CLI to use the new Next.js app router", diff --git a/cli/src/installers/index.ts b/cli/src/installers/index.ts index 776a16ba1c..492079ab69 100644 --- a/cli/src/installers/index.ts +++ b/cli/src/installers/index.ts @@ -86,4 +86,5 @@ export const buildPkgInstallerMap = ( inUse: true, installer: dynamicEslintInstaller, }, + //* TODO add biome installer here }); From 41d2811a98b307a1d2b1daa75d7d0901533ccfdb Mon Sep 17 00:00:00 2001 From: aidansunbury Date: Sat, 9 Nov 2024 16:13:41 -0800 Subject: [PATCH 03/11] working on being able to test initial version --- cli/src/cli/index.ts | 34 +++++- cli/src/installers/biome.ts | 22 ++++ cli/src/installers/dependencyVersionMap.ts | 4 + cli/src/installers/drizzle.ts | 2 +- cli/src/installers/index.ts | 9 +- cli/src/installers/tailwind.ts | 3 + cli/template/extras/config/biome.jsonc | 129 +++++++++++++++++++++ 7 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 cli/src/installers/biome.ts create mode 100644 cli/template/extras/config/biome.jsonc diff --git a/cli/src/cli/index.ts b/cli/src/cli/index.ts index 9b42d69795..4093272e15 100644 --- a/cli/src/cli/index.ts +++ b/cli/src/cli/index.ts @@ -37,6 +37,10 @@ interface CliFlags { appRouter: boolean; /** @internal Used in CI. */ dbProvider: DatabaseProvider; + /** @internal Used in CI */ + eslint: boolean; + /** @internal Used in CI */ + biome: boolean; } interface CliResults { @@ -62,6 +66,8 @@ const defaultOptions: CliResults = { importAlias: "~/", appRouter: false, dbProvider: "sqlite", + eslint: false, + biome: false, }, databaseProvider: "sqlite", }; @@ -140,12 +146,21 @@ export const runCli = async (): Promise => { )}`, defaultOptions.flags.dbProvider ) - //* Add eslint / biome options .option( "--appRouter [boolean]", "Explicitly tell the CLI to use the new Next.js app router", (value) => !!value && value !== "false" ) + .option( + "--eslint [boolean]", + "Experimental: Boolean value if we should install eslint and prettier. Must be used in conjunction with `--CI`.", + (value) => !!value && value !== "false" + ) + .option( + "--biome [boolean]", + "Experimental: Boolean value if we should install biome. Must be used in conjunction with `--CI`.", + (value) => !!value && value !== "false" + ) /** END CI-FLAGS */ .version(getVersion(), "-v, --version", "Display the version number") .addHelpText( @@ -184,6 +199,8 @@ export const runCli = async (): Promise => { if (cliResults.flags.prisma) cliResults.packages.push("prisma"); if (cliResults.flags.drizzle) cliResults.packages.push("drizzle"); if (cliResults.flags.nextAuth) cliResults.packages.push("nextAuth"); + if (cliResults.flags.eslint) cliResults.packages.push("eslint"); + if (cliResults.flags.biome) cliResults.packages.push("biome"); if (cliResults.flags.prisma && cliResults.flags.drizzle) { // We test a matrix of all possible combination of packages in CI. Checking for impossible // combinations here and exiting gracefully is easier than changing the CI matrix to exclude @@ -191,6 +208,10 @@ export const runCli = async (): Promise => { logger.warn("Incompatible combination Prisma + Drizzle. Exiting."); process.exit(0); } + if (cliResults.flags.biome && cliResults.flags.eslint) { + logger.warn("Incompatible combination Biome + ESLint. Exiting."); + process.exit(0); + }; if (databaseProviders.includes(cliResults.flags.dbProvider) === false) { logger.warn( `Incompatible database provided. Use: ${databaseProviders.join(", ")}. Exiting.` @@ -301,6 +322,16 @@ export const runCli = async (): Promise => { initialValue: "sqlite", }); }, + linter: () => { + return p.select({ + message: "Would you like to use ESLint and Prettier or Biome for linting and formatting?", + options: [ + { value: "eslint", label: "ESLint/Prettier" }, + { value: "biome", label: "Biome" }, + ], + initialValue: "eslint", + }); + }, ...(!cliResults.flags.noGit && { git: () => { return p.confirm({ @@ -342,6 +373,7 @@ export const runCli = async (): Promise => { if (project.authentication === "next-auth") packages.push("nextAuth"); if (project.database === "prisma") packages.push("prisma"); if (project.database === "drizzle") packages.push("drizzle"); + //* TODO may need to add something here? not sure return { appName: project.name ?? cliResults.appName, diff --git a/cli/src/installers/biome.ts b/cli/src/installers/biome.ts new file mode 100644 index 0000000000..8bb8686593 --- /dev/null +++ b/cli/src/installers/biome.ts @@ -0,0 +1,22 @@ +import path from "path"; +import fs from "fs-extra"; + +import { type Installer } from "~/installers/index.js"; +import { addPackageDependency } from "~/utils/addPackageDependency.js"; +import { PKG_ROOT } from "~/consts.js"; + +export const biomeInstaller: Installer = ({ projectDir }) => { + addPackageDependency({ + projectDir, + dependencies: [ + "@biomejs/biome", + ], + devMode: true, + }); + + const extrasDir = path.join(PKG_ROOT, "template/extras"); + const biomeConfigSrc = path.join(extrasDir, "config/biome.jsonc"); + const biomeConfigDest = path.join(projectDir, "biome.jsonc"); + + fs.copySync(biomeConfigSrc, biomeConfigDest); +}; diff --git a/cli/src/installers/dependencyVersionMap.ts b/cli/src/installers/dependencyVersionMap.ts index 868214a665..8e3787487d 100644 --- a/cli/src/installers/dependencyVersionMap.ts +++ b/cli/src/installers/dependencyVersionMap.ts @@ -36,5 +36,9 @@ export const dependencyVersionMap = { "@tanstack/react-query": "^5.50.0", superjson: "^2.2.1", "server-only": "^0.0.1", + + // biome + "@biomejs/biome": "1.9.4", + //* TODO also add prettier / eslint plugins here } as const; export type AvailableDependencies = keyof typeof dependencyVersionMap; diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index d9c5930b98..6b45f1e2a2 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -15,7 +15,7 @@ export const drizzleInstaller: Installer = ({ }) => { const devPackages: AvailableDependencies[] = [ "drizzle-kit", - "eslint-plugin-drizzle", + "eslint-plugin-drizzle", //* TODO make this depend on eslint being installed ]; addPackageDependency({ diff --git a/cli/src/installers/index.ts b/cli/src/installers/index.ts index 492079ab69..77809f1195 100644 --- a/cli/src/installers/index.ts +++ b/cli/src/installers/index.ts @@ -7,6 +7,7 @@ import { type PackageManager } from "~/utils/getUserPkgManager.js"; import { dbContainerInstaller } from "./dbContainer.js"; import { drizzleInstaller } from "./drizzle.js"; import { dynamicEslintInstaller } from "./eslint.js"; +import { biomeInstaller } from "./biome.js"; // Turning this into a const allows the list to be iterated over for programmatically creating prompt options // Should increase extensibility in the future @@ -18,6 +19,7 @@ export const availablePackages = [ "trpc", "envVariables", "eslint", + "biome", "dbContainer", ] as const; export type AvailablePackages = (typeof availablePackages)[number]; @@ -83,8 +85,11 @@ export const buildPkgInstallerMap = ( installer: envVariablesInstaller, }, eslint: { - inUse: true, + inUse: packages.includes("eslint"), installer: dynamicEslintInstaller, }, - //* TODO add biome installer here + biome: { + inUse: packages.includes("biome"), + installer: biomeInstaller, + }, }); diff --git a/cli/src/installers/tailwind.ts b/cli/src/installers/tailwind.ts index 86188a241d..53cc1a7137 100644 --- a/cli/src/installers/tailwind.ts +++ b/cli/src/installers/tailwind.ts @@ -35,6 +35,9 @@ export const tailwindInstaller: Installer = ({ projectDir }) => { // add format:* scripts to package.json const packageJsonPath = path.join(projectDir, "package.json"); + //* TODO for some reason the prettier related install stuff is here with tailwind, should probably be in its own installer + //* along with biome stuff + //* Update: prettier is not installed if you don't select tailwind css, which really doesn't make sense. Should move it to the eslint installer const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson; packageJsonContent.scripts = { ...packageJsonContent.scripts, diff --git a/cli/template/extras/config/biome.jsonc b/cli/template/extras/config/biome.jsonc new file mode 100644 index 0000000000..7ffee4f439 --- /dev/null +++ b/cli/template/extras/config/biome.jsonc @@ -0,0 +1,129 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false }, + "files": { "ignoreUnknown": false, "ignore": [] }, + "formatter": { + "enabled": true, + "useEditorconfig": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 80, + "attributePosition": "auto", + "bracketSpacing": true + }, + "organizeImports": { "enabled": true }, + "linter": { + "enabled": true, + "rules": { + "recommended": false, + "complexity": { + "noUselessTypeConstraint": "error", + "useLiteralKeys": "error", + "useOptionalChain": "error" + }, + "correctness": { "noUnusedVariables": "off", "useArrayLiterals": "off" }, + "style": { + "noInferrableTypes": "error", + "noNamespace": "error", + "useAsConstAssertion": "error", + "useConsistentArrayType": "off", + "useForOf": "error", + "useShorthandFunctionType": "error" + }, + "suspicious": { + "noEmptyBlockStatements": "error", + "noExplicitAny": "error", + "noExtraNonNullAssertion": "error", + "noMisleadingInstantiator": "error", + "noUnsafeDeclarationMerging": "error", + "useAwait": "off", + "useNamespaceKeyword": "error" + } + } + }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "all", + "semicolons": "asNeeded", + "arrowParentheses": "always", + "bracketSameLine": false, + "quoteStyle": "single", + "attributePosition": "auto", + "bracketSpacing": true + } + }, + "overrides": [ + { + "include": ["*.ts", "*.tsx", "*.mts", "*.cts"], + "linter": { + "rules": { + "correctness": { + "noConstAssign": "off", + "noGlobalObjectCalls": "off", + "noInvalidBuiltinInstantiation": "off", + "noInvalidConstructorSuper": "off", + "noNewSymbol": "off", + "noSetterReturn": "off", + "noUndeclaredVariables": "off", + "noUnreachable": "off", + "noUnreachableSuper": "off" + }, + "style": { + "noArguments": "error", + "noVar": "error", + "useConst": "error" + }, + "suspicious": { + "noClassAssign": "off", + "noDuplicateClassMembers": "off", + "noDuplicateObjectKeys": "off", + "noDuplicateParameters": "off", + "noFunctionAssign": "off", + "noImportAssign": "off", + "noRedeclare": "off", + "noUnsafeNegation": "off", + "useGetterReturn": "off" + } + } + } + }, + { + "include": ["*.ts", "*.tsx", "*.mts", "*.cts"], + "linter": { + "rules": { + "correctness": { + "noConstAssign": "off", + "noGlobalObjectCalls": "off", + "noInvalidBuiltinInstantiation": "off", + "noInvalidConstructorSuper": "off", + "noNewSymbol": "off", + "noSetterReturn": "off", + "noUndeclaredVariables": "off", + "noUnreachable": "off", + "noUnreachableSuper": "off" + }, + "style": { + "noArguments": "error", + "noVar": "error", + "useConst": "error" + }, + "suspicious": { + "noClassAssign": "off", + "noDuplicateClassMembers": "off", + "noDuplicateObjectKeys": "off", + "noDuplicateParameters": "off", + "noFunctionAssign": "off", + "noImportAssign": "off", + "noRedeclare": "off", + "noUnsafeNegation": "off", + "useGetterReturn": "off" + } + } + } + } + ] +} From bafb814b24da28cd879822ec7c345f01af4d729d Mon Sep 17 00:00:00 2001 From: aidansunbury Date: Sat, 9 Nov 2024 16:55:48 -0800 Subject: [PATCH 04/11] moved prettier related setup to eslint file so that it is not tied to tailwind css --- cli/src/installers/dependencyVersionMap.ts | 18 ++++-- cli/src/installers/drizzle.ts | 7 +-- cli/src/installers/eslint.ts | 56 ++++++++++++++++++- cli/src/installers/tailwind.ts | 23 -------- cli/template/base/package.json | 5 -- .../extras/config/_prettier.config.js | 6 +- .../config/_tailwind.prettier.config.js | 4 ++ 7 files changed, 75 insertions(+), 44 deletions(-) create mode 100644 cli/template/extras/config/_tailwind.prettier.config.js diff --git a/cli/src/installers/dependencyVersionMap.ts b/cli/src/installers/dependencyVersionMap.ts index 8e3787487d..e250b0afe1 100644 --- a/cli/src/installers/dependencyVersionMap.ts +++ b/cli/src/installers/dependencyVersionMap.ts @@ -16,7 +16,6 @@ export const dependencyVersionMap = { // Drizzle "drizzle-kit": "^0.24.0", "drizzle-orm": "^0.33.0", - "eslint-plugin-drizzle": "^0.2.3", mysql2: "^3.11.0", "@planetscale/database": "^1.19.0", postgres: "^3.4.4", @@ -24,9 +23,7 @@ export const dependencyVersionMap = { // TailwindCSS tailwindcss: "^3.4.3", - postcss: "^8.4.39", - prettier: "^3.3.2", - "prettier-plugin-tailwindcss": "^0.6.5", + postcss: "^8.4.39", // tRPC "@trpc/client": "^11.0.0-rc.446", @@ -39,6 +36,17 @@ export const dependencyVersionMap = { // biome "@biomejs/biome": "1.9.4", - //* TODO also add prettier / eslint plugins here + + // eslint / prettier + prettier: "^3.3.2", + "prettier-plugin-tailwindcss": "^0.6.5", + "eslint": "^8.57.0", + "eslint-config-next": "^15.0.1", + "eslint-plugin-drizzle": "^0.2.3", + "@types/eslint": "^8.56.10", + "@typescript-eslint/eslint-plugin": "^8.1.0", + "@typescript-eslint/parser": "^8.1.0", + //* TODO make sure all these are install normally when eslint is selected + } as const; export type AvailableDependencies = keyof typeof dependencyVersionMap; diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index 6b45f1e2a2..bd90ee0c79 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -5,7 +5,6 @@ import { type PackageJson } from "type-fest"; import { PKG_ROOT } from "~/consts.js"; import { type Installer } from "~/installers/index.js"; import { addPackageDependency } from "~/utils/addPackageDependency.js"; -import { type AvailableDependencies } from "./dependencyVersionMap.js"; export const drizzleInstaller: Installer = ({ projectDir, @@ -13,14 +12,10 @@ export const drizzleInstaller: Installer = ({ scopedAppName, databaseProvider, }) => { - const devPackages: AvailableDependencies[] = [ - "drizzle-kit", - "eslint-plugin-drizzle", //* TODO make this depend on eslint being installed - ]; addPackageDependency({ projectDir, - dependencies: devPackages, + dependencies: ["drizzle-kit"], devMode: true, }); addPackageDependency({ diff --git a/cli/src/installers/eslint.ts b/cli/src/installers/eslint.ts index 6600168271..549a23f16e 100644 --- a/cli/src/installers/eslint.ts +++ b/cli/src/installers/eslint.ts @@ -1,12 +1,66 @@ import path from "path"; import fs from "fs-extra"; +import { type PackageJson } from "type-fest"; +import { PKG_ROOT } from "~/consts.js"; import { _initialConfig } from "~/../template/extras/config/_eslint.js"; import { type Installer } from "~/installers/index.js"; +import { addPackageDependency } from "~/utils/addPackageDependency.js"; +import { type AvailableDependencies } from "./dependencyVersionMap.js"; + +// Also installs prettier export const dynamicEslintInstaller: Installer = ({ projectDir, packages }) => { - const usingDrizzle = !!packages?.drizzle?.inUse; + const devPackages: AvailableDependencies[] = [ + "prettier", + "eslint", + "eslint-config-next", + "@types/eslint", + "@typescript-eslint/eslint-plugin", + "@typescript-eslint/parser", + ]; + if (packages?.tailwind.inUse) { + devPackages.push("prettier-plugin-tailwindcss"); + } + if (packages?.drizzle.inUse) { + devPackages.push("eslint-plugin-drizzle"); + } + + addPackageDependency({ + projectDir, + dependencies: devPackages, + devMode: true, + }); + const extrasDir = path.join(PKG_ROOT, "template/extras"); + + // Prettier + let prettierSrc: string; + if (packages?.tailwind.inUse) { + prettierSrc = path.join(extrasDir, "config/_tailwind.prettier.config.js"); + } + else { + prettierSrc = path.join(extrasDir, "config/_prettier.config.js"); + } + const prettierDest = path.join(projectDir, "prettier.config.js"); + + fs.copySync(prettierSrc, prettierDest); + + // add format:* scripts to package.json + const packageJsonPath = path.join(projectDir, "package.json"); + const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson; + packageJsonContent.scripts = { + ...packageJsonContent.scripts, + "format:write": 'prettier --write "**/*.{ts,tsx,js,jsx,mdx}" --cache', + "format:check": 'prettier --check "**/*.{ts,tsx,js,jsx,mdx}" --cache', + }; + + fs.writeJSONSync(packageJsonPath, packageJsonContent, { + spaces: 2, + }); + + // eslint + const usingDrizzle = !!packages?.drizzle?.inUse; const eslintConfig = getEslintConfig({ usingDrizzle }); // Convert config from _eslint.config.json to .eslintrc.cjs diff --git a/cli/src/installers/tailwind.ts b/cli/src/installers/tailwind.ts index 53cc1a7137..559150def2 100644 --- a/cli/src/installers/tailwind.ts +++ b/cli/src/installers/tailwind.ts @@ -1,6 +1,5 @@ import path from "path"; import fs from "fs-extra"; -import { type PackageJson } from "type-fest"; import { PKG_ROOT } from "~/consts.js"; import { type Installer } from "~/installers/index.js"; @@ -12,8 +11,6 @@ export const tailwindInstaller: Installer = ({ projectDir }) => { dependencies: [ "tailwindcss", "postcss", - "prettier", - "prettier-plugin-tailwindcss", ], devMode: true, }); @@ -26,30 +23,10 @@ export const tailwindInstaller: Installer = ({ projectDir }) => { const postcssCfgSrc = path.join(extrasDir, "config/postcss.config.js"); const postcssCfgDest = path.join(projectDir, "postcss.config.js"); - const prettierSrc = path.join(extrasDir, "config/_prettier.config.js"); - const prettierDest = path.join(projectDir, "prettier.config.js"); - const cssSrc = path.join(extrasDir, "src/styles/globals.css"); const cssDest = path.join(projectDir, "src/styles/globals.css"); - // add format:* scripts to package.json - const packageJsonPath = path.join(projectDir, "package.json"); - - //* TODO for some reason the prettier related install stuff is here with tailwind, should probably be in its own installer - //* along with biome stuff - //* Update: prettier is not installed if you don't select tailwind css, which really doesn't make sense. Should move it to the eslint installer - const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson; - packageJsonContent.scripts = { - ...packageJsonContent.scripts, - "format:write": 'prettier --write "**/*.{ts,tsx,js,jsx,mdx}" --cache', - "format:check": 'prettier --check "**/*.{ts,tsx,js,jsx,mdx}" --cache', - }; - fs.copySync(twCfgSrc, twCfgDest); fs.copySync(postcssCfgSrc, postcssCfgDest); fs.copySync(cssSrc, cssDest); - fs.copySync(prettierSrc, prettierDest); - fs.writeJSONSync(packageJsonPath, packageJsonContent, { - spaces: 2, - }); }; diff --git a/cli/template/base/package.json b/cli/template/base/package.json index 00ca211bdc..df54d7c844 100644 --- a/cli/template/base/package.json +++ b/cli/template/base/package.json @@ -22,14 +22,9 @@ "zod": "^3.23.3" }, "devDependencies": { - "@types/eslint": "^8.56.10", "@types/node": "^20.14.10", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^8.1.0", - "@typescript-eslint/parser": "^8.1.0", - "eslint": "^8.57.0", - "eslint-config-next": "^15.0.1", "typescript": "^5.5.3" } } diff --git a/cli/template/extras/config/_prettier.config.js b/cli/template/extras/config/_prettier.config.js index da332bd898..6f1a202fbd 100644 --- a/cli/template/extras/config/_prettier.config.js +++ b/cli/template/extras/config/_prettier.config.js @@ -1,4 +1,2 @@ -/** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */ -export default { - plugins: ["prettier-plugin-tailwindcss"], -}; +/** @type {import('prettier').Config} */ +export default {}; diff --git a/cli/template/extras/config/_tailwind.prettier.config.js b/cli/template/extras/config/_tailwind.prettier.config.js new file mode 100644 index 0000000000..da332bd898 --- /dev/null +++ b/cli/template/extras/config/_tailwind.prettier.config.js @@ -0,0 +1,4 @@ +/** @type {import('prettier').Config & import('prettier-plugin-tailwindcss').PluginOptions} */ +export default { + plugins: ["prettier-plugin-tailwindcss"], +}; From d62917f2b75f534ceb1e04b943355d0088dc162e Mon Sep 17 00:00:00 2001 From: aidansunbury Date: Sat, 9 Nov 2024 17:20:30 -0800 Subject: [PATCH 05/11] added package.json scripts for biome --- cli/src/cli/index.ts | 8 +- cli/src/installers/biome.ts | 20 ++- cli/src/installers/dependencyVersionMap.ts | 6 +- cli/src/installers/drizzle.ts | 1 - cli/src/installers/eslint.ts | 6 +- cli/src/installers/index.ts | 2 +- cli/src/installers/tailwind.ts | 5 +- cli/template/extras/config/biome.jsonc | 152 ++++----------------- 8 files changed, 51 insertions(+), 149 deletions(-) diff --git a/cli/src/cli/index.ts b/cli/src/cli/index.ts index 4093272e15..4a0e236a37 100644 --- a/cli/src/cli/index.ts +++ b/cli/src/cli/index.ts @@ -211,7 +211,7 @@ export const runCli = async (): Promise => { if (cliResults.flags.biome && cliResults.flags.eslint) { logger.warn("Incompatible combination Biome + ESLint. Exiting."); process.exit(0); - }; + } if (databaseProviders.includes(cliResults.flags.dbProvider) === false) { logger.warn( `Incompatible database provided. Use: ${databaseProviders.join(", ")}. Exiting.` @@ -324,7 +324,8 @@ export const runCli = async (): Promise => { }, linter: () => { return p.select({ - message: "Would you like to use ESLint and Prettier or Biome for linting and formatting?", + message: + "Would you like to use ESLint and Prettier or Biome for linting and formatting?", options: [ { value: "eslint", label: "ESLint/Prettier" }, { value: "biome", label: "Biome" }, @@ -373,7 +374,8 @@ export const runCli = async (): Promise => { if (project.authentication === "next-auth") packages.push("nextAuth"); if (project.database === "prisma") packages.push("prisma"); if (project.database === "drizzle") packages.push("drizzle"); - //* TODO may need to add something here? not sure + if (project.linter === "eslint") packages.push("eslint"); + if (project.linter === "biome") packages.push("biome"); return { appName: project.name ?? cliResults.appName, diff --git a/cli/src/installers/biome.ts b/cli/src/installers/biome.ts index 8bb8686593..e31de626db 100644 --- a/cli/src/installers/biome.ts +++ b/cli/src/installers/biome.ts @@ -1,16 +1,15 @@ import path from "path"; import fs from "fs-extra"; +import { type PackageJson } from "type-fest"; +import { PKG_ROOT } from "~/consts.js"; import { type Installer } from "~/installers/index.js"; import { addPackageDependency } from "~/utils/addPackageDependency.js"; -import { PKG_ROOT } from "~/consts.js"; export const biomeInstaller: Installer = ({ projectDir }) => { addPackageDependency({ projectDir, - dependencies: [ - "@biomejs/biome", - ], + dependencies: ["@biomejs/biome"], devMode: true, }); @@ -19,4 +18,17 @@ export const biomeInstaller: Installer = ({ projectDir }) => { const biomeConfigDest = path.join(projectDir, "biome.jsonc"); fs.copySync(biomeConfigSrc, biomeConfigDest); + + // add format:* scripts to package.json + const packageJsonPath = path.join(projectDir, "package.json"); + const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson; + packageJsonContent.scripts = { + ...packageJsonContent.scripts, + "format:write": 'biome format --write "**/*.{ts,tsx,js,jsx,mdx}"', + "format:check": 'biome format --check "**/*.{ts,tsx,js,jsx,mdx}"', + }; + + fs.writeJSONSync(packageJsonPath, packageJsonContent, { + spaces: 2, + }); }; diff --git a/cli/src/installers/dependencyVersionMap.ts b/cli/src/installers/dependencyVersionMap.ts index e250b0afe1..b95d246220 100644 --- a/cli/src/installers/dependencyVersionMap.ts +++ b/cli/src/installers/dependencyVersionMap.ts @@ -23,7 +23,7 @@ export const dependencyVersionMap = { // TailwindCSS tailwindcss: "^3.4.3", - postcss: "^8.4.39", + postcss: "^8.4.39", // tRPC "@trpc/client": "^11.0.0-rc.446", @@ -40,13 +40,11 @@ export const dependencyVersionMap = { // eslint / prettier prettier: "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", - "eslint": "^8.57.0", + eslint: "^8.57.0", "eslint-config-next": "^15.0.1", "eslint-plugin-drizzle": "^0.2.3", "@types/eslint": "^8.56.10", "@typescript-eslint/eslint-plugin": "^8.1.0", "@typescript-eslint/parser": "^8.1.0", - //* TODO make sure all these are install normally when eslint is selected - } as const; export type AvailableDependencies = keyof typeof dependencyVersionMap; diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index bd90ee0c79..5b9ad9bcd0 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -12,7 +12,6 @@ export const drizzleInstaller: Installer = ({ scopedAppName, databaseProvider, }) => { - addPackageDependency({ projectDir, dependencies: ["drizzle-kit"], diff --git a/cli/src/installers/eslint.ts b/cli/src/installers/eslint.ts index 549a23f16e..e317d44939 100644 --- a/cli/src/installers/eslint.ts +++ b/cli/src/installers/eslint.ts @@ -2,13 +2,12 @@ import path from "path"; import fs from "fs-extra"; import { type PackageJson } from "type-fest"; -import { PKG_ROOT } from "~/consts.js"; import { _initialConfig } from "~/../template/extras/config/_eslint.js"; +import { PKG_ROOT } from "~/consts.js"; import { type Installer } from "~/installers/index.js"; import { addPackageDependency } from "~/utils/addPackageDependency.js"; import { type AvailableDependencies } from "./dependencyVersionMap.js"; - // Also installs prettier export const dynamicEslintInstaller: Installer = ({ projectDir, packages }) => { const devPackages: AvailableDependencies[] = [ @@ -38,8 +37,7 @@ export const dynamicEslintInstaller: Installer = ({ projectDir, packages }) => { let prettierSrc: string; if (packages?.tailwind.inUse) { prettierSrc = path.join(extrasDir, "config/_tailwind.prettier.config.js"); - } - else { + } else { prettierSrc = path.join(extrasDir, "config/_prettier.config.js"); } const prettierDest = path.join(projectDir, "prettier.config.js"); diff --git a/cli/src/installers/index.ts b/cli/src/installers/index.ts index 77809f1195..366760a23b 100644 --- a/cli/src/installers/index.ts +++ b/cli/src/installers/index.ts @@ -4,10 +4,10 @@ import { prismaInstaller } from "~/installers/prisma.js"; import { tailwindInstaller } from "~/installers/tailwind.js"; import { trpcInstaller } from "~/installers/trpc.js"; import { type PackageManager } from "~/utils/getUserPkgManager.js"; +import { biomeInstaller } from "./biome.js"; import { dbContainerInstaller } from "./dbContainer.js"; import { drizzleInstaller } from "./drizzle.js"; import { dynamicEslintInstaller } from "./eslint.js"; -import { biomeInstaller } from "./biome.js"; // Turning this into a const allows the list to be iterated over for programmatically creating prompt options // Should increase extensibility in the future diff --git a/cli/src/installers/tailwind.ts b/cli/src/installers/tailwind.ts index 559150def2..3df90bfa66 100644 --- a/cli/src/installers/tailwind.ts +++ b/cli/src/installers/tailwind.ts @@ -8,10 +8,7 @@ import { addPackageDependency } from "~/utils/addPackageDependency.js"; export const tailwindInstaller: Installer = ({ projectDir }) => { addPackageDependency({ projectDir, - dependencies: [ - "tailwindcss", - "postcss", - ], + dependencies: ["tailwindcss", "postcss"], devMode: true, }); diff --git a/cli/template/extras/config/biome.jsonc b/cli/template/extras/config/biome.jsonc index 7ffee4f439..adb632dfa2 100644 --- a/cli/template/extras/config/biome.jsonc +++ b/cli/template/extras/config/biome.jsonc @@ -1,129 +1,25 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", - "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false }, - "files": { "ignoreUnknown": false, "ignore": [] }, - "formatter": { - "enabled": true, - "useEditorconfig": true, - "formatWithErrors": false, - "indentStyle": "space", - "indentWidth": 2, - "lineEnding": "lf", - "lineWidth": 80, - "attributePosition": "auto", - "bracketSpacing": true - }, - "organizeImports": { "enabled": true }, - "linter": { - "enabled": true, - "rules": { - "recommended": false, - "complexity": { - "noUselessTypeConstraint": "error", - "useLiteralKeys": "error", - "useOptionalChain": "error" - }, - "correctness": { "noUnusedVariables": "off", "useArrayLiterals": "off" }, - "style": { - "noInferrableTypes": "error", - "noNamespace": "error", - "useAsConstAssertion": "error", - "useConsistentArrayType": "off", - "useForOf": "error", - "useShorthandFunctionType": "error" - }, - "suspicious": { - "noEmptyBlockStatements": "error", - "noExplicitAny": "error", - "noExtraNonNullAssertion": "error", - "noMisleadingInstantiator": "error", - "noUnsafeDeclarationMerging": "error", - "useAwait": "off", - "useNamespaceKeyword": "error" - } - } - }, - "javascript": { - "formatter": { - "jsxQuoteStyle": "double", - "quoteProperties": "asNeeded", - "trailingCommas": "all", - "semicolons": "asNeeded", - "arrowParentheses": "always", - "bracketSameLine": false, - "quoteStyle": "single", - "attributePosition": "auto", - "bracketSpacing": true - } - }, - "overrides": [ - { - "include": ["*.ts", "*.tsx", "*.mts", "*.cts"], - "linter": { - "rules": { - "correctness": { - "noConstAssign": "off", - "noGlobalObjectCalls": "off", - "noInvalidBuiltinInstantiation": "off", - "noInvalidConstructorSuper": "off", - "noNewSymbol": "off", - "noSetterReturn": "off", - "noUndeclaredVariables": "off", - "noUnreachable": "off", - "noUnreachableSuper": "off" - }, - "style": { - "noArguments": "error", - "noVar": "error", - "useConst": "error" - }, - "suspicious": { - "noClassAssign": "off", - "noDuplicateClassMembers": "off", - "noDuplicateObjectKeys": "off", - "noDuplicateParameters": "off", - "noFunctionAssign": "off", - "noImportAssign": "off", - "noRedeclare": "off", - "noUnsafeNegation": "off", - "useGetterReturn": "off" - } - } - } - }, - { - "include": ["*.ts", "*.tsx", "*.mts", "*.cts"], - "linter": { - "rules": { - "correctness": { - "noConstAssign": "off", - "noGlobalObjectCalls": "off", - "noInvalidBuiltinInstantiation": "off", - "noInvalidConstructorSuper": "off", - "noNewSymbol": "off", - "noSetterReturn": "off", - "noUndeclaredVariables": "off", - "noUnreachable": "off", - "noUnreachableSuper": "off" - }, - "style": { - "noArguments": "error", - "noVar": "error", - "useConst": "error" - }, - "suspicious": { - "noClassAssign": "off", - "noDuplicateClassMembers": "off", - "noDuplicateObjectKeys": "off", - "noDuplicateParameters": "off", - "noFunctionAssign": "off", - "noImportAssign": "off", - "noRedeclare": "off", - "noUnsafeNegation": "off", - "useGetterReturn": "off" - } - } - } - } - ] -} + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { "ignoreUnknown": false, "ignore": [] }, + "formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2 }, + "organizeImports": { "enabled": true }, + "linter": { + "enabled": true, + "rules": { + "nursery": { + "useSortedClasses": { + "level": "error", + "options": { + "functions": ["clsx", "cva", "cn"] + } + } + }, + "recommended": true } + }, + "javascript": { "formatter": { "quoteStyle": "double" } } +} \ No newline at end of file From 2c1f7f18b1b0cb1379a6f6fd4587caf174a3f404 Mon Sep 17 00:00:00 2001 From: aidansunbury Date: Sat, 9 Nov 2024 17:22:45 -0800 Subject: [PATCH 06/11] added changeset --- .changeset/late-tips-eat.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/late-tips-eat.md diff --git a/.changeset/late-tips-eat.md b/.changeset/late-tips-eat.md new file mode 100644 index 0000000000..197aae6389 --- /dev/null +++ b/.changeset/late-tips-eat.md @@ -0,0 +1,5 @@ +--- +"create-t3-app": minor +--- + +Added support for biome.js as a formatter and linter From cbad9bc5093082c50aa16a727066614577886603 Mon Sep 17 00:00:00 2001 From: aidansunbury Date: Thu, 14 Nov 2024 21:17:02 -0800 Subject: [PATCH 07/11] fixed biome scripts and created addPackageScript function --- cli/src/installers/biome.ts | 20 ++++++++------------ cli/src/installers/drizzle.ts | 25 ++++++++++--------------- cli/src/installers/eslint.ts | 22 ++++++++++------------ cli/src/installers/prisma.ts | 27 +++++++++++---------------- cli/src/utils/addPackageScript.ts | 25 +++++++++++++++++++++++++ cli/template/base/package.json | 3 --- 6 files changed, 64 insertions(+), 58 deletions(-) create mode 100644 cli/src/utils/addPackageScript.ts diff --git a/cli/src/installers/biome.ts b/cli/src/installers/biome.ts index e31de626db..dfa5254e2d 100644 --- a/cli/src/installers/biome.ts +++ b/cli/src/installers/biome.ts @@ -1,10 +1,10 @@ import path from "path"; import fs from "fs-extra"; -import { type PackageJson } from "type-fest"; import { PKG_ROOT } from "~/consts.js"; import { type Installer } from "~/installers/index.js"; import { addPackageDependency } from "~/utils/addPackageDependency.js"; +import { addPackageScript } from "~/utils/addPackageScript.js"; export const biomeInstaller: Installer = ({ projectDir }) => { addPackageDependency({ @@ -19,16 +19,12 @@ export const biomeInstaller: Installer = ({ projectDir }) => { fs.copySync(biomeConfigSrc, biomeConfigDest); - // add format:* scripts to package.json - const packageJsonPath = path.join(projectDir, "package.json"); - const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson; - packageJsonContent.scripts = { - ...packageJsonContent.scripts, - "format:write": 'biome format --write "**/*.{ts,tsx,js,jsx,mdx}"', - "format:check": 'biome format --check "**/*.{ts,tsx,js,jsx,mdx}"', - }; - - fs.writeJSONSync(packageJsonPath, packageJsonContent, { - spaces: 2, + addPackageScript({ + projectDir, + scripts: { + "format:unsafe": "biome check --write --unsafe .", + "format:write": "biome check --write .", + "format:check": "biome check .", + }, }); }; diff --git a/cli/src/installers/drizzle.ts b/cli/src/installers/drizzle.ts index 5b9ad9bcd0..c9bbae8590 100644 --- a/cli/src/installers/drizzle.ts +++ b/cli/src/installers/drizzle.ts @@ -1,10 +1,10 @@ import path from "path"; import fs from "fs-extra"; -import { type PackageJson } from "type-fest"; import { PKG_ROOT } from "~/consts.js"; import { type Installer } from "~/installers/index.js"; import { addPackageDependency } from "~/utils/addPackageDependency.js"; +import { addPackageScript } from "~/utils/addPackageScript.js"; export const drizzleInstaller: Installer = ({ projectDir, @@ -69,24 +69,19 @@ export const drizzleInstaller: Installer = ({ ); const clientDest = path.join(projectDir, "src/server/db/index.ts"); - // add db:* scripts to package.json - const packageJsonPath = path.join(projectDir, "package.json"); - - const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson; - packageJsonContent.scripts = { - ...packageJsonContent.scripts, - "db:push": "drizzle-kit push", - "db:studio": "drizzle-kit studio", - "db:generate": "drizzle-kit generate", - "db:migrate": "drizzle-kit migrate", - }; + addPackageScript({ + projectDir, + scripts: { + "db:push": "drizzle-kit push", + "db:studio": "drizzle-kit studio", + "db:generate": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + }, + }); fs.copySync(configFile, configDest); fs.mkdirSync(path.dirname(schemaDest), { recursive: true }); fs.writeFileSync(schemaDest, schemaContent); fs.writeFileSync(configDest, configContent); fs.copySync(clientSrc, clientDest); - fs.writeJSONSync(packageJsonPath, packageJsonContent, { - spaces: 2, - }); }; diff --git a/cli/src/installers/eslint.ts b/cli/src/installers/eslint.ts index e317d44939..4b0a872fd7 100644 --- a/cli/src/installers/eslint.ts +++ b/cli/src/installers/eslint.ts @@ -1,11 +1,11 @@ import path from "path"; import fs from "fs-extra"; -import { type PackageJson } from "type-fest"; import { _initialConfig } from "~/../template/extras/config/_eslint.js"; import { PKG_ROOT } from "~/consts.js"; import { type Installer } from "~/installers/index.js"; import { addPackageDependency } from "~/utils/addPackageDependency.js"; +import { addPackageScript } from "~/utils/addPackageScript.js"; import { type AvailableDependencies } from "./dependencyVersionMap.js"; // Also installs prettier @@ -44,17 +44,15 @@ export const dynamicEslintInstaller: Installer = ({ projectDir, packages }) => { fs.copySync(prettierSrc, prettierDest); - // add format:* scripts to package.json - const packageJsonPath = path.join(projectDir, "package.json"); - const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson; - packageJsonContent.scripts = { - ...packageJsonContent.scripts, - "format:write": 'prettier --write "**/*.{ts,tsx,js,jsx,mdx}" --cache', - "format:check": 'prettier --check "**/*.{ts,tsx,js,jsx,mdx}" --cache', - }; - - fs.writeJSONSync(packageJsonPath, packageJsonContent, { - spaces: 2, + addPackageScript({ + projectDir, + scripts: { + lint: "next lint", + "lint:fix": "next lint --fix", + check: "next lint && tsc --noEmit", + "format:write": 'prettier --write "**/*.{ts,tsx,js,jsx,mdx}" --cache', + "format:check": 'prettier --check "**/*.{ts,tsx,js,jsx,mdx}" --cache', + }, }); // eslint diff --git a/cli/src/installers/prisma.ts b/cli/src/installers/prisma.ts index f92da12395..36c2b7cff7 100644 --- a/cli/src/installers/prisma.ts +++ b/cli/src/installers/prisma.ts @@ -1,10 +1,10 @@ import path from "path"; import fs from "fs-extra"; -import { type PackageJson } from "type-fest"; import { PKG_ROOT } from "~/consts.js"; import { type Installer } from "~/installers/index.js"; import { addPackageDependency } from "~/utils/addPackageDependency.js"; +import { addPackageScript } from "~/utils/addPackageScript.js"; export const prismaInstaller: Installer = ({ projectDir, @@ -65,21 +65,16 @@ export const prismaInstaller: Installer = ({ ); const clientDest = path.join(projectDir, "src/server/db.ts"); - // add postinstall and push script to package.json - const packageJsonPath = path.join(projectDir, "package.json"); - - const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson; - packageJsonContent.scripts = { - ...packageJsonContent.scripts, - postinstall: "prisma generate", - "db:push": "prisma db push", - "db:studio": "prisma studio", - "db:generate": "prisma migrate dev", - "db:migrate": "prisma migrate deploy", - }; + addPackageScript({ + projectDir, + scripts: { + postinstall: "prisma generate", + "db:push": "prisma db push", + "db:studio": "prisma studio", + "db:generate": "prisma migrate dev", + "db:migrate": "prisma migrate deploy", + }, + }); fs.copySync(clientSrc, clientDest); - fs.writeJSONSync(packageJsonPath, packageJsonContent, { - spaces: 2, - }); }; diff --git a/cli/src/utils/addPackageScript.ts b/cli/src/utils/addPackageScript.ts new file mode 100644 index 0000000000..fc14a7d2df --- /dev/null +++ b/cli/src/utils/addPackageScript.ts @@ -0,0 +1,25 @@ +import path from "path"; +import fs from "fs-extra"; +import sortPackageJson from "sort-package-json"; +import { type PackageJson } from "type-fest"; + +export const addPackageScript = (opts: { + scripts: Record; + projectDir: string; +}) => { + const { scripts, projectDir } = opts; + + const packageJsonPath = path.join(projectDir, "package.json"); + const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson; + + packageJsonContent.scripts = { + ...packageJsonContent.scripts, + ...scripts, + }; + + const sortedPkgJson = sortPackageJson(packageJsonContent); + + fs.writeJSONSync(packageJsonPath, sortedPkgJson, { + spaces: 2, + }); +}; diff --git a/cli/template/base/package.json b/cli/template/base/package.json index df54d7c844..5ba41fb202 100644 --- a/cli/template/base/package.json +++ b/cli/template/base/package.json @@ -7,9 +7,6 @@ "dev": "next dev --turbo", "build": "next build", "start": "next start", - "lint": "next lint", - "lint:fix": "next lint --fix", - "check": "next lint && tsc --noEmit", "preview": "next build && next start", "typecheck": "tsc --noEmit" }, From f7e056ddbb4c9cce6c4e983f0eeae016d3dfb03f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20D=C3=ADaz=20Aguilera?= Date: Sat, 16 Nov 2024 19:46:09 +0000 Subject: [PATCH 08/11] Add mixed Biome and ESLint option to CLI --- cli/src/cli/index.ts | 24 +++++- cli/src/installers/index.ts | 6 ++ cli/src/installers/mixed-biome.ts | 82 +++++++++++++++++++++ cli/template/extras/config/eslintBiome.json | 15 ++++ 4 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 cli/src/installers/mixed-biome.ts create mode 100644 cli/template/extras/config/eslintBiome.json diff --git a/cli/src/cli/index.ts b/cli/src/cli/index.ts index 4a0e236a37..19ba93210e 100644 --- a/cli/src/cli/index.ts +++ b/cli/src/cli/index.ts @@ -41,6 +41,8 @@ interface CliFlags { eslint: boolean; /** @internal Used in CI */ biome: boolean; + /** @internal Used in CI */ + mixedBiome: boolean; } interface CliResults { @@ -68,6 +70,7 @@ const defaultOptions: CliResults = { dbProvider: "sqlite", eslint: false, biome: false, + mixedBiome: false, }, databaseProvider: "sqlite", }; @@ -161,6 +164,11 @@ export const runCli = async (): Promise => { "Experimental: Boolean value if we should install biome. Must be used in conjunction with `--CI`.", (value) => !!value && value !== "false" ) + .option( + "--mixedBiome [boolean]", + "Experimental: Boolean value if we should install biome and eslint. Must be used in conjunction with `--CI`.", + (value) => !!value && value !== "false" + ) /** END CI-FLAGS */ .version(getVersion(), "-v, --version", "Display the version number") .addHelpText( @@ -201,6 +209,7 @@ export const runCli = async (): Promise => { if (cliResults.flags.nextAuth) cliResults.packages.push("nextAuth"); if (cliResults.flags.eslint) cliResults.packages.push("eslint"); if (cliResults.flags.biome) cliResults.packages.push("biome"); + if (cliResults.flags.mixedBiome) cliResults.packages.push("mixedBiome"); if (cliResults.flags.prisma && cliResults.flags.drizzle) { // We test a matrix of all possible combination of packages in CI. Checking for impossible // combinations here and exiting gracefully is easier than changing the CI matrix to exclude @@ -208,10 +217,17 @@ export const runCli = async (): Promise => { logger.warn("Incompatible combination Prisma + Drizzle. Exiting."); process.exit(0); } - if (cliResults.flags.biome && cliResults.flags.eslint) { - logger.warn("Incompatible combination Biome + ESLint. Exiting."); + + if ( + (cliResults.flags.mixedBiome && cliResults.flags.biome) || + cliResults.flags.eslint + ) { + logger.warn( + "Incompatible combination Biome + ESLint. Please select one or the mixed one. Exiting." + ); process.exit(0); } + if (databaseProviders.includes(cliResults.flags.dbProvider) === false) { logger.warn( `Incompatible database provided. Use: ${databaseProviders.join(", ")}. Exiting.` @@ -325,10 +341,11 @@ export const runCli = async (): Promise => { linter: () => { return p.select({ message: - "Would you like to use ESLint and Prettier or Biome for linting and formatting?", + "Would you like to use ESLint and Prettier or ESLint and Biome or only Biome for linting and formatting?", options: [ { value: "eslint", label: "ESLint/Prettier" }, { value: "biome", label: "Biome" }, + { value: "mixedBiome", label: "ESLint + Biome" }, ], initialValue: "eslint", }); @@ -376,6 +393,7 @@ export const runCli = async (): Promise => { if (project.database === "drizzle") packages.push("drizzle"); if (project.linter === "eslint") packages.push("eslint"); if (project.linter === "biome") packages.push("biome"); + if (project.linter === "mixedBiome") packages.push("mixedBiome"); return { appName: project.name ?? cliResults.appName, diff --git a/cli/src/installers/index.ts b/cli/src/installers/index.ts index 366760a23b..bd97bf6f2b 100644 --- a/cli/src/installers/index.ts +++ b/cli/src/installers/index.ts @@ -8,6 +8,7 @@ import { biomeInstaller } from "./biome.js"; import { dbContainerInstaller } from "./dbContainer.js"; import { drizzleInstaller } from "./drizzle.js"; import { dynamicEslintInstaller } from "./eslint.js"; +import { mixedBiomeInstaller } from "./mixed-biome.js"; // Turning this into a const allows the list to be iterated over for programmatically creating prompt options // Should increase extensibility in the future @@ -21,6 +22,7 @@ export const availablePackages = [ "eslint", "biome", "dbContainer", + "mixedBiome", ] as const; export type AvailablePackages = (typeof availablePackages)[number]; @@ -92,4 +94,8 @@ export const buildPkgInstallerMap = ( inUse: packages.includes("biome"), installer: biomeInstaller, }, + mixedBiome: { + inUse: packages.includes("mixedBiome"), + installer: mixedBiomeInstaller, + }, }); diff --git a/cli/src/installers/mixed-biome.ts b/cli/src/installers/mixed-biome.ts new file mode 100644 index 0000000000..b00a377856 --- /dev/null +++ b/cli/src/installers/mixed-biome.ts @@ -0,0 +1,82 @@ +import path from "path"; +import fs from "fs-extra"; + +import { _initialConfig } from "~/../template/extras/config/_eslint.js"; +import { PKG_ROOT } from "~/consts.js"; +import { type Installer } from "~/installers/index.js"; +import { addPackageDependency } from "~/utils/addPackageDependency.js"; +import { addPackageScript } from "~/utils/addPackageScript.js"; +import { type AvailableDependencies } from "./dependencyVersionMap.js"; + +// Also installs prettier +export const mixedBiomeInstaller: Installer = ({ projectDir, packages }) => { + const devPackages: AvailableDependencies[] = [ + "@biomejs/biome", + "eslint", + "eslint-config-next", + "@types/eslint", + "@typescript-eslint/eslint-plugin", + "@typescript-eslint/parser", + ]; + + addPackageDependency({ + projectDir, + dependencies: devPackages, + devMode: true, + }); + const extrasDir = path.join(PKG_ROOT, "template/extras"); + + // Biome + + const biomeSrc = path.join(extrasDir, "config/eslintBiome.json"); + + const biomeDest = path.join(projectDir, "biome.jsonc"); + + fs.copySync(biomeSrc, biomeDest); + + addPackageScript({ + projectDir, + scripts: { + lint: "next lint", + "lint:fix": "next lint --fix", + check: "next lint && tsc --noEmit", + "format:write": "biome format --write .", + "format:check": "biome format .", + }, + }); + + // eslint + const usingDrizzle = !!packages?.drizzle?.inUse; + const eslintConfig = getEslintConfig({ usingDrizzle }); + + // Convert config from _eslint.config.json to .eslintrc.cjs + const eslintrcFileContents = [ + '/** @type {import("eslint").Linter.Config} */', + `const config = ${JSON.stringify(eslintConfig, null, 2)}`, + "module.exports = config;", + ].join("\n"); + + const eslintConfigDest = path.join(projectDir, ".eslintrc.cjs"); + fs.writeFileSync(eslintConfigDest, eslintrcFileContents, "utf-8"); +}; + +const getEslintConfig = ({ usingDrizzle }: { usingDrizzle: boolean }) => { + const eslintConfig = _initialConfig; + + if (usingDrizzle) { + eslintConfig.plugins = [...(eslintConfig.plugins ?? []), "drizzle"]; + + eslintConfig.rules = { + ...eslintConfig.rules, + "drizzle/enforce-delete-with-where": [ + "error", + { drizzleObjectName: ["db", "ctx.db"] }, + ], + "drizzle/enforce-update-with-where": [ + "error", + { drizzleObjectName: ["db", "ctx.db"] }, + ], + }; + } + return eslintConfig; +}; diff --git a/cli/template/extras/config/eslintBiome.json b/cli/template/extras/config/eslintBiome.json new file mode 100644 index 0000000000..7ac608e489 --- /dev/null +++ b/cli/template/extras/config/eslintBiome.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { "ignoreUnknown": false, "ignore": [] }, + "formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2 }, + "organizeImports": { "enabled": true }, + "linter": { + "enabled": false + }, + "javascript": { "formatter": { "quoteStyle": "double" } } +} \ No newline at end of file From d294e46c2e16219d911854811d013f91d10da0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20D=C3=ADaz=20Aguilera?= Date: Sat, 16 Nov 2024 19:51:46 +0000 Subject: [PATCH 09/11] Final commit. Co-authored-by: aidansunbury --- cli/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/README.md b/cli/README.md index c9544de68a..4094a6243a 100644 --- a/cli/README.md +++ b/cli/README.md @@ -30,6 +30,7 @@

+

Watch Theo's overview on Youtube here

From dd3273a4e8be6b804631f4333bb73a4ce605a71a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20D=C3=ADaz=20Aguilera?= Date: Sat, 16 Nov 2024 20:03:47 +0000 Subject: [PATCH 10/11] format files --- cli/README.md | 1 - cli/template/extras/config/eslintBiome.json | 30 ++++++++++----------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/cli/README.md b/cli/README.md index 4094a6243a..c9544de68a 100644 --- a/cli/README.md +++ b/cli/README.md @@ -30,7 +30,6 @@

-

Watch Theo's overview on Youtube here

diff --git a/cli/template/extras/config/eslintBiome.json b/cli/template/extras/config/eslintBiome.json index 7ac608e489..79602726cf 100644 --- a/cli/template/extras/config/eslintBiome.json +++ b/cli/template/extras/config/eslintBiome.json @@ -1,15 +1,15 @@ -{ - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false - }, - "files": { "ignoreUnknown": false, "ignore": [] }, - "formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2 }, - "organizeImports": { "enabled": true }, - "linter": { - "enabled": false - }, - "javascript": { "formatter": { "quoteStyle": "double" } } -} \ No newline at end of file +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { "ignoreUnknown": false, "ignore": [] }, + "formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2 }, + "organizeImports": { "enabled": true }, + "linter": { + "enabled": false + }, + "javascript": { "formatter": { "quoteStyle": "double" } } +} From c1aece27fadd2e6015149506450773c0e4b79f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20D=C3=ADaz=20Aguilera?= Date: Thu, 28 Nov 2024 21:29:08 +0100 Subject: [PATCH 11/11] Create IGNORE --- IGNORE | 1 + 1 file changed, 1 insertion(+) create mode 100644 IGNORE diff --git a/IGNORE b/IGNORE new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/IGNORE @@ -0,0 +1 @@ +