Skip to content

Commit

Permalink
it works but needs more testing
Browse files Browse the repository at this point in the history
  • Loading branch information
ronanru committed Oct 24, 2023
1 parent 4e3e458 commit 0e11e4e
Show file tree
Hide file tree
Showing 18 changed files with 257 additions and 33 deletions.
31 changes: 30 additions & 1 deletion cli/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import chalk from "chalk";
import { Command } from "commander";

import { CREATE_T3_APP, DEFAULT_APP_NAME } from "~/consts.js";
import { type AvailablePackages } from "~/installers/index.js";
import {
databaseProviders,
type AvailablePackages,
type DatabaseProvider,
} from "~/installers/index.js";
import { getVersion } from "~/utils/getT3Version.js";
import { getUserPkgManager } from "~/utils/getUserPkgManager.js";
import { IsTTYError } from "~/utils/isTTYError.js";
Expand Down Expand Up @@ -37,6 +41,7 @@ interface CliResults {
appName: string;
packages: AvailablePackages[];
flags: CliFlags;
databaseProvider: DatabaseProvider;
}

const defaultOptions: CliResults = {
Expand All @@ -55,6 +60,7 @@ const defaultOptions: CliResults = {
importAlias: "~/",
appRouter: false,
},
databaseProvider: "sqlite",
};

export const runCli = async (): Promise<CliResults> => {
Expand Down Expand Up @@ -124,6 +130,13 @@ export const runCli = async (): Promise<CliResults> => {
"Explicitly tell the CLI to use a custom import alias",
defaultOptions.flags.importAlias
)
.option(
"--db-provider",
`Choose a database provider to use. Possible values: ${databaseProviders.join(
", "
)}`,
defaultOptions.flags.importAlias
)
.option(
"--appRouter [boolean]",
"Explicitly tell the CLI to use the new Next.js app router",
Expand Down Expand Up @@ -262,6 +275,20 @@ export const runCli = async (): Promise<CliResults> => {
initialValue: false,
});
},
databaseProvider: ({ results }) => {
if (results.database === "none") return;
return p.select({
message: "What database provider would you like to use?",
options: [
{ value: "mysql", label: "MySQL" },
{ value: "postgres", label: "PostgreSQL" },
{ value: "sqlite", label: "SQLite" },
{ value: "planetscale", label: "Planetscale Serverless" },
{ value: "neon", label: "Neon Serverless" },
],
initialValue: "mysql",
});
},
...(!cliResults.flags.noGit && {
git: () => {
return p.confirm({
Expand Down Expand Up @@ -307,6 +334,8 @@ export const runCli = async (): Promise<CliResults> => {
return {
appName: project.name ?? cliResults.appName,
packages,
databaseProvider:
(project.databaseProvider as DatabaseProvider) || "sqlite",
flags: {
...cliResults.flags,
appRouter: project.appRouter ?? cliResults.flags.appRouter,
Expand Down
9 changes: 8 additions & 1 deletion cli/src/helpers/createProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ import {
selectLayoutFile,
selectPageFile,
} from "~/helpers/selectBoilerplate.js";
import { type PkgInstallerMap } from "~/installers/index.js";
import {
type DatabaseProvider,
type PkgInstallerMap,
} from "~/installers/index.js";
import { getUserPkgManager } from "~/utils/getUserPkgManager.js";

interface CreateProjectOptions {
Expand All @@ -20,6 +23,7 @@ interface CreateProjectOptions {
noInstall: boolean;
importAlias: string;
appRouter: boolean;
databaseProvider: DatabaseProvider;
}

export const createProject = async ({
Expand All @@ -28,6 +32,7 @@ export const createProject = async ({
packages,
noInstall,
appRouter,
databaseProvider,
}: CreateProjectOptions) => {
const pkgManager = getUserPkgManager();
const projectDir = path.resolve(process.cwd(), projectName);
Expand All @@ -40,6 +45,7 @@ export const createProject = async ({
scopedAppName,
noInstall,
appRouter,
databaseProvider,
});

// Install the selected packages
Expand All @@ -51,6 +57,7 @@ export const createProject = async ({
packages,
noInstall,
appRouter,
databaseProvider,
});

// Select necessary _app,index / layout,page files
Expand Down
2 changes: 2 additions & 0 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const main = async () => {
appName,
packages,
flags: { noGit, noInstall, importAlias, appRouter },
databaseProvider,
} = await runCli();

const usePackages = buildPkgInstallerMap(packages);
Expand All @@ -48,6 +49,7 @@ const main = async () => {
projectName: appDir,
scopedAppName,
packages: usePackages,
databaseProvider,
importAlias,
noInstall,
appRouter,
Expand Down
5 changes: 5 additions & 0 deletions cli/src/installers/dependencyVersionMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ export const dependencyVersionMap = {
"dotenv-cli": "^7.3.0",
mysql2: "^3.6.1",
"@planetscale/database": "^1.11.0",
pg: "^8.11.3",
"@types/pg": "^8.10.7",
"@types/better-sqlite3": "^7.6.6",
"better-sqlite3": "^9.0.0",
"@neondatabase/serverless": "^0.6.0",

// TailwindCSS
tailwindcss: "^3.3.3",
Expand Down
97 changes: 93 additions & 4 deletions cli/src/installers/drizzle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,39 @@ 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,
packages,
scopedAppName,
databaseProvider,
}) => {
const devPackages: AvailableDependencies[] = ["drizzle-kit", "dotenv-cli"];
if (databaseProvider === "planetscale") devPackages.push("mysql2");
if (databaseProvider === "postgres") devPackages.push("@types/pg");
if (databaseProvider === "sqlite") devPackages.push("@types/better-sqlite3");
if (databaseProvider === "neon") devPackages.push("pg");

addPackageDependency({
projectDir,
dependencies: ["drizzle-kit", "dotenv-cli", "mysql2"],
dependencies: devPackages,
devMode: true,
});
addPackageDependency({
projectDir,
dependencies: ["drizzle-orm", "@planetscale/database"],
dependencies: [
"drizzle-orm",
(
{
planetscale: "@planetscale/database",
mysql: "mysql2",
postgres: "pg",
sqlite: "better-sqlite3",
neon: "@neondatabase/serverless",
} as const
)[databaseProvider],
],
devMode: false,
});

Expand All @@ -42,10 +61,80 @@ export const drizzleInstaller: Installer = ({
"project1_${name}",
`${scopedAppName}_\${name}`
);

let configContent = fs.readFileSync(configFile, "utf-8");
const dbType = (
{
postgres: "pg",
neon: "pg",
sqlite: "sqlite",
mysql: "mysql",
planetscale: "mysql",
} as const
)[databaseProvider];
configContent = configContent.replace("project1_*", `${scopedAppName}_*`);
if (databaseProvider !== "mysql" && databaseProvider !== "planetscale") {
configContent = configContent.replace(
"mysql2",
{
postgres: "pg",
neon: "pg",
sqlite: "better-sqlite",
}[databaseProvider]
);
schemaContent = schemaContent.replace(
"drizzle-orm/mysql-core",
`drizzle-orm/${dbType}-core`
);
schemaContent = schemaContent.replaceAll(
"mysqlTableCreator",
`${dbType}TableCreator`
);
schemaContent = schemaContent.replaceAll(".onUpdateNow()", "");
if (dbType === "sqlite") {
schemaContent = schemaContent.replace(" varchar,\n", "");
schemaContent = schemaContent.replace(" bigint,\n", "");
schemaContent = schemaContent.replace(" timestamp,\n", "");
schemaContent = schemaContent.replaceAll("varchar", "text");
schemaContent = schemaContent.replaceAll("bigint", "int");
schemaContent = schemaContent.replace(
/timestamp\("([a-zA-Z\-_]+)", { mode: "date" }\)/g,
'int("$1", { mode: "timestamp_ms" })'
);
schemaContent = schemaContent.replace(
/timestamp\("([a-zA-Z\-_]+)"\)/g,
'int("$1", { mode: "timestamp" })'
);
schemaContent = schemaContent.replace(
`timestamp("emailVerified", {
mode: "date",
fsp: 3,
})`,
'int("emailVerified", { mode: "timestamp" })'
);
schemaContent = schemaContent.replaceAll(
".primaryKey().autoincrement()",
".primaryKey({ autoIncrement: true })"
);
}
if (dbType === "pg") {
schemaContent = schemaContent.replace(" bigint,\n", "");
schemaContent = schemaContent.replace(
" int,\n",
" integer as int,\n serial,\n"
);
schemaContent = schemaContent.replace(
'id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),',
'id: serial("post_id").primaryKey(),'
);
schemaContent = schemaContent.replace("fsp: ", "precision: ");
}
}

const clientSrc = path.join(extrasDir, "src/server/db/index-drizzle.ts");
const clientSrc = path.join(
extrasDir,
`src/server/db/index-drizzle/with-${databaseProvider}.ts`
);
const clientDest = path.join(projectDir, "src/server/db/index.ts");

// add db:push script to package.json
Expand All @@ -54,7 +143,7 @@ export const drizzleInstaller: Installer = ({
const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson;
packageJsonContent.scripts = {
...packageJsonContent.scripts,
"db:push": "dotenv drizzle-kit push:mysql",
"db:push": `dotenv drizzle-kit push:${dbType}`,
"db:studio": "dotenv drizzle-kit studio",
};

Expand Down
41 changes: 29 additions & 12 deletions cli/src/installers/envVars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,25 @@ import path from "path";
import fs from "fs-extra";

import { PKG_ROOT } from "~/consts.js";
import { type Installer } from "~/installers/index.js";
import { type DatabaseProvider, type Installer } from "~/installers/index.js";

export const envVariablesInstaller: Installer = ({ projectDir, packages }) => {
export const envVariablesInstaller: Installer = ({
projectDir,
packages,
databaseProvider,
}) => {
const usingAuth = packages?.nextAuth.inUse;
const usingPrisma = packages?.prisma.inUse;
const usingDrizzle = packages?.drizzle.inUse;

const usingDb = usingPrisma || usingDrizzle;

const envContent = getEnvContent(!!usingAuth, !!usingPrisma, !!usingDrizzle);
const envContent = getEnvContent(
!!usingAuth,
!!usingPrisma,
!!usingDrizzle,
databaseProvider
);

const envFile =
usingAuth && usingDb
Expand Down Expand Up @@ -42,7 +51,8 @@ export const envVariablesInstaller: Installer = ({ projectDir, packages }) => {
const getEnvContent = (
usingAuth: boolean,
usingPrisma: boolean,
usingDrizzle: boolean
usingDrizzle: boolean,
databaseProvider: DatabaseProvider
) => {
let content = `
# When adding additional environment variables, the schema in "/src/env.mjs"
Expand All @@ -55,16 +65,23 @@ const getEnvContent = (
content += `
# Prisma
# https://www.prisma.io/docs/reference/database-reference/connection-urls#env
DATABASE_URL="file:./db.sqlite"
`;

if (usingDrizzle) {
content += `
# Drizzle
# Get the Database URL from the "prisma" dropdown selector in PlanetScale.
# Change the query params at the end of the URL to "?ssl={"rejectUnauthorized":true}"
DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?ssl={"rejectUnauthorized":true}'
`;
if (usingDrizzle) content += "\n# Drizzle\n";

if (usingPrisma || usingDrizzle) {
if (databaseProvider === "planetscale") {
content += `Get the Database URL from the "prisma" dropdown selector in PlanetScale.
# Change the query params at the end of the URL to "?ssl={"rejectUnauthorized":true}"
DATABASE_URL='mysql://YOUR_MYSQL_URL_HERE?ssl={"rejectUnauthorized":true}'`;
} else if (databaseProvider === "mysql") {
content += `DATABASE_URL='mysql://username:password@localhost:3306/db_name?schema=public'`;
} else if (databaseProvider === "postgres" || databaseProvider === "neon") {
content += `DATABASE_URL='postgresql://username:password@localhost:5432/db_name?schema=public'`;
} else if (databaseProvider === "sqlite") {
content += `DATABASE_URL='file:./db.sqlite'`;
}
content += "\n";
}

if (usingAuth)
Expand Down
10 changes: 10 additions & 0 deletions cli/src/installers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ export const availablePackages = [
] as const;
export type AvailablePackages = (typeof availablePackages)[number];

export const databaseProviders = [
"mysql",
"postgres",
"sqlite",
"planetscale",
"neon",
] as const;
export type DatabaseProvider = (typeof databaseProviders)[number];

export interface InstallerOptions {
projectDir: string;
pkgManager: PackageManager;
Expand All @@ -26,6 +35,7 @@ export interface InstallerOptions {
appRouter?: boolean;
projectName: string;
scopedAppName: string;
databaseProvider: DatabaseProvider;
}

export type Installer = (opts: InstallerOptions) => void;
Expand Down
Loading

0 comments on commit 0e11e4e

Please sign in to comment.