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 @@
+